blob: 3fff110b67869b755b813832612cc48af1509bf5 [file] [log] [blame]
/* Copyright (c) 2012-2015 Tresys Technology, LLC. All rights reserved.
*
* Developed by: Tresys Technology, LLC
* http://www.tresys.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal with
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimers.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimers in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the names of Tresys Technology, nor the names of its contributors
* may be used to endorse or promote products derived from this Software
* without specific prior written permission.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
* SOFTWARE.
*/
package edu.illinois.ncsa.daffodil.dpath
import edu.illinois.ncsa.daffodil.exceptions._
import edu.illinois.ncsa.daffodil.util._
import edu.illinois.ncsa.daffodil.xml.NamedQName
import edu.illinois.ncsa.daffodil.processors._
import edu.illinois.ncsa.daffodil.dsom._
import edu.illinois.ncsa.daffodil.util.Maybe
import edu.illinois.ncsa.daffodil.util.Maybe._
import edu.illinois.ncsa.daffodil.calendar.DFDLCalendar
import edu.illinois.ncsa.daffodil.processors.unparsers.UState
import edu.illinois.ncsa.daffodil.processors.unparsers.UnparseError
import edu.illinois.ncsa.daffodil.equality._; object EqualityNoWarn { EqualitySuppressUnusedImportWarning() }
import edu.illinois.ncsa.daffodil.api.DataLocation
import edu.illinois.ncsa.daffodil.api.Diagnostic
import AsIntConverters._
class ExpressionEvaluationException(e: Throwable, s: ParseOrUnparseState)
extends ProcessingError("Expression Error",
One(s.schemaFileLocation),
Nope,
"Expression evaluation failed: %s",
DiagnosticUtils.getSomeMessage(e).get)
final class RuntimeExpressionDPath[T <: AnyRef](qn: NamedQName, tt: NodeInfo.Kind, recipe: CompiledDPath,
dpathText: String,
ci: DPathCompileInfo,
isEvaluatedAbove: Boolean,
override val contentReferencedElementInfos: Set[DPathElementCompileInfo],
override val valueReferencedElementInfos: Set[DPathElementCompileInfo])
extends CompiledExpression[T](qn, dpathText) {
override def targetType = tt
// TODO: fix this check below. There is a unierse of target types which is
// muuch smaller than the set of all types, so some check is useful to be sure
// we stay within the subset of types that are actually used as target types.
// Assert.usage(targetType == NodeInfo.AnyType // used by debugger eval stmt
// || targetType == NodeInfo.NonEmptyString // string-valued properties
// || targetType == NodeInfo.Long // length and occurs expressions
// || targetType == NodeInfo.Boolean // assert/discriminator test expressions
// || targetType.isInstanceOf[NodeInfo.AnyAtomic.Kind] // inputValueCalc, outputValueCalc,
// // setVariable, defineVariable (default expressions), newVariableInstance
// , "not an accepted targetType")
override lazy val prettyExpr = dpathText
def isKnownNonEmpty = true // expressions are not allowed to return empty string
private def UE(msg: String, maybeCL: Maybe[DataLocation]) = UnparseError(One(ci.schemaFileLocation), maybeCL, msg)
private def doPE(e: Throwable, state: ParseOrUnparseState): Null = {
val msg = "Expression evaluation failed due to: %s.".format(DiagnosticUtils.getSomeMessage(e).get)
state match {
case null => Assert.usageError("state cannot be null")
case ustate: UState => UE(msg, One(ustate.currentLocation))
case pstate: PState => {
val pe = new ExpressionEvaluationException(e, state) // One(ci.schemaFileLocation), One(pstate.currentLocation), msg)
pstate.setFailed(pe)
}
case compState: CompileState => {
val d = e match {
case d: Diagnostic => d
case _ =>
new ExpressionEvaluationException(e, state)
}
compState.setFailed(d)
}
}
null
}
private def handleCompileState(e: Diagnostic, state: ParseOrUnparseState) = {
state match {
case cs: CompileState => {
state.setFailed(e)
null
}
/**
* Almost anything that can go wrong in an expression maps to a SDE.
*
* E.g., node doesn't exist? Your expression is supposed to test for that
* using fn:exists(...) rather than just use the location and expect backtracking
* if it doesn't exist. An SDE isn't backtracked, so enforces this provision.
*/
case _ => throw e
}
}
// This was a bug: you can't have state here. These things are shared across threads.
// private var blockLocation = MaybeULong.Nope
/**
* For unparsing of forward-referencing (outputValueCalc) expressions
*
* Depends on the dstate havng been properly initialized.
* E.g., the variable map, current node, mode, etc.
*
* The whereBlockedInfo is a return data structure that is side-effected to indicate
* the block location. I.e., where in the infoset are we blocked. Used for forward-progress-checking
* so as to detect deadlocks.
*/
def evaluateForwardReferencing(state: ParseOrUnparseState, whereBlockedInfo: Suspension): Maybe[T] = {
var value: Maybe[AnyRef] = Nope
try {
// TODO: This assumes a distinct state object (with its own dState) for every expression that
// can be in evaluation simultaneously.
val dstate = evaluateExpression(state, state.dState)
value = Maybe(processForwardExpressionResults(dstate))
whereBlockedInfo.setDone
} catch {
case unfin: InfosetNodeNotFinalException =>
whereBlockedInfo.block(unfin.node, unfin.node.erd.dpathElementCompileInfo, 0, unfin)
case noChild: InfosetNoSuchChildElementException =>
whereBlockedInfo.block(noChild.diComplex, noChild.info, 0, noChild)
case noArrayIndex: InfosetArrayIndexOutOfBoundsException =>
whereBlockedInfo.block(noArrayIndex.diArray, noArrayIndex.diArray.erd.dpathElementCompileInfo, noArrayIndex.index, noArrayIndex)
case nd: InfosetNoDataException if nd.erd.outputValueCalcExpr.isDefined => {
// we got a no-data exception from an element with outputValueCalc
// that is, some OVC element requested the value of another OVC element
val ovc = new OutputValueCalcEvaluationException(nd)
whereBlockedInfo.block(nd.diElement, nd.erd.dpathElementCompileInfo, 0, ovc)
}
case ve: VariableException =>
whereBlockedInfo.block(ve.qname, ve.context, 0, ve)
case noLength: InfosetLengthUnknownException =>
whereBlockedInfo.block(noLength.diElement, noLength.erd, 0, noLength)
case th: Throwable => handleThrow(th, state)
}
val value1 = postProcess(value, state)
value1
}
private def processForwardExpressionResults(dstate: DState): AnyRef = {
val v: AnyRef = {
dstate.currentNode match {
case null => {
// there is no element. Can happen if one evaluates say, 5 + 6 or 5 + $variable
Assert.invariant(dstate.currentValue != null)
dstate.currentValue
}
case n: DIElement if n.isNilled => n
case c: DIComplex => {
Assert.invariant(!targetType.isInstanceOf[NodeInfo.AnyAtomic.Kind])
c
}
case s: DISimple => {
s.dataValue
}
case _ => Assert.invariantFailed("must be an element, simple or complex.")
}
}
v
}
private def evaluateExpression(state: ParseOrUnparseState, dstate: DState): DState = {
recipe.runExpression(state, dstate) // initializes dstate from state, then runs
state match {
case ustate: UState => // nothing. These are shared already
case pstate: PState => pstate.setVariableMap(dstate.vmap) // TODO: if we're always copying this back, then no point in trying to isolate. Just share just like in UState case.
}
dstate
}
/**
* For parsing or unparsing of backward-referencing expressions.
* That is, not outputValueCalc.
*/
private def evaluateMaybe(state: ParseOrUnparseState): Maybe[T] = {
val value =
try {
val dstate = evaluateExpression(state, state.dState)
processExpressionResults(dstate)
} catch {
case th: Throwable => handleThrow(th, state)
}
val value1 = postProcess(Maybe(value), state)
value1
}
final def evaluate(state: ParseOrUnparseState): T = {
val maybeRes = evaluateMaybe(state)
if (maybeRes.isDefined) {
maybeRes.value
} else {
Assert.invariant(state.status ne Success)
throw state.status.asInstanceOf[Failure].cause
}
}
def isConstant = false
def constant: T = Assert.usageError("Not a constant.")
private def processExpressionResults(dstate: DState): AnyRef = {
val v = {
dstate.currentNode match {
case null => {
// there is no element. Can happen if one evaluates say, 5 + 6 in the debugger in which case
// there is a value, but no node.
dstate.currentValue
}
case n: DIElement if n.isNilled => n
case c: DIComplex => {
Assert.invariant(!targetType.isInstanceOf[NodeInfo.AnyAtomic.Kind])
c
}
case s: DISimple => {
try {
s.dataValue
} catch {
case ovc: OutputValueCalcEvaluationException => {
Assert.invariantFailed("OVC should always have a data value by the time it reaches here.")
}
}
}
case _ => Assert.invariantFailed("must be an element, simple or complex.")
}
}
v
}
private def handleThrow(th: Throwable, state: ParseOrUnparseState): Null = {
th match {
//
// Here we catch exceptions that indicate something went wrong with the
// expression, but by that we mean legal evaluation of a compiled expression
// produced an error such as divide by zero or string having wrong format for
// conversion to another type. I.e., things the DFDL schema author could get
// wrong that some data inputs would exacerbate.
//
// This should not catch things that indicate a Daffodil code problem e.g.,
// class cast exceptions.
//
// Of course some things that are daffodil code bugs can hide - if for example
// there is an arithmetic error in daffodil code, this catch can't distinguish
// that error (which should be an abort, from an arithmetic exception
// due to an expression dividing by zero say.
case e: InfosetException => handleCompileState(e, state)
case e: VariableException => handleCompileState(e, state)
case e: IllegalStateException => doPE(e, state)
case e: NumberFormatException => doPE(e, state)
case e: IllegalArgumentException => doPE(e, state)
case e: ArithmeticException => doPE(e, state)
case th => throw th
}
}
/**
* Accepts a value or null to mean there is no value.
*
* Note: Can't use a Maybe[T] here because T is required to be an AnyRef, and that would exclude
* Long, Int, etc. from being used as values.
*/
private def postProcess(v: Maybe[AnyRef], state: ParseOrUnparseState): Maybe[T] = {
if (v.isEmpty) Nope
else {
val value = v.get
val value1 =
if (!value.isInstanceOf[DIElement]) {
targetType match {
case NodeInfo.AnyType => value // ok
case NodeInfo.Long => asLong(value)
case NodeInfo.UnsignedLong => asLong(value)
case NodeInfo.NonEmptyString => {
Assert.invariant(value.isInstanceOf[String])
ci.schemaDefinitionUnless(value.asInstanceOf[String].length > 0,
"Non-empty string required.")
value
}
case NodeInfo.DateTime | NodeInfo.Date | NodeInfo.Time => {
Assert.invariant(value.isInstanceOf[DFDLCalendar])
value
}
case _: NodeInfo.String.Kind => {
Assert.invariant(value.isInstanceOf[String])
value
}
case NodeInfo.Boolean => {
Assert.invariant(value.isInstanceOf[Boolean])
value
}
case NodeInfo.HexBinary => {
Assert.invariant(value.isInstanceOf[Array[Byte]])
value
}
case _ => // TODO: add more checks. E.g., that proper type matching occurred for all the number
// and date types as well.
value
}
} else {
value
}
One(value1.asInstanceOf[T])
}
}
}