diff --git a/daffodil-japi/src/main/scala/org/apache/daffodil/japi/Daffodil.scala b/daffodil-japi/src/main/scala/org/apache/daffodil/japi/Daffodil.scala index 8dff857a6a..83a96800ec 100644 --- a/daffodil-japi/src/main/scala/org/apache/daffodil/japi/Daffodil.scala +++ b/daffodil-japi/src/main/scala/org/apache/daffodil/japi/Daffodil.scala @@ -22,49 +22,36 @@ import java.net.URI import java.nio.channels.ReadableByteChannel import java.nio.channels.WritableByteChannel import scala.collection.JavaConverters._ - -import org.apache.daffodil.core.compiler.{ Compiler => SCompiler } -import org.apache.daffodil.core.compiler.{ InvalidParserException => SInvalidParserException } -import org.apache.daffodil.core.compiler.{ ProcessorFactory => SProcessorFactory } +import org.apache.daffodil.core.compiler.{Compiler => SCompiler} +import org.apache.daffodil.core.compiler.{InvalidParserException => SInvalidParserException} +import org.apache.daffodil.core.compiler.{ProcessorFactory => SProcessorFactory} import org.apache.daffodil.core.dsom.ExpressionCompilers import org.apache.daffodil.core.dsom.walker.RootView import org.apache.daffodil.japi.debugger._ import org.apache.daffodil.japi.infoset._ import org.apache.daffodil.japi.io.InputSourceDataInputStream import org.apache.daffodil.japi.packageprivate._ +import org.apache.daffodil.japi.schema.{RuntimeSchemaHandler, RuntimeSchemaWalker} import org.apache.daffodil.lib.api.URISchemaSource import org.apache.daffodil.lib.api.Validator -import org.apache.daffodil.lib.api.{ DataLocation => SDataLocation } -import org.apache.daffodil.lib.api.{ Diagnostic => SDiagnostic } -import org.apache.daffodil.lib.api.{ LocationInSchemaFile => SLocationInSchemaFile } -import org.apache.daffodil.lib.api.{ WithDiagnostics => SWithDiagnostics } +import org.apache.daffodil.lib.api.{DataLocation => SDataLocation} +import org.apache.daffodil.lib.api.{Diagnostic => SDiagnostic} +import org.apache.daffodil.lib.api.{LocationInSchemaFile => SLocationInSchemaFile} +import org.apache.daffodil.lib.api.{WithDiagnostics => SWithDiagnostics} import org.apache.daffodil.lib.xml.DFDLCatalogResolver import org.apache.daffodil.lib.xml.XMLUtils -import org.apache.daffodil.runtime1.api.DFDL.{ - DaffodilUnhandledSAXException => SDaffodilUnhandledSAXException, -} -import org.apache.daffodil.runtime1.api.DFDL.{ - DaffodilUnparseContentHandler => SDaffodilUnparseContentHandler, -} -import org.apache.daffodil.runtime1.api.DFDL.{ - DaffodilUnparseErrorSAXException => SDaffodilUnparseErrorSAXException, -} +import org.apache.daffodil.runtime1.api.DFDL.{DaffodilUnhandledSAXException => SDaffodilUnhandledSAXException} +import org.apache.daffodil.runtime1.api.DFDL.{DaffodilUnparseContentHandler => SDaffodilUnparseContentHandler} +import org.apache.daffodil.runtime1.api.DFDL.{DaffodilUnparseErrorSAXException => SDaffodilUnparseErrorSAXException} import org.apache.daffodil.runtime1.debugger.Debugger -import org.apache.daffodil.runtime1.debugger.{ InteractiveDebugger => SInteractiveDebugger } -import org.apache.daffodil.runtime1.debugger.{ TraceDebuggerRunner => STraceDebuggerRunner } -import org.apache.daffodil.runtime1.processors.{ - DaffodilParseXMLReader => SDaffodilParseXMLReader, -} -import org.apache.daffodil.runtime1.processors.{ DataProcessor => SDataProcessor } -import org.apache.daffodil.runtime1.processors.{ - ExternalVariableException => SExternalVariableException, -} -import org.apache.daffodil.runtime1.processors.{ - InvalidUsageException => SInvalidUsageException, -} -import org.apache.daffodil.runtime1.processors.{ ParseResult => SParseResult } -import org.apache.daffodil.runtime1.processors.{ UnparseResult => SUnparseResult } - +import org.apache.daffodil.runtime1.debugger.{InteractiveDebugger => SInteractiveDebugger} +import org.apache.daffodil.runtime1.debugger.{TraceDebuggerRunner => STraceDebuggerRunner} +import org.apache.daffodil.runtime1.processors.{DaffodilParseXMLReader => SDaffodilParseXMLReader} +import org.apache.daffodil.runtime1.processors.{DataProcessor => SDataProcessor} +import org.apache.daffodil.runtime1.processors.{ExternalVariableException => SExternalVariableException} +import org.apache.daffodil.runtime1.processors.{InvalidUsageException => SInvalidUsageException} +import org.apache.daffodil.runtime1.processors.{ParseResult => SParseResult} +import org.apache.daffodil.runtime1.processors.{UnparseResult => SUnparseResult} import org.xml.sax.Attributes import org.xml.sax.ContentHandler import org.xml.sax.DTDHandler @@ -401,6 +388,12 @@ class DataProcessor private[japi] (private var dp: SDataProcessor) extends WithDiagnostics(dp) with Serializable { + /** + * For use by the JAPI implementation only. + * @return the underlying data processor + */ + private [japi] def getUnderlyingDataProcessor = dp + private def copy(dp: SDataProcessor = dp) = new DataProcessor(dp) /** @@ -514,6 +507,11 @@ class DataProcessor private[japi] (private var dp: SDataProcessor) */ def save(output: WritableByteChannel): Unit = dp.save(output) + def walkRuntimeSchema(handler: RuntimeSchemaHandler): Unit = { + val walker = new RuntimeSchemaWalker(dp) + walker.walk(handler) + } + /** * Obtain a new [[DaffodilParseXMLReader]] from the current [[DataProcessor]]. */ diff --git a/daffodil-japi/src/main/scala/org/apache/daffodil/japi/schema/RuntimeSchemaWalker.scala b/daffodil-japi/src/main/scala/org/apache/daffodil/japi/schema/RuntimeSchemaWalker.scala new file mode 100644 index 0000000000..4afa350ecc --- /dev/null +++ b/daffodil-japi/src/main/scala/org/apache/daffodil/japi/schema/RuntimeSchemaWalker.scala @@ -0,0 +1,135 @@ +package org.apache.daffodil.japi.schema + +import org.apache.daffodil.japi.{ DataProcessor => JDataProcessor } +import org.apache.daffodil.lib.exceptions.Assert +import org.apache.daffodil.runtime1.dpath.NodeInfo.PrimType +import org.apache.daffodil.runtime1.processors.{ + ChoiceRuntimeData, + DataProcessor => SDataProcessor, + ElementRuntimeData, + ErrorERD, + SequenceRuntimeData, + TermRuntimeData, +} + +/** + * Base class used by japi clients who want to walk the runtime schema information. + */ +abstract class RuntimeSchemaHandler() { + + /** + * Called for simple type element declarations. + * + * @param qnameString + * @param primTypeName + * @param isArray only true if more than one occurrence is possible. Disjoint with isOptional + * @param isOptional true if 0 or 1 occurrence only. Allows for a different representation of optional from a 0..1 array + */ + def elementSimple( + qnameString: String, + primTypeName: String, + isArray: Boolean, + isOptional: Boolean, + ): Unit + + /** + * Called for complex type element declarations + * + * Subsequent calls will be for the model group making up the content + * of the element. + * + * @param qnameString + */ + def startElementComplex(qnameString: String, isArray: Boolean, isOptional: Boolean): Unit + def endElementComplex(qnameString: String, isArray: Boolean, isOptional: Boolean): Unit + + def startSequence(): Unit + + def endSequence(): Unit + + def startChoice(): Unit + + def endChoice(): Unit + +} + +/** + * Walks the schema, but not the DSOM schema, it walks the RuntimeData objects that + * represent the DFDL schema at runtime. + * + * @param dp + */ +class RuntimeSchemaWalker(private val dp: SDataProcessor) { + + // provided so that people can write various tests from JAPI only. + // we wouldn't need this otherwise. + def this(jdp: JDataProcessor) = this(jdp.getUnderlyingDataProcessor) + + private lazy val rootERD = dp.ssrd.elementRuntimeData + + def walk(handler: RuntimeSchemaHandler): Unit = { + walkTerm(handler, rootERD) + } + + private def walkTerm(handler: RuntimeSchemaHandler, trd: TermRuntimeData): Unit = { + trd match { + case err: ErrorERD => Assert.invariantFailed("should not get ErrorERDs") + case erd: ElementRuntimeData => walkElement(handler, erd) + case srd: SequenceRuntimeData => walkSequence(handler, srd) + case crd: ChoiceRuntimeData => walkChoice(handler, crd) + case _ => Assert.invariantFailed(s"unrecognized TermRuntimeData subtype: $trd") + } + } + + private def walkElement(handler: RuntimeSchemaHandler, erd: ElementRuntimeData): Unit = { + if (erd.optComplexTypeModelGroupRuntimeData.isDefined) + walkComplexElement(handler, erd) + else + walkSimpleElement(handler, erd) + } + + private def walkComplexElement( + handler: RuntimeSchemaHandler, + erd: ElementRuntimeData, + ): Unit = { + val qname = erd.namedQName.toQNameString + val isArray = erd.isArray + val isOptional = erd.isOptional + val mgrd = erd.optComplexTypeModelGroupRuntimeData.getOrElse { + Assert.invariantFailed("not a complex type element") + } + handler.startElementComplex(qname, isArray, isOptional) + walkTerm(handler, mgrd) + handler.endElementComplex(qname, isArray, isOptional) + } + + private def walkSimpleElement( + handler: RuntimeSchemaHandler, + erd: ElementRuntimeData, + ): Unit = { + val qname = erd.namedQName.toQNameString + val isArray = erd.isArray + val isOptional = erd.isOptional + val primType: PrimType = erd.optPrimType.getOrElse { + erd.optSimpleTypeRuntimeData.map { _.primType }.get + } + handler.elementSimple(qname, primType.toString, isArray, isOptional) + } + + private def walkSequence(handler: RuntimeSchemaHandler, srd: SequenceRuntimeData): Unit = { + handler.startSequence() + srd.groupMembers.map { trd => + walkTerm(handler, trd) + } + handler.endSequence() + } + + private def walkChoice(handler: RuntimeSchemaHandler, crd: ChoiceRuntimeData): Unit = { + handler.startChoice() + crd.groupMembers.map { trd => + walkTerm(handler, trd) + } + handler.endChoice() + } + +}