mbeckerle commented on code in PR #1176:
URL: https://github.com/apache/daffodil/pull/1176#discussion_r1511949267


##########
daffodil-runtime1-layers/src/main/resources/org/apache/daffodil/layers/xsd/boundaryMarkLayer.dfdl.xsd:
##########
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+<schema
+  xmlns:xs="http://www.w3.org/2001/XMLSchema";
+  xmlns="http://www.w3.org/2001/XMLSchema";
+  xmlns:dfdl="http://www.ogf.org/dfdl/dfdl-1.0/";
+  xmlns:dfdlx="http://www.ogf.org/dfdl/dfdl-1.0/extensions";
+  xmlns:bm="urn:org.apache.daffodil.layers.boundaryMark"
+  targetNamespace="urn:org.apache.daffodil.layers.boundaryMark">
+
+  <annotation>
+    <appinfo source="http://www.ogf.org/dfdl/";>
+
+      <dfdl:defineVariable name="boundaryMark" type="xs:string"/>
+      <dfdl:defineVariable name="layerEncoding" type="xs:string"/>

Review Comment:
   Should it be there or in the javadoc?
   
   For DFDL schema users it should be in these files I think. I have not 
written this yet however. 



##########
daffodil-runtime1-layers/src/main/scala/org/apache/daffodil/layers/runtime1/FixedLengthLayer.scala:
##########
@@ -0,0 +1,144 @@
+/*
+ * 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.layers.runtime1
+
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.io.InputStream
+import java.io.OutputStream
+import java.lang.{ Long => JLong }
+import java.nio.ByteBuffer
+
+import org.apache.daffodil.lib.exceptions.Assert
+import org.apache.daffodil.runtime1.layers.api.Layer
+import org.apache.daffodil.runtime1.layers.api.LayerRuntime
+
+import org.apache.commons.io.IOUtils
+
+/**
+ * Suitable only for small sections of data, not large data streams or whole 
files.
+ * See the maxFixedLength value defined herein for the maximum.
+ *
+ * The entire fixed length region of the data will be pulled into a byte 
buffer in memory.
+ *
+ * TODO: Someday, enhance to make this streaming.
+ *
+ * One DFDL Variable is a parameter
+ *   - fixedLength - an unsignedInt giving the fixed length of this layer.
+ *   This length is enforced on both parsing and unparsing the layer.
+ * There are no output/result DFDL variables from this layer.
+ */
+final class FixedLengthLayer(var fixedLength: JLong)
+  extends Layer("fixedLength", "urn:org.apache.daffodil.layers.fixedLength") {
+
+  Assert.invariant(fixedLength > 0)
+
+  /** Required for SPI class loading */
+  def this() = this(1)
+
+  private def maxFixedLength = Short.MaxValue
+
+  override def wrapLayerInput(jis: InputStream, lr: LayerRuntime): InputStream 
= {
+
+    if (fixedLength > maxFixedLength)
+      lr.processingError(
+        s"fixedLength value of $fixedLength is above the maximum of 
$maxFixedLength.",
+      )
+
+    new FixedLengthInputStream(fixedLength.toInt, jis, lr)
+  }
+
+  override def wrapLayerOutput(jos: OutputStream, lr: LayerRuntime): 
OutputStream = {
+
+    if (fixedLength > maxFixedLength)

Review Comment:
   I think this is not worth it.
   
   It's better to just construct the object at schema compile time so as to 
verify everything works, and then do it again, once, on loading.  The latter 
check is really only needed for the chance case that you use the wrong 
jar/class files that don't match the schema.  And even in that case the check 
just verifies that the number of param args and their types and the return 
getters and their types haven't changed. You could still have the wrong jar. 
   



##########
daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/layers/LayerDriver.scala:
##########
@@ -0,0 +1,227 @@
+/*
+ * 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.runtime1.layers
+
+import java.io.FilterInputStream
+import java.io.FilterOutputStream
+import java.io.InputStream
+import java.io.OutputStream
+
+import org.apache.daffodil.io.DataInputStream.Mark
+import org.apache.daffodil.io.DataOutputStream
+import org.apache.daffodil.io.DirectOrBufferedDataOutputStream
+import org.apache.daffodil.io.FormatInfo
+import org.apache.daffodil.io.InputSourceDataInputStream
+import org.apache.daffodil.lib.exceptions.Assert
+import org.apache.daffodil.lib.schema.annotation.props.gen.BitOrder
+import org.apache.daffodil.lib.util.Maybe
+import org.apache.daffodil.lib.util.Maybe.Nope
+import org.apache.daffodil.lib.util.Maybe.One
+import org.apache.daffodil.lib.util.Misc
+import org.apache.daffodil.runtime1.layers.api.Layer
+import org.apache.daffodil.runtime1.processors.unparsers.UState
+
+import passera.unsigned.ULong
+
+/**
+ * Driving mechanism that incorporates a layer at runtime to transform the 
data stream.
+ *
+ * A layer driver is created at runtime as part of a single parse/unparse call.
+ * Hence, they can be stateful without causing thread-safety issues.
+ */
+class LayerDriver(
+  layerRuntimeData: LayerRuntimeData,
+  layer: Layer,
+  layerVarsRuntime: LayerVarsRuntime,
+) {
+
+  private def wrapJavaInputStream(
+    s: InputSourceDataInputStream,
+    layerRuntimeImpl: LayerRuntimeImpl,
+  ): InputStream =
+    new JavaIOInputStream(s, layerRuntimeImpl.finfo)
+
+  private def wrapJavaOutputStream(
+    s: DataOutputStream,
+    layerRuntimeImpl: LayerRuntimeImpl,
+  ): OutputStream =
+    new JavaIOOutputStream(s, layer, layerRuntimeImpl, layerVarsRuntime)
+
+  /**
+   *  Override these if we ever have transformers that don't have these
+   *  requirements.
+   */
+  val mandatoryLayerAlignmentInBits: Int = 8
+
+  final def addInputLayer(
+    s: InputSourceDataInputStream,
+    layerRuntimeImpl: LayerRuntimeImpl,
+  ): InputSourceDataInputStream = {
+    val jis = wrapJavaInputStream(s, layerRuntimeImpl)
+    val decodedInputStream =
+      new AssuredCloseInputStream(layer.wrapLayerInput(jis, layerRuntimeImpl), 
jis)
+    val newDIS = InputSourceDataInputStream(decodedInputStream)
+    newDIS.cst.setPriorBitOrder(
+      BitOrder.MostSignificantBitFirst,
+    ) // must initialize priorBitOrder
+    newDIS.setDebugging(s.areDebugging)
+    newDIS
+  }
+
+  /**
+   * Parsing works as a tree traversal, so when the parser unwinds from
+   * parsing the layer we can invoke this to handle cleanups, and
+   * finalization issues like assigning the result variables
+   */
+  final def removeInputLayer(
+    s: InputSourceDataInputStream,
+    layerRuntimeImpl: LayerRuntimeImpl,
+  ): Unit = {
+    layerVarsRuntime.callGettersToPopulateResultVars(layer, layerRuntimeImpl)

Review Comment:
   The LayerFactory has to _get_ variable values. The LayerDriver has to call 
the getters to assign the output variables. 
   
   This is ordinary I think. One could do something more java-beans flavored, 
and have setters for the args and getters for the results, but I think that's 
clumsier. Never did like java beans approach. So many lines of code to do 
nothing much. 



##########
daffodil-runtime1-layers/src/main/resources/org/apache/daffodil/layers/xsd/base64_MIMELayer.dfdl.xsd:
##########
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+<schema
+  xmlns:xs="http://www.w3.org/2001/XMLSchema";
+  xmlns="http://www.w3.org/2001/XMLSchema";
+  xmlns:dfdl="http://www.ogf.org/dfdl/dfdl-1.0/";
+  xmlns:dfdlx="http://www.ogf.org/dfdl/dfdl-1.0/extensions";
+  xmlns:b64="urn:org.apache.daffodil.layers.base64_MIME"
+  targetNamespace="urn:org.apache.daffodil.layers.base64_MIME">
+
+  <!--
+  This layer does not define any variables for parameters or results
+  -->
+  <annotation>
+    <appinfo source="http://www.ogf.org/dfdl/";>
+      <dfdl:defineFormat name="base64_MIME">
+        <dfdl:format dfdlx:layerTransform="b64:base64_MIME"/>
+      </dfdl:defineFormat>
+    </appinfo>
+  </annotation>

Review Comment:
   Getting rid of the dfdl:ref. They add no value over just dfdlx:layer with a 
QName arg. 



##########
daffodil-runtime1-layers/src/main/scala/org/apache/daffodil/layers/runtime1/FixedLengthLayer.scala:
##########
@@ -0,0 +1,144 @@
+/*
+ * 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.layers.runtime1
+
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.io.InputStream
+import java.io.OutputStream
+import java.lang.{ Long => JLong }
+import java.nio.ByteBuffer
+
+import org.apache.daffodil.lib.exceptions.Assert
+import org.apache.daffodil.runtime1.layers.api.Layer
+import org.apache.daffodil.runtime1.layers.api.LayerRuntime
+
+import org.apache.commons.io.IOUtils
+
+/**
+ * Suitable only for small sections of data, not large data streams or whole 
files.
+ * See the maxFixedLength value defined herein for the maximum.
+ *
+ * The entire fixed length region of the data will be pulled into a byte 
buffer in memory.
+ *
+ * TODO: Someday, enhance to make this streaming.
+ *
+ * One DFDL Variable is a parameter
+ *   - fixedLength - an unsignedInt giving the fixed length of this layer.
+ *   This length is enforced on both parsing and unparsing the layer.
+ * There are no output/result DFDL variables from this layer.
+ */
+final class FixedLengthLayer(var fixedLength: JLong)

Review Comment:
   Not needed.



##########
daffodil-core/src/main/scala/org/apache/daffodil/core/layers/LayerCompiler.scala:
##########
@@ -0,0 +1,100 @@
+/*
+ * 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.core.layers
+
+import org.apache.daffodil.core.dsom.SequenceGroupTermBase
+import org.apache.daffodil.lib.exceptions.Assert
+import org.apache.daffodil.lib.xml.RefQName
+import org.apache.daffodil.runtime1.layers.LayerFactory
+import org.apache.daffodil.runtime1.layers.LayerRegistry
+import org.apache.daffodil.runtime1.layers.LayerRuntimeData
+import org.apache.daffodil.runtime1.layers.api.Layer
+
+object LayerCompiler {
+
+  /**
+   * Compiles the layer.
+   *
+   * This is mostly checking for errant use of DFDL properties that
+   * can't be used on layers.
+   *
+   * The real compilation - creating runtime data structures based
+   * on constructor signatures and DFDL variables that are in the
+   * layer's namespace, that is called here, but it is part of
+   * the daffodil runtime because that step has to be carried out
+   * *also* at runtime to verify that the dynamically loaded layer
+   * classes are compatible with the variables and their definitions.
+   *
+   * Constructs a LayerFactory which is the serializable runtime data structure
+   * used by the LayerParser, and LayerUnparser at runtime.
+   */
+  def compileLayer(sq: SequenceGroupTermBase): LayerFactory = {
+    val lc = new LayerCompiler(sq)
+    val res = lc.compile()
+    res
+  }
+}
+
+/**
+ *
+ * @param sq
+ */
+private class LayerCompiler private (sq: SequenceGroupTermBase) {
+
+  Assert.usage(sq.isLayered)
+
+  private def srd = sq.sequenceRuntimeData
+  private def lrd = sq.optionLayerRuntimeData.get
+
+  private def layerQName: RefQName = lrd.layerQName
+  private def layerName = layerQName.local
+  private def layerNamespace = layerQName.namespace
+
+  private val layeredSequenceAllowedProps = Seq(
+    "ref",

Review Comment:
   Removing. 



##########
daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/layers/LayerFactory.scala:
##########
@@ -0,0 +1,326 @@
+/*
+ * 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.runtime1.layers
+
+import java.lang.reflect.Constructor
+import java.lang.reflect.Method
+import scala.collection.immutable.ListSet
+import scala.collection.mutable
+
+import org.apache.daffodil.lib.exceptions.Assert
+import org.apache.daffodil.runtime1.dpath.NodeInfo.PrimType
+import org.apache.daffodil.runtime1.infoset.DataValue
+import org.apache.daffodil.runtime1.layers.api.Layer
+import org.apache.daffodil.runtime1.layers.api.LayerException
+import org.apache.daffodil.runtime1.processors.VariableRuntimeData
+
+object LayerFactory {
+
+  /** cache that maps spiName of layer to the LayerVarsRuntime */
+  private lazy val alreadyCheckedLayers =
+    new mutable.LinkedHashMap[String, LayerVarsRuntime]()
+
+  /**
+   * Computes the things that can be computed once-only to make calling the 
constructor
+   * and passing the parameter variables faster at runtime. This also does all 
the
+   * checking that the constructor has an argument for each variable of 
properly matching type, and
+   * has a getter for each result variable, again returning the proper type.
+   *
+   * This is called at schema compile time to ensure the layer code is 
properly defined and matches
+   * the layer variable definitions.
+   *
+   * It is called again at runtime after the Layer class is loaded by the SPI
+   * to ensure that the loaded layer class constructor signature at least 
matches the layer
+   * variables defined in the schema.
+   * @param lrd
+   * @param protoLayer the layer instance allocated by the SPI loader 
(zero-arg constructed)
+   * @return
+   */
+  def computeLayerVarsRuntime(lrd: LayerRuntimeData, protoLayer: Layer): 
LayerVarsRuntime = {
+    val optLayerVarsRuntime = alreadyCheckedLayers.get(protoLayer.name())
+    optLayerVarsRuntime.getOrElse {
+      // we know there is a default zero arg constructor
+      // find another constructor and check that there is an argument
+      // corresponding to each of the layer variables.
+      val c = protoLayer.getClass
+      val allConstructors = c.getConstructors.toSeq
+
+      val constructor: Constructor[_] =
+        if (allConstructors.length == 1) {
+          // There is only the default constructor.
+          // That's ok if there are no variables for the layer, which we check 
later.
+          allConstructors.head
+        } else if (allConstructors.length == 2) {
+          allConstructors.filter(_.getParameterCount > 0).head
+        } else {
+          def tooManyConstructorsMsg: String = {
+            s"""Layer class $c has multiple non-default constructors. It 
should have a default (no args)
+               | constructor and a single additional constructor with 
arguments for
+               | each of the layer's parameter variables.""".stripMargin
+          }
+          lrd.context.SDE(tooManyConstructorsMsg)
+        }
+
+      if (lrd.vmap.isEmpty && allConstructors.length == 1) {
+        // there are no vars, we're done
+        new LayerVarsRuntime(constructor, Nil, Nil)
+      } else {
+        // there is a constructor with args that are supposed to correspond to 
bound vars
+        val params = constructor.getParameters.toSeq
+        val nParams = params.length
+        val nVars = lrd.vmap.size
+
+        val paramTypes = constructor.getParameterTypes.toSeq
+        val paramVRDs = params.map { p =>
+          lrd.vmap.getOrElse(
+            p.getName,
+            lrd.context.SDE(s"No layer DFDL variable named '$p.getName' was 
found."),
+          )
+        }.toSeq
+
+        // Now we deal with the result getters and the corresponding vars
+        //
+        val allLayerVRDs = ListSet(lrd.vmap.toSeq.map(_._2): _*)
+        val returnVRDs = allLayerVRDs -- paramVRDs // set subtraction
+        val allMethods = c.getMethods
+        val allVarResultGetters =
+          ListSet(allMethods.filter { m =>
+            val nom = m.getName
+            nom.startsWith(varResultPrefix)
+          }.toSeq: _*)
+        // each returnVRD needs to have a corresponding getter method
+        val returnVRDNames = returnVRDs.map(_.globalQName.local)
+        val resultGettersNames = 
allVarResultGetters.map(_.getName.replace(varResultPrefix, ""))
+        val nResultGetters = resultGettersNames.size
+        def javaConstructorArgs =
+          paramVRDs
+            .map {
+              case vrd => {
+                s"type: ${PrimType.toJavaTypeString(vrd.primType.dfdlType)} 
name: ${vrd.globalQName.local}"
+              }
+            }
+            .mkString(", ")
+        def badConstructorMsg: String = {
+          s"""Layer class $c does not have a constructor with arguments for 
each of the layer's variables.
+             | It should have a constructor with these arguments in any order, 
such as
+             | ($javaConstructorArgs)""".stripMargin
+        }
+
+        lrd.context.schemaDefinitionUnless(nParams + nResultGetters == nVars, 
badConstructorMsg)
+
+        // at this point the number of vars and number of constructor args 
match
+
+        val typePairs = (paramVRDs.zip(paramTypes)).toSeq
+        typePairs.foreach { case (vrd, pt) =>
+          val vrdClass = PrimType.toJavaType(vrd.primType.dfdlType)
+          lrd.context.schemaDefinitionUnless(
+            vrdClass == pt,
+            s"""Layer constructor argument ${vrd.globalQName.local} and the 
corresponding
+               |Layer DFDL variable have differing types: ${pt.getName}
+               | and ${vrdClass.getName} respectively.""".stripMargin,
+          )
+        }
+
+        val returnVRDsWithoutGetters = returnVRDNames -- resultGettersNames
+        val resultGettersWithoutVRDs = resultGettersNames -- returnVRDNames
+        lrd.context.schemaDefinitionUnless(
+          returnVRDsWithoutGetters.isEmpty,
+          s"""The layer variables ${returnVRDsWithoutGetters.mkString(
+              ",",
+            )} have no corresponding getters.""",
+        )
+        lrd.context.schemaDefinitionUnless(
+          resultGettersWithoutVRDs.isEmpty, {
+            val getterFullNames = returnVRDsWithoutGetters.map { vname =>
+              this.varResultPrefix + vname
+            }
+            s"""The getters ${getterFullNames.mkString(
+                ",",
+              )} have no corresponding layer variables."""
+          },
+        )
+        // at this point we know each variable that was not a parameter of the 
constructor
+        // has a getter with matching name.
+        val resultVarPairs = resultGettersNames.map { rgn: String =>
+          val getter: Method =
+            allVarResultGetters.find { g: Method => g.getName.endsWith(rgn) 
}.getOrElse {
+              Assert.invariantFailed("no getter for getter name.")
+            }
+          val vrd = returnVRDs.find { vrd => vrd.globalQName.local == rgn 
}.getOrElse {
+            Assert.invariantFailed("no vrd for getter name.")
+          }
+          (vrd, getter)
+        }
+        resultVarPairs.foreach { case (vrd, getter) =>
+          val vrdClass = PrimType.toJavaType(vrd.primType.dfdlType)
+          val gt = getter.getReturnType
+          lrd.context.schemaDefinitionUnless(
+            vrdClass == gt,
+            s"""Layer return variable ${vrd.globalQName.local} and the 
corresponding
+               |Layer getter have differing types: ${vrdClass.getName}
+               | and ${gt.getName} respectively.""".stripMargin,
+          )
+        }
+        val lrv = new LayerVarsRuntime(constructor, paramVRDs, 
resultVarPairs.toSeq)
+        alreadyCheckedLayers.put(lrd.spiName, lrv)
+        lrv
+      }
+    }
+  }
+
+  private def varResultPrefix = "getDFDLResultVariable_"
+
+  type ParameterVarsInfo = Seq[Seq[(String, Class[_])]]
+  type ResultVarsInfo = Seq[Method]
+
+  def analyzeClass(obj: Any): (ParameterVarsInfo, ResultVarsInfo) = {
+    val c = obj.getClass
+    val cs = c.getConstructors.toSeq
+    val constructorInfo = cs.map { c =>
+      val parms = c.getParameters.toSeq
+      parms.map { p =>
+        val pn: String = p.getName
+        val pt = p.getType
+        (pn, pt)
+      }
+    }
+    val getterInfo: Array[Method] = c.getMethods.filter {
+      _.getName.startsWith(varResultPrefix)
+    }
+    (constructorInfo, getterInfo)
+  }
+
+}
+
+/**
+ * Factory for a layer
+ *
+ * This is the serialized object which is saved as part of a processor.
+ * It constructs the layer at runtime when newInstance() is called.
+ *
+ * This allows the layer instance itself to be stateful and not serializable.
+ */
+class LayerFactory(val layerRuntimeData: LayerRuntimeData) extends 
Serializable {
+  import LayerFactory._
+
+  /**
+   * Call at runtime to create a layer object. This layer object can be 
stateful
+   * and non-thread safe.
+   *
+   * Called by the LayeredSequenceParser or LayeredSequenceUnparser to 
allocate the
+   * layer, and the result is used to carry out the layering mechanism.
+   *
+   * @param lri the layer runtime info which includes both static and runtime
+   *           state-based information for the parse or unparse
+   * @return the Layer properly initialized/constructed for this layer
+   */
+  def newInstance(lri: LayerRuntimeImpl): LayerDriver = {
+    val optCache = alreadyCheckedLayers.get(lri.layerRuntimeData.spiName)
+    val layerVarsRuntime: LayerVarsRuntime = optCache.getOrElse {
+      val optLayerInstance = 
LayerRegistry.findLayer(lri.layerRuntimeData.spiName)
+      val spiLayerInstance: Layer = optLayerInstance.getOrElse {
+        lri.runtimeSchemaDefinitionError(
+          new LayerException(
+            lri,
+            s"Unable to load class for layerTransform 
'${lri.layerRuntimeData.layerQName.toQNameString}'.",
+          ),
+        )
+      }
+      // Since layer implementations are dynamically loaded, we must re-verify 
that
+      // the layer implementation matches the DFDL schema's layer variable 
definitions.
+      // This prevents using a layer class file that doesn't match the schema, 
at
+      // least as far as the number and type of the DFDL variables it consumes 
and writes goes.
+      // However, we want to do this exactly once, not every time this method 
is called,
+      // So there is a cache of instances that have already been through these 
checks,
+      // at runtime.
+      // We compute this data structure once only at the time the SPI Loads 
the layer
+      // class.
+      // In addition, this process of verifying the DFDL variables used by the 
layer
+      // pre-computes some data structures that facilitate fast run-time 
processing
+
+      val lvr = computeLayerVarsRuntime(lri.layerRuntimeData, spiLayerInstance)

Review Comment:
   This one I have to think about. Perhaps we can do this. Once we have the 
layer we have the layer namespace, and that will let us get the variables if we 
have the data processor. That's the issue. Do we have the data processor object 
(with schema set runtime data) already in hand at this point, or is that 'in 
process' of being created?



##########
daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/NodeInfo.scala:
##########
@@ -932,6 +936,33 @@ object NodeInfo extends Enum {
         DFDLTimeConversion.fromXMLString(s)
       }
     }
+
+    def toJavaType(dfdlType: DFDLPrimType): Class[_] = {
+      dfdlType match {
+        case DFDLPrimType.String => classOf[JString]
+        case DFDLPrimType.Int => classOf[JInt]
+        case DFDLPrimType.Byte => classOf[JByte]
+        case DFDLPrimType.Short => classOf[JShort]
+        case DFDLPrimType.Long => classOf[JLong]
+        case DFDLPrimType.Integer => classOf[JBigInt]
+        case DFDLPrimType.Decimal => classOf[JBigDecimal]
+        case DFDLPrimType.UnsignedInt => classOf[JLong]
+        case DFDLPrimType.UnsignedByte => classOf[JShort]
+        case DFDLPrimType.UnsignedShort => classOf[JInt]
+        case DFDLPrimType.UnsignedLong => classOf[JBigInt]
+        case DFDLPrimType.NonNegativeInteger => classOf[JBigInt]
+        case DFDLPrimType.Double => classOf[JDouble]
+        case DFDLPrimType.Float => classOf[JFloat]
+        case DFDLPrimType.HexBinary => classOf[Array[Byte]]
+        case DFDLPrimType.AnyURI => classOf[java.net.URI]
+        case DFDLPrimType.Boolean => classOf[JBoolean]
+        case DFDLPrimType.DateTime => classOf[ICUCalendar]
+        case DFDLPrimType.Date => classOf[ICUCalendar]
+        case DFDLPrimType.Time => classOf[ICUCalendar]

Review Comment:
   I am just taking this from the data interface I built for the Drill 
implementation. 
   
   For performance reasons I don't think we want to hand back strings and made 
people parse them to create their own preferred calendar object instances. 
   
   I am wondering if we should just narrow this to just the truly necessary 
types for layers: String and integers. 
   
   Generalizing this to all the simple types seems silly when we have no 
examples of a layer needing to compute say, a time. 
   
   



##########
daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/layers/LayerFactory.scala:
##########
@@ -0,0 +1,326 @@
+/*
+ * 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.runtime1.layers
+
+import java.lang.reflect.Constructor
+import java.lang.reflect.Method
+import scala.collection.immutable.ListSet
+import scala.collection.mutable
+
+import org.apache.daffodil.lib.exceptions.Assert
+import org.apache.daffodil.runtime1.dpath.NodeInfo.PrimType
+import org.apache.daffodil.runtime1.infoset.DataValue
+import org.apache.daffodil.runtime1.layers.api.Layer
+import org.apache.daffodil.runtime1.layers.api.LayerException
+import org.apache.daffodil.runtime1.processors.VariableRuntimeData
+
+object LayerFactory {
+
+  /** cache that maps spiName of layer to the LayerVarsRuntime */
+  private lazy val alreadyCheckedLayers =
+    new mutable.LinkedHashMap[String, LayerVarsRuntime]()
+
+  /**
+   * Computes the things that can be computed once-only to make calling the 
constructor
+   * and passing the parameter variables faster at runtime. This also does all 
the
+   * checking that the constructor has an argument for each variable of 
properly matching type, and
+   * has a getter for each result variable, again returning the proper type.
+   *
+   * This is called at schema compile time to ensure the layer code is 
properly defined and matches
+   * the layer variable definitions.
+   *
+   * It is called again at runtime after the Layer class is loaded by the SPI
+   * to ensure that the loaded layer class constructor signature at least 
matches the layer
+   * variables defined in the schema.
+   * @param lrd
+   * @param protoLayer the layer instance allocated by the SPI loader 
(zero-arg constructed)
+   * @return
+   */
+  def computeLayerVarsRuntime(lrd: LayerRuntimeData, protoLayer: Layer): 
LayerVarsRuntime = {
+    val optLayerVarsRuntime = alreadyCheckedLayers.get(protoLayer.name())
+    optLayerVarsRuntime.getOrElse {
+      // we know there is a default zero arg constructor
+      // find another constructor and check that there is an argument
+      // corresponding to each of the layer variables.
+      val c = protoLayer.getClass
+      val allConstructors = c.getConstructors.toSeq
+
+      val constructor: Constructor[_] =
+        if (allConstructors.length == 1) {
+          // There is only the default constructor.
+          // That's ok if there are no variables for the layer, which we check 
later.
+          allConstructors.head
+        } else if (allConstructors.length == 2) {
+          allConstructors.filter(_.getParameterCount > 0).head
+        } else {
+          def tooManyConstructorsMsg: String = {
+            s"""Layer class $c has multiple non-default constructors. It 
should have a default (no args)
+               | constructor and a single additional constructor with 
arguments for
+               | each of the layer's parameter variables.""".stripMargin
+          }
+          lrd.context.SDE(tooManyConstructorsMsg)
+        }
+
+      if (lrd.vmap.isEmpty && allConstructors.length == 1) {
+        // there are no vars, we're done
+        new LayerVarsRuntime(constructor, Nil, Nil)
+      } else {
+        // there is a constructor with args that are supposed to correspond to 
bound vars
+        val params = constructor.getParameters.toSeq
+        val nParams = params.length
+        val nVars = lrd.vmap.size
+
+        val paramTypes = constructor.getParameterTypes.toSeq
+        val paramVRDs = params.map { p =>
+          lrd.vmap.getOrElse(
+            p.getName,
+            lrd.context.SDE(s"No layer DFDL variable named '$p.getName' was 
found."),
+          )
+        }.toSeq
+
+        // Now we deal with the result getters and the corresponding vars
+        //
+        val allLayerVRDs = ListSet(lrd.vmap.toSeq.map(_._2): _*)
+        val returnVRDs = allLayerVRDs -- paramVRDs // set subtraction

Review Comment:
   I am documenting this in the Layer class javadoc. 



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to