This is an automated email from the ASF dual-hosted git repository.
jadams pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/daffodil.git
The following commit(s) were added to refs/heads/master by this push:
new 04beffc New variable instances should inherit external values
04beffc is described below
commit 04beffc77afb93e51098c78eaf33ca0c38956e5b
Author: Josh Adams <[email protected]>
AuthorDate: Mon Mar 15 13:30:55 2021 -0400
New variable instances should inherit external values
There was a bug where variables that were given external values were not
having those values passed on by default to newVariableInstance's. This
commit addresses this issue by setting NVI's to the externally provided
value by default if no other default value is specified for the NVI.
This commit also provides a general cleanup of how we handle the state
tracking for variables in general, which makes following the logic of
how we handle variables much easier to understand.
DAFFODIL-2481
---
.../apache/daffodil/dsom/DFDLDefineVariable.scala | 2 +-
.../daffodil/dsom/TestExternalVariablesNew.scala | 9 +-
.../externalvars/TestExternalVariablesLoader.scala | 132 ---------------
.../TestExternalVariablesLoaderNew.scala | 66 --------
.../unparsers/ExpressionEvaluatingUnparsers.scala | 28 ++--
.../org/apache/daffodil/dpath/DPathRuntime.scala | 10 +-
.../apache/daffodil/processors/DataProcessor.scala | 7 +
.../apache/daffodil/processors/RuntimeData.scala | 27 +--
.../apache/daffodil/processors/VariableMap1.scala | 182 +++++++++------------
.../parsers/ExpressionEvaluatingParsers.scala | 13 +-
.../daffodil/processors/parsers/PState.scala | 6 +-
.../external_variables/external_variables.tdml | 105 +++++++++++-
.../daffodil/section07/variables/variables.tdml | 30 ++++
.../external_variables/TestExternalVariables.scala | 12 ++
.../section07/variables/TestVariables.scala | 1 +
15 files changed, 274 insertions(+), 356 deletions(-)
diff --git
a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/DFDLDefineVariable.scala
b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/DFDLDefineVariable.scala
index e51bde1..ab8f9eb 100644
---
a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/DFDLDefineVariable.scala
+++
b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/DFDLDefineVariable.scala
@@ -131,7 +131,7 @@ final class DFDLNewVariableInstance(node: Node, decl:
AnnotatedSchemaComponent)
private lazy val defaultValueAsElement = node.child.text.trim
final lazy val defaultValue = (defaultValueAsAttribute,
defaultValueAsElement) match {
- case (None, "") => defv.defaultValue
+ case (None, "") => None
case (None, str) => Some(str)
case (Some(str), "") => Some(str)
case (Some(str), v) => schemaDefinitionError("Default value of variable
was supplied both as attribute and element value: %s", node.toString)
diff --git
a/daffodil-core/src/test/scala/org/apache/daffodil/dsom/TestExternalVariablesNew.scala
b/daffodil-core/src/test/scala/org/apache/daffodil/dsom/TestExternalVariablesNew.scala
index 8ac487f..55a4345 100644
---
a/daffodil-core/src/test/scala/org/apache/daffodil/dsom/TestExternalVariablesNew.scala
+++
b/daffodil-core/src/test/scala/org/apache/daffodil/dsom/TestExternalVariablesNew.scala
@@ -155,7 +155,12 @@ class TestExternalVariablesNew {
case Success(None) => fail("Did not find " + keyToFind + " in the
VariableMap.")
case Success(Some(variab)) => {
// Found var1 but is the value correct?
-
assertTrue(variab.toString.contains("VariableInstance(VariableDefined,DataValue("
+ expectedValue + ")"))
+ // Variables that aren't externally defined will be undefined, but
+ // expected value should be in the expression
+ if (variab.toString.contains("VariableDefined"))
+ assertTrue(variab.toString.contains("DataValue(" + expectedValue +
")"))
+ else
+ assertTrue(variab.toString.contains("CompiledExpression(" +
expectedValue + ")"))
}
}
}
@@ -252,7 +257,7 @@ class TestExternalVariablesNew {
checkResult(dp.variableMap, "{}var2", "value2")
// The other var2's namespace was http://example.com, so we expect
- // it to be unchanged.
+ // it to be undefined and without value.
checkResult(dp.variableMap, "{http://example.com}var2", "default2.1")
}
diff --git
a/daffodil-core/src/test/scala/org/apache/daffodil/externalvars/TestExternalVariablesLoader.scala
b/daffodil-core/src/test/scala/org/apache/daffodil/externalvars/TestExternalVariablesLoader.scala
deleted file mode 100644
index 9d0e3bb..0000000
---
a/daffodil-core/src/test/scala/org/apache/daffodil/externalvars/TestExternalVariablesLoader.scala
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * 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.externalvars
-
-import org.apache.daffodil.xml._
-import org.apache.daffodil.util._
-import scala.xml._
-import org.junit.Assert._
-import org.junit.Test
-import org.apache.daffodil.dsom.SchemaSet
-import org.apache.daffodil.xml.NS
-import org.apache.daffodil.Implicits._
-import org.junit.Test
-import org.apache.daffodil.dsom.SchemaDefinitionError
-import scala.util.Success
-import org.apache.daffodil.processors.VariableMap
-import org.apache.daffodil.api.DaffodilTunables
-
-class TestExternalVariablesLoader extends Logging {
- val xsd = XMLUtils.XSD_NAMESPACE
- val dfdl = XMLUtils.DFDL_NAMESPACE
- val xsi = XMLUtils.XSI_NAMESPACE
- val example = XMLUtils.EXAMPLE_NAMESPACE
-
- val dummyGroupRef = null // just because otherwise we have to construct too
many things.
-
- val tunable = DaffodilTunables()
-
- def generateSD(topLevelAnnotations: Seq[Node] = <dfdl:format
ref="tns:GeneralFormat"/>) = {
- lazy val sch = SchemaUtils.dfdlTestSchema(
- <xs:include
schemaLocation="org/apache/daffodil/xsd/DFDLGeneralFormat.dfdl.xsd"/>,
- topLevelAnnotations,
- <xs:element name="fake" type="xs:string" dfdl:lengthKind="delimited"/>
- <xs:element name="fake2" type="tns:fakeCT"/>
- <xs:complexType name="fakeCT">
- <xs:sequence>
- <xs:group ref="tns:fakeGroup"/>
- <xs:element ref="tns:fake"/>
- </xs:sequence>
- </xs:complexType>
- <xs:group name="fakeGroup">
- <xs:choice>
- <xs:sequence/>
- </xs:choice>
- </xs:group>)
- lazy val xsd_sset = new SchemaSet(sch, "http://example.com", "fake")
- lazy val xsd_schema = xsd_sset.getSchema(NS("http://example.com")).get
- lazy val fakeSD = xsd_schema.schemaDocuments(0)
- (fakeSD, xsd_sset)
- }
-
- def FindValue(collection: Map[String, String], key: String, value: String):
Boolean = {
- val found: Boolean = Option(collection.find(x => x._1 == key && x._2 ==
value)) match {
- case Some(_) => true
- case None => false
- }
- found
- }
-
- @Test def test_override_success() = {
- val extVarFile1 = Misc.getRequiredResource("/test/external_vars_1.xml")
-
- val topLevelAnnotations = {
- <dfdl:format ref="tns:GeneralFormat"/>
- <dfdl:defineVariable name="v_no_default" type="xs:int" external="true"/>
- <dfdl:defineVariable name="v_with_default" type="xs:int" external="true"
defaultValue="42"/>
- }
-
- val Success(v_no_default) =
- QName.refQNameFromExtendedSyntax("{http://example.com}v_no_default").map
{ _.toGlobalQName }
- val Success(v_with_default) =
-
QName.refQNameFromExtendedSyntax("{http://example.com}v_with_default").map {
_.toGlobalQName }
-
- val (sd, sset) = generateSD(topLevelAnnotations)
- val initialVMap = sset.variableMap
-
- val abuf_v_no_default = initialVMap.getVariableBindings(v_no_default)
- val abuf_v_with_default = initialVMap.getVariableBindings(v_with_default)
-
- val var_v_no_default = abuf_v_no_default.last
- val var_v_with_default = abuf_v_with_default.last
-
- val v_no_default_vrd = var_v_no_default.rd
- val v_with_default_vrd = var_v_with_default.rd
-
- // Verify that v_no_default isn't defined
- assertFalse(var_v_no_default.value.isDefined)
-
- // Verify that v_with_default is defined and is 42.
- assertTrue(var_v_with_default.value.isDefined)
- assertEquals("42", var_v_with_default.value.getAnyRef.toString())
-
- val bindings = ExternalVariablesLoader.uriToBindings(extVarFile1)
- val vmap = ExternalVariablesLoader.loadVariables(bindings, sd, initialVMap)
-
- // Verify that the external variables override the previous values
- // in the VariableMap
- val value1 = vmap.find(v_no_default_vrd.globalQName).get.value
- assertEquals(1, value1.getAnyRef)
- val value2 = vmap.find(v_with_default_vrd.globalQName).get.value
- assertEquals(2, value2.getAnyRef)
- }
-
- @Test def test_ext_var_not_match_defined_var() = {
- val extVarFile1 = Misc.getRequiredResource("/test/external_vars_1.xml")
-
- val e = intercept[SchemaDefinitionError] {
- // fakeSD does not contain any defineVariables
- // Because we are trying to load external variables and none are defined
we should SDE.
- val bindings = ExternalVariablesLoader.uriToBindings((extVarFile1))
- ExternalVariablesLoader.loadVariables(bindings, Fakes.fakeSD, new
VariableMap())
- }
- val err = e.getMessage()
- assertTrue(err.contains("unknown variable ex:v_no_default"))
- }
-
-}
diff --git
a/daffodil-core/src/test/scala/org/apache/daffodil/externalvars/TestExternalVariablesLoaderNew.scala
b/daffodil-core/src/test/scala/org/apache/daffodil/externalvars/TestExternalVariablesLoaderNew.scala
deleted file mode 100644
index 5f04309..0000000
---
a/daffodil-core/src/test/scala/org/apache/daffodil/externalvars/TestExternalVariablesLoaderNew.scala
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * 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.externalvars
-
-import org.apache.daffodil.xml.XMLUtils
-import org.apache.daffodil.util._
-import scala.xml._
-import org.apache.daffodil.dsom.SchemaSet
-import org.apache.daffodil.xml.NS
-import org.apache.daffodil.Implicits._; object INoWarn1 {
ImplicitsSuppressUnusedImportWarning() }
-
-class TestExternalVariablesLoaderNew extends Logging {
- val xsd = XMLUtils.XSD_NAMESPACE
- val dfdl = XMLUtils.DFDL_NAMESPACE
- val xsi = XMLUtils.XSI_NAMESPACE
- val example = XMLUtils.EXAMPLE_NAMESPACE
-
- val dummyGroupRef = null // just because otherwise we have to construct too
many things.
-
- def generateSD(topLevelAnnotations: Seq[Node] = <dfdl:format
ref="tns:GeneralFormat"/>) = {
- lazy val sch = SchemaUtils.dfdlTestSchema(
- <xs:include
schemaLocation="org/apache/daffodil/xsd/DFDLGeneralFormat.dfdl.xsd"/>,
- topLevelAnnotations,
- <xs:element name="fake" type="xs:string" dfdl:lengthKind="delimited"/>
- <xs:element name="fake2" type="tns:fakeCT"/>
- <xs:complexType name="fakeCT">
- <xs:sequence>
- <xs:group ref="tns:fakeGroup"/>
- <xs:element ref="tns:fake"/>
- </xs:sequence>
- </xs:complexType>
- <xs:group name="fakeGroup">
- <xs:choice>
- <xs:sequence/>
- </xs:choice>
- </xs:group>)
- lazy val xsd_sset = new SchemaSet(sch, "http://example.com", "fake")
- lazy val xsd_schema = xsd_sset.getSchema(NS("http://example.com")).get
- lazy val fakeSD = xsd_schema.schemaDocuments(0)
- (fakeSD, xsd_sset)
- }
-
- def FindValue(collection: Map[String, String], key: String, value: String):
Boolean = {
- val found: Boolean = Option(collection.find(x => x._1 == key && x._2 ==
value)) match {
- case Some(_) => true
- case None => false
- }
- found
- }
-
-}
diff --git
a/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/ExpressionEvaluatingUnparsers.scala
b/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/ExpressionEvaluatingUnparsers.scala
index c237eeb..39eca0d 100644
---
a/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/ExpressionEvaluatingUnparsers.scala
+++
b/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/ExpressionEvaluatingUnparsers.scala
@@ -30,8 +30,8 @@ import org.apache.daffodil.processors.NonTermRuntimeData
import org.apache.daffodil.processors.Success
import org.apache.daffodil.processors.TypeCalculator
import org.apache.daffodil.processors.VariableRuntimeData
-import org.apache.daffodil.processors.{ VariableInProcess, VariableDefined }
-//import org.apache.daffodil.processors.SuspendableOperation
+import org.apache.daffodil.processors.VariableInProcess
+import org.apache.daffodil.processors.VariableInstance
import org.apache.daffodil.util.Maybe
import org.apache.daffodil.util.MaybeULong
@@ -78,16 +78,14 @@ final class SetVariableUnparser(
}
-final class NewVariableInstanceSuspendableExpression(
+final class NewVariableInstanceDefaultValueSuspendableExpression(
override val expr: CompiledExpression[AnyRef],
- override val rd: VariableRuntimeData)
+ override val rd: VariableRuntimeData,
+ nvi: VariableInstance)
extends SuspendableExpression {
override protected def processExpressionResult(ustate: UState, v:
DataValuePrimitive): Unit = {
- val vi = ustate.variableMap.find(rd.globalQName)
- Assert.invariant(vi.isDefined)
- vi.get.setState(VariableDefined)
- vi.get.setValue(v)
+ nvi.setDefaultValue(v) // This also sets variable state to VariableDefined
}
override protected def maybeKnownLengthInBits(ustate: UState) = MaybeULong(0)
@@ -103,13 +101,17 @@ class NewVariableInstanceStartUnparser(override val
context: VariableRuntimeData
override lazy val childProcessors = Vector()
override def unparse(state: UState) = {
- state.variableMap.newVariableInstance(context)
+ val nvi = state.variableMap.newVariableInstance(context)
+
if (context.maybeDefaultValueExpr.isDefined) {
- val maybeVar = state.variableMap.find(context.globalQName)
- Assert.invariant(maybeVar.isDefined)
- maybeVar.get.setState(VariableInProcess)
- val suspendableExpression = new
NewVariableInstanceSuspendableExpression(context.maybeDefaultValueExpr.get,
context)
+ val dve = context.maybeDefaultValueExpr.get
+ nvi.setState(VariableInProcess)
+ val suspendableExpression = new
NewVariableInstanceDefaultValueSuspendableExpression(dve, context, nvi)
suspendableExpression.run(state)
+ } else if (nvi.firstInstanceInitialValue.isDefined) {
+ // The NVI will inherit the default value of the original variable
instance
+ // This will also inherit any externally provided bindings.
+ nvi.setDefaultValue(nvi.firstInstanceInitialValue)
}
}
}
diff --git
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/dpath/DPathRuntime.scala
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/dpath/DPathRuntime.scala
index edd51e3..1337696 100644
---
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/dpath/DPathRuntime.scala
+++
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/dpath/DPathRuntime.scala
@@ -55,9 +55,15 @@ class CompiledDPath(val ops: RecipeOp*) extends Serializable
{
*/
def runExpression(state: ParseOrUnparseState, dstate: DState): Unit = {
dstate.opIndex = 0
- dstate.setCurrentNode(state.thisElement.asInstanceOf[DINode])
+
+ // This if statement is necessary as defineVariable statements can contain
+ // expressions that are evaluated prior to an infoset being generated
+ if (state.currentNode.isDefined) {
+ dstate.setCurrentNode(state.thisElement.asInstanceOf[DINode])
+ dstate.setContextNode(state.thisElement.asInstanceOf[DINode]) // used
for diagnostics
+ }
+
dstate.setVMap(state.variableMap)
- dstate.setContextNode(state.thisElement.asInstanceOf[DINode]) // used for
diagnostics
dstate.setArrayPos(state.arrayPos)
dstate.setErrorOrWarn(state)
dstate.setParseOrUnparseState(state)
diff --git
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/DataProcessor.scala
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/DataProcessor.scala
index 50c0692..ef6c426 100644
---
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/DataProcessor.scala
+++
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/DataProcessor.scala
@@ -478,6 +478,7 @@ class DataProcessor private (
// Force the evaluation of any defineVariable's with non-constant
default
// value expressions
state.variableMap.forceExpressionEvaluations(state)
+ state.variableMap.setFirstInstanceInitialValues()
this.startElement(state, p)
p.parse1(state)
@@ -627,6 +628,12 @@ class DataProcessor private (
mtrd.isDefined &&
(mtrd.get eq rootUnparser.context)
}
+
+ // Force the evaluation of any defineVariable's with non-constant default
+ // value expressions
+ state.variableMap.forceExpressionEvaluations(state)
+ state.variableMap.setFirstInstanceInitialValues()
+
rootUnparser.unparse1(state)
state.popTRD(rootUnparser.context.asInstanceOf[TermRuntimeData])
diff --git
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/RuntimeData.scala
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/RuntimeData.scala
index 00ef060..8e941dd 100644
---
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/RuntimeData.scala
+++
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/RuntimeData.scala
@@ -22,7 +22,6 @@ import
org.apache.daffodil.Implicits.ImplicitsSuppressUnusedImportWarning
import org.apache.daffodil.dpath.NodeInfo
import org.apache.daffodil.dpath.NodeInfo.PrimType
import org.apache.daffodil.dsom.CompiledExpression
-import org.apache.daffodil.dsom.ConstantExpression
import org.apache.daffodil.dsom.DPathCompileInfo
import org.apache.daffodil.dsom.DPathElementCompileInfo
import org.apache.daffodil.dsom.FacetTypes
@@ -55,7 +54,6 @@ import java.util.regex.Matcher
import org.apache.daffodil.api.UnqualifiedPathStepPolicy
import org.apache.daffodil.infoset.DISimple
import org.apache.daffodil.infoset.DataValue
-import org.apache.daffodil.infoset.DataValue.DataValuePrimitiveNullable
import
org.apache.daffodil.infoset.DataValue.DataValuePrimitiveOrUseNilForDefaultOrNull
import org.apache.daffodil.processors.unparsers.UnparseError
import org.apache.daffodil.schema.annotation.props.gen.OccursCountKind
@@ -978,29 +976,6 @@ final class VariableRuntimeData(
@throws(classOf[java.io.IOException])
final private def writeObject(out: java.io.ObjectOutputStream): Unit =
serializeObject(out)
- private lazy val state =
- if (!maybeDefaultValueExpr.isDefined) VariableUndefined
- else VariableDefined
-
- private lazy val value: DataValuePrimitiveNullable =
- if (maybeDefaultValueExpr.isEmpty) DataValue.NoValue
- else {
- val defaultValueExpr = maybeDefaultValueExpr.get
- defaultValueExpr match {
- case constExpr: ConstantExpression[_] =>
DataValue.unsafeFromAnyRef(constExpr.constant)
- case _ => DataValue.NoValue
- }
- }
-
- // Pass by name/lazy arg is needed here as we need to postpone evaluating the
- // defaultValue, which is most likely an expression, until after the inital
- // VariableMap is created. Otherwise when we attempt to evaluate the
- // expression it will look in the VariableMap, which hasn't been fully
- // created yet and will trigger an OOLAG Circular Definition Exception
- def createVariableInstance(defaultValue: => DataValuePrimitiveNullable):
VariableInstance = {
- VariableInstance(state, defaultValue, this, maybeDefaultValueExpr)
- }
-
- def createVariableInstance(): VariableInstance = VariableInstance(state,
value, this, maybeDefaultValueExpr)
+ def createVariableInstance(): VariableInstance = VariableInstance(rd=this)
}
diff --git
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/VariableMap1.scala
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/VariableMap1.scala
index 958ba3d..a51c2c5 100644
---
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/VariableMap1.scala
+++
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/VariableMap1.scala
@@ -18,7 +18,6 @@
package org.apache.daffodil.processors
import org.apache.daffodil.api.ThinDiagnostic
-import org.apache.daffodil.dsom.CompiledExpression
import org.apache.daffodil.dpath.InvalidPrimitiveDataException
import org.apache.daffodil.exceptions.Assert
import org.apache.daffodil.exceptions.ThrowsSDE
@@ -29,8 +28,6 @@ import
org.apache.daffodil.infoset.DataValue.DataValuePrimitiveNullable
import org.apache.daffodil.infoset.RetryableException
import org.apache.daffodil.util.Maybe
import org.apache.daffodil.util.Maybe.Nope
-import org.apache.daffodil.util.PreSerialization
-import org.apache.daffodil.util.TransientParam
import org.apache.daffodil.xml.{GlobalQName, UnspecifiedNamespace, NamedQName,
RefQName}
import org.apache.daffodil.processors.parsers.PState
import org.apache.daffodil.processors.unparsers.UState
@@ -40,17 +37,36 @@ import scala.collection.mutable.{Map, ArrayBuffer}
sealed abstract class VariableState extends Serializable
+/**
+ * The variable has no value. Attempting to read the variable will result in an
+ * error. This is the initial state of all variables. Variables will remain in
+ * this state until their defaultValue expression (if it exists) is evaluated
or
+ * until the variable is set.
+ */
case object VariableUndefined extends VariableState
+/**
+ * The variable is defined with either a default or externally provided value.
+ * Both reading and setting are allowed.
+ */
case object VariableDefined extends VariableState
/**
- * Used to detect circular definitions of variables
+ * We are in the process of setting a default value for a defineVariable
+ * statement and this state is used to catch circular definitions of variables.
*/
case object VariableBeingDefined extends VariableState
+/**
+ * The variable has been set to a value, but has not been read yet. Reading is
+ * allowed, but multiple sets will result in an error.
+ */
case object VariableSet extends VariableState
+/**
+ * The variable's value has been read. The value can no longer change. Setting
+ * will cause an error.
+ */
case object VariableRead extends VariableState
/**
@@ -65,123 +81,70 @@ case object VariableInProcess extends VariableState
/**
* Class for maintaining the state of a variable
- *
- * Note: stateArg, valueArg, and defaultValueExprArg are pass by name/lazy
- * values as it is necessary to postpone their evaluation beyond when the
- * VariableInstance is created. Attempting to evaluate these arguments will
- * likely trigger an expression to be evaluated, which will query the
- * VariableMap if the expression references another variable. If this is
- * occurring within a defineVariable call, the VariableMap hasn't been fully
- * created and attempting to evaluate such an expression will result in an
OOLAG
- * Circular Definition Exception
*/
object VariableInstance {
- def apply(
- stateArg: => VariableState,
- valueArg: => DataValuePrimitiveNullable,
- rd: VariableRuntimeData,
- defaultValueExprArg: => Maybe[CompiledExpression[AnyRef]],
- priorState: VariableState = VariableUndefined,
- priorValue: DataValuePrimitiveNullable = DataValue.NoValue) = {
-
- new VariableInstance(stateArg, valueArg, rd, defaultValueExprArg,
priorState, priorValue)
+ def apply(rd: VariableRuntimeData) = {
+ new VariableInstance(rd)
}
}
/**
* See documentation for object VariableInstance
*/
-class VariableInstance private (
- @TransientParam stateArg: => VariableState,
- @TransientParam valueArg: => DataValuePrimitiveNullable,
- val rd: VariableRuntimeData,
- @TransientParam defaultValueExprArg: => Maybe[CompiledExpression[AnyRef]],
- var priorState: VariableState = VariableUndefined,
- var priorValue: DataValuePrimitiveNullable = DataValue.NoValue)
- extends Serializable
- with PreSerialization {
-
- lazy val defaultValueExpr = defaultValueExprArg
-
- private var _value: Option[DataValuePrimitiveNullable] = None
- private var _state: VariableState = null
-
- /**
- * Returns the current value of the VariableInstance
- *
- * This function allows value to be set while also enabling valueArg to be
- * lazily evaluated. This allows the VariableInstance to support setting the
- * value later and allows the construction of the VariableMap containing the
- * VariableInstance before evaluating the default value expression
- */
- def value = {
- if (_value.isEmpty) {
- _value = Some(valueArg)
- }
-
- _value.get
- }
+class VariableInstance private (val rd: VariableRuntimeData)
+ extends Serializable {
- /**
- * Returns the current state of the VariableInstance
- *
- * This function allows state to be set while also enabling stateArg to be
- * lazily evaluated. This allows the VariableInstance to support setting the
- * state later and allows the construction of the VariableMap containing the
- * VariableInstance before evaluating the default value expression
- */
- def state = {
- if (_state == null) {
- _state = stateArg
- }
+ var state: VariableState = VariableUndefined
+ var priorState: VariableState = VariableUndefined
+ var value: DataValuePrimitiveNullable = DataValue.NoValue
+ var priorValue: DataValuePrimitiveNullable = DataValue.NoValue
- _state
- }
+ // This represents the default value at the start of processing, provided by
+ // either the defaultValue expression or by an external binding
+ var firstInstanceInitialValue: DataValuePrimitiveNullable = DataValue.NoValue
def setState(s: VariableState) = {
this.priorState = this.state
- this._state = s
+ this.state = s
}
def setValue(v: DataValuePrimitiveNullable) = {
this.priorValue = this.value
- this._value = Some(v)
+ this.value = v
}
- def setDefaultValue(v: DataValuePrimitiveNullable) = this._value = Some(v)
-
- override def preSerialization: Unit = {
- defaultValueExpr
- value
- state
+ /* This is used to set a default value without assigning a priorValue */
+ def setDefaultValue(v: DataValuePrimitiveNullable) = {
+ Assert.invariant((this.state == VariableUndefined || this.state ==
VariableInProcess) && v.isDefined)
+ this.state = VariableDefined
+ this.value = v
}
- override def toString: String =
"VariableInstance(%s,%s,%s,%s,%s,%s,%s)".format(
+ override def toString: String = "VariableInstance(%s,%s,%s,%s,%s,%s)".format(
state,
value,
rd,
- defaultValueExpr,
+ rd.maybeDefaultValueExpr,
priorState,
- priorValue,
- this.hashCode)
+ priorValue)
def copy(
state: VariableState = state,
value: DataValuePrimitiveNullable = value,
rd: VariableRuntimeData = rd,
- defaultValueExpr: Maybe[CompiledExpression[AnyRef]] = defaultValueExpr,
priorState: VariableState = priorState,
priorValue: DataValuePrimitiveNullable = priorValue) = {
- val inst = new VariableInstance(state, value, rd, defaultValueExpr,
priorState, priorValue)
- inst.init()
+ val inst = new VariableInstance(rd)
+ inst.state = state
+ inst.priorState = priorState
+ inst.value = value
+ inst.priorValue = priorValue
inst
}
- def init() = preSerialization
-
def reset() = {
Assert.invariant(this.state != VariableUndefined)
- (this.state, this.priorState, this.defaultValueExpr.isDefined) match {
+ (this.state, this.priorState, this.rd.maybeDefaultValueExpr.isDefined)
match {
case (VariableRead, VariableSet, _) => this.setState(VariableSet)
case (VariableRead, _, true) => this.setState(VariableDefined)
case (VariableSet, _, _) => {
@@ -324,17 +287,31 @@ class VariableMap private(vTable: Map[GlobalQName,
ArrayBuffer[VariableInstance]
// have default value expressions or are defined externally.
def forceExpressionEvaluations(state: ParseOrUnparseState): Unit = {
vTable.foreach { case (_, variableInstances) => {
variableInstances.foreach { inst => {
- (inst.state, inst.value, inst.defaultValueExpr.isDefined) match {
+ (inst.state, inst.rd.maybeDefaultValueExpr.isDefined) match {
// Evaluate defineVariable statements with non-constant default value
expressions
- case (VariableDefined, DataValue.NoValue, true) => {
- val res = inst.defaultValueExpr.get.evaluate(state)
- inst.setValue(DataValue.unsafeFromAnyRef(res))
+ case (VariableUndefined, true) => {
+ val res = inst.rd.maybeDefaultValueExpr.get.evaluate(state)
+ inst.setDefaultValue(DataValue.unsafeFromAnyRef(res))
}
- case (_, _, _) => // Do nothing
+ case (_, _) => // Do nothing
}
}}}}
}
+
+ /**
+ * This function is called immediately after forceExpressionEvaluations in
+ * order to set the firstInstanceInitialValue for each variable instance.
+ * These initial values will be inherited by future new variable instances if
+ * the newVariableInstance statement does not provide a default value
+ * expression
+ */
+ def setFirstInstanceInitialValues(): Unit = {
+ vTable.foreach { case (_, variableInstances) => {
+ variableInstances(0).firstInstanceInitialValue =
variableInstances(0).value
+ }}
+ }
+
def find(qName: GlobalQName): Option[VariableInstance] = {
val optBuf = vTable.get(qName)
val variab = {
@@ -395,9 +372,9 @@ class VariableMap private(vTable: Map[GlobalQName,
ArrayBuffer[VariableInstance]
// of parsing, before an infoset is generated, via the
// forceExpressionEvaluations function after which all variables should
// have a defined value
- case VariableDefined if (!variable.value.isDefined &&
variable.defaultValueExpr.isDefined) => {
+ case VariableUndefined if
(variable.rd.maybeDefaultValueExpr.isDefined) => {
variable.setState(VariableBeingDefined)
- val res =
DataValue.unsafeFromAnyRef(variable.defaultValueExpr.get.evaluate(pstate))
+ val res =
DataValue.unsafeFromAnyRef(variable.rd.maybeDefaultValueExpr.get.evaluate(pstate))
// Need to update the variable's value with the result of the
// expression
@@ -463,26 +440,16 @@ class VariableMap private(vTable: Map[GlobalQName,
ArrayBuffer[VariableInstance]
}
/**
- * Creates a new instance of a variable with default value
- */
- def newVariableInstance(vrd: VariableRuntimeData, defaultValue:
DataValuePrimitive) = {
- val varQName = vrd.globalQName
- val variableInstances = vTable.get(varQName)
- Assert.invariant(variableInstances.isDefined)
-
- variableInstances.get +=
vrd.createVariableInstance(VariableUtils.convert(defaultValue.getAnyRef.toString,
vrd, vrd))
- }
-
- /**
* Creates a new instance of a variable without default value
*/
- def newVariableInstance(vrd: VariableRuntimeData) = {
+ def newVariableInstance(vrd: VariableRuntimeData): VariableInstance = {
val varQName = vrd.globalQName
val variableInstances = vTable.get(varQName)
Assert.invariant(variableInstances.isDefined)
-
- val v = vrd.createVariableInstance()
- variableInstances.get += v
+ val nvi = vrd.createVariableInstance()
+ nvi.firstInstanceInitialValue =
variableInstances.get(0).firstInstanceInitialValue
+ variableInstances.get += nvi
+ nvi
}
def removeVariableInstance(vrd: VariableRuntimeData): Unit = {
@@ -547,7 +514,8 @@ class VariableMap private(vTable: Map[GlobalQName,
ArrayBuffer[VariableInstance]
}
case _ => {
- variable.setValue(VariableUtils.convert(newValue.getAnyRef.toString,
variable.rd, referringContext))
+ val value = VariableUtils.convert(newValue.getAnyRef.toString,
variable.rd, referringContext)
+ variable.setValue(value)
variable.setState(VariableDefined)
}
}
diff --git
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/ExpressionEvaluatingParsers.scala
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/ExpressionEvaluatingParsers.scala
index 14cc553..5f5c98b 100644
---
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/ExpressionEvaluatingParsers.scala
+++
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/ExpressionEvaluatingParsers.scala
@@ -165,11 +165,16 @@ class NewVariableInstanceStartParser(override val
context: VariableRuntimeData)
override lazy val runtimeDependencies = Vector()
def parse(start: PState): Unit = {
- start.newVariableInstance(context)
+ val nvi = start.newVariableInstance(context)
+
if (context.maybeDefaultValueExpr.isDefined) {
- val v = start.variableMap.find(context.globalQName).get
- val res =
DataValue.unsafeFromAnyRef(context.maybeDefaultValueExpr.get.evaluate(start))
- v.setDefaultValue(res)
+ val dve = context.maybeDefaultValueExpr.get
+ val res = DataValue.unsafeFromAnyRef(dve.evaluate(start))
+ nvi.setDefaultValue(res)
+ } else if (nvi.firstInstanceInitialValue.isDefined){
+ // The NVI will inherit the default value of the original variable
instance
+ // This will also inherit any externally provided bindings.
+ nvi.setDefaultValue(nvi.firstInstanceInitialValue)
}
}
}
diff --git
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/PState.scala
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/PState.scala
index 9dbc258..c7352b4 100644
---
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/PState.scala
+++
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/PState.scala
@@ -49,6 +49,7 @@ import org.apache.daffodil.processors.RuntimeData
import org.apache.daffodil.processors.TermRuntimeData
import org.apache.daffodil.processors.VariableMap
import org.apache.daffodil.processors.VariableRuntimeData
+import org.apache.daffodil.processors.VariableInstance
import org.apache.daffodil.processors.dfa
import org.apache.daffodil.processors.dfa.DFADelimiter
import org.apache.daffodil.util.MStack
@@ -343,9 +344,10 @@ final class PState private (
changedVariablesStack.top += vrd.globalQName
}
- def newVariableInstance(vrd: VariableRuntimeData): Unit = {
- variableMap.newVariableInstance(vrd)
+ def newVariableInstance(vrd: VariableRuntimeData): VariableInstance = {
+ val v = variableMap.newVariableInstance(vrd)
changedVariablesStack.top += vrd.globalQName
+ v
}
def removeVariableInstance(vrd: VariableRuntimeData): Unit = {
diff --git
a/daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/external_variables.tdml
b/daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/external_variables.tdml
index e3aa44e..07c2f36 100644
---
a/daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/external_variables.tdml
+++
b/daffodil-test/src/test/resources/org/apache/daffodil/section07/external_variables/external_variables.tdml
@@ -148,6 +148,27 @@
</tdml:dfdlInfoset>
</tdml:infoset>
+ </tdml:parserTestCase>
+
+ <!-- Overrides define variables -->
+ <tdml:defineConfig name="cfg_err">
+ <daf:externalVariableBindings>
+ <daf:bind name="ex:does_not_exist">1</daf:bind>
+ <daf:bind name="ex:v_with_default">2</daf:bind>
+ </daf:externalVariableBindings>
+ </tdml:defineConfig>
+
+ <!-- Overrides define variables -->
+ <tdml:parserTestCase name="external_var_dne"
+ root="c" model="v" config="cfg_err"
+ description="Should error, attempting to bind to a non-existent
variable">
+
+ <tdml:document />
+
+ <tdml:errors>
+ <tdml:error>Schema Definition Error</tdml:error>
+ <tdml:error>unknown variable</tdml:error>
+ </tdml:errors>
</tdml:parserTestCase>
<!-- Overrides define variables -->
@@ -340,7 +361,89 @@
</tdml:dfdlInfoset>
</tdml:infoset>
- </tdml:parserTestCase>
+ </tdml:parserTestCase>
+
+ <tdml:defineSchema name="v5">
+ <dfdl:defineVariable name="v_with_default" type="xsd:int"
+ defaultValue="1" external="true" />
+ <xs:include
schemaLocation="org/apache/daffodil/xsd/DFDLGeneralFormat.dfdl.xsd"/>
+ <dfdl:format ref="ex:GeneralFormat" />
+
+ <xs:element name="c">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:annotation>
+ <xs:appinfo source="http://www.ogf.org/dfdl/">
+ <dfdl:setVariable ref="ex:v_with_default" value="2" />
+ </xs:appinfo>
+ </xs:annotation>
+ <xs:element name="beforeNVIsetVar" type="xsd:int"
dfdl:inputValueCalc="{ $ex:v_with_default }" />
+ <xs:sequence>
+ <xs:annotation>
+ <xs:appinfo source="http://www.ogf.org/dfdl/">
+ <dfdl:newVariableInstance ref="ex:v_with_default" />
+ </xs:appinfo>
+ </xs:annotation>
+ <xs:element name="afterNVI" type="xsd:int" dfdl:inputValueCalc="{
$ex:v_with_default }" />
+ <xs:sequence>
+ <xs:annotation>
+ <xs:appinfo source="http://www.ogf.org/dfdl/">
+ <dfdl:newVariableInstance ref="ex:v_with_default"
defaultValue="4" />
+ </xs:appinfo>
+ </xs:annotation>
+ <xs:element name="afterNVIwithDefault" type="xsd:int"
dfdl:inputValueCalc="{ $ex:v_with_default }" />
+ </xs:sequence>
+ </xs:sequence>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+
+ </tdml:defineSchema>
+
+ <tdml:defineConfig name="config_06">
+ <daf:externalVariableBindings>
+ <daf:bind name="ex:v_with_default">3</daf:bind>
+ </daf:externalVariableBindings>
+ </tdml:defineConfig>
+
+ <!-- Overrides some define variables -->
+ <tdml:parserTestCase name="override_define_vars_06"
+ root="c" model="v5" config="config_06"
+ description="Verify that a newVariableInstance of an externally defined
variable inherits the external value by default">
+
+ <tdml:document />
+
+ <tdml:infoset>
+ <tdml:dfdlInfoset>
+ <ex:c>
+ <beforeNVIsetVar xsi:type="xsd:int">2</beforeNVIsetVar>
+ <afterNVI xsi:type="xsd:int">3</afterNVI>
+ <afterNVIwithDefault xsi:type="xsd:int">4</afterNVIwithDefault>
+ </ex:c>
+
+ </tdml:dfdlInfoset>
+ </tdml:infoset>
+ </tdml:parserTestCase>
+
+ <!-- Overrides some define variables -->
+ <tdml:parserTestCase name="override_define_vars_07"
+ root="c" model="v5"
+ description="Verify that a newVariableInstance of an externally defined
variable inherits the external value by default">
+
+ <tdml:document />
+
+ <tdml:infoset>
+ <tdml:dfdlInfoset>
+ <ex:c>
+ <beforeNVIsetVar xsi:type="xsd:int">2</beforeNVIsetVar>
+ <afterNVI xsi:type="xsd:int">1</afterNVI>
+ <afterNVIwithDefault xsi:type="xsd:int">4</afterNVIwithDefault>
+ </ex:c>
+
+ </tdml:dfdlInfoset>
+ </tdml:infoset>
+ </tdml:parserTestCase>
+
<!-- TODO: The test below doesn't work - left over data.
Furthermore, TDML runner doesn't currently compare attributes, so
diff --git
a/daffodil-test/src/test/resources/org/apache/daffodil/section07/variables/variables.tdml
b/daffodil-test/src/test/resources/org/apache/daffodil/section07/variables/variables.tdml
index 589e658..b56f3d1 100644
---
a/daffodil-test/src/test/resources/org/apache/daffodil/section07/variables/variables.tdml
+++
b/daffodil-test/src/test/resources/org/apache/daffodil/section07/variables/variables.tdml
@@ -521,6 +521,20 @@
</xs:complexType>
</xs:element>
+ <xs:element name="nvi15">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:annotation>
+ <xs:appinfo source="http://www.ogf.org/dfdl/">
+ <dfdl:newVariableInstance ref="ex:myVar1" />
+ </xs:appinfo>
+ </xs:annotation>
+ <xs:element name="a" type="xsd:int" dfdl:lengthKind="explicit"
dfdl:length="1"
+ dfdl:outputValueCalc="{ $ex:myVar1 }" />
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+
<xs:element name="defNonConst">
<xs:complexType>
<xs:sequence>
@@ -972,6 +986,22 @@
</tdml:infoset>
</tdml:parserTestCase>
+ <tdml:unparserTestCase name="varInstance_15" root="nvi15"
+ model="v" roundTrip="false"
+ description="Demonstrates that newVariableInstance inherits default value
from original definition">
+
+ <tdml:infoset>
+ <tdml:dfdlInfoset>
+ <nvi15 xmlns="http://example.com">
+ <a xsi:type="xsd:int">8</a>
+ </nvi15>
+ </tdml:dfdlInfoset>
+ </tdml:infoset>
+
+ <tdml:document>6</tdml:document>
+
+ </tdml:unparserTestCase>
+
<tdml:parserTestCase name="varInstance_08" root="nvi8"
model="v"
description="Demonstrates that newVariableInstance default values cannot
be used in combination with setVariable when unparsing">
diff --git
a/daffodil-test/src/test/scala/org/apache/daffodil/section07/external_variables/TestExternalVariables.scala
b/daffodil-test/src/test/scala/org/apache/daffodil/section07/external_variables/TestExternalVariables.scala
index f30e413..a06f917 100644
---
a/daffodil-test/src/test/scala/org/apache/daffodil/section07/external_variables/TestExternalVariables.scala
+++
b/daffodil-test/src/test/scala/org/apache/daffodil/section07/external_variables/TestExternalVariables.scala
@@ -56,6 +56,14 @@ class TestExternalVariables {
runner.runOneTest("override_define_vars_05")
}
+ @Test def test_override_define_vars_06(): Unit = {
+ runner.runOneTest("override_define_vars_06")
+ }
+
+ @Test def test_override_define_vars_07(): Unit = {
+ runner.runOneTest("override_define_vars_07")
+ }
+
@Test def test_access_default_predefined_vars(): Unit = {
runner.runOneTest("access_default_predefined_vars")
}
@@ -63,4 +71,8 @@ class TestExternalVariables {
@Test def test_set_predefined_var(): Unit = {
runner.runOneTest("set_predefined_var")
}
+
+ @Test def test_external_var_dne(): Unit = {
+ runner.runOneTest("external_var_dne")
+ }
}
diff --git
a/daffodil-test/src/test/scala/org/apache/daffodil/section07/variables/TestVariables.scala
b/daffodil-test/src/test/scala/org/apache/daffodil/section07/variables/TestVariables.scala
index cc6716d..9c75358 100644
---
a/daffodil-test/src/test/scala/org/apache/daffodil/section07/variables/TestVariables.scala
+++
b/daffodil-test/src/test/scala/org/apache/daffodil/section07/variables/TestVariables.scala
@@ -62,6 +62,7 @@ class TestVariables {
@Test def test_varInstance_12(): Unit = {
runner.runOneTest("varInstance_12") }
@Test def test_varInstance_13(): Unit = {
runner.runOneTest("varInstance_13") }
@Test def test_varInstance_14(): Unit = {
runner.runOneTest("varInstance_14") }
+ @Test def test_varInstance_15(): Unit = {
runner.runOneTest("varInstance_15") }
@Test def test_varDirection_1(): Unit = {
runner.runOneTest("varDirection_1") }
@Test def test_varDirection_2(): Unit = {
runner.runOneTest("varDirection_2") }