stevedlawrence commented on code in PR #1112: URL: https://github.com/apache/daffodil/pull/1112#discussion_r1391117119
########## daffodil-core/src/test/scala/org/apache/daffodil/core/api/TestMetadataWalking.scala: ########## @@ -0,0 +1,228 @@ +/* + * 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.api + +import scala.collection.mutable.ArrayBuffer +import scala.xml.Elem + +import org.apache.daffodil.core.util.TestUtils +import org.apache.daffodil.io.InputSourceDataInputStream +import org.apache.daffodil.lib.util._ +import org.apache.daffodil.runtime1.api.ChoiceMetadata +import org.apache.daffodil.runtime1.api.ComplexElementMetadata +import org.apache.daffodil.runtime1.api.DFDL.ParseResult +import org.apache.daffodil.runtime1.api.ElementMetadata +import org.apache.daffodil.runtime1.api.InfosetArray +import org.apache.daffodil.runtime1.api.InfosetComplexElement +import org.apache.daffodil.runtime1.api.InfosetElement +import org.apache.daffodil.runtime1.api.InfosetItem +import org.apache.daffodil.runtime1.api.InfosetSimpleElement +import org.apache.daffodil.runtime1.api.Metadata +import org.apache.daffodil.runtime1.api.MetadataHandler +import org.apache.daffodil.runtime1.api.SequenceMetadata +import org.apache.daffodil.runtime1.api.SimpleElementMetadata +import org.apache.daffodil.runtime1.infoset.ExceptionOccurredMixin +import org.apache.daffodil.runtime1.infoset.InfosetOutputter +import org.apache.daffodil.runtime1.infoset.InfosetOutputterImpl +import org.apache.daffodil.runtime1.processors.DataProcessor + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test + +class TestMetadataWalking { + + def compileAndWalkMetadata(schema: Elem, mh: MetadataHandler): DataProcessor = { + val dp = TestUtils.compileSchema(schema) + assertTrue(!dp.isError) + dp.walkMetadata(mh) + dp + } + + def parseAndWalkData(dp: DataProcessor, infosetOutputter: InfosetOutputter)( + data: Array[Byte], + ): ParseResult = { + val isdis = InputSourceDataInputStream(data) + val res = dp.parse(isdis, infosetOutputter) + res + } + + class GatherMetadata extends MetadataHandler { + + private val buf = new ArrayBuffer[Metadata](); + + def getResult: Seq[Metadata] = { + val res: Seq[Metadata] = buf.toVector // makes a copy + buf.clear() + res + } + + override def simpleElementMetadata(m: SimpleElementMetadata): Unit = buf += m + + override def startComplexElementMetadata(m: ComplexElementMetadata): Unit = buf += m + + override def endComplexElementMetadata(m: ComplexElementMetadata): Unit = buf += m + + override def startSequenceMetadata(m: SequenceMetadata): Unit = buf += m + + override def endSequenceMetadata(m: SequenceMetadata): Unit = buf += m + + override def startChoiceMetadata(m: ChoiceMetadata): Unit = buf += m + + override def endChoiceMetadata(m: ChoiceMetadata): Unit = buf += m + } + + class GatherData + extends InfosetOutputterImpl + with InfosetOutputter + with ExceptionOccurredMixin { + + private val buf = new ArrayBuffer[InfosetItem] + + def getResult: Seq[InfosetItem] = { + val res = buf.toVector + reset() + res + } + + override def reset(): Unit = { buf.clear() } + + override def startDocument(): Unit = {} + + override def endDocument(): Unit = {} + + override def startSimple(diSimple: InfosetSimpleElement): Unit = { buf += diSimple } + + override def endSimple(diSimple: InfosetSimpleElement): Unit = {} Review Comment: This is slightly different for the MetaDataHandler, which just has a "simpleElement" function, instead of separate start/endSimple functions. Having start/end events for simple in the InfosetOutputter API maybe have been a mistake. In fact, most implementations make endSimple be a no-op. The only one that doesn't is the saxInfosetOutputter, which could do all it's logic in startSimple. We are already breaking backwards compatibility pretty heavily here, maybe we should also drop endSimple and just have a single method for handling simple elements in the InfosetOutputter? ########## daffodil-japi/src/test/java/org/apache/daffodil/example/TestInfosetOutputter.java: ########## @@ -51,45 +50,50 @@ public void endDocument() { } @Override - public void startSimple(DISimple diSimple) { + public void startSimple(InfosetSimpleElement diSimple) { events.add( TestInfosetEvent.startSimple( - diSimple.erd().name(), - diSimple.erd().namedQName().namespace().toString(), + diSimple.metadata().name(), + diSimple.metadata().namespace(), diSimple.dataValueAsString(), - diSimple.erd().isNillable() ? diSimple.isNilled() : null)); + diSimple.metadata().isNillable() ? diSimple.isNilled() : null)); Review Comment: Rename `diSimple` to just `simple`? Same with other tests. ########## daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/api/Metadata.scala: ########## @@ -0,0 +1,269 @@ +/* + * 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.api + +import scala.xml.NamespaceBinding + +/** + * This is the supportable API for access to the RuntimeData structures + * which provide access to static information about a given . + * + * This is used to interface other data processing fabrics to Daffodil + * data and metadata, by mapping to/from these metadata objects. + */ +trait Metadata { + + /** + * Provides the file context of a metadata component. This refers to the specific + * DFDL schema file where the corresponding DFDL schema text resides corresponding + * to this metadata object. + * <p/> + * This is for use in diagnostic messaging. It is not the actual file URI, because + * those may contain personal-identifying information about the person/acccount and + * system that compiled the schema. It will provide enough content about the file URI that + * a user will be able to identify which file, but some prefix of the path + * components trimmed to make it of a manageable length. + * <p/> + * Used along with [[org.apache.daffodil.runtime1.api.Metadata.schemaFileLineNumber]] + * and [[org.apache.daffodil.runtime1.api.Metadata.schemaFileLineColumnNumber]], + * this can give a precise location in the DFDL schema file. + * @return a string containing the file information, or null if unknown. + */ + def schemaFileInfo: String + + /** + * Provides the line number to go with [[org.apache.daffodil.runtime1.api.Metadata.schemaFileInfo]]. + * @return the line number as a string, or null if unknown. + */ + def schemaFileLineNumber: String + + /** + * Provides the column number within the text line, to go with [[org.apache.daffodil.runtime1.api.Metadata.schemaFileLineNumber]]. + * @return the column number within the text line, as a string, or null if unknown. + */ + def schemaFileLineColumnNumber: String Review Comment: Can line/column be Long's instead? Or does Daffodil internally represent these as strings so it's just easier to pass that through? If we need the `null` to mean for no-information, they can be boxed java longs so we can still return null? ########## daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/infoset/InfosetOutputter.scala: ########## @@ -84,75 +85,98 @@ trait InfosetOutputter { * value, nil, name, namespace, etc. */ @throws[Exception] - def endSimple(diSimple: DISimple): Unit + def endSimple(diSimple: InfosetSimpleElement): Unit /** * Called by Daffodil internals to signify the beginning of a complex element. * * Throws java.lang.Exception if there was an error and Daffodil should stop parsing * - * @param diComplex the complex element that is started. Various fields of + * @param complex the complex element that is started. Various fields of * DIComplex can be accessed to determine things like the * nil, name, namespace, etc. */ @throws[Exception] - def startComplex(diComplex: DIComplex): Unit + def startComplex(complex: InfosetComplexElement): Unit /** * Called by Daffodil internals to signify the end of a complex element. * * Throws java.lang.Exception if there was an error and Daffodil should stop parsing * - * @param diComplex the complex element that is ended. Various fields of + * @param complex the complex element that is ended. Various fields of * DIComplex can be accessed to determine things like the * nil, name, namespace, etc. */ @throws[Exception] - def endComplex(diComplex: DIComplex): Unit + def endComplex(complex: InfosetComplexElement): Unit /** * Called by Daffodil internals to signify the beginning of an array of elements. * * Throws java.lang.Exception if there was an error and Daffodil should stop parsing * - * @param diComplex the array that is started. Various fields of + * @param array the array that is started. Various fields of * DIArray can be accessed to determine things like the * name, namespace, etc. */ @throws[Exception] - def startArray(diArray: DIArray): Unit + def startArray(array: InfosetArray): Unit /** * Called by Daffodil internals to signify the end of an array of elements. * * Throws java.lang.Exception if there was an error and Daffodil should stop parsing * - * @param diComplex the array that is ended. Various fields of + * @param array the array that is ended. Various fields of * DIArray can be accessed to determine things like the * name, namespace, etc. */ @throws[Exception] - def endArray(diArray: DIArray): Unit + def endArray(array: InfosetArray): Unit - def getStatus(): Status = { - // Done, Ready (Not started), Visiting (part way done - can retry to visit more)... - status - } + def getStatus(): Status /** - * Helper function to determine if an element is nilled or not, taking into - * account whether or not the nilled state has been set yet. + * Override this to be fed any exceptions thrown by the start/end methods of this class. + * This is mostly to facilitate users debugging their code that uses the API. * - * @param diElement the element to check the nilled state of + * This saves the user of the API from having to wrap try/catch around their + * start/end method bodies. * - * @return true if the nilled state has been set and is true. false if the - * nilled state is false or if the nilled state has not been set yet - * (e.g. during debugging) + * @param e the exception thrown by one of the other methods. */ - final def isNilled(diElement: DIElement): Boolean = { - val maybeIsNilled = diElement.maybeIsNilled - maybeIsNilled.isDefined && maybeIsNilled.get == true - } + def exceptionOccurred(e: Exception): Unit + +} + +/** + * Methods that provide Blob (Binary Large Object) support. + * + * FIXME: Scaladoc + */ +trait BlobMethodsMixin { + + def setBlobAttributes(blobDir: Path, blobPrefix: String, blobSuffix: String): Unit + def setBlobPaths(empty: Seq[Path]): Unit + def getBlobDirectory(): Path + def getBlobPrefix(): String + def getBlobSuffix(): String + +} + +/** + * An available basic implementation of the BLOB methods. + * Stores blobs in files in directory identified by Java system property + * `java.io.tempdir`. + * + * FIXME: Unclear if Daffodil implements blob copying based on these + * alone or not, and what else people have to do for BLOB support + * than mixin this to their InfosetOutputter. Review Comment: Nothing needs to be done to support blobs. These functions are just a way to configure where blobs get written to. That information is set on the infoset outputter because there isn't really a good other place to do it. This also provides a way to get paths to all blobs wihtout having to traverse the infoset. ########## daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/infoset/JsonInfosetOutputter.scala: ########## @@ -112,11 +116,12 @@ class JsonInfosetOutputter private (writer: java.io.Writer, pretty: Boolean) } } - override def endSimple(simple: DISimple): Unit = { + override def endSimple(se: InfosetSimpleElement): Unit = { // nothing to do } - override def startComplex(complex: DIComplex): Unit = { + override def startComplex(ce: InfosetComplexElement): Unit = { + val complex = ce.asInstanceOf[DIComplex] Review Comment: Can this asInstanceOf go away? Same with others in the class. Or is that planned for a later PR? ########## daffodil-core/src/test/scala/org/apache/daffodil/core/api/TestMetadataWalking.scala: ########## @@ -0,0 +1,228 @@ +/* + * 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.api + +import scala.collection.mutable.ArrayBuffer +import scala.xml.Elem + +import org.apache.daffodil.core.util.TestUtils +import org.apache.daffodil.io.InputSourceDataInputStream +import org.apache.daffodil.lib.util._ +import org.apache.daffodil.runtime1.api.ChoiceMetadata +import org.apache.daffodil.runtime1.api.ComplexElementMetadata +import org.apache.daffodil.runtime1.api.DFDL.ParseResult +import org.apache.daffodil.runtime1.api.ElementMetadata +import org.apache.daffodil.runtime1.api.InfosetArray +import org.apache.daffodil.runtime1.api.InfosetComplexElement +import org.apache.daffodil.runtime1.api.InfosetElement +import org.apache.daffodil.runtime1.api.InfosetItem +import org.apache.daffodil.runtime1.api.InfosetSimpleElement +import org.apache.daffodil.runtime1.api.Metadata +import org.apache.daffodil.runtime1.api.MetadataHandler +import org.apache.daffodil.runtime1.api.SequenceMetadata +import org.apache.daffodil.runtime1.api.SimpleElementMetadata +import org.apache.daffodil.runtime1.infoset.ExceptionOccurredMixin +import org.apache.daffodil.runtime1.infoset.InfosetOutputter +import org.apache.daffodil.runtime1.infoset.InfosetOutputterImpl +import org.apache.daffodil.runtime1.processors.DataProcessor + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test + +class TestMetadataWalking { + + def compileAndWalkMetadata(schema: Elem, mh: MetadataHandler): DataProcessor = { + val dp = TestUtils.compileSchema(schema) + assertTrue(!dp.isError) + dp.walkMetadata(mh) + dp + } + + def parseAndWalkData(dp: DataProcessor, infosetOutputter: InfosetOutputter)( + data: Array[Byte], + ): ParseResult = { + val isdis = InputSourceDataInputStream(data) + val res = dp.parse(isdis, infosetOutputter) + res + } + + class GatherMetadata extends MetadataHandler { + + private val buf = new ArrayBuffer[Metadata](); + + def getResult: Seq[Metadata] = { + val res: Seq[Metadata] = buf.toVector // makes a copy + buf.clear() + res + } + + override def simpleElementMetadata(m: SimpleElementMetadata): Unit = buf += m + + override def startComplexElementMetadata(m: ComplexElementMetadata): Unit = buf += m + + override def endComplexElementMetadata(m: ComplexElementMetadata): Unit = buf += m + + override def startSequenceMetadata(m: SequenceMetadata): Unit = buf += m + + override def endSequenceMetadata(m: SequenceMetadata): Unit = buf += m + + override def startChoiceMetadata(m: ChoiceMetadata): Unit = buf += m + + override def endChoiceMetadata(m: ChoiceMetadata): Unit = buf += m + } + + class GatherData + extends InfosetOutputterImpl + with InfosetOutputter + with ExceptionOccurredMixin { + + private val buf = new ArrayBuffer[InfosetItem] + + def getResult: Seq[InfosetItem] = { + val res = buf.toVector + reset() + res + } + + override def reset(): Unit = { buf.clear() } + + override def startDocument(): Unit = {} + + override def endDocument(): Unit = {} Review Comment: Does a MetadatHandler not of a start/endDocument like an InfosetOutputter does? ########## daffodil-core/src/main/scala/org/apache/daffodil/core/runtime1/SequenceTermRuntime1Mixin.scala: ########## @@ -82,6 +83,7 @@ trait ChoiceBranchImpliedSequenceRuntime1Mixin { self: ChoiceBranchImpliedSequen FillByteUseNotAllowedEv, Maybe.Nope, Maybe.Nope, + false, Review Comment: Suggest `isHidden = false` like in other places? ########## daffodil-core/src/test/scala/org/apache/daffodil/core/api/TestMetadataWalking.scala: ########## @@ -0,0 +1,228 @@ +/* + * 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.api + +import scala.collection.mutable.ArrayBuffer +import scala.xml.Elem + +import org.apache.daffodil.core.util.TestUtils +import org.apache.daffodil.io.InputSourceDataInputStream +import org.apache.daffodil.lib.util._ +import org.apache.daffodil.runtime1.api.ChoiceMetadata +import org.apache.daffodil.runtime1.api.ComplexElementMetadata +import org.apache.daffodil.runtime1.api.DFDL.ParseResult +import org.apache.daffodil.runtime1.api.ElementMetadata +import org.apache.daffodil.runtime1.api.InfosetArray +import org.apache.daffodil.runtime1.api.InfosetComplexElement +import org.apache.daffodil.runtime1.api.InfosetElement +import org.apache.daffodil.runtime1.api.InfosetItem +import org.apache.daffodil.runtime1.api.InfosetSimpleElement +import org.apache.daffodil.runtime1.api.Metadata +import org.apache.daffodil.runtime1.api.MetadataHandler +import org.apache.daffodil.runtime1.api.SequenceMetadata +import org.apache.daffodil.runtime1.api.SimpleElementMetadata +import org.apache.daffodil.runtime1.infoset.ExceptionOccurredMixin +import org.apache.daffodil.runtime1.infoset.InfosetOutputter +import org.apache.daffodil.runtime1.infoset.InfosetOutputterImpl +import org.apache.daffodil.runtime1.processors.DataProcessor + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test + +class TestMetadataWalking { + + def compileAndWalkMetadata(schema: Elem, mh: MetadataHandler): DataProcessor = { + val dp = TestUtils.compileSchema(schema) + assertTrue(!dp.isError) + dp.walkMetadata(mh) + dp + } + + def parseAndWalkData(dp: DataProcessor, infosetOutputter: InfosetOutputter)( + data: Array[Byte], + ): ParseResult = { + val isdis = InputSourceDataInputStream(data) + val res = dp.parse(isdis, infosetOutputter) + res + } + + class GatherMetadata extends MetadataHandler { + + private val buf = new ArrayBuffer[Metadata](); + + def getResult: Seq[Metadata] = { + val res: Seq[Metadata] = buf.toVector // makes a copy + buf.clear() + res + } + + override def simpleElementMetadata(m: SimpleElementMetadata): Unit = buf += m + + override def startComplexElementMetadata(m: ComplexElementMetadata): Unit = buf += m + + override def endComplexElementMetadata(m: ComplexElementMetadata): Unit = buf += m + + override def startSequenceMetadata(m: SequenceMetadata): Unit = buf += m + + override def endSequenceMetadata(m: SequenceMetadata): Unit = buf += m + + override def startChoiceMetadata(m: ChoiceMetadata): Unit = buf += m + + override def endChoiceMetadata(m: ChoiceMetadata): Unit = buf += m Review Comment: Do we need `Metadata` in all the names of these functions? Seems unnecessarily verbose since we're already in a Metaata handler? ########## daffodil-japi/src/main/scala/org/apache/daffodil/japi/Daffodil.scala: ########## @@ -514,6 +515,20 @@ class DataProcessor private[japi] (private var dp: SDataProcessor) */ def save(output: WritableByteChannel): Unit = dp.save(output) + /** + * Walks the handler over the runtime [[org.apache.daffodil.runtime1.api.Metadata]] structures. + * These provide information about name, namespace, type, simple/complex, etc. + * + * This is used to interface Daffodil runtime1 metadata to the metadata structures + * of other software systems. + * + * See [[org.apache.daffodil.runtime1.api.MetadataHandler]] for more motivating materials about + * runtime1 metadata walking. + * + * @param handler - the handler is called-back during the walk as each metadata structure is encountered. + */ + def walkMetadata(handler: MetadataHandler): Unit = dp.walkMetadata(handler) Review Comment: We should implement tests in TestJavaAPI.java to get coverage from this and make sure everything is easily accessible using reasonable Java without any weird scala-isms. ########## daffodil-japi/src/main/scala/org/apache/daffodil/japi/infoset/Infoset.scala: ########## @@ -154,69 +153,69 @@ abstract class InfosetOutputter extends SInfosetOutputter { /** * Called by Daffodil internals to signify the beginning of a simple element. * - * @param diSimple the simple element that is started. Various fields of + * @param simple the simple element that is started. Various fields of * DISimple can be accessed to determine things like the * value, nil, name, namespace, etc. * @throws Exception if there was an error and Daffodil should stop parsing */ @throws[Exception] - def startSimple(diSimple: DISimple): Unit + def startSimple(simple: InfosetSimpleElement): Unit Review Comment: When we look at java doc, we need to make sure to see what this looks like. InfosetSimpleElement and the others are not in the daffodil-japi project, so I dont' think will have any java doc associated with them. Maybe we should consider options to make that available? I really don't like our the proxy kinof thing we've been doing in past, I'm not sure if there are other alternatives though. ########## daffodil-japi/src/main/scala/org/apache/daffodil/japi/infoset/Infoset.scala: ########## @@ -154,69 +153,69 @@ abstract class InfosetOutputter extends SInfosetOutputter { /** * Called by Daffodil internals to signify the beginning of a simple element. * - * @param diSimple the simple element that is started. Various fields of + * @param simple the simple element that is started. Various fields of * DISimple can be accessed to determine things like the * value, nil, name, namespace, etc. * @throws Exception if there was an error and Daffodil should stop parsing */ @throws[Exception] - def startSimple(diSimple: DISimple): Unit + def startSimple(simple: InfosetSimpleElement): Unit /** * Called by Daffodil internals to signify the end of a simple element. * - * @param diSimple the simple element that is ended. Various fields of + * @param simple the simple element that is ended. Various fields of * DISimple can be accessed to determine things like the * value, nil, name, namespace, etc. * @throws Exception if there was an error and Daffodil should stop parsing */ @throws[Exception] - def endSimple(diSimple: DISimple): Unit + def endSimple(simple: InfosetSimpleElement): Unit /** * Called by Daffodil internals to signify the beginning of a complex element. * - * @param diComplex the complex element that is started. Various fields of + * @param complex the complex element that is started. Various fields of * DIComplex can be accessed to determine things like the * nil, name, namespace, etc. * @throws Exception if there was an error and Daffodil should stop parsing */ @throws[Exception] - def startComplex(diComplex: DIComplex): Unit + def startComplex(complex: InfosetComplexElement): Unit /** * Called by Daffodil internals to signify the end of a complex element. * - * @param diComplex the complex element that is ended. Various fields of + * @param complex the complex element that is ended. Various fields of * DIComplex can be accessed to determine things like the * nil, name, namespace, etc. * @throws Exception if there was an error and Daffodil should stop parsing */ @throws[Exception] - def endComplex(diComplex: DIComplex): Unit + def endComplex(complex: InfosetComplexElement): Unit /** * Called by Daffodil internals to signify the beginning of an array of elements. * - * @param diArray the array that is started. Various fields of + * @param array the array that is started. Various fields of * DIArray can be accessed to determine things like the * name, namespace, etc. * @throws Exception if there was an error and Daffodil should stop parsing */ @throws[Exception] - def startArray(diArray: DIArray): Unit + def startArray(array: InfosetArray): Unit /** * Called by Daffodil internals to signify the end of an array of elements. * - * @param diArray the array that is ended. Various fields of + * @param array the array that is ended. Various fields of * DIArray can be accessed to determine things like the * name, namespace, etc. * @throws Exception if there was an error and Daffodil should stop parsing */ @throws[Exception] - def endArray(diArray: DIArray): Unit + def endArray(array: InfosetArray): Unit Review Comment: This is all a pretty big backwards-icompabitle change. Maybe that's fine and will just be another thing that warrants the next version being 4.0.0. We could maybe do somthing where this API has both variants of functions, and the new one calls the old one, e.g. ```scala def endArray(diArray: DIArray): Unit def endArray(array: InfosetAray): Unit = endArray(array.asInstanceOf[DIArray]) ``` This way old infoset outputters still work, but new infoset ouputters can be written with the new API. Not sure the extra complication is worth it, especially if we go to 4.0.0. We should add a `Deprecation/Compatibility` section to the commit message though so we remember to mention this in the release notes. ########## daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/api/Infoset.scala: ########## @@ -0,0 +1,266 @@ +/* + * 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.api + +import java.lang.{ Boolean => JBoolean } +import java.lang.{ Byte => JByte } +import java.lang.{ Double => JDouble } +import java.lang.{ Float => JFloat } +import java.lang.{ Integer => JInt } +import java.lang.{ Long => JLong } +import java.lang.{ Number => JNumber } +import java.lang.{ Short => JShort } +import java.lang.{ String => JString } +import java.math.{ BigDecimal => JBigDecimal } +import java.math.{ BigInteger => JBigInt } +import java.net.URI + +import com.ibm.icu.util.Calendar + +/** + * API access to array objects in the DFDL Infoset + */ +trait InfosetArray extends InfosetItem { + + /** + * @return the metadata of the element that is an array + */ + override def metadata: ElementMetadata + + /** + * @return the number of items in the array. + */ + def length: Long + + /** + * Access to an infoset element of the array. + * @param occursIndex1b the one-based index of the element to be accessed + * @return the infoset element located at the given index + */ + def apply(occursIndex1b: Long): InfosetElement +} + +/** + * API access to elements of the DFDL Infoset of both + * complex and simple type. + */ +trait InfosetElement extends InfosetItem { + + /** + * In DFDL both simple and complex type elements can be + * nilled. + * + * @return true if the element is nilled, false otherwise. + */ + def isNilled: Boolean + + /** + * @return true if the element is a string or hexBinary of length 0, false otherwise. Review Comment: Seems an odd special case to support. Can't the user just get the string or byte array and ask if that is empty? What are the cases where users would even care if they are empty? For example, empty string often isn't a special case. ########## daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/api/Infoset.scala: ########## @@ -0,0 +1,266 @@ +/* + * 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.api + +import java.lang.{ Boolean => JBoolean } +import java.lang.{ Byte => JByte } +import java.lang.{ Double => JDouble } +import java.lang.{ Float => JFloat } +import java.lang.{ Integer => JInt } +import java.lang.{ Long => JLong } +import java.lang.{ Number => JNumber } +import java.lang.{ Short => JShort } +import java.lang.{ String => JString } +import java.math.{ BigDecimal => JBigDecimal } +import java.math.{ BigInteger => JBigInt } +import java.net.URI + +import com.ibm.icu.util.Calendar + +/** + * API access to array objects in the DFDL Infoset + */ +trait InfosetArray extends InfosetItem { + + /** + * @return the metadata of the element that is an array + */ + override def metadata: ElementMetadata + + /** + * @return the number of items in the array. + */ + def length: Long + + /** + * Access to an infoset element of the array. + * @param occursIndex1b the one-based index of the element to be accessed + * @return the infoset element located at the given index + */ + def apply(occursIndex1b: Long): InfosetElement Review Comment: What are array `length` add `apply` used for? My concern is when an `InfosetWalker` walks an array, the full length might not be known at that time. I don't remember if we wait for an array to be completely finished before walking into it (I would guess we don't?), but if we don't this could cause weird behavior. Can we get away with removing these? ########## daffodil-japi/src/test/java/org/apache/daffodil/example/TestInfosetOutputter.java: ########## @@ -51,45 +50,50 @@ public void endDocument() { } @Override - public void startSimple(DISimple diSimple) { + public void startSimple(InfosetSimpleElement diSimple) { events.add( TestInfosetEvent.startSimple( - diSimple.erd().name(), - diSimple.erd().namedQName().namespace().toString(), + diSimple.metadata().name(), + diSimple.metadata().namespace(), diSimple.dataValueAsString(), - diSimple.erd().isNillable() ? diSimple.isNilled() : null)); + diSimple.metadata().isNillable() ? diSimple.isNilled() : null)); } @Override - public void endSimple(DISimple diSimple) { + public void endSimple(InfosetSimpleElement diSimple) { events.add( TestInfosetEvent.endSimple( - diSimple.erd().name(), - diSimple.erd().namedQName().namespace().toString())); + diSimple.metadata().name(), + diSimple.metadata().namespace())); } @Override - public void startComplex(DIComplex diComplex) { + public void startComplex(InfosetComplexElement complex) { events.add( TestInfosetEvent.startComplex( - diComplex.erd().name(), - diComplex.erd().namedQName().namespace().toString(), - diComplex.erd().isNillable() ? diComplex.isNilled() : null)); + complex.metadata().name(), + complex.metadata().namespace(), + complex.metadata().isNillable() ? complex.isNilled() : null)); Review Comment: Unrlated to this PR, but do you know if we must call `isNillabled` before asking `isNilled`? Seems odd if so. It would be nice if isNilled just returns false if something isn't nillable. But maybe this test infoset outputter just doing something fancy and wants differentiable nillable but not nil vs not nillable. ########## daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/api/Infoset.scala: ########## @@ -0,0 +1,266 @@ +/* + * 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.api + +import java.lang.{ Boolean => JBoolean } +import java.lang.{ Byte => JByte } +import java.lang.{ Double => JDouble } +import java.lang.{ Float => JFloat } +import java.lang.{ Integer => JInt } +import java.lang.{ Long => JLong } +import java.lang.{ Number => JNumber } +import java.lang.{ Short => JShort } +import java.lang.{ String => JString } +import java.math.{ BigDecimal => JBigDecimal } +import java.math.{ BigInteger => JBigInt } +import java.net.URI + +import com.ibm.icu.util.Calendar + +/** + * API access to array objects in the DFDL Infoset + */ +trait InfosetArray extends InfosetItem { + + /** + * @return the metadata of the element that is an array + */ + override def metadata: ElementMetadata + + /** + * @return the number of items in the array. + */ + def length: Long + + /** + * Access to an infoset element of the array. + * @param occursIndex1b the one-based index of the element to be accessed + * @return the infoset element located at the given index + */ + def apply(occursIndex1b: Long): InfosetElement +} + +/** + * API access to elements of the DFDL Infoset of both + * complex and simple type. + */ +trait InfosetElement extends InfosetItem { + + /** + * In DFDL both simple and complex type elements can be + * nilled. + * + * @return true if the element is nilled, false otherwise. + */ + def isNilled: Boolean + + /** + * @return true if the element is a string or hexBinary of length 0, false otherwise. + */ + def isEmpty: Boolean + + /* + * Access to the metadata information about this element. + * See [[ElementMetadata]] + */ + def metadata: ElementMetadata + +} + +/** + * Methods specific complex elements in the infoset + */ +trait InfosetComplexElement extends InfosetElement { + + /* + * Access to the metadata information about this element. + * See [[ComplexElementMetadata]] + */ + override def metadata: ComplexElementMetadata +} + +/** + * Methods specific to simple elements in the infoset + */ +trait InfosetSimpleElement extends InfosetElement { + + /* + * Access to the metadata information about this element. + * See [[SimpleElementMetadata]] + */ + override def metadata: SimpleElementMetadata + + /** + * @return true if the element has a value, false for a nilled element. + */ + def hasValue: Boolean Review Comment: How is this different from the `isNilled` method inherited from `InfosetElement`. Seems if isNilled is false, then the element must have a value. Can we drop this to simplify the API? ########## daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/api/Infoset.scala: ########## @@ -0,0 +1,266 @@ +/* + * 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.api + +import java.lang.{ Boolean => JBoolean } +import java.lang.{ Byte => JByte } +import java.lang.{ Double => JDouble } +import java.lang.{ Float => JFloat } +import java.lang.{ Integer => JInt } +import java.lang.{ Long => JLong } +import java.lang.{ Number => JNumber } +import java.lang.{ Short => JShort } +import java.lang.{ String => JString } +import java.math.{ BigDecimal => JBigDecimal } +import java.math.{ BigInteger => JBigInt } +import java.net.URI + +import com.ibm.icu.util.Calendar + +/** + * API access to array objects in the DFDL Infoset + */ +trait InfosetArray extends InfosetItem { + + /** + * @return the metadata of the element that is an array + */ + override def metadata: ElementMetadata + + /** + * @return the number of items in the array. + */ + def length: Long + + /** + * Access to an infoset element of the array. + * @param occursIndex1b the one-based index of the element to be accessed + * @return the infoset element located at the given index + */ + def apply(occursIndex1b: Long): InfosetElement +} + +/** + * API access to elements of the DFDL Infoset of both + * complex and simple type. + */ +trait InfosetElement extends InfosetItem { + + /** + * In DFDL both simple and complex type elements can be + * nilled. + * + * @return true if the element is nilled, false otherwise. + */ + def isNilled: Boolean + + /** + * @return true if the element is a string or hexBinary of length 0, false otherwise. + */ + def isEmpty: Boolean + + /* + * Access to the metadata information about this element. + * See [[ElementMetadata]] + */ + def metadata: ElementMetadata + +} + +/** + * Methods specific complex elements in the infoset + */ +trait InfosetComplexElement extends InfosetElement { + + /* + * Access to the metadata information about this element. + * See [[ComplexElementMetadata]] + */ + override def metadata: ComplexElementMetadata +} + +/** + * Methods specific to simple elements in the infoset + */ +trait InfosetSimpleElement extends InfosetElement { + + /* + * Access to the metadata information about this element. + * See [[SimpleElementMetadata]] + */ + override def metadata: SimpleElementMetadata + + /** + * @return true if the element has a value, false for a nilled element. + */ + def hasValue: Boolean + + /** + * Obtains the value, then converts it to a string. + * Caches the string so we're not allocating strings repeatedly + */ + def dataValueAsString: String Review Comment: Thoughts on something like `getText` instead? This follows the `getXYZ` convention of the other functions, but maybe makes it clear that it is getting a textual representation of the underlying value? ########## daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/api/Infoset.scala: ########## @@ -0,0 +1,266 @@ +/* + * 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.api + +import java.lang.{ Boolean => JBoolean } +import java.lang.{ Byte => JByte } +import java.lang.{ Double => JDouble } +import java.lang.{ Float => JFloat } +import java.lang.{ Integer => JInt } +import java.lang.{ Long => JLong } +import java.lang.{ Number => JNumber } +import java.lang.{ Short => JShort } +import java.lang.{ String => JString } +import java.math.{ BigDecimal => JBigDecimal } +import java.math.{ BigInteger => JBigInt } +import java.net.URI + +import com.ibm.icu.util.Calendar + +/** + * API access to array objects in the DFDL Infoset + */ +trait InfosetArray extends InfosetItem { + + /** + * @return the metadata of the element that is an array + */ + override def metadata: ElementMetadata + + /** + * @return the number of items in the array. + */ + def length: Long + + /** + * Access to an infoset element of the array. + * @param occursIndex1b the one-based index of the element to be accessed + * @return the infoset element located at the given index + */ + def apply(occursIndex1b: Long): InfosetElement +} + +/** + * API access to elements of the DFDL Infoset of both + * complex and simple type. + */ +trait InfosetElement extends InfosetItem { + + /** + * In DFDL both simple and complex type elements can be + * nilled. + * + * @return true if the element is nilled, false otherwise. + */ + def isNilled: Boolean + + /** + * @return true if the element is a string or hexBinary of length 0, false otherwise. + */ + def isEmpty: Boolean + + /* + * Access to the metadata information about this element. + * See [[ElementMetadata]] + */ + def metadata: ElementMetadata + +} + +/** + * Methods specific complex elements in the infoset + */ +trait InfosetComplexElement extends InfosetElement { + + /* + * Access to the metadata information about this element. + * See [[ComplexElementMetadata]] + */ + override def metadata: ComplexElementMetadata +} + +/** + * Methods specific to simple elements in the infoset + */ +trait InfosetSimpleElement extends InfosetElement { + + /* + * Access to the metadata information about this element. + * See [[SimpleElementMetadata]] + */ + override def metadata: SimpleElementMetadata + + /** + * @return true if the element has a value, false for a nilled element. + */ + def hasValue: Boolean + + /** + * Obtains the value, then converts it to a string. + * Caches the string so we're not allocating strings repeatedly + */ + def dataValueAsString: String + + /* + * These are so that API users don't have to know about our + * very Scala-oriented DataValue type system. + */ + + /** + * @return the value of this simple element as a Scala AnyRef, which is + * equivalent to a Java Object. + */ + def getAnyRef: AnyRef + + /** + * @return the value of this simple element as an Object (java.lang.Object), + * which is equivalent to Scala AnyRef. + */ + final def getObject: java.lang.Object = getAnyRef + + // Note: I could not get @throws in scaladoc to work right. + // Complains "Could not find any member to link for ... and I tried various formulations of + // ClassCastException, with package, with and without [[..]]. + // So I've just converted it to plain text. + + /** + * @return Casts the value of this simple element as a java.math.BigDecimal. + * <p/>throws `ClassCastException` if the element value is not of type Decimal. + */ + def getBigDecimal: JBigDecimal + + /** + * @return Casts the value of this Date, Time, or DateTime simple element as a + * `com.ibm.icu.util.Calendar` + * <p/>throws `ClassCastException` if the element value is not of type Date, Time, or DateTime + */ + def getCalendar: Calendar + + /** + * @return the value of this simple element cast to `com.ibm.icu.util.Calendar`. + * <p/>throws `ClassCastException` if the element value is not of the required type. + */ + def getDate: Calendar + + /** + * @return the value of this simple element cast to `com.ibm.icu.util.Calendar`. + * <p/>throws `ClassCastException` if the element value is not of the required type. + */ + def getTime: Calendar + + /** + * @return the value of this simple element cast to `com.ibm.icu.util.Calendar`. + * <p/>throws `ClassCastException` if the element value is not of the required type. + */ + def getDateTime: Calendar + + /** + * @return the value of this simple element of HexBinary type cast to `Array[Byte]`. + * <p/>throws `ClassCastException` if the element value is not of HexBinary type. + */ + def getByteArray: Array[Byte] + + /** + * @return the value of this simple element of Boolean type cast to java.lang.Boolean. + * <p/>throws `ClassCastException` if the element value is not of Boolean type. + */ + def getBoolean: JBoolean + + /** + * @return the value of this simple element of numeric type cast to java.lang.Number. + * <p/>throws `ClassCastException` if the element value is not of numeric type. + */ + def getNumber: JNumber Review Comment: Suggest we remove getNumber? The only useful functions provided by Number ways to get the number as a byte, short, int, long, float, or double. Presumably users are going to need to know which type of number they need anyways and call the right getXZY function? ########## daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/api/Metadata.scala: ########## @@ -0,0 +1,269 @@ +/* + * 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.api + +import scala.xml.NamespaceBinding + +/** + * This is the supportable API for access to the RuntimeData structures + * which provide access to static information about a given . + * + * This is used to interface other data processing fabrics to Daffodil + * data and metadata, by mapping to/from these metadata objects. + */ +trait Metadata { + + /** + * Provides the file context of a metadata component. This refers to the specific + * DFDL schema file where the corresponding DFDL schema text resides corresponding + * to this metadata object. + * <p/> + * This is for use in diagnostic messaging. It is not the actual file URI, because + * those may contain personal-identifying information about the person/acccount and + * system that compiled the schema. It will provide enough content about the file URI that + * a user will be able to identify which file, but some prefix of the path + * components trimmed to make it of a manageable length. + * <p/> + * Used along with [[org.apache.daffodil.runtime1.api.Metadata.schemaFileLineNumber]] + * and [[org.apache.daffodil.runtime1.api.Metadata.schemaFileLineColumnNumber]], + * this can give a precise location in the DFDL schema file. + * @return a string containing the file information, or null if unknown. + */ + def schemaFileInfo: String + + /** + * Provides the line number to go with [[org.apache.daffodil.runtime1.api.Metadata.schemaFileInfo]]. + * @return the line number as a string, or null if unknown. + */ + def schemaFileLineNumber: String + + /** + * Provides the column number within the text line, to go with [[org.apache.daffodil.runtime1.api.Metadata.schemaFileLineNumber]]. + * @return the column number within the text line, as a string, or null if unknown. + */ + def schemaFileLineColumnNumber: String + + /** + * The name of the schema component, in a form suitable for diagnostic messages. + * Unnamed components like sequence or choice groups have a diagnosticDebugName, despite not having + * any actual name. + * @return the name of the component, suitable for use in diagnostic messages. + */ + def diagnosticDebugName: String + + /** + * The standard conversion of the metadata component to a string. + * Unless overridden this uses the [[org.apache.daffodil.runtime1.api.Metadata.diagnosticDebugName]]. + * @return string representation of this metadata component. + */ + override def toString = diagnosticDebugName +} + +/* + * Provides metadata access that is common to all Terms, which include + * Elements of simple or complex type, as well as the Sequence and Choice groups. + */ +trait TermMetadata extends Metadata { + // nothing here +} + +/** + * Common metadata access for all elements, of simple or complex type. + */ +trait ElementMetadata extends TermMetadata { + + /** + * @return the name of this element. In the case of a global/qualified name, this is only the local + * part of the QName. + */ + def name: String + + /** + * @return the namespace URI as a string, or null if no namespace. + */ + def namespace: String + + /** + * @return the namespace bindings needed to construct an XML element from a Daffodil infoset + * element of simple or complex type. + */ + def minimizedScope: NamespaceBinding + + /** + * @return the namespace prefix part of the XML QName of this component, or null if there + * is no prefix defined or no namespace. + */ + def prefix: String + + /** + * @return true if two or more occurrences are possible. + * Note that having only 0 or 1 occurrence is not considered an array, + * but rather an optional element. + */ + def isArray: Boolean + + /** + * @return true if only 0 or 1 occurrence are possible. + */ + def isOptional: Boolean + + /** + * @return the QName string for this element. + */ + def toQName: String + + /** + * @return true if the element is declared to be nillable. + */ + def isNillable: Boolean + + /** + * Provides access to the runtime properties. This is an extended collection of + * name-value pairs which are associated with a schema component. + * <p/> + * Runtime properties are intended to use for new ad-hoc property extensions to + * DFDL. These name-value pairs are visible to infoset outputters as well. + * + * @return a java-compatible map of name-value pairs. + */ + def runtimeProperties: java.util.Map[String, String] + +} + +/** + * Access to metadata values exclusive to elements of complex type. + */ +trait ComplexElementMetadata extends ElementMetadata { + + /** + * @return an ordered sequence of the child elements within this complex type regardless + * of their nesting within sequence and choice groups. + */ + def childMetadata: Seq[ElementMetadata] +} + +/** + * Access to metadata values exclusive to elements of simple type. + */ +trait SimpleElementMetadata extends ElementMetadata { + + /** + * The primitive type of this element as a string. + * + * @return one of boolean, double, float, date, time, dateTime, string, anyURI, hexBinary, decimal, + * integer, nonNegativeInteger, byte, short, int, long, unsignedByte, unsignedShort, + * unsignedInt, unsignedLong as a string. Note the initial lower-case letter in all these + * type names. + */ + def primTypeName: String Review Comment: Why not use a enum for this? If java it would allow you to swich on it since you can't switch on a string. I think it would also give warnings if a match/case didn't cover all enum values. Maybe it's a bit annoying to convert from the internal primType objects to enum values? ########## daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/api/Infoset.scala: ########## @@ -0,0 +1,266 @@ +/* + * 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.api + +import java.lang.{ Boolean => JBoolean } +import java.lang.{ Byte => JByte } +import java.lang.{ Double => JDouble } +import java.lang.{ Float => JFloat } +import java.lang.{ Integer => JInt } +import java.lang.{ Long => JLong } +import java.lang.{ Number => JNumber } +import java.lang.{ Short => JShort } +import java.lang.{ String => JString } +import java.math.{ BigDecimal => JBigDecimal } +import java.math.{ BigInteger => JBigInt } +import java.net.URI + +import com.ibm.icu.util.Calendar + +/** + * API access to array objects in the DFDL Infoset + */ +trait InfosetArray extends InfosetItem { + + /** + * @return the metadata of the element that is an array + */ + override def metadata: ElementMetadata + + /** + * @return the number of items in the array. + */ + def length: Long + + /** + * Access to an infoset element of the array. + * @param occursIndex1b the one-based index of the element to be accessed + * @return the infoset element located at the given index + */ + def apply(occursIndex1b: Long): InfosetElement +} + +/** + * API access to elements of the DFDL Infoset of both + * complex and simple type. + */ +trait InfosetElement extends InfosetItem { + + /** + * In DFDL both simple and complex type elements can be + * nilled. + * + * @return true if the element is nilled, false otherwise. + */ + def isNilled: Boolean + + /** + * @return true if the element is a string or hexBinary of length 0, false otherwise. + */ + def isEmpty: Boolean + + /* + * Access to the metadata information about this element. + * See [[ElementMetadata]] + */ + def metadata: ElementMetadata + +} + +/** + * Methods specific complex elements in the infoset + */ +trait InfosetComplexElement extends InfosetElement { + + /* + * Access to the metadata information about this element. + * See [[ComplexElementMetadata]] + */ + override def metadata: ComplexElementMetadata +} + +/** + * Methods specific to simple elements in the infoset + */ +trait InfosetSimpleElement extends InfosetElement { + + /* + * Access to the metadata information about this element. + * See [[SimpleElementMetadata]] + */ + override def metadata: SimpleElementMetadata + + /** + * @return true if the element has a value, false for a nilled element. + */ + def hasValue: Boolean + + /** + * Obtains the value, then converts it to a string. + * Caches the string so we're not allocating strings repeatedly + */ + def dataValueAsString: String + + /* + * These are so that API users don't have to know about our + * very Scala-oriented DataValue type system. + */ + + /** + * @return the value of this simple element as a Scala AnyRef, which is + * equivalent to a Java Object. + */ + def getAnyRef: AnyRef + + /** + * @return the value of this simple element as an Object (java.lang.Object), + * which is equivalent to Scala AnyRef. + */ + final def getObject: java.lang.Object = getAnyRef + + // Note: I could not get @throws in scaladoc to work right. + // Complains "Could not find any member to link for ... and I tried various formulations of + // ClassCastException, with package, with and without [[..]]. + // So I've just converted it to plain text. + + /** + * @return Casts the value of this simple element as a java.math.BigDecimal. + * <p/>throws `ClassCastException` if the element value is not of type Decimal. + */ + def getBigDecimal: JBigDecimal Review Comment: A thought about all these getXYZ function is that they sortof leak internal implementation details and require infoset outputter implementations to know how daffodil internally represents the primitive types. For example, if `metadata.primTypeName` returns `unsignedInt`, then infoset outputters need to know to use `getLong` since we implement `unsignedInt` as a `Long`. It also means we can't easily change thing in the future. For example, maybe one day we decide to implement all signed integergs (byte, short, int, long) as `Long`s to simply things. That change would then leak into infoset walkers and potentially break them I'm not sure of a good way to deal with this though. Maybe the primTypeName returns the underlying type? That would mean primTypeNames would never have unsigned types, since Java has no concept of that. ########## daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/infoset/InfosetImpl.scala: ########## @@ -1575,9 +1617,12 @@ sealed class DIComplex(override val erd: ElementRuntimeData) with DIComplexSharedImplMixin with InfosetComplexElement { diComplex => + override def metadata: ComplexElementMetadata = erd + final override def isSimple = false final override def isComplex = true final override def isArray = false + // final override def optArray: Option[DIArray] = None Review Comment: Delete comment? ########## daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/api/Metadata.scala: ########## @@ -0,0 +1,269 @@ +/* + * 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.api + +import scala.xml.NamespaceBinding + +/** + * This is the supportable API for access to the RuntimeData structures + * which provide access to static information about a given . + * + * This is used to interface other data processing fabrics to Daffodil + * data and metadata, by mapping to/from these metadata objects. + */ +trait Metadata { + + /** + * Provides the file context of a metadata component. This refers to the specific + * DFDL schema file where the corresponding DFDL schema text resides corresponding + * to this metadata object. + * <p/> + * This is for use in diagnostic messaging. It is not the actual file URI, because + * those may contain personal-identifying information about the person/acccount and + * system that compiled the schema. It will provide enough content about the file URI that + * a user will be able to identify which file, but some prefix of the path + * components trimmed to make it of a manageable length. + * <p/> + * Used along with [[org.apache.daffodil.runtime1.api.Metadata.schemaFileLineNumber]] + * and [[org.apache.daffodil.runtime1.api.Metadata.schemaFileLineColumnNumber]], + * this can give a precise location in the DFDL schema file. + * @return a string containing the file information, or null if unknown. + */ + def schemaFileInfo: String + + /** + * Provides the line number to go with [[org.apache.daffodil.runtime1.api.Metadata.schemaFileInfo]]. + * @return the line number as a string, or null if unknown. + */ + def schemaFileLineNumber: String + + /** + * Provides the column number within the text line, to go with [[org.apache.daffodil.runtime1.api.Metadata.schemaFileLineNumber]]. + * @return the column number within the text line, as a string, or null if unknown. + */ + def schemaFileLineColumnNumber: String + + /** + * The name of the schema component, in a form suitable for diagnostic messages. + * Unnamed components like sequence or choice groups have a diagnosticDebugName, despite not having + * any actual name. + * @return the name of the component, suitable for use in diagnostic messages. + */ + def diagnosticDebugName: String + + /** + * The standard conversion of the metadata component to a string. + * Unless overridden this uses the [[org.apache.daffodil.runtime1.api.Metadata.diagnosticDebugName]]. + * @return string representation of this metadata component. + */ + override def toString = diagnosticDebugName +} + +/* + * Provides metadata access that is common to all Terms, which include + * Elements of simple or complex type, as well as the Sequence and Choice groups. + */ +trait TermMetadata extends Metadata { + // nothing here +} + +/** + * Common metadata access for all elements, of simple or complex type. + */ +trait ElementMetadata extends TermMetadata { + + /** + * @return the name of this element. In the case of a global/qualified name, this is only the local + * part of the QName. + */ + def name: String + + /** + * @return the namespace URI as a string, or null if no namespace. + */ + def namespace: String + + /** + * @return the namespace bindings needed to construct an XML element from a Daffodil infoset + * element of simple or complex type. + */ + def minimizedScope: NamespaceBinding + + /** + * @return the namespace prefix part of the XML QName of this component, or null if there + * is no prefix defined or no namespace. + */ + def prefix: String + + /** + * @return true if two or more occurrences are possible. + * Note that having only 0 or 1 occurrence is not considered an array, + * but rather an optional element. + */ + def isArray: Boolean + + /** + * @return true if only 0 or 1 occurrence are possible. + */ + def isOptional: Boolean + + /** + * @return the QName string for this element. + */ + def toQName: String + + /** + * @return true if the element is declared to be nillable. + */ + def isNillable: Boolean + + /** + * Provides access to the runtime properties. This is an extended collection of + * name-value pairs which are associated with a schema component. + * <p/> + * Runtime properties are intended to use for new ad-hoc property extensions to + * DFDL. These name-value pairs are visible to infoset outputters as well. + * + * @return a java-compatible map of name-value pairs. + */ + def runtimeProperties: java.util.Map[String, String] + +} + +/** + * Access to metadata values exclusive to elements of complex type. + */ +trait ComplexElementMetadata extends ElementMetadata { + + /** + * @return an ordered sequence of the child elements within this complex type regardless + * of their nesting within sequence and choice groups. + */ + def childMetadata: Seq[ElementMetadata] +} + +/** + * Access to metadata values exclusive to elements of simple type. + */ +trait SimpleElementMetadata extends ElementMetadata { + + /** + * The primitive type of this element as a string. + * + * @return one of boolean, double, float, date, time, dateTime, string, anyURI, hexBinary, decimal, + * integer, nonNegativeInteger, byte, short, int, long, unsignedByte, unsignedShort, + * unsignedInt, unsignedLong as a string. Note the initial lower-case letter in all these + * type names. + */ + def primTypeName: String +} + +/** + * Access to metadata values shared by both sequences and choices + * which are known collectively as Model Groups. + */ +trait ModelGroupMetadata extends TermMetadata {} Review Comment: Should ModelGroupMetaData include childMetadata like we do with complex elements? ########## daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/infoset/InfosetOutputter.scala: ########## @@ -84,75 +85,98 @@ trait InfosetOutputter { * value, nil, name, namespace, etc. */ @throws[Exception] - def endSimple(diSimple: DISimple): Unit + def endSimple(diSimple: InfosetSimpleElement): Unit /** * Called by Daffodil internals to signify the beginning of a complex element. * * Throws java.lang.Exception if there was an error and Daffodil should stop parsing * - * @param diComplex the complex element that is started. Various fields of + * @param complex the complex element that is started. Various fields of * DIComplex can be accessed to determine things like the * nil, name, namespace, etc. */ @throws[Exception] - def startComplex(diComplex: DIComplex): Unit + def startComplex(complex: InfosetComplexElement): Unit /** * Called by Daffodil internals to signify the end of a complex element. * * Throws java.lang.Exception if there was an error and Daffodil should stop parsing * - * @param diComplex the complex element that is ended. Various fields of + * @param complex the complex element that is ended. Various fields of * DIComplex can be accessed to determine things like the * nil, name, namespace, etc. */ @throws[Exception] - def endComplex(diComplex: DIComplex): Unit + def endComplex(complex: InfosetComplexElement): Unit /** * Called by Daffodil internals to signify the beginning of an array of elements. * * Throws java.lang.Exception if there was an error and Daffodil should stop parsing * - * @param diComplex the array that is started. Various fields of + * @param array the array that is started. Various fields of * DIArray can be accessed to determine things like the * name, namespace, etc. */ @throws[Exception] - def startArray(diArray: DIArray): Unit + def startArray(array: InfosetArray): Unit /** * Called by Daffodil internals to signify the end of an array of elements. * * Throws java.lang.Exception if there was an error and Daffodil should stop parsing * - * @param diComplex the array that is ended. Various fields of + * @param array the array that is ended. Various fields of * DIArray can be accessed to determine things like the * name, namespace, etc. */ @throws[Exception] - def endArray(diArray: DIArray): Unit + def endArray(array: InfosetArray): Unit - def getStatus(): Status = { - // Done, Ready (Not started), Visiting (part way done - can retry to visit more)... - status - } + def getStatus(): Status /** - * Helper function to determine if an element is nilled or not, taking into - * account whether or not the nilled state has been set yet. + * Override this to be fed any exceptions thrown by the start/end methods of this class. + * This is mostly to facilitate users debugging their code that uses the API. * - * @param diElement the element to check the nilled state of + * This saves the user of the API from having to wrap try/catch around their + * start/end method bodies. Review Comment: Personally, I'm not really a huge fan of this. As annoying as wrapping function in try/catch blocks is, it's kindof the way it's done. And really, users should be handling exceptions individually instead of wrapping an entire function. That's probably bad practice. I'm also wondering if it many cases it's even too late to do anything about it and debugging wouldn't be that useful. The code and variables where the exception was thrown are out of scope, so important information might be lost. If an exception is thrown, it seems the best course of action is to look at the stack trace and point a breakpoint in the right spot. If our SDE isn't making that stacktrace available, maybe we should do that instead? That said, if we do keep this, we need to specify what a user should do with this exception in this funtion. In most cases most cases that's should probably be nothing. But I also assume they shouldn't rethrow or do anything that might cause another exception to be thrown, not sure if we handle that case. ########## daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/api/Metadata.scala: ########## @@ -0,0 +1,269 @@ +/* + * 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.api + +import scala.xml.NamespaceBinding + +/** + * This is the supportable API for access to the RuntimeData structures + * which provide access to static information about a given . + * + * This is used to interface other data processing fabrics to Daffodil + * data and metadata, by mapping to/from these metadata objects. + */ +trait Metadata { + + /** + * Provides the file context of a metadata component. This refers to the specific + * DFDL schema file where the corresponding DFDL schema text resides corresponding + * to this metadata object. + * <p/> + * This is for use in diagnostic messaging. It is not the actual file URI, because + * those may contain personal-identifying information about the person/acccount and + * system that compiled the schema. It will provide enough content about the file URI that + * a user will be able to identify which file, but some prefix of the path + * components trimmed to make it of a manageable length. + * <p/> + * Used along with [[org.apache.daffodil.runtime1.api.Metadata.schemaFileLineNumber]] + * and [[org.apache.daffodil.runtime1.api.Metadata.schemaFileLineColumnNumber]], + * this can give a precise location in the DFDL schema file. + * @return a string containing the file information, or null if unknown. + */ + def schemaFileInfo: String + + /** + * Provides the line number to go with [[org.apache.daffodil.runtime1.api.Metadata.schemaFileInfo]]. + * @return the line number as a string, or null if unknown. + */ + def schemaFileLineNumber: String + + /** + * Provides the column number within the text line, to go with [[org.apache.daffodil.runtime1.api.Metadata.schemaFileLineNumber]]. + * @return the column number within the text line, as a string, or null if unknown. + */ + def schemaFileLineColumnNumber: String + + /** + * The name of the schema component, in a form suitable for diagnostic messages. + * Unnamed components like sequence or choice groups have a diagnosticDebugName, despite not having + * any actual name. + * @return the name of the component, suitable for use in diagnostic messages. + */ + def diagnosticDebugName: String + + /** + * The standard conversion of the metadata component to a string. + * Unless overridden this uses the [[org.apache.daffodil.runtime1.api.Metadata.diagnosticDebugName]]. + * @return string representation of this metadata component. + */ + override def toString = diagnosticDebugName +} + +/* + * Provides metadata access that is common to all Terms, which include + * Elements of simple or complex type, as well as the Sequence and Choice groups. + */ +trait TermMetadata extends Metadata { + // nothing here +} + +/** + * Common metadata access for all elements, of simple or complex type. + */ +trait ElementMetadata extends TermMetadata { + + /** + * @return the name of this element. In the case of a global/qualified name, this is only the local + * part of the QName. + */ + def name: String + + /** + * @return the namespace URI as a string, or null if no namespace. + */ + def namespace: String + + /** + * @return the namespace bindings needed to construct an XML element from a Daffodil infoset + * element of simple or complex type. + */ + def minimizedScope: NamespaceBinding + + /** + * @return the namespace prefix part of the XML QName of this component, or null if there + * is no prefix defined or no namespace. + */ + def prefix: String + + /** + * @return true if two or more occurrences are possible. + * Note that having only 0 or 1 occurrence is not considered an array, + * but rather an optional element. + */ + def isArray: Boolean + + /** + * @return true if only 0 or 1 occurrence are possible. + */ + def isOptional: Boolean + + /** + * @return the QName string for this element. + */ + def toQName: String + + /** + * @return true if the element is declared to be nillable. + */ + def isNillable: Boolean + + /** + * Provides access to the runtime properties. This is an extended collection of + * name-value pairs which are associated with a schema component. + * <p/> + * Runtime properties are intended to use for new ad-hoc property extensions to + * DFDL. These name-value pairs are visible to infoset outputters as well. + * + * @return a java-compatible map of name-value pairs. + */ + def runtimeProperties: java.util.Map[String, String] + +} + +/** + * Access to metadata values exclusive to elements of complex type. + */ +trait ComplexElementMetadata extends ElementMetadata { + + /** + * @return an ordered sequence of the child elements within this complex type regardless + * of their nesting within sequence and choice groups. + */ + def childMetadata: Seq[ElementMetadata] Review Comment: Why does this not include sequence and choice metadata? If we're granted access to children, it seems important to also grant access to know if those children are in hidden sequences or are in choices/unions. Personally I would prefere it we didn't grant child access at all. Can the use case of a docRoot having one complex repeating child not be figured out using the walker? ########## daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/infoset/InfosetOutputter.scala: ########## @@ -84,75 +85,98 @@ trait InfosetOutputter { * value, nil, name, namespace, etc. */ @throws[Exception] - def endSimple(diSimple: DISimple): Unit + def endSimple(diSimple: InfosetSimpleElement): Unit /** * Called by Daffodil internals to signify the beginning of a complex element. * * Throws java.lang.Exception if there was an error and Daffodil should stop parsing * - * @param diComplex the complex element that is started. Various fields of + * @param complex the complex element that is started. Various fields of * DIComplex can be accessed to determine things like the * nil, name, namespace, etc. */ @throws[Exception] - def startComplex(diComplex: DIComplex): Unit + def startComplex(complex: InfosetComplexElement): Unit /** * Called by Daffodil internals to signify the end of a complex element. * * Throws java.lang.Exception if there was an error and Daffodil should stop parsing * - * @param diComplex the complex element that is ended. Various fields of + * @param complex the complex element that is ended. Various fields of * DIComplex can be accessed to determine things like the * nil, name, namespace, etc. */ @throws[Exception] - def endComplex(diComplex: DIComplex): Unit + def endComplex(complex: InfosetComplexElement): Unit /** * Called by Daffodil internals to signify the beginning of an array of elements. * * Throws java.lang.Exception if there was an error and Daffodil should stop parsing * - * @param diComplex the array that is started. Various fields of + * @param array the array that is started. Various fields of * DIArray can be accessed to determine things like the * name, namespace, etc. */ @throws[Exception] - def startArray(diArray: DIArray): Unit + def startArray(array: InfosetArray): Unit /** * Called by Daffodil internals to signify the end of an array of elements. * * Throws java.lang.Exception if there was an error and Daffodil should stop parsing * - * @param diComplex the array that is ended. Various fields of + * @param array the array that is ended. Various fields of * DIArray can be accessed to determine things like the * name, namespace, etc. */ @throws[Exception] - def endArray(diArray: DIArray): Unit + def endArray(array: InfosetArray): Unit - def getStatus(): Status = { - // Done, Ready (Not started), Visiting (part way done - can retry to visit more)... - status - } + def getStatus(): Status /** - * Helper function to determine if an element is nilled or not, taking into - * account whether or not the nilled state has been set yet. + * Override this to be fed any exceptions thrown by the start/end methods of this class. + * This is mostly to facilitate users debugging their code that uses the API. * - * @param diElement the element to check the nilled state of + * This saves the user of the API from having to wrap try/catch around their + * start/end method bodies. * - * @return true if the nilled state has been set and is true. false if the - * nilled state is false or if the nilled state has not been set yet - * (e.g. during debugging) + * @param e the exception thrown by one of the other methods. */ - final def isNilled(diElement: DIElement): Boolean = { - val maybeIsNilled = diElement.maybeIsNilled - maybeIsNilled.isDefined && maybeIsNilled.get == true - } + def exceptionOccurred(e: Exception): Unit Review Comment: Sould this have a default implementation of `{}` so that infoset outputters aren't forced to implement it? ########## daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/api/Metadata.scala: ########## @@ -0,0 +1,269 @@ +/* + * 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.api + +import scala.xml.NamespaceBinding + +/** + * This is the supportable API for access to the RuntimeData structures + * which provide access to static information about a given . + * + * This is used to interface other data processing fabrics to Daffodil + * data and metadata, by mapping to/from these metadata objects. + */ +trait Metadata { + + /** + * Provides the file context of a metadata component. This refers to the specific + * DFDL schema file where the corresponding DFDL schema text resides corresponding + * to this metadata object. + * <p/> + * This is for use in diagnostic messaging. It is not the actual file URI, because + * those may contain personal-identifying information about the person/acccount and + * system that compiled the schema. It will provide enough content about the file URI that + * a user will be able to identify which file, but some prefix of the path + * components trimmed to make it of a manageable length. + * <p/> + * Used along with [[org.apache.daffodil.runtime1.api.Metadata.schemaFileLineNumber]] + * and [[org.apache.daffodil.runtime1.api.Metadata.schemaFileLineColumnNumber]], + * this can give a precise location in the DFDL schema file. + * @return a string containing the file information, or null if unknown. + */ + def schemaFileInfo: String + + /** + * Provides the line number to go with [[org.apache.daffodil.runtime1.api.Metadata.schemaFileInfo]]. + * @return the line number as a string, or null if unknown. + */ + def schemaFileLineNumber: String + + /** + * Provides the column number within the text line, to go with [[org.apache.daffodil.runtime1.api.Metadata.schemaFileLineNumber]]. + * @return the column number within the text line, as a string, or null if unknown. + */ + def schemaFileLineColumnNumber: String + + /** + * The name of the schema component, in a form suitable for diagnostic messages. + * Unnamed components like sequence or choice groups have a diagnosticDebugName, despite not having + * any actual name. + * @return the name of the component, suitable for use in diagnostic messages. + */ + def diagnosticDebugName: String Review Comment: Seems odd to provide a debug thing in a public API. Should we remove this? ########## daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/infoset/InfosetOutputter.scala: ########## @@ -190,3 +213,10 @@ object Status extends Enumeration { type Status = Value val DONE, READY, VISITING = Value } + +trait ExceptionOccurredMixin { + def exceptionOccurred(e: Exception): Unit = { + val ex = e // because sometimes the debugger won't show the exception Review Comment: > // because sometimes the debugger won't show the exception Is this the Daffodil CLI debugger, or an IDE debugger? The InfosetWalker will convert any exceptions thrown by an InfosetOutputter to an SDE, maybe that is what is making it hard to see the exceptions? Maybe that should include the stack trace? ########## daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/infoset/InfosetOutputterImpl.scala: ########## @@ -0,0 +1,47 @@ +/* + * 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.infoset + +import org.apache.daffodil.runtime1.api.InfosetElement + +abstract class InfosetOutputterImpl() extends BlobMethodsImpl with InfosetOutputter { + + import Status._ + + private def status: Status = READY + + override def getStatus(): Status = { + // Done, Ready (Not started), Visiting (part way done - can retry to visit more)... + status + } Review Comment: This staus stuff doesn't even seem to be use? Can we remove it? I dont' remember what it was originally for. ########## daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/infoset/JsonInfosetOutputter.scala: ########## @@ -92,12 +96,12 @@ class JsonInfosetOutputter private (writer: java.io.Writer, pretty: Boolean) if (pretty) outputIndentation(writer) } - override def startSimple(simple: DISimple): Unit = { + override def startSimple(simple: InfosetSimpleElement): Unit = { startNode() startElement(simple) - if (!isNilled(simple) && simple.hasValue) { + if (!simple.isNilled && simple.hasValue) { Review Comment: I asked elsewhere if `hasValue` could be removed. Now that I look at this code, I think maybe the answer is "no", but has nothing to with nillables. I *think* it is because an infoset outputter could be use by the CLI debugger, which configures the walker to walk in places where it normally wouldn't, and so parts of an infoset might not actually have a value. So most normal infoset outputters (e.g. one uses in Drill) should never need to use `hasValue`. It's only those that could be used by the CLI debugger. I wonder if there's a better way we can handle this so that infoset outputters don't ever have to be concerned with possibly not having a value if used during debugging, and we can move that logic somewhere else? ########## daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/infoset/InfosetOutputter.scala: ########## @@ -84,75 +85,98 @@ trait InfosetOutputter { * value, nil, name, namespace, etc. */ @throws[Exception] - def endSimple(diSimple: DISimple): Unit + def endSimple(diSimple: InfosetSimpleElement): Unit /** * Called by Daffodil internals to signify the beginning of a complex element. * * Throws java.lang.Exception if there was an error and Daffodil should stop parsing * - * @param diComplex the complex element that is started. Various fields of + * @param complex the complex element that is started. Various fields of * DIComplex can be accessed to determine things like the * nil, name, namespace, etc. */ @throws[Exception] - def startComplex(diComplex: DIComplex): Unit + def startComplex(complex: InfosetComplexElement): Unit /** * Called by Daffodil internals to signify the end of a complex element. * * Throws java.lang.Exception if there was an error and Daffodil should stop parsing * - * @param diComplex the complex element that is ended. Various fields of + * @param complex the complex element that is ended. Various fields of * DIComplex can be accessed to determine things like the * nil, name, namespace, etc. */ @throws[Exception] - def endComplex(diComplex: DIComplex): Unit + def endComplex(complex: InfosetComplexElement): Unit /** * Called by Daffodil internals to signify the beginning of an array of elements. * * Throws java.lang.Exception if there was an error and Daffodil should stop parsing * - * @param diComplex the array that is started. Various fields of + * @param array the array that is started. Various fields of * DIArray can be accessed to determine things like the * name, namespace, etc. */ @throws[Exception] - def startArray(diArray: DIArray): Unit + def startArray(array: InfosetArray): Unit /** * Called by Daffodil internals to signify the end of an array of elements. * * Throws java.lang.Exception if there was an error and Daffodil should stop parsing * - * @param diComplex the array that is ended. Various fields of + * @param array the array that is ended. Various fields of * DIArray can be accessed to determine things like the * name, namespace, etc. */ @throws[Exception] - def endArray(diArray: DIArray): Unit + def endArray(array: InfosetArray): Unit - def getStatus(): Status = { - // Done, Ready (Not started), Visiting (part way done - can retry to visit more)... - status - } + def getStatus(): Status /** - * Helper function to determine if an element is nilled or not, taking into - * account whether or not the nilled state has been set yet. + * Override this to be fed any exceptions thrown by the start/end methods of this class. + * This is mostly to facilitate users debugging their code that uses the API. * - * @param diElement the element to check the nilled state of + * This saves the user of the API from having to wrap try/catch around their + * start/end method bodies. * - * @return true if the nilled state has been set and is true. false if the - * nilled state is false or if the nilled state has not been set yet - * (e.g. during debugging) + * @param e the exception thrown by one of the other methods. */ - final def isNilled(diElement: DIElement): Boolean = { - val maybeIsNilled = diElement.maybeIsNilled - maybeIsNilled.isDefined && maybeIsNilled.get == true - } + def exceptionOccurred(e: Exception): Unit + +} + +/** + * Methods that provide Blob (Binary Large Object) support. + * + * FIXME: Scaladoc + */ +trait BlobMethodsMixin { + + def setBlobAttributes(blobDir: Path, blobPrefix: String, blobSuffix: String): Unit + def setBlobPaths(empty: Seq[Path]): Unit + def getBlobDirectory(): Path + def getBlobPrefix(): String + def getBlobSuffix(): String + +} Review Comment: I think I asked in the other review, but I can't find the quesiton or answer if it was answered. What is the goal of splitting BlobMethodsMixin into a separate trait instead of staying on the InofsetOutputter trait? ########## daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/infoset/InfosetOutputterImpl.scala: ########## @@ -0,0 +1,47 @@ +/* + * 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.infoset + +import org.apache.daffodil.runtime1.api.InfosetElement + +abstract class InfosetOutputterImpl() extends BlobMethodsImpl with InfosetOutputter { + + import Status._ + + private def status: Status = READY + + override def getStatus(): Status = { + // Done, Ready (Not started), Visiting (part way done - can retry to visit more)... + status + } + + /** + * Helper function to determine if an element is nilled or not, taking into + * account whether or not the nilled state has been set yet. + * + * @param elem the element to check the nilled state of + * @return true if the nilled state has been set and is true. false if the + * nilled state is false or if the nilled state has not been set yet + * (e.g. during debugging) + */ + final def isNilled(elem: InfosetElement): Boolean = { + val diElement = elem.asInstanceOf[DIElement] + val maybeIsNilled = diElement.maybeIsNilled + maybeIsNilled.isDefined && maybeIsNilled.get == true + } Review Comment: Can this live on the InfosetOutputter trait? It would be nice if we didn't need to have a separate InfosetOutputter and InfosetOutputterImpl classes. Seems like this is the only real differentiator. ########## daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/RuntimeData.scala: ########## @@ -665,14 +675,29 @@ sealed class ElementRuntimeData( fillByteEvArg, maybeCheckByteAndBitOrderEvArg, maybeCheckBitOrderAndCharsetEvArg, - ) { + ) + with ElementMetadata + with SimpleElementMetadata + with ComplexElementMetadata { + + override def toQName: String = namedQName.toQNameString override def isRequiredScalar = !isArray && isRequiredInUnparseInfoset final def childERDs = children + final override def childMetadata: Seq[ElementRuntimeData] = childERDs Review Comment: Not covered, I think this is only used by the drill walker? Any chance this could be remove and the walker could figure out what this is needed without direct access to children metadata? Maybe it's a bit more complicated, but it would be nice to minimize the API. ########## daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/unparsers/UState.scala: ########## @@ -168,7 +168,7 @@ abstract class UState( Assert.invariant(Maybe.WithNulls.isDefined(currentInfosetNode)) currentInfosetNode match { case a: DIArray => { - a.getOccurrence(arrayIterationPos) + a(arrayIterationPos) Review Comment: Is this line not possible to hit? Is the currentInfosetNode alway a DIElement? I think it is? Can we remove this case? ########## daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/RuntimeData.scala: ########## @@ -871,10 +901,11 @@ sealed abstract class ModelGroupRuntimeData( fillByteEvArg, maybeCheckByteAndBitOrderEvArg, maybeCheckBitOrderAndCharsetEvArg, - ) { + ) + with ModelGroupMetadata { final override def isRequiredScalar = true - final override def isArray = false + final def isArray = false Review Comment: Can we remove this if does override anything and isn't used? -- 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]
