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") }

Reply via email to