| /* |
| * 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 org.apache.daffodil.exceptions.Assert |
| import org.apache.daffodil.util.Maybe |
| import org.apache.daffodil.util.Maybe._ |
| import org.apache.daffodil.processors.ElementRuntimeData |
| import org.apache.daffodil.infoset.DISimple |
| import org.apache.daffodil.infoset.DIComplex |
| import org.apache.daffodil.dpath.SuspendableExpression |
| import org.apache.daffodil.processors.UnparseTargetLengthInBitsEv |
| import org.apache.daffodil.util.MaybeULong |
| import org.apache.daffodil.processors.Evaluatable |
| import org.apache.daffodil.infoset.RetryableException |
| import org.apache.daffodil.util.MaybeBoolean |
| import org.apache.daffodil.infoset.DataValue.DataValuePrimitive |
| /** |
| * Elements that, when unparsing, have no length specified. |
| * |
| * That is, lengtKind delimited, pattern, and implicit(for complexTypes) |
| */ |
| class ElementUnspecifiedLengthUnparser( |
| erd: ElementRuntimeData, |
| setVarUnparsers: Array[Unparser], |
| eBeforeUnparser: Maybe[Unparser], |
| eUnparser: Maybe[Unparser], |
| eAfterUnparser: Maybe[Unparser], |
| eReptypeUnparser: Maybe[Unparser]) |
| extends ElementUnparserBase( |
| erd, |
| setVarUnparsers, |
| eBeforeUnparser, |
| eUnparser, |
| eAfterUnparser, |
| eReptypeUnparser) |
| with RegularElementUnparserStartEndStrategy |
| with RepMoveMixin { |
| |
| override lazy val runtimeDependencies = Vector() |
| |
| } |
| |
| sealed trait RepMoveMixin { |
| def move(start: UState): Unit = { |
| val childIndex = start.childIndexStack.pop() |
| start.childIndexStack.push(childIndex + 1) |
| } |
| } |
| |
| /** |
| * The unparser used for an element that has inputValueCalc. |
| * |
| * The only thing we do is move over one child element, because the |
| * inputValueCalc element does take up one child element position. |
| * However, not in the group - because that is what is used to decide |
| * whether to place separators, and there should not be any separator |
| * corresponding to an IVC element. |
| */ |
| class ElementUnparserNoRep( |
| erd: ElementRuntimeData, |
| setVarUnparsers: Array[Unparser]) |
| extends ElementUnparserBase( |
| erd, |
| setVarUnparsers, |
| Nope, |
| Nope, |
| Nope, |
| Nope) |
| with RegularElementUnparserStartEndStrategy { |
| |
| override lazy val runtimeDependencies = Vector() |
| |
| /** |
| * Move over in the element children, but not in the group. |
| * This avoids separators for this IVC element. |
| */ |
| override def move(state: UState): Unit = { |
| val childIndex = state.childIndexStack.pop() |
| state.childIndexStack.push(childIndex + 1) |
| } |
| } |
| |
| class ElementOVCUnspecifiedLengthUnparser( |
| erd: ElementRuntimeData, |
| setVarUnparsers: Array[Unparser], |
| eBeforeUnparser: Maybe[Unparser], |
| eUnparser: Maybe[Unparser], |
| eAfterUnparser: Maybe[Unparser]) |
| extends ElementUnparserBase( |
| erd, |
| setVarUnparsers, |
| eBeforeUnparser, |
| eUnparser, |
| eAfterUnparser, |
| Nope) |
| with OVCStartEndStrategy |
| with RepMoveMixin { |
| |
| override lazy val runtimeDependencies = Vector() |
| |
| } |
| |
| /** |
| * Base class for unparsing elements |
| * |
| * Depends on use of separate unparsers for the padding/fill regions which |
| * calculate their own sizes, generally after the length of the value region |
| * has been determined. |
| */ |
| sealed abstract class ElementUnparserBase( |
| val erd: ElementRuntimeData, |
| val setVarUnparsers: Array[Unparser], |
| val eBeforeUnparser: Maybe[Unparser], |
| val eUnparser: Maybe[Unparser], |
| val eAfterUnparser: Maybe[Unparser], |
| val eReptypeUnparser: Maybe[Unparser]) |
| extends CombinatorUnparser(erd) |
| with RepMoveMixin |
| with ElementUnparserStartEndStrategy { |
| |
| final override lazy val childProcessors = |
| (eBeforeUnparser.toList ++ eUnparser.toList ++ eAfterUnparser.toList ++ eReptypeUnparser.toList ++ setVarUnparsers.toList).toVector |
| |
| private val name = erd.name |
| |
| override def toBriefXML(depthLimit: Int = -1): String = { |
| if (depthLimit == 0) "..." else |
| "<Element name='" + name + "'>" + |
| (if (eBeforeUnparser.isDefined) eBeforeUnparser.value.toBriefXML(depthLimit - 1) else "") + |
| (if (eReptypeUnparser.isDefined) eReptypeUnparser.value.toBriefXML(depthLimit - 1) else "") + |
| (if (eUnparser.isDefined) eUnparser.value.toBriefXML(depthLimit - 1) else "") + |
| (if (eAfterUnparser.isDefined) eAfterUnparser.value.toBriefXML(depthLimit - 1) else "") + |
| setVarUnparsers.map { _.toBriefXML(depthLimit - 1) }.mkString + |
| "</Element>" |
| } |
| |
| final def computeSetVariables(state: UState): Unit = { |
| // variable assignment. Always after the element value, but |
| // also still with this element itself as the context, so before |
| // we end element. |
| { |
| var i = 0; |
| while (i < setVarUnparsers.length) { |
| setVarUnparsers(i).unparse1(state) |
| i += 1 |
| } |
| } |
| } |
| |
| protected def doBeforeContentUnparser(state: UState): Unit = { |
| if (eBeforeUnparser.isDefined) |
| eBeforeUnparser.get.unparse1(state) |
| } |
| |
| protected def doAfterContentUnparser(state: UState): Unit = { |
| if (eAfterUnparser.isDefined) |
| eAfterUnparser.get.unparse1(state) |
| } |
| |
| protected def runContentUnparser(state: UState): Unit = { |
| if (eReptypeUnparser.isDefined) { |
| eReptypeUnparser.get.unparse1(state) |
| } else if (eUnparser.isDefined) |
| eUnparser.get.unparse1(state) |
| } |
| |
| override def unparse(state: UState): Unit = { |
| |
| if (state.dataProc.isDefined) state.dataProc.value.startElement(state, this) |
| |
| unparseBegin(state) |
| |
| captureRuntimeValuedExpressionValues(state) |
| |
| doBeforeContentUnparser(state) |
| |
| // |
| // We must push the TermRuntimeData for all model-groups. |
| // The starting point for this is the model-group of a complex type. |
| // Simple types don't have model groups, so no pushing those. |
| // |
| if (erd.isComplexType) |
| state.pushTRD(erd.optComplexTypeModelGroupRuntimeData.get) |
| |
| runContentUnparser(state) |
| |
| if (erd.isComplexType) |
| state.popTRD(erd.optComplexTypeModelGroupRuntimeData.get) |
| |
| doAfterContentUnparser(state) |
| |
| computeSetVariables(state) |
| |
| unparseEnd(state) |
| |
| if (state.dataProc.isDefined) state.dataProc.value.endElement(state, this) |
| |
| } |
| |
| def validate(state: UState): Unit = { |
| ??? // TODO: JIRA DFDL-1582 - Is the ticket for implementing the Unparser - validation feature |
| |
| // val currentElement = state.thisElement |
| // |
| // if (currentElement.valid.isDefined) { return } |
| // |
| // val resultState = DFDLCheckConstraintsFunction.validate(state) match { |
| // case Right(boolVal) => { |
| // log(LogLevel.Debug, "Validation succeeded for %s", currentElement.toXML()) |
| // currentElement.setValid(true) |
| // } |
| // case Left(failureMessage) => { |
| // log(LogLevel.Debug, |
| // "Validation failed for %s due to %s. The element value was %s.", |
| // context.toString, failureMessage, currentElement.toXML()) |
| // state.reportValidationError("%s failed facet checks due to: %s", |
| // context.toString, failureMessage) |
| // currentElement.setValid(false) |
| // } |
| // } |
| } |
| } |
| |
| trait ElementSpecifiedLengthMixin { |
| |
| protected def maybeTargetLengthEv: Maybe[UnparseTargetLengthInBitsEv] |
| protected def erd: ElementRuntimeData |
| |
| /** |
| * This is a maybeTLOp so that this base class can be used to handle |
| * data types that do not have specified length as well. |
| * |
| * An example is lengthKind 'pattern' which while not "specified" length, |
| * uses this same code path, just there is no possibility of pad/fill regions. |
| * |
| * It's a degenerate case of specified length. |
| * |
| * Note: thread safety: This must be def, not val/lazyval because TargetLengthOperation is |
| * a stateful class instance, so cannot be a static member of an unparser |
| * object (unparsers are shared by multiple threads. Suspensions cannot be.) |
| */ |
| // private def maybeTLOp = { |
| // val mtlop = if (maybeTargetLengthEv.isDefined) |
| // One(new TargetLengthOperation(erd, maybeTargetLengthEv.get)) |
| // else |
| // Nope |
| // mtlop |
| // } |
| |
| protected def computeTargetLength(state: UState): Unit = { |
| if (maybeTargetLengthEv.isDefined) { |
| val tlEv = maybeTargetLengthEv.get |
| if (tlEv.isConstant) { |
| // bypass creating a suspension when we know the target length is constant |
| // do nothing |
| } else { |
| // it is an expression. It might suspend. |
| val op = new TargetLengthOperation(erd, tlEv) |
| op.run(state) |
| } |
| } |
| } |
| } |
| |
| /** |
| * For regular (not dfdl:outputValueCalc) elements. |
| */ |
| class ElementSpecifiedLengthUnparser( |
| context: ElementRuntimeData, |
| override val maybeTargetLengthEv: Maybe[UnparseTargetLengthInBitsEv], |
| setVarUnparsers: Array[Unparser], |
| eBeforeUnparser: Maybe[Unparser], |
| eUnparser: Maybe[Unparser], |
| eAfterUnparser: Maybe[Unparser], |
| eReptypeUnparser: Maybe[Unparser]) |
| extends ElementUnparserBase( |
| context, |
| setVarUnparsers, |
| eBeforeUnparser, |
| eUnparser, |
| eAfterUnparser, |
| eReptypeUnparser) |
| with RegularElementUnparserStartEndStrategy |
| with ElementSpecifiedLengthMixin { |
| |
| override lazy val runtimeDependencies = maybeTargetLengthEv.toList.toVector |
| |
| override def runContentUnparser(state: UState): Unit = { |
| computeTargetLength(state) // must happen before run() so that we can take advantage of knowing the length |
| super.runContentUnparser(state) // setup unparsing, which will block for no valu |
| } |
| |
| } |
| |
| /** |
| * For dfdl:outputValueCalc elements. |
| */ |
| class ElementOVCSpecifiedLengthUnparserSuspendableExpresion( |
| callingUnparser: ElementOVCSpecifiedLengthUnparser) |
| extends SuspendableExpression { |
| |
| override def rd = callingUnparser.erd |
| |
| override lazy val expr = rd.outputValueCalcExpr.get |
| |
| override final protected def processExpressionResult(state: UState, v: DataValuePrimitive): Unit = { |
| val diSimple = state.currentInfosetNode.asSimple |
| |
| diSimple.setDataValue(v) |
| |
| // |
| // These are now done in the main unparse, but they will |
| // suspend if they cannot be evaluated because there is not data value yet. |
| // |
| // callingUnparser.computeSetVariables(state) |
| } |
| |
| override protected def maybeKnownLengthInBits(ustate: UState): MaybeULong = MaybeULong(0L) |
| |
| } |
| |
| class ElementOVCSpecifiedLengthUnparser( |
| context: ElementRuntimeData, |
| override val maybeTargetLengthEv: Maybe[UnparseTargetLengthInBitsEv], |
| setVarUnparsers: Array[Unparser], |
| eBeforeUnparser: Maybe[Unparser], |
| eUnparser: Maybe[Unparser], |
| eAfterUnparser: Maybe[Unparser]) |
| extends ElementUnparserBase( |
| context, |
| setVarUnparsers, |
| eBeforeUnparser, |
| eUnparser, |
| eAfterUnparser, |
| Nope) |
| with OVCStartEndStrategy |
| with ElementSpecifiedLengthMixin { |
| |
| override lazy val runtimeDependencies = maybeTargetLengthEv.toList.toVector |
| |
| private def suspendableExpression = |
| new ElementOVCSpecifiedLengthUnparserSuspendableExpresion(this) |
| |
| Assert.invariant(context.outputValueCalcExpr.isDefined) |
| |
| override def runContentUnparser(state: UState): Unit = { |
| computeTargetLength(state) // must happen before run() so that we can take advantage of knowing the length |
| suspendableExpression.run(state) // run the expression. It might or might not have a value. |
| super.runContentUnparser(state) // setup unparsing, which will block for no valu |
| } |
| |
| } |
| |
| /** |
| * specifies the way the element will consume infoset events, |
| */ |
| sealed trait ElementUnparserStartEndStrategy { |
| /** |
| * Consumes the required infoset events and changes context so that the |
| * element's DIElement node is the context element. |
| */ |
| protected def unparseBegin(state: UState): Unit |
| |
| /** |
| * Restores prior context. Consumes end-element event. |
| */ |
| protected def unparseEnd(state: UState): Unit |
| |
| protected def captureRuntimeValuedExpressionValues(ustate: UState): Unit |
| |
| protected def move(start: UState): Unit |
| |
| protected def erd: ElementRuntimeData |
| |
| def runtimeDependencies: Vector[Evaluatable[AnyRef]] |
| } |
| |
| sealed trait RegularElementUnparserStartEndStrategy |
| extends ElementUnparserStartEndStrategy { |
| /** |
| * Consumes the required infoset events and changes context so that the |
| * element's DIElement node is the context element. |
| */ |
| final override protected def unparseBegin(state: UState): Unit = { |
| if (erd.isQuasiElement) { |
| //Quasi elements are used for TypeValueCalc, and have no corresponding events in the infoset inputter |
| //The parent parser will push a DIElement for us to consume containing the logical value, so we do |
| //not need to do so here |
| Assert.invariant(state.currentInfosetNode.isSimple) |
| Assert.invariant(state.currentInfosetNode.asSimple.erd eq erd) |
| () |
| } else { |
| val elem = |
| if (!state.withinHiddenNest) { |
| // Elements in a hidden context are not in the infoset, so we will never get an event |
| // for them. Only try to consume start events for non-hidden elements |
| val event = state.advanceOrError |
| if (!event.isStart || event.erd != erd) { |
| // it's not a start element event, or it's a start element event, but for a different element. |
| // this indicates that the incoming infoset (as events) doesn't match the schema |
| UnparseError(Nope, One(state.currentLocation), "Expected element start event for %s, but received %s.", |
| erd.namedQName.toExtendedSyntax, event) |
| } |
| val res = event.info.element |
| val mCurNode = state.currentInfosetNodeMaybe |
| if (mCurNode.isDefined) { |
| val c = mCurNode.get.asComplex |
| Assert.invariant(!c.isFinal) |
| if (c.maybeIsNilled == MaybeBoolean.True) { |
| // cannot add content to a nilled complex element |
| UnparseError(One(erd.schemaFileLocation), Nope, "Nilled complex element %s has content from %s", |
| c.erd.namedQName.toExtendedSyntax, |
| res.erd.namedQName.toExtendedSyntax) |
| } |
| c.addChild(res, state.tunable) |
| } else { |
| val doc = state.documentElement |
| doc.addChild(res) // DIDocument, which is never a current node, must have the child added |
| doc.setFinal() // that's the only child. |
| } |
| res |
| } else { |
| Assert.invariant(state.withinHiddenNest) |
| // Since we never get events for elements in hidden contexts, their infoset elements |
| // will have never been created. This means we need to manually create them |
| val e = if (erd.isComplexType) new DIComplex(erd) else new DISimple(erd) |
| e.setHidden() |
| state.currentInfosetNode.asComplex.addChild(e, state.tunable) |
| e |
| } |
| |
| // When the infoset events are being advanced, the currentInfosetNodeStack |
| // is pushing and popping to match the events. This provides the proper |
| // context for evaluation of expressions. |
| val e = One(elem) |
| state.currentInfosetNodeStack.push(e) |
| } |
| } |
| |
| /** |
| * Restores prior context. Consumes end-element event. |
| */ |
| final override protected def unparseEnd(state: UState): Unit = { |
| if (erd.isQuasiElement) { |
| //Quasi elements are used for TypeValueCalc, and have no corresponding events in the infoset inputter |
| //The parent parser will handle pushing and poping the Infoset, so we do not need to do anything here. |
| Assert.invariant(state.currentInfosetNode.isSimple) |
| Assert.invariant(state.currentInfosetNode.asSimple.erd eq erd) |
| () |
| } else { |
| if (!state.withinHiddenNest) { |
| // Hidden elements are not in the infoset, so we will never get an event |
| // for them. Only try to consume end events for non-hidden elements |
| val event = state.advanceOrError |
| if (!event.isEnd || event.erd != erd) { |
| // it's not an end-element event, or it's an end element event, but for a different element. |
| // this indicates that the incoming infoset (as events) doesn't match the schema |
| UnparseError(Nope, One(state.currentLocation), "Expected element end event for %s, but received %s.", |
| erd.namedQName.toExtendedSyntax, event) |
| } |
| } |
| val cur = state.currentInfosetNode |
| if (cur.isComplex) |
| cur.asComplex.setFinal() |
| state.currentInfosetNodeStack.pop |
| |
| move(state) |
| } |
| } |
| |
| final override protected def captureRuntimeValuedExpressionValues(ustate: UState): Unit = { |
| //do nothing |
| } |
| |
| } |
| |
| trait OVCStartEndStrategy |
| extends ElementUnparserStartEndStrategy { |
| |
| /** |
| * For OVC, the behavior w.r.t. consuming infoset events is different. |
| */ |
| protected final override def unparseBegin(state: UState): Unit = { |
| val elem = |
| if (!state.withinHiddenNest) { |
| // outputValueCalc elements are optional in the infoset. If the next event |
| // is for this OVC element, then consume the start/end events. |
| // Otherwise, the next event is for a following element, and we do not want |
| // to consume it. Don't even bother checking all this if it's hidden. It |
| // definitely won't be in the infoset in that case. |
| val eventMaybe = state.inspectMaybe |
| if (eventMaybe.isDefined && eventMaybe.get.erd == erd) { |
| // Event existed for this OVC element, should be a start and end events |
| val startEv = state.advanceOrError // Consume the start event |
| Assert.invariant(startEv.isStart && startEv.erd == erd) |
| val endEv = state.advanceOrError // Consume the end event |
| Assert.invariant(endEv.isEnd && endEv.erd == erd) |
| |
| val e = new DISimple(erd) |
| state.currentInfosetNode.asComplex.addChild(e, state.tunable) |
| // Remove any state that was set by what created this event. Later |
| // code asserts that OVC elements do not have a value |
| e.resetValue |
| e |
| } else { |
| // Event was optional and didn't exist, create a new InfosetElement and add it |
| val e = new DISimple(erd) |
| state.currentInfosetNode.asComplex.addChild(e, state.tunable) |
| e |
| } |
| } else { |
| // Event was hidden and will never exist, create a new InfosetElement and add it |
| val e = new DISimple(erd) |
| e.setHidden() |
| state.currentInfosetNode.asComplex.addChild(e, state.tunable) |
| e |
| } |
| |
| val e = One(elem) |
| state.currentInfosetNodeStack.push(e) |
| } |
| |
| protected final override def unparseEnd(state: UState): Unit = { |
| state.currentInfosetNodeStack.pop |
| |
| // if an OVC element existed, the start AND end events were consumed in |
| // unparseBegin. No need to advance the cursor here. |
| |
| move(state) |
| } |
| |
| // For OVC, or for a target length expression, |
| // |
| // If we delayed evaluating the expressions, some variables might not be read |
| // until we come back to retrying the suspension. Those variables might |
| // be set downstream. That set would be illegal (set after read), but we |
| // would not detect it, because we would not have read it at the point where |
| // the element was first encountered. |
| // |
| // This problem would presumably be caught if the same schema was being used |
| // to parse data. |
| // |
| // Note that we can't try to be clever and just evaluate all the variables |
| // in the expressions... because expressions can have conditional branches |
| // so we can't tell which variables will be read if they're in the arms of |
| // conditionals. |
| // |
| // For runtime-valued properties, the expressions cannot block, so we can evaluate |
| // them at the time we first encounter the element. We could, at that point |
| // remember them, or we could just re-evaluate them again later when their |
| // values are actually needed. Variables that are read will have the right |
| // state. |
| // |
| // So caching the result values of the runtime-valued properties is allowed, |
| // but not required. Really we just need the side-effects of the expressions |
| // on reading of variables. |
| // |
| // Additionally, even if the target length is computed without suspending, some other |
| // aspect of the content might be suspended (something as simple as |
| // a mandatory text alignment, for example) |
| // |
| // This can cause value lengths/content lengths for many things to be |
| // non computable. That can cause arbitrary unparsing to block. That unparsing |
| // might need say, properties like those for controlling text number format, |
| // many of which are runtime-valued. |
| // |
| // Those expressions might reference variables. |
| // |
| // Only runtime-valued properties relevant to this element are needed. |
| |
| // TODO: Performance: Ideas: maybe each unparse should sport a captureRuntimeValueProperties, and |
| // similarly each Ev. Then suspend could capture exactly and only what is |
| // needed. (Similarly, for parts of the UState that need to be saved, each |
| // unparser could have specific methods for capturing that information and |
| // caching it on the Infoset node. (E.g., delimiter stack - but only when |
| // delimiters are relevant instead of every time as we do now.) |
| // |
| // Right now we pessimistically clone the entire DOS, the entire UState |
| // (though we share the variable map) including various stacks. (We need the whole |
| // delimiter stack, but probably could get a way with only top of stack for |
| // many other things. |
| |
| final override protected def captureRuntimeValuedExpressionValues(state: UState): Unit = { |
| // |
| // Forces the evaluation of runtime-valued things, and this will cause those |
| // that actually are runtime-expressions to be cached on the infoset element. |
| // |
| // Then later when the actual unparse occurs, these will be accessed off the |
| // infoset element's cache. |
| // |
| // So we have to do this here in order to Freeze the state of these |
| // evaluations on the Infoset at the time this unparse call happens. |
| |
| runtimeDependencies.foreach { dep => |
| try { |
| dep.evaluate(state) // these evaluations will force dependencies of the dependencies. So we just do 1 tier, not a tree walk. |
| } catch { |
| case _: RetryableException => () |
| } |
| } |
| } |
| } |