stevedlawrence commented on code in PR #1176: URL: https://github.com/apache/daffodil/pull/1176#discussion_r1512824908
########## 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: A thought, is there any value in saying that input variables are defined by by the constructor parameters and output variables are defined by the getters, and additional variables are ignored. This would allow layers to have variables that are sort of "helper" variables? For example, what about something like this: **myLayer.dfdl.xsd** ```xml ... <defineVariable name="foo" type="xs:int" defaultValue="2" /> <defineVariable name="bar" type="xs:int" defaultValue="{ $foo * 100}" /> ... ``` The constructor might only accept and use `bar`, but a user to do something like this: `daffodil parse -Dlyr:foo=10 ...` So they could change the default value of `foo`, which would change the value of `bar`, which is what the layer actually uses. Or the user could just set `lyr:bar` directory if they want that extra control. I'm not sure if that adds value or not. But it might simplify the implementation of this function since some of the logic here is making sure all defined variables are used? I guess the argument against this is that a user might forget to use a variable and we want to alert them of that? -- 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]
