blob: 5e4f0954cabf4f5ed92964e4a14da991a962aa85 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.daffodil.processors.unparsers
import java.nio.charset.MalformedInputException
import java.nio.charset.UnmappableCharacterException
import passera.unsigned.ULong
import org.apache.daffodil.exceptions.Assert
import org.apache.daffodil.infoset.DIComplex
import org.apache.daffodil.infoset.DIElement
import org.apache.daffodil.infoset.DISimple
import org.apache.daffodil.io.DataOutputStream
import org.apache.daffodil.io.DirectOrBufferedDataOutputStream
import org.apache.daffodil.io.ZeroLengthStatus
import org.apache.daffodil.processors.CharsetEv
import org.apache.daffodil.processors.ElementRuntimeData
import org.apache.daffodil.processors.Evaluatable
import org.apache.daffodil.processors.LengthEv
import org.apache.daffodil.processors.ModelGroupRuntimeData
import org.apache.daffodil.processors.RuntimeData
import org.apache.daffodil.processors.SuspendableOperation
import org.apache.daffodil.processors.UnparseTargetLengthInBitsEv
import org.apache.daffodil.processors.charset.BitsCharset
import org.apache.daffodil.schema.annotation.props.gen.LengthUnits
import org.apache.daffodil.util.Logger
import org.apache.daffodil.util.Maybe
import org.apache.daffodil.util.Maybe._
import org.apache.daffodil.util.MaybeChar
import org.apache.daffodil.util.MaybeJULong
import org.apache.daffodil.util.MaybeULong
import org.apache.daffodil.util.Misc
/*
* Notes on variable-width characters with lengthUnits 'characters'
*
* (Not worth doing for complex types, at least initially)
*
* (Similarly, don't implement dfdl:contentLength nor dfdl:valueLength
* functions in units 'characters' for complex types if the encoding is
* variable width).
*
* Simple types:
*
* For parsing, we must decode characters one by one until we accumulate
* the number of characters.
*
* The dfdl:contentLength in characters is this number of characters. This requires
* specific unparsers for all the textual data types for this case.
*
* If we are not trimming off pad characters, then the dfdl:valueLength is the
* same as the dfdl:contentLength.
*
* A single-pass algorithm that does trimming on left/right, and filling of the
* rightFill region would be:
*
* If we are trimming on the left (justification right or center) we must
* (1) record bit position as start of dfdl:contentLength in bits.
* (2) decode characters, counting up to N, keeping count also of the number of
* pad characters encountered (and discarding those pad characters). When a
* non-pad-character is encountered, the start position (in bits) is the start
* of the dfdl:valueLength in bits.
* (3) decode characters, counting up to N, and this part is tricky.
* If we are trimming on the right (justification center and left) then keeping
* track of the position of the last non-pad character, and the length of a
* current run of adjacent pad characters since the last non-pad character. When
* we reach N characters, the end of value position (in bits) is the position after the last
* non-pad character, the final position after N characters is the dfdl:contentLength
* in bits. The dfdl:valueLength in characters is the dfdl:valueLength in characters
* minus the length of the run of pad characters on the right, and the number of
* pad characters counted on the left.
*
* For unparsing, we must encode characters one by one until we have
* encoded the number of characters, or we run out of characters.
* If we run out of characters, then if we are padding, the length of the left
* and right padding regions is computed, and that many are encoded into
* those. If we are not padding and we run out of characters it is an error.
* If we have too many characters, then if simpleType string and
* truncateVariableLengthString, then we discard any excess characters, otherwise
* it is an error.
*
* The contentLength and valueLength in characters need to be stored on the infoset
* node explicitly by these processors.
*
* The infoset value is NOT modified by truncation nor padding.
* The fn:stringLength of the value is constant throughout this.
*
* Complex Types:
*
* (Not worth doing. Should SDE - not implemented by Daffodil - complex types with
* specified length with length units characters, with variable length encoding.)
*
* For parsing, we record the start of content bit position (and start of value bit
* position is the same), then we decode N characters, and the new bit position
* is the end of content bit position. Behavior on a decode error is controlled by
* dfdl:encodingErrorPolicy. So dfdl:contentLength in 'characters' is N,
* and we have the positions to enable us to compute dfdl:contentLength in bits.
* Then we backup to the start of content bit position and recursively parse
* the complex type body, in an environment where the data limit is set to prevent
* parsing beyond the content length in bits. When this recursive parse
* returns, the bit position is the end of value bit position, and we then
* skip to the content end position.
*
* For unparsing, we record the start of content bit position and start of value
* bit position is the same. Then we recursively unparse the complex type body into
* a buffer. Then we scan and decode this counting the characters. If a decode
* error occurs, dfdl:encodingErrorPolicy is used to decide whether to error, or
* count 1 for the unicodeReplacementCharacter that is the replacement.
*
* This makes length of a complex type in characters fundamentally unreliable if
* decode errors are possible. User beware. Use length in bytes or bits instead.
*
* When the recursive unparse completes, we block on the end bit pos
* and the ElementUnused region is filled with the number of characters to reach total N.
* If the number of characters is greater than N it is an error.
*/
/*
* Notes on variable-width characters when length units are bits
*
* (Really does need to be implemented, examples like 80-byte records, but where
* the characters can be utf-8 not just ascii = would be a typical Unicode
* upgrade to a legacy 80-byte oriented application.)
*
* In this case we know the number of bits, we don't know how many characters
* will be parsed or unparsed.
*
* Content and value length regions in bits/bytes are computed by the framework,
* but content and/or value length in characters must be determined by keeping
* count of the number of characters parsed by the pad/trim processors, and the
* value processor. These counts need to be stored on the infoset node.
*
*/
/*
* non-text or fixed-width characters, units are bits
*
* For parsing or unparsing, we know the exact length in bits.
*
* However, if textual, the number of bits does not necessarily divide by
* the width of a character in bits. There may be a fragment of a character
* at the end.
*
* An example would be if there are 56 bits (7 bytes), but utf-16 characters
* which are 16 bits each, will hold 3 characters with 8 bits left over.
*
* For unparsing, in the case where all the characters do not fit, we may
* discard the extra characters, or we may fail.
*
* We don't have 'bytes' here because that is always converted to bits.
*
* Note that the dfdl:contentLength and dfdl:valueLength can be requested in 'characters'
* and in that case, we can just divide by the character set width to convert
* the number of bits to characters.
*/
class SimpleTypeRetryUnparserSuspendableOperation(
override val rd: ElementRuntimeData,
maybeUnparserTargetLengthInBitsEv: Maybe[UnparseTargetLengthInBitsEv], vUnparser: Unparser)
extends SuspendableOperation {
override protected def maybeKnownLengthInBits(ustate: UState): MaybeULong = {
if (maybeUnparserTargetLengthInBitsEv.isDefined) {
// maybeUnparserTargetLengthInBitsEv should only be defined if we know at
// schema compile time that it will evaluate to a value, and that value
// will match the actual unparsed length (e.g. there will not be any
// padding/fill). Here we assert that if the target length evaluatable was
// passed into this class, then it must evaluate to a value. When we
// deliver buffered content, we will also assert that the starting bit
// position of the buffered DOS that results from this suspension matches
// the direct DOS (i.e. this length is used correctly)
val maybeLen = maybeUnparserTargetLengthInBitsEv.get.evaluate(ustate)
Assert.invariant(maybeLen.isDefined)
maybeLen.toMaybeULong
} else {
MaybeULong.Nope
}
}
protected def test(state: UState) = {
state.currentInfosetNode.asSimple.hasValue
}
protected def continuation(state: UState): Unit = {
vUnparser.unparse1(state)
}
}
class SimpleTypeRetryUnparser(
override val context: ElementRuntimeData,
maybeUnparserTargetLengthInBitsEv: Maybe[UnparseTargetLengthInBitsEv], vUnparser: Unparser)
extends PrimUnparser
with SuspendableUnparser {
override final def runtimeDependencies = maybeUnparserTargetLengthInBitsEv.toSeq.toVector
final override lazy val childProcessors = Vector(vUnparser)
def suspendableOperation = new SimpleTypeRetryUnparserSuspendableOperation(
context, maybeUnparserTargetLengthInBitsEv, vUnparser)
}
class CaptureStartOfContentLengthUnparser(override val context: ElementRuntimeData)
extends PrimUnparser {
override lazy val runtimeDependencies = Vector()
override def unparse(state: UState): Unit = {
val dos = state.dataOutputStream
val elem = state.currentInfosetNode.asInstanceOf[DIElement]
if (dos.maybeAbsBitPos0b.isDefined) {
elem.contentLength.setAbsStartPos0bInBits(dos.maybeAbsBitPos0b.getULong)
} else {
elem.contentLength.setRelStartPos0bInBits(dos.relBitPos0b, dos)
}
}
}
class CaptureEndOfContentLengthUnparser(override val context: ElementRuntimeData, maybeFixedLengthInBits: MaybeULong)
extends PrimUnparser {
override lazy val runtimeDependencies = Vector()
override def unparse(state: UState): Unit = {
val dos = state.dataOutputStream.asInstanceOf[DirectOrBufferedDataOutputStream]
val elem = state.currentInfosetNode.asInstanceOf[DIElement]
if (elem.contentLength.isStartAbsolute && dos.maybeAbsBitPos0b.isEmpty && maybeFixedLengthInBits.isDefined) {
// If this element has an absolute starting bit position, but the current
// DOS bit position is only known relatively, that means there was some
// suspension related to this element that had an unknown length.
// However, if this is a fixed length element, we can calculate the
// absolute position of this DOS based on its absolute starting position
// position, its length, and the relative position of the current DOS
val startAbsBitPos0b: ULong = elem.contentLength.maybeStartPos0bInBits.getULong
val currentAbsPos0b = startAbsBitPos0b + maybeFixedLengthInBits.getULong
dos.setAbsStartingBitPos0b(currentAbsPos0b - dos.relBitPos0b)
}
if (dos.maybeAbsBitPos0b.isDefined) {
elem.contentLength.setAbsEndPos0bInBits(dos.maybeAbsBitPos0b.getULong)
} else {
elem.contentLength.setRelEndPos0bInBits(dos.relBitPos0b, dos)
}
}
}
class CaptureStartOfValueLengthUnparser(override val context: ElementRuntimeData)
extends PrimUnparser {
override lazy val runtimeDependencies = Vector()
override def unparse(state: UState): Unit = {
val dos = state.dataOutputStream
val elem = state.currentInfosetNode.asInstanceOf[DIElement]
if (dos.maybeAbsBitPos0b.isDefined) {
elem.valueLength.setAbsStartPos0bInBits(dos.maybeAbsBitPos0b.getULong)
} else {
elem.valueLength.setRelStartPos0bInBits(dos.relBitPos0b, dos)
}
}
}
class CaptureEndOfValueLengthUnparser(override val context: ElementRuntimeData)
extends PrimUnparser {
override lazy val runtimeDependencies = Vector()
override def unparse(state: UState): Unit = {
val dos = state.dataOutputStream
val elem = state.currentInfosetNode.asInstanceOf[DIElement]
if (dos.maybeAbsBitPos0b.isDefined) {
elem.valueLength.setAbsEndPos0bInBits(dos.maybeAbsBitPos0b.getULong)
} else {
elem.valueLength.setRelEndPos0bInBits(dos.relBitPos0b, dos)
}
}
}
/**
* Carries out computation of the target length for a specified-length element.
*
* This is not a SuspendableExpression because the dfdl:length property cannot
* be forward referencing. However, it can refer backward to elements that have
* dfdl:outputValueCalc or variables that have not yet been computed. So we have
* to retry this in order to get the target length used to compute the amount of
* padding or the amount of unused space.
*/
class TargetLengthOperation(
override val rd: ElementRuntimeData,
targetLengthEv: UnparseTargetLengthInBitsEv)
extends SuspendableOperation {
override val isReadOnly = true
override def toString = "target length for " + rd.diagnosticDebugName + " expr " + targetLengthEv.lengthInBitsEv.lengthEv.toBriefXML()
/**
* This override indicates that this operation itself doesn't correspond
* to any bits in the unparsed data stream. It's just a computation.
*/
override protected def maybeKnownLengthInBits(ustate: UState): MaybeULong = MaybeULong(0L)
override def test(ustate: UState): Boolean = {
//
// regular evaluation - can only look backwards
//
targetLengthEv.evaluate(ustate) // can we successfully evaluate without blocking (blocking would throw)
true
}
override def continuation(state: UState): Unit = {
// once we have evaluated the targetLengthEv, nothing else to do
// here
}
}
/**
* Several sub-unparsers need to have the value length, and the target length
* in order to compute their own length.
*/
sealed trait NeedValueAndTargetLengthMixin {
def targetLengthEv: Evaluatable[MaybeJULong]
def maybeLengthEv: Maybe[LengthEv]
def maybeCharsetEv: Maybe[CharsetEv]
def maybeLiteralNilEv: Maybe[NilStringLiteralForUnparserEv]
protected final def hasTargetLength(ustate: UState): Boolean = {
targetLengthEv.evaluate(ustate)
true
}
protected def test(ustate: UState): Boolean = {
hasTargetLength(ustate) && {
val e = ustate.currentInfosetNode.asInstanceOf[DIElement]
val hasValueLength = e.valueLength.maybeLengthInBits().isDefined
hasValueLength
}
}
/**
* returns the value as a string, or if nillable and isNilled, the nil value
* for use when unparsing.
*/
private def valueString(s: DISimple, ustate: UState) = {
val vs =
if (s.erd.isNillable && s.isNilled) {
Assert.invariant(this.maybeLiteralNilEv.isDefined)
maybeLiteralNilEv.get.evaluate(ustate)
} else {
s.dataValueAsString
}
vs
}
/**
* Returns number of bits to skip.
*
* This can be negative if the unparsed data was too big.
*
* It is up to the caller to determine if this is an error or not.
*/
protected def getSkipBits(ustate: UState): Long = {
val e = ustate.currentInfosetNode.asInstanceOf[DIElement]
val mtl = targetLengthEv.evaluate(ustate)
if (mtl.isDefined) {
val tl = mtl.get
val vl = e.valueLength.lengthInBits.longValue
val skipInBits = tl - vl
skipInBits
} else {
// it's measured in variable width characters
if (maybeLengthEv.isDefined) {
val lengthEv = maybeLengthEv.get
val tlChars = lengthEv.evaluate(ustate)
e match {
case s: DISimple => {
val v = valueString(s, ustate)
val vlChars = v.length
val nPadChars = tlChars - vlChars // negative if data too long for available space.
val cs: BitsCharset = maybeCharsetEv.get.evaluate(ustate)
val paddingLengthInBits = cs.padCharWidthInBits * nPadChars
paddingLengthInBits
}
case c: DIComplex =>
??? // FIXME: This code was left incomplete - JIRA DAFFODIL-1952
}
} else {
0L // must be delimited, so we don't pad unless there's a minLength, which
// is not this case.
}
}
}
}
trait SkipTheBits { self: SuspendableOperation =>
protected val rd: RuntimeData
protected final def skipTheBits(ustate: UState, skipInBits: Long): Unit = {
if (skipInBits > 0) {
val dos = ustate.dataOutputStream
if (!dos.skip(skipInBits, ustate))
UE(ustate, "Unable to skip %s(bits).", skipInBits)
}
if (skipInBits == 0) {
Logger.log.debug(s"${Misc.getNameFromClass(this)} no fill for ${rd.diagnosticDebugName} DOS ${ustate.dataOutputStream}.")
} else {
Logger.log.debug(s"${Misc.getNameFromClass(this)} filled ${skipInBits} bits for ${rd.diagnosticDebugName} DOS ${ustate.dataOutputStream}.")
}
}
}
class ElementUnusedUnparserSuspendableOperation(
override val rd: ElementRuntimeData,
override val targetLengthEv: UnparseTargetLengthInBitsEv,
override val maybeLengthEv: Maybe[LengthEv],
override val maybeCharsetEv: Maybe[CharsetEv],
override val maybeLiteralNilEv: Maybe[NilStringLiteralForUnparserEv])
extends SuspendableOperation
with SkipTheBits
with NeedValueAndTargetLengthMixin {
/**
* determine delta between value length and target length
*
* and skip that many bits.
*/
override def continuation(ustate: UState): Unit = {
val skipInBits = getSkipBits(ustate)
if (skipInBits < 0)
UE(ustate, "Data too long by %s bits. Unable to truncate.", -skipInBits)
skipTheBits(ustate, skipInBits)
}
}
class ElementUnusedUnparser(
override val context: ElementRuntimeData,
targetLengthEv: UnparseTargetLengthInBitsEv,
maybeLengthEv: Maybe[LengthEv],
maybeCharsetEv: Maybe[CharsetEv],
maybeLiteralNilEv: Maybe[NilStringLiteralForUnparserEv])
extends PrimUnparser
with SuspendableUnparser {
override lazy val runtimeDependencies = Vector(targetLengthEv)
override def suspendableOperation =
new ElementUnusedUnparserSuspendableOperation(
context, targetLengthEv, maybeLengthEv, maybeCharsetEv, maybeLiteralNilEv)
}
class ChoiceUnusedUnparserSuspendableOperation(
override val rd: ModelGroupRuntimeData,
targetLengthInBits: Long)
extends SuspendableOperation
with StreamSplitter
with SkipTheBits {
private var zlStatus_ : ZeroLengthStatus = ZeroLengthStatus.Unknown
private var maybeDOSStart : Maybe[DataOutputStream] = Maybe.Nope
private var maybeDOSEnd : Maybe[DataOutputStream] = Maybe.Nope
def captureDOSStartForChoiceUnused(state: UState): Unit = {
val splitter = new RegionSplitUnparser(rd)
splitter.unparse(state)
maybeDOSStart = Maybe(splitter.dataOutputStream)
}
def captureDOSEndForChoiceUnused(state: UState): Unit = {
val splitter = new RegionSplitUnparser(rd)
splitter.unparse(state)
maybeDOSEnd = Maybe(splitter.dataOutputStream)
}
private lazy val dosToCheck_ = {
Assert.usage(maybeDOSStart.isDefined)
val dosForStart = maybeDOSStart.get
val dosForEnd = maybeDOSEnd.get
val primaryDOSList = getDOSFromAtoB(
dosForStart,
dosForEnd)
primaryDOSList
}
override def test(ustate: UState): Boolean = {
if (zlStatus_ ne ZeroLengthStatus.Unknown)
true
else if (maybeDOSStart.isEmpty)
false
else {
Assert.invariant(maybeDOSStart.isDefined)
if (dosToCheck_.exists { dos =>
val dosZLStatus = dos.zeroLengthStatus
dosZLStatus eq ZeroLengthStatus.NonZero
}) {
zlStatus_ = ZeroLengthStatus.NonZero
true
} else if (dosToCheck_.forall { dos =>
val dosZLStatus = dos.zeroLengthStatus
dosZLStatus eq ZeroLengthStatus.Zero
}) {
zlStatus_ = ZeroLengthStatus.Zero
true
} else {
Assert.invariant(zlStatus_ eq ZeroLengthStatus.Unknown)
false
}
}
}
/**
* determine delta between value length and target length
*
* and skip that many bits.
*/
override def continuation(ustate: UState): Unit = {
val startPos0b = dosToCheck_(0).relBitPos0b
val endPos0b = dosToCheck_.last.relBitPos0b + startPos0b
val vl = (endPos0b - startPos0b).toLong
val skipInBits = targetLengthInBits - vl
if (skipInBits < 0)
UE(ustate, "Data too long for dfdl:choiceLength (%s bits) by %s bits. Unable to truncate.", targetLengthInBits, -skipInBits)
skipTheBits(ustate, skipInBits)
}
}
class ChoiceUnusedUnparser(
override val context: ModelGroupRuntimeData,
targetLengthInBits: Long,
suspendableOp: SuspendableOperation)
extends PrimUnparser
with SuspendableUnparser {
override lazy val runtimeDependencies = Vector()
override def suspendableOperation = suspendableOp
}
trait PaddingUnparserMixin
extends NeedValueAndTargetLengthMixin { self: SuspendableOperation =>
protected def charsKind = "pad"
protected def maybePadChar: MaybeChar
override def test(ustate: UState): Boolean = {
super.test(ustate) && {
// we know there is a charset. We can't have a padChar without one
val charsetEv = maybeCharsetEv.get
charsetEv.evaluate(ustate)
true
}
}
protected def numPadChars(skipInBits: Long, charWidthInBits: Long) =
skipInBits / charWidthInBits // discarding any fragment of a character
protected final def charset(state: UState) =
maybeCharsetEv.get.evaluate(state)
protected final def charWidthInBits(charset: BitsCharset) = {
val res = charset.maybeFixedWidth.get
res
}
override def continuation(state: UState): Unit = {
val skipInBits = getSkipBits(state)
if (skipInBits <= 0) return // padding doesn't worry about data too long. RightFill and ElementUnused do.
val cs = charset(state)
val nChars = numPadChars(skipInBits, cs.padCharWidthInBits)
if (nChars > 0) {
val dos = state.dataOutputStream
var i = 0
val padChar = maybePadChar.get
val padString = padChar.toString
while (i < nChars) {
try {
if (dos.putString(padString, state) != 1)
UE(state, "Unable to output %s %s characters.", nChars, charsKind)
} catch {
case m: MalformedInputException => { UnparseError(One(self.rd.schemaFileLocation), One(state.currentLocation), "MalformedInputException: \n%s", m.getMessage()) }
case u: UnmappableCharacterException => { UnparseError(One(self.rd.schemaFileLocation), One(state.currentLocation), "UnmappableCharacterException: \n%s", u.getMessage()) }
}
i += 1
}
}
}
}
class OnlyPaddingUnparserSuspendableOperation(
override val rd: ElementRuntimeData,
override val targetLengthEv: Evaluatable[MaybeJULong],
override val maybeLengthEv: Maybe[LengthEv],
override val maybeCharsetEv: Maybe[CharsetEv],
override val maybeLiteralNilEv: Maybe[NilStringLiteralForUnparserEv],
override val maybePadChar: MaybeChar)
extends SuspendableOperation
with PaddingUnparserMixin
/**
* Doesn't matter if we're left or right padding if we're the only padding
*/
class OnlyPaddingUnparser(
override val context: ElementRuntimeData,
targetLengthEv: Evaluatable[MaybeJULong],
maybeLengthEv: Maybe[LengthEv],
maybeCharsetEv: Maybe[CharsetEv],
maybeLiteralNilEv: Maybe[NilStringLiteralForUnparserEv],
maybePadChar: MaybeChar)
extends TextPrimUnparser
with SuspendableUnparser {
override lazy val runtimeDependencies = Vector(targetLengthEv)
override def suspendableOperation =
new OnlyPaddingUnparserSuspendableOperation(
context, targetLengthEv, maybeLengthEv, maybeCharsetEv, maybeLiteralNilEv, maybePadChar)
}
class NilLiteralCharacterUnparserSuspendableOperation(
override val rd: ElementRuntimeData,
override val targetLengthEv: UnparseTargetLengthInBitsEv,
override val maybeLengthEv: Maybe[LengthEv],
override val maybeCharsetEv: Maybe[CharsetEv],
literalNilChar: Char)
extends SuspendableOperation
with PaddingUnparserMixin {
override def maybeLiteralNilEv: Maybe[NilStringLiteralForUnparserEv] = Nope
override def charsKind = "dfdl:nilKind 'literalCharacter'"
override val maybePadChar: MaybeChar = MaybeChar(literalNilChar)
//
// We don't wait for the valueLength, because the unparsed
// nil is part of the valueLength
//
override def test(state: UState) =
hasTargetLength(state) && {
val e = state.currentInfosetNode.asInstanceOf[DISimple]
val isNilled = e.isNilled
isNilled
}
override protected def getSkipBits(ustate: UState): Long = {
val mtl = targetLengthEv.evaluate(ustate)
val tl = mtl.get
val skipInBits = tl
skipInBits
}
}
class NilLiteralCharacterUnparser(
override val context: ElementRuntimeData,
val targetLengthEv: UnparseTargetLengthInBitsEv,
val maybeLengthEv: Maybe[LengthEv],
val maybeCharsetEv: Maybe[CharsetEv],
literalNilChar: Char)
extends TextPrimUnparser
with SuspendableUnparser {
override lazy val runtimeDependencies = Vector(targetLengthEv)
override def suspendableOperation = new NilLiteralCharacterUnparserSuspendableOperation(
context, targetLengthEv, maybeLengthEv, maybeCharsetEv, literalNilChar)
}
class RightCenteredPaddingUnparserSuspendaableOperation(
rd: ElementRuntimeData,
targetLengthEv: Evaluatable[MaybeJULong],
maybeLengthEv: Maybe[LengthEv],
maybeCharsetEv: Maybe[CharsetEv],
maybeLiteralNilEv: Maybe[NilStringLiteralForUnparserEv],
maybePadChar: MaybeChar)
extends OnlyPaddingUnparserSuspendableOperation(rd, targetLengthEv, maybeLengthEv, maybeCharsetEv, maybeLiteralNilEv, maybePadChar) {
override def numPadChars(skipInBits: Long, charWidthInBits: Long) = {
val numChars = super.numPadChars(skipInBits, charWidthInBits)
numChars / 2
}
}
class RightCenteredPaddingUnparser(
rd: ElementRuntimeData,
targetLengthEv: Evaluatable[MaybeJULong],
maybeLengthEv: Maybe[LengthEv],
maybeCharsetEv: Maybe[CharsetEv],
maybeLiteralNilEv: Maybe[NilStringLiteralForUnparserEv],
maybePadChar: MaybeChar)
extends OnlyPaddingUnparser(rd, targetLengthEv, maybeLengthEv, maybeCharsetEv, maybeLiteralNilEv, maybePadChar) {
override def suspendableOperation =
new RightCenteredPaddingUnparserSuspendaableOperation(
rd, targetLengthEv, maybeLengthEv, maybeCharsetEv, maybeLiteralNilEv, maybePadChar)
}
class LeftCenteredPaddingUnparserSuspendableOperation(
override val rd: ElementRuntimeData,
targetLengthEv: Evaluatable[MaybeJULong],
maybeLengthEv: Maybe[LengthEv],
maybeCharsetEv: Maybe[CharsetEv],
maybeLiteralNilEv: Maybe[NilStringLiteralForUnparserEv],
maybePadChar: MaybeChar)
extends OnlyPaddingUnparserSuspendableOperation(rd, targetLengthEv, maybeLengthEv, maybeCharsetEv, maybeLiteralNilEv, maybePadChar) {
override def numPadChars(skipInBits: Long, charWidthInBits: Long) = {
val numChars = super.numPadChars(skipInBits, charWidthInBits)
if ((numChars & 1) == 0)
numChars / 2
else
(numChars / 2) + 1
}
}
class LeftCenteredPaddingUnparser(
rd: ElementRuntimeData,
targetLengthEv: Evaluatable[MaybeJULong],
maybeLengthEv: Maybe[LengthEv],
maybeCharsetEv: Maybe[CharsetEv],
maybeLiteralNilEv: Maybe[NilStringLiteralForUnparserEv],
maybePadChar: MaybeChar)
extends OnlyPaddingUnparser(rd, targetLengthEv, maybeLengthEv, maybeCharsetEv, maybeLiteralNilEv, maybePadChar) {
override def suspendableOperation =
new LeftCenteredPaddingUnparserSuspendableOperation(
rd, targetLengthEv, maybeLengthEv, maybeCharsetEv, maybeLiteralNilEv, maybePadChar)
}
class RightFillUnparserSuspendableOperation(
rd: ElementRuntimeData,
targetLengthEv: UnparseTargetLengthInBitsEv,
maybeLengthEv: Maybe[LengthEv],
maybeCharsetEv: Maybe[CharsetEv],
maybeLiteralNilEv: Maybe[NilStringLiteralForUnparserEv],
override val maybePadChar: MaybeChar)
extends ElementUnusedUnparserSuspendableOperation(rd, targetLengthEv, maybeLengthEv, maybeCharsetEv, maybeLiteralNilEv)
with PaddingUnparserMixin {
override def continuation(state: UState): Unit = {
val skipInBits = getSkipBits(state)
if (skipInBits == 0L) return
if (skipInBits > 0) {
val cs = charset(state)
val skipInBitsMinusPadding =
if (maybePadChar.isDefined) {
skipInBits % cs.padCharWidthInBits
} else {
skipInBits
}
skipTheBits(state, skipInBitsMinusPadding)
} else {
UE(state, "Data too long by %s bits. Unable to truncate.", -skipInBits)
}
}
}
class RightFillUnparser(
rd: ElementRuntimeData,
targetLengthEv: UnparseTargetLengthInBitsEv,
maybeLengthEv: Maybe[LengthEv],
maybeCharsetEv: Maybe[CharsetEv],
maybeLiteralNilEv: Maybe[NilStringLiteralForUnparserEv],
val maybePadChar: MaybeChar)
extends ElementUnusedUnparser(rd, targetLengthEv, maybeLengthEv, maybeCharsetEv, maybeLiteralNilEv) {
override def suspendableOperation =
new RightFillUnparserSuspendableOperation(
rd, targetLengthEv, maybeLengthEv, maybeCharsetEv, maybeLiteralNilEv, maybePadChar)
}
class PrefixLengthSuspendableOperation(
override val rd: ElementRuntimeData,
elem: DIElement,
plElem: DISimple,
override val lengthUnits: LengthUnits,
override val prefixedLengthAdjustmentInUnits: Long)
extends SuspendableOperation
with CalculatedPrefixedLengthUnparserMixin {
override val isReadOnly = true
override def toString = "prefix length for " + rd.diagnosticDebugName
/**
* This override indicates that this operation itself doesn't correspond
* to any bits in the unparsed data stream. It's just a computation.
*/
override protected def maybeKnownLengthInBits(ustate: UState): MaybeULong = MaybeULong(0L)
override def test(ustate: UState): Boolean = {
elem.valueLength.maybeLengthInBits.isDefined
}
override def continuation(state: UState): Unit = {
val len = elem.valueLength.maybeLengthInBits.isDefined
assignPrefixLength(state, elem, plElem)
}
}