blob: cef533145c47a9fc7dddf37e8aa305eb1df46602 [file] [log] [blame]
package edu.illinois.ncsa.daffodil.processors
/* Copyright (c) 2012-2013 Tresys Technology, LLC. All rights reserved.
*
* Developed by: Tresys Technology, LLC
* http://www.tresys.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal with
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimers.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimers in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the names of Tresys Technology, nor the names of its contributors
* may be used to endorse or promote products derived from this Software
* without specific prior written permission.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
* SOFTWARE.
*/
import scala.xml.Node
import edu.illinois.ncsa.daffodil.ExecutionMode
import edu.illinois.ncsa.daffodil.api.DFDL
import edu.illinois.ncsa.daffodil.api.DataLocation
import edu.illinois.ncsa.daffodil.api.Diagnostic
import edu.illinois.ncsa.daffodil.api.LocationInSchemaFile
import edu.illinois.ncsa.daffodil.debugger.Debugger
import edu.illinois.ncsa.daffodil.dsom.AnnotatedSchemaComponent
import edu.illinois.ncsa.daffodil.dsom.DiagnosticImplMixin
import edu.illinois.ncsa.daffodil.dsom.ElementBase
import edu.illinois.ncsa.daffodil.dsom.GlobalElementDecl
import edu.illinois.ncsa.daffodil.dsom.RuntimeSchemaDefinitionError
import edu.illinois.ncsa.daffodil.dsom.RuntimeSchemaDefinitionWarning
import edu.illinois.ncsa.daffodil.dsom.SchemaComponent
import edu.illinois.ncsa.daffodil.dsom.SchemaComponentRegistry
import edu.illinois.ncsa.daffodil.dsom.SchemaDefinitionError
import edu.illinois.ncsa.daffodil.dsom.Term
import edu.illinois.ncsa.daffodil.exceptions.Assert
import edu.illinois.ncsa.daffodil.exceptions.SchemaFileLocatable
import edu.illinois.ncsa.daffodil.exceptions.ThrowsSDE
import edu.illinois.ncsa.daffodil.exceptions.UnsuppressableException
import edu.illinois.ncsa.daffodil.grammar.Gram
import edu.illinois.ncsa.daffodil.util.LogLevel
import edu.illinois.ncsa.daffodil.util.Logging
import edu.illinois.ncsa.daffodil.util.Misc
import edu.illinois.ncsa.daffodil.xml.NS
import edu.illinois.ncsa.daffodil.api._
import edu.illinois.ncsa.daffodil.api.DFDL.DataProcessor
import edu.illinois.ncsa.daffodil.dsom.ValidationError
import edu.illinois.ncsa.daffodil.externalvars.ExternalVariablesLoader
abstract class ProcessingError extends Exception with DiagnosticImplMixin
class ParseError(sc: SchemaComponent, val pstate: Option[PState], kind: String, args: Any*)
extends ProcessingError {
override def getLocationsInSchemaFiles: Seq[LocationInSchemaFile] = List(sc)
override def getDataLocations: Seq[DataLocation] = pstate.map { _.currentLocation }.toList
def componentText: String = ""
override def toString = {
lazy val argsAsString = args.map { _.toString }.mkString(", ")
//
// Right here is where we would lookup the symbolic error kind id, and
// choose a locale-based message string.
//
// For now, we'll just do an automatic English message.
//
val msg = {
if (args.size > 0) kind.format(args: _*)
else kind
}
val res = "Parse Error: " + msg +
componentText +
"\nSchema context: %s %s".format(sc, sc.locationDescription) +
pstate.map { ps => "\nData location was preceding %s".format(ps.currentLocation) }.getOrElse("(no data location)")
res
}
override def getMessage = toString
}
class AssertionFailed(sc: SchemaComponent, state: PState, msg: String, details: Option[String] = None)
extends ParseError(sc, Some(state), "Assertion failed. %s", msg) {
override def componentText: String = {
val currentElem = state.infoset
val parsedValue = currentElem.jdomElt match {
case Some(jdomElem) => {
"\nParsed value was: " + {
if (jdomElem.getChildren().size() > 0) {
// Complex
val name = jdomElem.getName()
"<" + name + ">...</" + name + ">"
} else {
// Simple
currentElem.toBriefXML.toString
}
}
}
case None => ""
}
val finalString = details match {
case Some(d) => "\nDetails: " + d + parsedValue
case None => parsedValue
}
finalString
}
}
class ParseAlternativeFailed(sc: SchemaComponent, state: PState, val errors: Seq[Diagnostic])
extends ParseError(sc, Some(state), "Alternative failed. Reason(s): %s", errors)
class AltParseFailed(sc: SchemaComponent, state: PState,
diags: Seq[Diagnostic])
extends ParseError(sc, Some(state), "All alternatives failed. Reason(s): %s", diags) {
override def getLocationsInSchemaFiles: Seq[LocationInSchemaFile] = diags.flatMap { _.getLocationsInSchemaFiles }
override def getDataLocations: Seq[DataLocation] = {
// all should have the same starting location if they are alternatives.
val dataLocs = diags.flatMap { _.getDataLocations }
// TBD: what is the idiom for "insert a equals sign between all the elements of the list...??"
// Well, this works, but isn't there a one-liner for this idiom.
val allAreSame = dataLocs match {
case f :: r => !r.exists { _ != f }
case _ => true
}
Assert.invariant(allAreSame)
diags.head.getDataLocations
}
}
/**
* Encapsulates lower-level parsing with a uniform interface
*/
abstract class Parser(val context: SchemaComponent) extends Logging {
def PE(pstate: PState, s: String, args: Any*) = {
pstate.failed(new ParseError(context, Some(pstate), s, args: _*))
}
def processingError(state: PState, str: String, args: Any*) =
PE(state, str, args) // long form synonym
protected def parse(pstate: PState): PState
final def parse1(pstate: PState, context: SchemaComponent): PState = {
Debugger.before(pstate, this)
val afterState = parse(pstate)
Debugger.after(pstate, afterState, this)
afterState
}
// TODO: other methods for things like asking for the ending position of something
// which would enable fixed-length formats to skip over data and not parse it at all.
/**
* BriefXML is XML-style output, but intended for specific purposes. It is NOT
* an XML serialization of the data structure. It's an XML-style string, suitable to
* manipulate, by people, in XML tooling. E.g., can stick into an XML editor to
* then get it all indented nicely, use a structure editor to expand/collapse subregions,
* but it is NOT intended to capture all of the state of the object.
*/
def toBriefXML(depthLimit: Int = -1): String
}
/**
* Mix this into parsers that have deep algorithms that are spread over multiple classes.
*
* These allow one to bend the rule about parsers not throwing ParseError so that
* if you are inside a parser, but you are way down a bunch of calls away from the parser itself
* you can throw, and it will be intercepted and proper behavior (not throwing, but returning
* a failed status) will result.
*
* Use like this:
* @example {{{
* withParseErrorThrowing(pstate) { // something enclosing like the parser
* ...
* // calls something which calls something which eventually calls
* PECheck(bitOffset % 8 == 0, "must be byte boundary, not bit %s", bitOffset)
* ...
* }
* }}}
*/
trait WithParseErrorThrowing {
def context: SchemaComponent
/**
* Use to check for parse errors.
*
* Must be used only in the context of the withParseErrorThrowing wrapper.
*
* The schema component providing the context is implicit (via def context virtual member)
*/
def PECheck(
testTrueMeansOK: => Boolean,
kind: String, args: Any*) {
Assert.usage(WithParseErrorThrowing.flag, "Must use inside of withParseErrorThrowing construct.")
if (!testTrueMeansOK) {
throw new ParseError(context, None, kind, args: _*)
}
}
/**
* Passing the context explicitly
*/
def PECheck(contextArg: SchemaComponent,
testTrueMeansOK: => Boolean,
kind: String, args: Any*) {
Assert.usage(WithParseErrorThrowing.flag, "Must use inside of withParseErrorThrowing construct.")
if (!testTrueMeansOK) {
throw new ParseError(contextArg, None, kind, args: _*)
}
}
def PE(kind: String, args: Any*): Nothing = {
PE(context, kind, args: _*)
}
def PE(context: SchemaComponent, kind: String, args: Any*): Nothing = {
Assert.usage(WithParseErrorThrowing.flag, "Must use inside of withParseErrorThrowing construct.")
throw new ParseError(context, None, kind, args: _*)
}
/**
* Wrap around parser code that wants to throw parse errors (e.g., parsers which call things which
* call things which detect a parse error want to throw back to this)
*
* This wrapper then implements the required behavior for parsers
* that being returning a failed parser state.
*/
def withParseErrorThrowing(pstate: PState)(body: => PState): PState = {
val saveCanThrowParseErrors = WithParseErrorThrowing.flag
WithParseErrorThrowing.flag = true
val result =
try body
catch {
case e: ParseError => {
val maybePS = e.pstate
// if there is a maybePS, then use it to create the failed state (because it
// is probably more specific about the failure location), otherwise
// use the one passed as an argument.
val res = maybePS.map { _.failed(e) }.getOrElse(pstate.failed(e))
res
}
// TODO: Runtime SDEs should be distinguished somehow usefully.
// case e : SchemaDefinitionError => {
// val res = pstate.failed(e)
// res
// }
//
// Note: We specifically do not catch other exceptions here
// On purpose. If those exist, then there's someplace that should have already caught them
// and turned them into a thrown parse error, or a schema definition error.
//
// Other kinds of spontaneous throws are bugs, and we don't want to mask them by
// putting blanket catches in.
//
} finally {
WithParseErrorThrowing.flag = saveCanThrowParseErrors
}
result
}
/**
* Use to check things that really are schema-definition issues, but we can't check until run-time.
* E.g., since byteOrder might be an expression, if the expression returns neither bigEndian nor littleEndian,
* then it's an SDE, but we didn't know until runtime.
*
* No catching for this SDE throw, since SDEs are fatal.
*/
def SDECheck(testTrueMeansOK: => Boolean, context: SchemaComponent, pstate: PState, kind: String, args: Any*) = {
if (!testTrueMeansOK) {
throw new SchemaDefinitionError(Some(context), None, kind, args: _*)
}
}
}
/**
* Global flag to insure we aren't throwing ParseErrors in a context that won't catch them
* properly.
*/
object WithParseErrorThrowing {
// TODO: FIXME Bad bad global state. This flag needs to live in the DataProcessor object,
// or find a way to have it go away entirely.
var flag: Boolean = false
/**
* for unit tests and other context where you want to exercise runtime code that might throw PEs
* but you are not inside a parser
*/
def pretendThisIsAParser[T](body: => T) = {
val savedFlag: Boolean = WithParseErrorThrowing.flag
try {
WithParseErrorThrowing.flag = true
body
} finally {
WithParseErrorThrowing.flag = savedFlag
}
}
}
// No-op, in case an optimization lets one of these sneak thru.
// TODO: make this fail, and test optimizer sufficiently to know these
// do NOT get through.
class EmptyGramParser(context: Term = null) extends Parser(context) {
def parse(pstate: PState) = Assert.invariantFailed("EmptyGramParsers are all supposed to optimize out!")
def toBriefXML(depthLimit: Int = -1) = "<empty/>"
override def toString = toBriefXML()
}
class ErrorParser(context: Term = null) extends Parser(context) {
def parse(pstate: PState): PState = Assert.abort("Error Parser")
def toBriefXML(depthLimit: Int = -1) = "<error/>"
override def toString = "Error Parser"
}
trait ToBriefXMLImpl {
def nom: String
def childParsers: Seq[Parser]
// TODO: make this do indenting and newlines (maybe optionally?)
def toBriefXML(depthLimit: Int = -1) = {
if (depthLimit == 0) "..."
else if (depthLimit == 1) "<seq>...</seq>"
else {
val lessDepth = depthLimit - 1
"<" + nom + ">" + childParsers.map { _.toBriefXML(lessDepth) }.mkString + "</" + nom + ">"
}
}
override def toString = toBriefXML() // pParser.toString + " ~ " + qParser.toString
}
class SeqCompParser(context: AnnotatedSchemaComponent, children: Seq[Gram])
extends Parser(context)
with ToBriefXMLImpl {
Assert.invariant(!children.exists { _.isEmpty })
val nom = "seq"
val childParsers = children.map { _.parser }
def parse(pstate: PState): PState = {
var pResult = pstate
childParsers.foreach { parser =>
{
pResult = parser.parse1(pResult, context)
if (pResult.status != Success) {
// failed in a sequence
return pResult
}
pResult = pResult
}
}
pResult
}
}
class AltCompParser(context: AnnotatedSchemaComponent, children: Seq[Gram])
extends Parser(context)
with ToBriefXMLImpl {
Assert.invariant(!children.exists { _.isEmpty })
val nom = "alt"
val childParsers = children.map { _.parser }
def parse(pInitial: PState): PState = {
val pStart = pInitial.withNewPointOfUncertainty
var pResult: PState = null
var diagnostics: Seq[Diagnostic] = Nil
val cloneNode = pStart.captureInfosetElementState // we must undo side-effects on the JDOM if we backtrack.
childParsers.foreach { parser =>
{
log(LogLevel.Debug, "Trying choice alternative: %s", parser)
try {
pResult = parser.parse1(pStart, context)
} catch {
case u: UnsuppressableException => throw u
case rsde: RuntimeSchemaDefinitionError => throw rsde
case e: Exception => Assert.invariantFailed("Runtime parsers should not throw exceptions: " + e)
}
if (pResult.status == Success) {
log(LogLevel.Debug, "Choice alternative success: %s", parser)
val res = pResult.withRestoredPointOfUncertainty
return res
}
// If we get here, then we had a failure
log(LogLevel.Debug, "Choice alternative failed: %s", parser)
// Unwind any side effects on the Infoset
// The infoset is the primary non-functional data structure. We have to un-side-effect it.
pStart.restoreInfosetElementState(cloneNode)
val diag = new ParseAlternativeFailed(context, pStart, pResult.diagnostics)
diagnostics = diag +: diagnostics
// check for discriminator evaluated to true.
if (pResult.discriminator == true) {
log(LogLevel.Debug, "Failure, but discriminator true. Additional alternatives discarded.")
// If so, then we don't run the next alternative, we
// consume this discriminator status result (so it doesn't ripple upward)
// and return the failed state withall the diagnostics.
//
val allDiags = new AltParseFailed(context, pResult, diagnostics.reverse)
val res = pResult.failed(allDiags).withRestoredPointOfUncertainty
return res
}
//
// Here we have a failure, but no discriminator was set, so we try the next alternative.
// Which means we just go around the loop
}
}
// Out of alternatives. All of them failed.
val allDiags = new AltParseFailed(context, pStart, diagnostics.reverse)
val allFailedResult = pStart.failed(allDiags)
log(LogLevel.Debug, "All AltParser alternatives failed.")
val result = allFailedResult.withRestoredPointOfUncertainty
result
}
}
case class DummyParser(sc: SchemaComponent) extends Parser(null) {
def parse(pstate: PState): PState = sc.SDE("Parser for " + sc + " is not yet implemented.")
def toBriefXML(depthLimit: Int = -1) = "<dummy/>"
override def toString = if (sc == null) "Dummy[null]" else "Dummy[" + sc + "]"
}
class GeneralParseFailure(msg: String) extends Throwable with DiagnosticImplMixin {
Assert.usage(msg != null && msg != "")
override def getMessage() = msg
}
/**
* A parser takes a state, and returns an updated state
*
* The fact that there are side-effects/mutations on parts of the state
* enables us to reuse low-level java primitives that mutate streams.
*
* The goal however, is to hide that fact so that the only places that have to
* know are the places doing the mutation, and the places rolling them back
* which should be isolated to the alternative parser, and repParsers, i.e.,
* places where points-of-uncertainty are handled.
*/
case class PState(
val schemaComponentRegistry: SchemaComponentRegistry,
val inStream: InStream,
val infoset: InfosetItem,
val variableMap: VariableMap,
val target: NS,
val status: ProcessorResult,
val groupIndexStack: List[Long],
val childIndexStack: List[Long],
val arrayIndexStack: List[Long],
val occursCountStack: List[Long],
val diagnostics: List[Diagnostic],
val discriminatorStack: List[Boolean],
val dataProc: DataProcessor) extends DFDL.State with ThrowsSDE {
def bytePos = bitPos >> 3
def whichBit = bitPos % 8
def groupPos = if (groupIndexStack != Nil) groupIndexStack.head else -1
def childPos = if (childIndexStack != Nil) childIndexStack.head else -1
def arrayPos = if (arrayIndexStack != Nil) arrayIndexStack.head else -1
def occursCount = if (occursCountStack != Nil) occursCountStack.head else -1
override def toString() = {
"PState( bitPos=%s charPos=%s status=%s )".format(bitPos, charPos, status)
}
def getContext(): ElementBase = {
// Assumes that a JDOM element was already created
val currentElement = parentElement
val res = currentElement.schemaComponent(this)
res
}
/**
* Added because ThrowsSDE is a SchemaFileLocatable
*/
def contextLocatable: SchemaFileLocatable = {
val ctxt = getContext()
ctxt.contextLocatable
}
/**
* Added because ThrowsSDE is a SchemaFileLocatable
*/
def fileName: String = {
val ctxt = getContext()
ctxt.fileName
}
/**
* Added because ThrowsSDE is a SchemaFileLocatable
*/
def xml: Node = {
val ctxt = getContext()
ctxt.xml
}
def SDE(str: String, args: Any*) = {
ExecutionMode.requireRuntimeMode
val ctxt = getContext()
val rsde = new RuntimeSchemaDefinitionError(ctxt, this, str, args: _*)
ctxt.toss(rsde)
}
// TODO: Do we want these to reside on PState at all? SDEButContinue and SDW
// Had to implement so that we could add ThrowsSDE as a trait to PState
// Could just make private and Assert.impossible
def SDEButContinue(id: String, args: Any*): Unit = {
ExecutionMode.requireRuntimeMode
val ctxt = getContext
val rsde = new RuntimeSchemaDefinitionError(ctxt, this, id, args: _*)
ctxt.error(rsde)
}
def SDW(id: String, args: Any*): Unit = {
ExecutionMode.requireRuntimeMode
val ctxt = getContext
val rsdw = new RuntimeSchemaDefinitionWarning(ctxt, this, id, args: _*)
ctxt.warn(rsdw)
}
def discriminator = discriminatorStack.head
def currentLocation: DataLocation = new DataLoc(bitPos, bitLimit, inStream)
// def inStreamState = inStreamStateStack top
def bitPos = inStream.bitPos
def bitLimit = inStream.bitLimit
def charPos = inStream.charPos
def charLimit = inStream.charLimit
def parentElement = infoset.asInstanceOf[InfosetElement]
def parentDocument = infoset.asInstanceOf[InfosetDocument]
def textReader = inStream.reader
/**
* Convenience functions for creating a new state, changing only
* one or a related subset of the state components to a new one.
*/
// def withPos(bitPos: Long, charPos: Long, newStatus: ProcessorResult = Success) = {
// val newInStream = inStream.withPos(bitPos, charPos)
// copy(inStream = newInStream, status = newStatus)
// }
def withEndBitLimit(bitLimit: Long, newStatus: ProcessorResult = this.status) = {
var newInStream = inStream.withEndBitLimit(bitLimit)
copy(inStream = newInStream, status = newStatus)
}
def withParent(newParent: InfosetItem, newStatus: ProcessorResult = Success) =
copy(infoset = newParent, status = newStatus)
def withVariables(newVariableMap: VariableMap, newStatus: ProcessorResult = Success) =
copy(variableMap = newVariableMap, status = newStatus)
def withGroupIndexStack(newGroupIndexStack: List[Long], newStatus: ProcessorResult = Success) =
copy(groupIndexStack = newGroupIndexStack, status = newStatus)
def withChildIndexStack(newChildIndexStack: List[Long], newStatus: ProcessorResult = Success) =
copy(childIndexStack = newChildIndexStack, status = newStatus)
def withArrayIndexStack(newArrayIndexStack: List[Long], newStatus: ProcessorResult = Success) =
copy(arrayIndexStack = newArrayIndexStack, status = newStatus)
def setOccursCount(oc: Long) =
copy(occursCountStack = oc :: occursCountStack.tail)
def withOccursCountStack(ocs: List[Long]) =
copy(occursCountStack = ocs)
def withValidationError(msg: String, args: Any*) = {
val ctxt = getContext()
val vde = new ValidationError(Some(ctxt), this, msg, args: _*)
copy(diagnostics = vde :: diagnostics)
}
def withValidationErrorNoContext(msg: String, args: Any*) = {
val vde = new ValidationError(None, this, msg, args: _*)
copy(diagnostics = vde :: diagnostics)
}
def failed(msg: => String): PState =
failed(new GeneralParseFailure(msg))
def failed(failureDiagnostic: Diagnostic) = {
copy(status = new Failure(failureDiagnostic.getMessage),
diagnostics = failureDiagnostic :: diagnostics)
}
def withNewPointOfUncertainty = {
copy(discriminatorStack = false +: discriminatorStack)
}
def withRestoredPointOfUncertainty =
copy(discriminatorStack = discriminatorStack.tail)
def withDiscriminator(disc: Boolean) =
copy(discriminatorStack = disc +: discriminatorStack.tail)
/**
* withPos changes the bit position of the stream, and maintains the char reader
* which is available to decode characters at that position.
*
* It is critical to performance that the reader be preserved if it can be. That is, if we are
* moving through characters of text in the same encoding, with no binary data or alignment going on, then
* we *must* retain the reader. Creating a new reader has high overhead in that as soon as you create one and
* read anything from it, it will read-ahead a large block of characters. If every element was creating
* a new reader, we'd be reading data over and over again.
*
* So it is NOT ok to just pass None as the third argument. Only do that if you have
* just been handling binary data, or just did an alignmentFill that really inserted some bits.
*
* It is well worth it to test and branch to preserve the reader. E.g., AlignmentFill should not
* create a new reader unless it actually moved over some number of bits. If the alignment is 1 (bit),
* or the actual amount of alignment fill to be skipped in a particular data stream is 0, then
* one should preserve the reader.
*
* This method mostly just delegates to the inStream now. But the caller of this method
* needs to avoid just passing None also. So this Scaladoc appears both here and on the withPos
* method of inStream.
*/
def withPos(bitPos: Long, charPos: Long, reader: Option[DFDLCharReader]) = {
val newInStream = inStream.withPos(bitPos, charPos, reader)
copy(inStream = newInStream)
}
// Need last state for Assertion Pattern
// def withLastState = copy(inStreamStateStack = inStreamStateStack.pop)
/**
* advance our position, as a child element of a parent, and our index within the current sequence group.
*
* These can be different because an element can have sequences nested directly in sequences. Those effectively all
* get flattened into children of the element. The start of a sequence doesn't start the numbering of children. It's
* the start of a complex type that does that.
*/
def moveOverByOneElement = {
val s1 = moveOverOneGroupIndexOnly
val s2 = s1.moveOverOneElementChildOnly
// val s3 = s2.moveOverOneArrayIndexOnly // move over in array happens only in the RepParsers
s2
}
def moveOverOneElementChildOnly = {
childIndexStack match {
case Nil => this
case hd :: tl => {
val newChildIndex = hd + 1
withChildIndexStack(newChildIndex :: tl)
}
}
}
def moveOverOneGroupIndexOnly = {
groupIndexStack match {
case Nil => this
case hd :: tl => {
val newGroupIndex = hd + 1
withGroupIndexStack(newGroupIndex :: tl)
}
}
}
def moveOverOneArrayIndexOnly = {
arrayIndexStack match {
case Nil => this
case hd :: tl => {
val newArrayIndex = hd + 1
withArrayIndexStack(newArrayIndex :: tl)
}
}
}
def captureInfosetElementState = parentElement.captureState()
def restoreInfosetElementState(st: Infoset.ElementState) = parentElement.restoreState(st)
/**
* calling this forces the entire input into memory
*
*/
def lengthInBytes: Long = inStream.lengthInBytes
}
object PState {
/**
* Initialize the state block given our InStream and a root element declaration.
*/
def createInitialState(scr: SchemaComponentRegistry,
rootElemDecl: GlobalElementDecl,
in: InStream,
dataProc: DataProcessor): PState = {
val dataProcessor = dataProc
val doc = Infoset.newDocument()
val variables = dataProc.getVariables
val targetNamespace = rootElemDecl.schemaDocument.targetNamespace
val status = Success
val groupIndexStack = Nil
val childIndexStack = Nil
val arrayIndexStack = Nil
val occursCountStack = Nil
val diagnostics = Nil
val discriminator = false
val textReader: Option[DFDLCharReader] = None
val newState = PState(scr, in, doc, variables, targetNamespace, status, groupIndexStack,
childIndexStack, arrayIndexStack, occursCountStack, diagnostics, List(false), dataProc)
newState
}
/**
* For testing it is convenient to just hand it strings for data.
*/
def createInitialState(scr: SchemaComponentRegistry,
rootElemDecl: GlobalElementDecl,
data: String,
bitOffset: Long,
dataProc: DataProcessor): PState = {
val in = Misc.stringToReadableByteChannel(data)
createInitialState(scr, rootElemDecl, in, dataProc, data.length, bitOffset)
}
/**
* Construct our InStream object and initialize the state block.
*/
def createInitialState(scr: SchemaComponentRegistry,
rootElemDecl: GlobalElementDecl,
input: DFDL.Input,
dataProc: DataProcessor,
bitOffset: Long = 0,
bitLengthLimit: Long = -1): PState = {
val inStream =
InStream.fromByteChannel(rootElemDecl, input, bitOffset, bitLengthLimit)
createInitialState(scr, rootElemDecl, inStream, dataProc)
}
}