blob: 9e10ec1480b4b21e64353005ea4029bb4951471d [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.dpath
import org.apache.daffodil.processors._; import org.apache.daffodil.infoset._
import org.apache.daffodil.exceptions._
import org.apache.daffodil.util.Maybe
import org.apache.daffodil.util.Maybe._
import org.apache.daffodil.calendar.DFDLCalendar
import org.apache.daffodil.equality._; object EqualityNoWarn2 { EqualitySuppressUnusedImportWarning() }
import org.apache.daffodil.api.DataLocation
import java.math.{ BigDecimal => JBigDecimal, BigInteger => JBigInt }
import org.apache.daffodil.api.WarnID
import org.apache.daffodil.dsom.DPathCompileInfo
import org.apache.daffodil.xml.GlobalQName
import org.apache.daffodil.processors.TypeCalculatorCompiler.TypeCalcMap
/**
* Modes for expression evaluation.
*/
sealed trait EvalMode
/**
* Parsing always uses this mode, for everything except discriminators.
*
* Unparsing never uses this mode for anything.
*
* In this mode, evaluation is simple. If a child doesn't exist
* but is needed to be traversed, an exception is thrown. If a value doesn't exist
* an exception is thrown. fn:exists and fn:count return the answers
* immediately based on the current state of the infoset object.
*
* In this mode, if an infoset node or value that is needed for evaluation is not found,
* then it is a Runtime Schema Definition Error, i.e., a fatal error.
*/
sealed trait ParserMode extends EvalMode
case object ParserNonBlocking extends ParserMode
/**
* Used when evaluating discriminators, which only occurs during parsing.
*
* In this mode, if a failure occurs in that an infoset node or value is not found
* then it is a Processing error.
*/
case object ParserDiscriminatorNonBlocking extends ParserMode
/**
* Unparsing uses this mode for outputValueCalc, variables,
* and length expressions, when it wants to actually suspend a co-routine that
* can be resumed.
*
* In this mode, we must be running on a co-routine which can be blocked.
*
* In this mode, evaluation can suspend waiting for a child
* node to be added/appended, or for a value to be set.
*
* fn:count can block if the array isn't final yet.
* fn:exists can block if the array or element isn't final yet.
*
* Once the situation leading to the suspension changes, evaluation is
* retried.
*/
case object UnparserBlocking extends EvalMode
/**
* Unparsing uses this mode for evaluation to determine whether or not
* something will block.
*
* In this mode, we are running on the main co-routine, so we can't actually block.
*
* However, unlike ParserNonBlocking mode, in this mode fn:count of a non-final
* array will error, indicating to the caller that the expression would have
* blocked waiting for that array to be finalized. Similarly fn:exists can
* error, as can many other things. Anything that would have blocked in UnparserBlocking
* mode will instead error.
*/
case object UnparserNonBlocking extends EvalMode
/**
* expression evaluation side-effects this state block.
*
* For DPath expressions, all infoset content must be obtained via methods on this object so that
* if used in a forward referencing expression the expression can block until the information
* becomes available.
*/
case class DState(val maybeSsrd: Maybe[SchemaSetRuntimeData]) {
import org.apache.daffodil.util.Numbers._
var isCompile = false
var opIndex: Int = 0
/*
* Used by TypeValueCalc to pass the logical or representation value to the DPath runtime.
* In principle, TypeValueCalc can be used recursively, so these data structures are really a stack.
* Instead of storing the stack explicitly, we rely on the runtime stack.
* When ExpressionTypeCalculator begins a computation, it saves the previous value of logical/repValue to a local variable
* (eg. to the runtime stack), and replaces the below fields.
* At the end of the computation, it should restore the below fields.
*/
var logicalValue: Maybe[AnyRef] = Maybe.Nope
var repValue: Maybe[AnyRef] = Maybe.Nope
/**
* The currentValue is used when we have a value that is not
* associated with an element of simple type. E.g., If I have
* the expression 5 + $x, then none of those literals, nor their
* nor variable value, nor their sum, has an element associated with it.
*/
private var _currentValue: AnyRef = null
private var _mode: EvalMode = ParserNonBlocking
/**
* Set to UnparserBlocking for forward-referencing expressions during unparsing.
*/
def setMode(m: EvalMode) {
_mode = m
}
def mode = _mode
//
// The purpose of this commented out code was to retry expressions, not
// from the begining, but closer to where the expression failed. That is
// for a big complex expression, we would not redo the whole thing, but would
// block the co-routine right where it was unable to proceed e.g., in a call to
// node.dataValue, such that when it was resumed, it would resume right there.
// retry the call to node.dataValue, and then carry on with the rest of the
// expression.
//
// If we don't do this, then we're not taking advantage of that optimization
// Then we don't really need coroutines at all to implement blocking. We just
// keep the saved/cloned state around, and retry whatever needed to be done, from scratch.
//
// The vast bulk of expressions are going to be fairly small, so this extra
// overhead vs. the complexity of coroutines?? I think we should move away
// From coroutines.
//
// However, if expressions were to block on the infoset, by queuing themselves
// adjacent to what they need, then... well you can still just retry the whole
// expression again, this time knowing it won't block in the same place.
//
// private var thisExpressionCoroutine_ : Maybe[Coroutine[AnyRef]] = Nope
//
// def setThisExpressionCoroutine(co: Maybe[Coroutine[AnyRef]]) {
// Assert.usage(mode eq UnparserBlocking)
// thisExpressionCoroutine_ = co
// }
//
// def thisExpressionCoroutine = {
// Assert.usage(mode eq UnparserBlocking)
// thisExpressionCoroutine_
// }
//
// private var coroutineToResumeIfBlocked_ : Maybe[Coroutine[AnyRef]] = Nope
//
// def setCoroutineToResumeIfBlocked(co: Maybe[Coroutine[AnyRef]]) {
// Assert.usage(mode eq UnparserBlocking)
// coroutineToResumeIfBlocked_ = co
// }
//
// def coroutineToResumeIfBlocked = {
// Assert.usage(mode eq UnparserBlocking)
// coroutineToResumeIfBlocked_
// }
def resetValue() {
_currentValue = null
}
def currentValue: AnyRef = {
if (_currentValue eq null)
withRetryIfBlocking {
currentSimple.dataValue
}
else _currentValue
}
def setCurrentValue(v: AnyRef) {
_currentValue = v
_currentNode = null
}
def setCurrentValue(v: Long) {
_currentValue = asAnyRef(v)
_currentNode = null
}
def setCurrentValue(v: Boolean) {
_currentValue = asAnyRef(v)
_currentNode = null
}
def booleanValue: Boolean = currentValue.asInstanceOf[Boolean]
def longValue: Long = asLong(currentValue)
def intValue: Int = longValue.toInt
def doubleValue: Double = asDouble(currentValue)
def integerValue: JBigInt = asBigInt(currentValue)
def decimalValue: JBigDecimal = asBigDecimal(currentValue)
def stringValue: String = currentValue.asInstanceOf[String]
def isNilled: Boolean = currentElement.isNilled
private def isAnArray(): Boolean = {
if (!currentNode.isInstanceOf[DIArray]) {
Assert.invariant(errorOrWarn.isDefined)
if (currentNode.isInstanceOf[DIElement]) {
errorOrWarn.get.SDW(WarnID.PathNotToArray, "The specified path to element %s is not to an array. Suggest using fn:exists instead.", currentElement.name)
} else {
errorOrWarn.get.SDW(WarnID.PathNotToArray, "The specified path is not to an array. Suggest using fn:exists instead.")
}
false
} else {
true
}
}
def arrayLength: Long =
if (isAnArray()) currentArray.length
else 1L
def finalArrayLength: Long =
if (isAnArray()) {
currentArray.requireFinal
currentArray.length
} else 1L
def exists: Boolean = true // we're at a node, so it must exist.
def dateValue: DFDLCalendar = currentValue.asInstanceOf[DFDLCalendar]
def timeValue: DFDLCalendar = currentValue.asInstanceOf[DFDLCalendar]
def dateTimeValue: DFDLCalendar = currentValue.asInstanceOf[DFDLCalendar]
/**
* Array index calculations (that is [expr], what XPath
* calls 'predicate')
*/
def index: Int = longValue.toInt
private var _currentNode: DINode = null
def currentNode = _currentNode
def setCurrentNode(n: DINode) {
_currentNode = n
_currentValue = null
}
def currentSimple = {
Assert.usage(currentNode != null)
Assert.usage(mode != null)
val cs = currentNode.asSimple
//
// If this is a computed value (for unparsing for dfdl:outputValueCalc property)
// then if it is not yet computed, compute it.
//
// TODO: remove this code perhaps? This is demanded elsewhere....
//
// if (mode =:= UnparseMode && !cs.hasValue && cs.runtimeData.outputValueCalcExpr.isDefined) {
// val expr = cs.runtimeData.outputValueCalcExpr.get
// val expressionValue = expr.evaluate(ustate)
// cs.setDataValue(expressionValue)
// }
cs
}
def currentElement = currentNode.asInstanceOf[DIElement]
def currentArray = currentNode.asInstanceOf[DIArray]
def currentComplex = currentNode.asComplex
def nextSibling = {
val contents = currentElement.parent.asInstanceOf[DIComplex].contents
//TOOD, currentNode should really know this
val i = contents.indexOf(currentNode)
if (i == contents.length - 1) {
throw new InfosetNoNextSiblingException(currentNode.asSimple, currentNode.erd.dpathElementCompileInfo)
} else {
contents(i + 1)
}
}
private var _vbox: VariableBox = null
def vmap = {
Assert.usage(_vbox ne null)
_vbox.vmap
}
/**
* Used by PState and parser, where we want to isolate the modifications
* to the vmap per expression evaluation. This isolate makes backtracking
* changes to the vmap easy. Just don't copy the vmap back from the DState
* into the PState, and the changes are gone.
*/
def setVMap(m: VariableMap) {
if (_vbox eq null) {
_vbox = new VariableBox(m)
} else {
_vbox.setVMap(m)
}
}
/**
* Used by UState, where we want to shared the vmap as modified by the
* expression evaluations that use the DState.
*/
def setVBox(box: VariableBox) {
_vbox = box
}
def runtimeData = {
if (contextNode.isDefined) One(contextNode.get.erd)
else Nope
}
// Overwritten in DStateForConstantFolding, so it should
// be safe to assume we have runtime data
def compileInfo = runtimeData.get.dpathCompileInfo
private var _contextNode: Maybe[DINode] = Nope
def contextNode = _contextNode
def setContextNode(node: DINode) {
_contextNode = One(node)
}
// private var _bitPos1b: MaybeULong = MaybeULong.Nope
// private var _bitLimit1b: MaybeULong = MaybeULong.Nope
// private var _dataStream: Maybe[DataStreamCommon] = Nope
//
// def setLocationInfo(bitPos1b: Long, bitLimit1b: MaybeULong, dataStream: Maybe[DataStreamCommon]) {
// _bitPos1b = MaybeULong(bitPos1b)
// _bitLimit1b = bitLimit1b
// _dataStream = dataStream
// }
//
// def setLocationInfo() {
// _bitPos1b = MaybeULong.Nope
// _bitLimit1b = MaybeULong.Nope
// _dataStream = Nope
// }
def contextLocation: Maybe[DataLocation] = {
Nope
}
def typeCalculators = maybeSsrd.get.typeCalculators
private var _savesErrorsAndWarnings: Maybe[SavesErrorsAndWarnings] = Nope
def errorOrWarn = _savesErrorsAndWarnings
def setErrorOrWarn(s: SavesErrorsAndWarnings) {
_savesErrorsAndWarnings = One(s)
}
private var _arrayPos: Long = -1L // init to -1L so that we must set before use.
def arrayPos = _arrayPos
def setArrayPos(arrayPos1b: Long) {
_arrayPos = arrayPos1b
}
private var _parseOrUnparseState: Maybe[ParseOrUnparseState] = Nope
def parseOrUnparseState = _parseOrUnparseState
def setParseOrUnparseState(state: ParseOrUnparseState) {
_parseOrUnparseState = One(state)
}
def SDE(formatString: String, args: Any*) = {
if (isCompile) {
compileInfo.SDE(formatString, args: _*)
} else {
Assert.usage(runtimeData.isDefined)
errorOrWarn.get.SDE(formatString, args: _*)
}
}
// These exists so we can override it in our fake DState we use when
// checking expressions to see if they are constants. SelfMove
// for real is a no-op, but when we're evaluating an expression to see if
// it is a constant, the expression "." aka self, isn't constant.
// Similarly, if you call fn:exists(....) and the contents are not gong to
// exist at constant fold time. But we don't want fn:exists to say the result
// is always a constant (false) because at constant folding time, hey, nothing
// exists... this hook lets us change behavior for constant folding to throw.
def selfMove(): Unit = {}
def fnExists(): Unit = {}
// @inline // TODO: Performance maybe this won't allocate a closure if this is inline? If not replace with macro
final def withRetryIfBlocking[T](body: => T): T =
DState.withRetryIfBlocking(this)(body)
}
object DState {
// private object ToBeIgnored
// @inline
final def withRetryIfBlocking[T](ds: DState)(body: => T): T = { // TODO: Performance maybe this won't allocate a closure if this is inline? If not replace with macro
ds.mode match {
case _: ParserMode => body
case UnparserNonBlocking => body
case UnparserBlocking => {
var isDone = false
var res: T = null.asInstanceOf[T]
while (!isDone) {
try {
res = body
isDone = true
} catch {
case e: RetryableException => {
// we're to block here, and retry subsequently.
isDone = false
// if (ds.thisExpressionCoroutine.isDefined && ds.coroutineToResumeIfBlocked.isDefined) {
// ds.thisExpressionCoroutine.get.resume(ds.coroutineToResumeIfBlocked.get, ToBeIgnored)
// } else {
throw e
// }
}
}
}
res
}
}
}
}
class DStateForConstantFolding(
override val compileInfo: DPathCompileInfo) extends DState(Nope) {
private def die = throw new java.lang.IllegalStateException("No infoset at compile time.")
override def currentSimple = currentNode.asInstanceOf[DISimple]
override def currentElement = die
override def currentArray = die
override def currentComplex = die
override def currentNode = new FakeDINode
override def runtimeData = die
override def vmap = die
override def selfMove() = die
override def fnExists() = die
override def arrayPos = die
override def arrayLength = die
override val typeCalculators = compileInfo.typeCalcMap
isCompile = true
}