blob: f70f76d4c0fc57f4841c0758fa403db9c2583bf9 [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 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 => ()
}
}
}
}