Skip to content

Commit

Permalink
Metadata walker for JAPI
Browse files Browse the repository at this point in the history
Enables systems that want to interface tightly with Daffodil
via JAPI, to walk the runtime schema information so as to construct
their own corresponding metadata.

DAFFODIL-2832
  • Loading branch information
mbeckerle committed Oct 12, 2023
1 parent 48b3f70 commit e3953cd
Show file tree
Hide file tree
Showing 2 changed files with 165 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Check warning on line 395 in daffodil-japi/src/main/scala/org/apache/daffodil/japi/Daffodil.scala

View check run for this annotation

Codecov / codecov/patch

daffodil-japi/src/main/scala/org/apache/daffodil/japi/Daffodil.scala#L395

Added line #L395 was not covered by tests

private def copy(dp: SDataProcessor = dp) = new DataProcessor(dp)

/**
Expand Down Expand Up @@ -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)

Check warning on line 512 in daffodil-japi/src/main/scala/org/apache/daffodil/japi/Daffodil.scala

View check run for this annotation

Codecov / codecov/patch

daffodil-japi/src/main/scala/org/apache/daffodil/japi/Daffodil.scala#L511-L512

Added lines #L511 - L512 were not covered by tests
}

/**
* Obtain a new [[DaffodilParseXMLReader]] from the current [[DataProcessor]].
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -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)

Check warning on line 66 in daffodil-japi/src/main/scala/org/apache/daffodil/japi/schema/RuntimeSchemaWalker.scala

View check run for this annotation

Codecov / codecov/patch

daffodil-japi/src/main/scala/org/apache/daffodil/japi/schema/RuntimeSchemaWalker.scala#L66

Added line #L66 was not covered by tests

private lazy val rootERD = dp.ssrd.elementRuntimeData

def walk(handler: RuntimeSchemaHandler): Unit = {
walkTerm(handler, rootERD)

Check warning on line 71 in daffodil-japi/src/main/scala/org/apache/daffodil/japi/schema/RuntimeSchemaWalker.scala

View check run for this annotation

Codecov / codecov/patch

daffodil-japi/src/main/scala/org/apache/daffodil/japi/schema/RuntimeSchemaWalker.scala#L71

Added line #L71 was not covered by tests
}

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

Check warning on line 80 in daffodil-japi/src/main/scala/org/apache/daffodil/japi/schema/RuntimeSchemaWalker.scala

View check run for this annotation

Codecov / codecov/patch

daffodil-japi/src/main/scala/org/apache/daffodil/japi/schema/RuntimeSchemaWalker.scala#L76-L80

Added lines #L76 - L80 were not covered by tests
}
}

private def walkElement(handler: RuntimeSchemaHandler, erd: ElementRuntimeData): Unit = {
if (erd.optComplexTypeModelGroupRuntimeData.isDefined)
walkComplexElement(handler, erd)

Check warning on line 86 in daffodil-japi/src/main/scala/org/apache/daffodil/japi/schema/RuntimeSchemaWalker.scala

View check run for this annotation

Codecov / codecov/patch

daffodil-japi/src/main/scala/org/apache/daffodil/japi/schema/RuntimeSchemaWalker.scala#L85-L86

Added lines #L85 - L86 were not covered by tests
else
walkSimpleElement(handler, erd)

Check warning on line 88 in daffodil-japi/src/main/scala/org/apache/daffodil/japi/schema/RuntimeSchemaWalker.scala

View check run for this annotation

Codecov / codecov/patch

daffodil-japi/src/main/scala/org/apache/daffodil/japi/schema/RuntimeSchemaWalker.scala#L88

Added line #L88 was not covered by tests
}

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

Check warning on line 99 in daffodil-japi/src/main/scala/org/apache/daffodil/japi/schema/RuntimeSchemaWalker.scala

View check run for this annotation

Codecov / codecov/patch

daffodil-japi/src/main/scala/org/apache/daffodil/japi/schema/RuntimeSchemaWalker.scala#L95-L99

Added lines #L95 - L99 were not covered by tests
}
handler.startElementComplex(qname, isArray, isOptional)
walkTerm(handler, mgrd)
handler.endElementComplex(qname, isArray, isOptional)

Check warning on line 103 in daffodil-japi/src/main/scala/org/apache/daffodil/japi/schema/RuntimeSchemaWalker.scala

View check run for this annotation

Codecov / codecov/patch

daffodil-japi/src/main/scala/org/apache/daffodil/japi/schema/RuntimeSchemaWalker.scala#L101-L103

Added lines #L101 - L103 were not covered by tests
}

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

Check warning on line 114 in daffodil-japi/src/main/scala/org/apache/daffodil/japi/schema/RuntimeSchemaWalker.scala

View check run for this annotation

Codecov / codecov/patch

daffodil-japi/src/main/scala/org/apache/daffodil/japi/schema/RuntimeSchemaWalker.scala#L110-L114

Added lines #L110 - L114 were not covered by tests
}
handler.elementSimple(qname, primType.toString, isArray, isOptional)

Check warning on line 116 in daffodil-japi/src/main/scala/org/apache/daffodil/japi/schema/RuntimeSchemaWalker.scala

View check run for this annotation

Codecov / codecov/patch

daffodil-japi/src/main/scala/org/apache/daffodil/japi/schema/RuntimeSchemaWalker.scala#L116

Added line #L116 was not covered by tests
}

private def walkSequence(handler: RuntimeSchemaHandler, srd: SequenceRuntimeData): Unit = {
handler.startSequence()
srd.groupMembers.map { trd =>
walkTerm(handler, trd)

Check warning on line 122 in daffodil-japi/src/main/scala/org/apache/daffodil/japi/schema/RuntimeSchemaWalker.scala

View check run for this annotation

Codecov / codecov/patch

daffodil-japi/src/main/scala/org/apache/daffodil/japi/schema/RuntimeSchemaWalker.scala#L120-L122

Added lines #L120 - L122 were not covered by tests
}
handler.endSequence()

Check warning on line 124 in daffodil-japi/src/main/scala/org/apache/daffodil/japi/schema/RuntimeSchemaWalker.scala

View check run for this annotation

Codecov / codecov/patch

daffodil-japi/src/main/scala/org/apache/daffodil/japi/schema/RuntimeSchemaWalker.scala#L124

Added line #L124 was not covered by tests
}

private def walkChoice(handler: RuntimeSchemaHandler, crd: ChoiceRuntimeData): Unit = {
handler.startChoice()
crd.groupMembers.map { trd =>
walkTerm(handler, trd)

Check warning on line 130 in daffodil-japi/src/main/scala/org/apache/daffodil/japi/schema/RuntimeSchemaWalker.scala

View check run for this annotation

Codecov / codecov/patch

daffodil-japi/src/main/scala/org/apache/daffodil/japi/schema/RuntimeSchemaWalker.scala#L128-L130

Added lines #L128 - L130 were not covered by tests
}
handler.endChoice()

Check warning on line 132 in daffodil-japi/src/main/scala/org/apache/daffodil/japi/schema/RuntimeSchemaWalker.scala

View check run for this annotation

Codecov / codecov/patch

daffodil-japi/src/main/scala/org/apache/daffodil/japi/schema/RuntimeSchemaWalker.scala#L132

Added line #L132 was not covered by tests
}

}

0 comments on commit e3953cd

Please sign in to comment.