blob: 96048031e983d68e501b078d642597089b80ff84 [file] [log] [blame]
/*
* 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.processors.unparsers
import java.io.ByteArrayOutputStream
import java.nio.CharBuffer
import java.nio.LongBuffer
import scala.Left
import org.apache.daffodil.api.DFDL
import org.apache.daffodil.api.DaffodilTunables
import org.apache.daffodil.api.DataLocation
import org.apache.daffodil.api.Diagnostic
import org.apache.daffodil.dpath.UnparserBlocking
import org.apache.daffodil.equality.EqualitySuppressUnusedImportWarning
import org.apache.daffodil.exceptions.Assert
import org.apache.daffodil.exceptions.SavesErrorsAndWarnings
import org.apache.daffodil.exceptions.ThrowsSDE
import org.apache.daffodil.infoset.DIArray
import org.apache.daffodil.infoset.DIDocument
import org.apache.daffodil.infoset.DIElement
import org.apache.daffodil.infoset.DINode
import org.apache.daffodil.infoset.InfosetAccessor
import org.apache.daffodil.infoset.InfosetInputter
import org.apache.daffodil.io.DirectOrBufferedDataOutputStream
import org.apache.daffodil.io.StringDataInputStreamForUnparse
import org.apache.daffodil.processors.DataLoc
import org.apache.daffodil.processors.DataProcessor
import org.apache.daffodil.processors.DelimiterStackUnparseNode
import org.apache.daffodil.processors.EscapeSchemeUnparserHelper
import org.apache.daffodil.processors.Failure
import org.apache.daffodil.processors.NonTermRuntimeData
import org.apache.daffodil.processors.ParseOrUnparseState
import org.apache.daffodil.processors.Suspension
import org.apache.daffodil.processors.SuspensionTracker
import org.apache.daffodil.processors.TermRuntimeData
import org.apache.daffodil.processors.UnparseResult
import org.apache.daffodil.processors.VariableBox
import org.apache.daffodil.processors.VariableMap
import org.apache.daffodil.processors.VariableRuntimeData
import org.apache.daffodil.processors.charset.BitsCharset
import org.apache.daffodil.processors.charset.BitsCharsetDecoder
import org.apache.daffodil.processors.charset.BitsCharsetEncoder
import org.apache.daffodil.processors.dfa.DFADelimiter
import org.apache.daffodil.util.Cursor
import org.apache.daffodil.util.LocalStack
import org.apache.daffodil.util.MStackOf
import org.apache.daffodil.util.MStackOfLong
import org.apache.daffodil.util.MStackOfMaybe
import org.apache.daffodil.util.Maybe
import org.apache.daffodil.util.Maybe.Nope
import org.apache.daffodil.util.Maybe.One
object ENoWarn { EqualitySuppressUnusedImportWarning() }
abstract class UState(
vbox: VariableBox,
diagnosticsArg: List[Diagnostic],
dataProcArg: Maybe[DataProcessor],
tunable: DaffodilTunables,
areDebugging: Boolean)
extends ParseOrUnparseState(vbox, diagnosticsArg, dataProcArg, tunable)
with Cursor[InfosetAccessor] with ThrowsSDE with SavesErrorsAndWarnings {
/**
* Push onto the dynamic TRD context stack
*/
def pushTRD(trd: TermRuntimeData): Unit
/**
* Returns the top of the stack if it exists. No state change to stack contents.
*/
def maybeTopTRD(): Maybe[TermRuntimeData]
/**
* Pop the dynamic TRD context stack. The popped TRD should be the same as the argument rd.
* The popped TRD is returned.
*/
def popTRD(trd: TermRuntimeData): TermRuntimeData
// def unparse1(unparser: Unparser): Unit
override def toString = {
val elt = if (this.currentInfosetNodeMaybe.isDefined) "node=" + this.currentInfosetNode.toString else ""
"UState(" + elt + " DOS=" + dataOutputStream.toString() + ")"
}
var dataOutputStream: DirectOrBufferedDataOutputStream
def currentInfosetNode: DINode
def currentInfosetNodeMaybe: Maybe[DINode]
def escapeSchemeEVCache: MStackOfMaybe[EscapeSchemeUnparserHelper]
def setVariables(newVariableMap: VariableMap): Unit
// def charBufferDataOutputStream: LocalStack[CharBufferDataOutputStream]
def withUnparserDataInputStream: LocalStack[StringDataInputStreamForUnparse]
def withByteArrayOutputStream: LocalStack[(ByteArrayOutputStream, DirectOrBufferedDataOutputStream)]
def allTerminatingMarkup: List[DFADelimiter]
def localDelimiters: DelimiterStackUnparseNode
def pushDelimiters(node: DelimiterStackUnparseNode): Unit
def popDelimiters(): Unit
def currentInfosetNodeStack: MStackOfMaybe[DINode]
def arrayIndexStack: MStackOfLong
def childIndexStack: MStackOfLong
def groupIndexStack: MStackOfLong
def moveOverOneArrayIndexOnly(): Unit
def moveOverOneGroupIndexOnly(): Unit
def moveOverOneElementChildOnly(): Unit
def inspectOrError: InfosetAccessor
def advanceOrError: InfosetAccessor
def isInspectArrayEnd: Boolean
override def dataStream = Maybe(dataOutputStream)
override def currentNode = currentInfosetNodeMaybe
override def hasInfoset = currentInfosetNodeMaybe.isDefined
override def infoset = {
Assert.invariant(Maybe.WithNulls.isDefined(currentInfosetNode))
currentInfosetNode match {
case a: DIArray => {
a.getOccurrence(arrayPos)
}
case e: DIElement => thisElement
}
}
override def thisElement: DIElement = {
Assert.usage(Maybe.WithNulls.isDefined(currentInfosetNode))
val curNode = currentInfosetNode
curNode match {
case e: DIElement => e
case a: DIArray => a.parent
}
}
private def maybeCurrentInfosetElement: Maybe[DIElement] = {
if (!Maybe.WithNulls.isDefined(currentInfosetNode)) Nope
else {
currentInfosetNode match {
case e: DIElement => One(e)
case a: DIArray => Nope
}
}
}
def currentLocation: DataLocation = {
val m = maybeCurrentInfosetElement
val mrd = if (m.isDefined) Maybe(m.value.runtimeData) else Nope
val isAtEnd = false // TODO: this isn't right, but what does it mean to be at the end? Nothing appears to use this value when unparsing
new DataLoc(bitPos1b, bitLimit1b, isAtEnd, Left(dataOutputStream), mrd)
}
lazy val unparseResult = new UnparseResult(dataProc.get, this)
def bitPos0b = if (dataOutputStream.maybeAbsBitPos0b.isDefined) dataOutputStream.maybeAbsBitPos0b.get else 0L
def bitLimit0b = dataOutputStream.maybeRelBitLimit0b
def charPos = -1L
final def notifyDebugging(flag: Boolean): Unit = {
dataOutputStream.setDebugging(flag)
}
def addUnparseError(ue: UnparseError): Unit = {
diagnostics = ue :: diagnostics
_processorStatus = new Failure(ue)
}
/**
* Checks for legal bitOrder change (byte boundary required), or splits the
* DOS so that the check will occur later when they are collapsed back together.
*
* If you think about it, the only way we could not have the absoluteBitPos is
* because something variable-length preceded us, and couldn't be computed due
* to suspended computation (forward referencing expression somewhere prior).
*
* At some point, that suspension will get resolved, and forward collapsing of
* the DataOutputStreams will occur. When it encounters a split created here,
* we already know that the bit orders are different (or we wouldn't have put in
* the split), so we just have to see if we're on a byte boundary. That could happen
* if the original DOS ended in a frag byte, but previous to it, was something
* that was variable bits wide (all bits shift such that original DOS's frag byte
* becomes a whole byte.)
*
* The invariant here is that the original DOS will get collapsed together with
* DOS preceding it. After that collapsing, it has to end at a byte boundary (no
* frag byte). If it doesn't then it's a bit order-change error. Otherwise
* we're ok.
*
* This is why we can always proceed with a new buffered DOS, knowing we're
* going to be on a byte boundary with the bit order needed.
*/
final override protected def checkBitOrder(): Unit = {
//
// Check for bitOrder change. If yes, then unless we know we're byte aligned
// we must split the DOS until we find out. That way the new buffered DOS
// can be assumed to be byte aligned (which will be checked on combining),
// and the bytes in it will actually start out byte aligned.
//
val dos = this.dataOutputStream
val isChanging = isUnparseBitOrderChanging(dos)
if (isChanging) {
//
// the bit order is changing. Let's be sure
// that it's legal to do so w.r.t. other properties
// These checks will have been evaluated at compile time if
// all the properties are static, so this is really just
// in case the charset or byteOrder are runtime-valued.
//
this.processor.context match {
case trd: TermRuntimeData => {
val mcboc = trd.maybeCheckBitOrderAndCharsetEv
val mcbbo = trd.maybeCheckByteAndBitOrderEv
if (mcboc.isDefined) mcboc.get.evaluate(this)
if (mcbbo.isDefined) mcbbo.get.evaluate(this)
}
case _ => // ok
}
// TODO: Figure out why this setPriorBitOrder is needed here.
// If we remove it, then test_ep2 (an envelope-payload test with
// bigEndian MSBF envelope and littleEndian LSBF payload)
// fails with Assert.invariant(isWritable)
// when writing a long. The buffered DOS it is writing to is finished.
//
// It's unclear why setting the prior bit order here affects whether
// a DOS is active or finished elsewhere, but it does.
//
val bo = this.bitOrder // will NOT recurse back to here. It *will* hit cache.
dos.setPriorBitOrder(bo)
// If we can't check right now because we don't have absolute bit position
// then split the DOS so it gets checked later.
//
splitOnUknownByteAlignmentBitOrderChange(dos)
}
}
private def isUnparseBitOrderChanging(dos: DirectOrBufferedDataOutputStream): Boolean = {
val ctxt = this.processor.context
ctxt match {
case ntrd: NonTermRuntimeData => false
case _ => {
val priorBitOrder = dos.priorBitOrder
val newBitOrder = this.bitOrder
priorBitOrder ne newBitOrder
}
}
}
/**
* If necessary, split DOS so bitOrder proper byte boundary is checked later.
*
* If we can't check because of unknown absolute bit position,
* then we split the DOS, start a new buffering one (assumed to be
* byte aligned, with the new bitOrder).
*
* The bit order would not be unknown except that something of
* variable length precedes us and is suspended.
* When that eventually is resolved, then the DOS will collapse forward
* and the boundary between the original (dos here), and the buffered
* one will be checked as part of the collapsing logic.
*
* That is, this split does NOT queue a suspension object, it
* Just inserts a split in the DOS. This gets put together later when
* the DOS are collapsed together, and the check for byte boundary occurs
* at that time.
*/
private def splitOnUknownByteAlignmentBitOrderChange(dos: DirectOrBufferedDataOutputStream): Unit = {
val mabp = dos.maybeAbsBitPos0b
val mabpDefined = mabp.isDefined
val isSplitNeeded: Boolean = {
if (mabpDefined && dos.isAligned(8)) {
//
// Not only do we have to be logically aligned, we also have
// to be physically aligned in the buffered stream, otherwise we
// cannot switch bit orders, and we have to split off a new
// stream to start the accumulation of the new bit-order material.
//
// fragmentLastByteLimit == 0 means there is no fragment byte,
// which only happens if we're on a byte boundary in the implementation.
//
if (dos.fragmentLastByteLimit == 0) false
else true
} else if (!mabpDefined) true
else {
// mabp is defined, and we're not on a byte boundary
// and the bit order is changing.
// Error: bit order change on non-byte boundary
val bp1b = mabp.get + 1
SDE("Can only change dfdl:bitOrder on a byte boundary. Bit pos (1b) was %s. Should be 1 mod 8, was %s (mod 8)", bp1b, bp1b % 8)
}
}
if (isSplitNeeded) {
Assert.invariant(dos.isBuffering) // Direct DOS always has absolute position, so has to be buffering.
val newDOS = dos.addBuffered
dataOutputStream = newDOS
//
// Just splitting to start a new bitOrder on a byte boundary in a new
// buffered DOS
// So the prior DOS can be finished. Nothing else will be added to it.
//
// Note: unlike a suspension, in this case, we're not going to write anything
// more to the end of that DOS. A bitOrder change occurs before we get to
// any such content being unparsed, or suspended. So after a bitOrder change,
// the unparsing occurs, possibly buffered, and works as if the
// bitOrder change was legal and happened, even though we cannot know yet
// if that is the case, and it will get checked later.
//
// Finished means you won't add data to the end of it any more.
// It does NOT prevent information like the absoluteBitPos to
// propagate.
dos.setFinished(this)
}
}
def regexMatchBuffer: CharBuffer = Assert.usageError("Not to be used.")
def regexMatchBitPositionBuffer: LongBuffer = Assert.usageError("Not to be used.")
def documentElement: DIDocument
def newVariableInstance(vrd: VariableRuntimeData): Unit = {
variableMap.newVariableInstance(vrd, this)
}
def removeVariableInstance(vrd: VariableRuntimeData): Unit = {
variableMap.removeVariableInstance(vrd)
}
final val releaseUnneededInfoset: Boolean = !areDebugging && tunable.releaseUnneededInfoset
def delimitedParseResult = Nope
}
/**
* When we create a suspension during unparse, we need to clone the UStateMain
* for when the suspension is later resumed. However, we do not need nearly as
* much information for these cloned ustates as the main unparse. Either we can
* access the necessary information directly from the main UState, or the
* information isn't used and there's no need to copy it/take up valuable
* memory.
*/
final class UStateForSuspension(
val mainUState: UStateMain,
override var dataOutputStream: DirectOrBufferedDataOutputStream,
vbox: VariableBox,
override val currentInfosetNode: DINode,
occursIndex: Long,
escapeSchemeEVCacheMaybe: Maybe[MStackOfMaybe[EscapeSchemeUnparserHelper]],
delimiterStackMaybe: Maybe[MStackOf[DelimiterStackUnparseNode]],
tunable: DaffodilTunables,
areDebugging: Boolean)
extends UState(vbox, mainUState.diagnostics, mainUState.dataProc, tunable, areDebugging) {
dState.setMode(UnparserBlocking)
dState.setCurrentNode(thisElement.asInstanceOf[DINode])
dState.setContextNode(thisElement.asInstanceOf[DINode])
dState.setVBox(vbox)
dState.setErrorOrWarn(this)
private def die = Assert.invariantFailed("Function should never be needed in UStateForSuspension")
override def getDecoder(cs: BitsCharset): BitsCharsetDecoder = mainUState.getDecoder(cs)
override def getEncoder(cs: BitsCharset): BitsCharsetEncoder = mainUState.getEncoder(cs)
// override def charBufferDataOutputStream = mainUState.charBufferDataOutputStream
override def withUnparserDataInputStream = mainUState.withUnparserDataInputStream
override def withByteArrayOutputStream = mainUState.withByteArrayOutputStream
override def advance: Boolean = die
override def advanceAccessor: InfosetAccessor = die
override def inspect: Boolean = die
override def inspectAccessor: InfosetAccessor = die
override def fini: Unit = {
//do nothing
}
override def inspectOrError = die
override def advanceOrError = die
override def isInspectArrayEnd = die
override def currentInfosetNodeStack = die
override def currentInfosetNodeMaybe = Maybe(currentInfosetNode)
override def arrayIndexStack = die
override def moveOverOneArrayIndexOnly() = die
override def arrayPos = occursIndex
override def groupIndexStack = die
override def moveOverOneGroupIndexOnly() = die
override def groupPos = 0 // was die, but this is called when copying state during debugging
override def childIndexStack = die
override def moveOverOneElementChildOnly() = die
override def childPos = 0 // was die, but this is called when copying state during debugging.
override def pushDelimiters(node: DelimiterStackUnparseNode) = die
override def popDelimiters() = die
override def localDelimiters = delimiterStackMaybe.get.top
override def allTerminatingMarkup = {
delimiterStackMaybe.get.iterator.flatMap { dnode =>
dnode.separator ++ dnode.terminator
}.toList
}
override def escapeSchemeEVCache: MStackOfMaybe[EscapeSchemeUnparserHelper] = escapeSchemeEVCacheMaybe.get
override def setVariables(newVariableMap: VariableMap) = die
override def pushTRD(trd: TermRuntimeData): Unit = die
override def maybeTopTRD() = die
override def popTRD(trd: TermRuntimeData): TermRuntimeData = die
override def documentElement = mainUState.documentElement
override def incrementHiddenDef = Assert.usageError("Unparser suspended UStates need not be aware of hidden contexts")
override def decrementHiddenDef = Assert.usageError("Unparser suspended UStates need not be aware of hidden contexts")
override def withinHiddenNest = Assert.usageError("Unparser suspended UStates need not be aware of hidden contexts")
}
final class UStateMain private (
private val inputter: InfosetInputter,
outStream: java.io.OutputStream,
vbox: VariableBox,
diagnosticsArg: List[Diagnostic],
dataProcArg: DataProcessor,
tunable: DaffodilTunables,
areDebugging: Boolean)
extends UState(vbox, diagnosticsArg, One(dataProcArg), tunable, areDebugging) {
dState.setMode(UnparserBlocking)
def this(
inputter: InfosetInputter,
outputStream: java.io.OutputStream,
vmap: VariableMap,
diagnosticsArg: List[Diagnostic],
dataProcArg: DataProcessor,
tunable: DaffodilTunables,
areDebugging: Boolean) =
this(inputter, outputStream, new VariableBox(vmap), diagnosticsArg, dataProcArg,
tunable, areDebugging)
override var dataOutputStream: DirectOrBufferedDataOutputStream = {
val out = DirectOrBufferedDataOutputStream(
outStream,
null, // null means no other stream created this one.
isLayer = false,
tunable.outputStreamChunkSizeInBytes,
tunable.maxByteArrayOutputStreamBufferSizeInBytes,
tunable.tempFilePath)
out
}
def cloneForSuspension(suspendedDOS: DirectOrBufferedDataOutputStream): UState = {
val es =
if (!escapeSchemeEVCache.isEmpty) {
// If there are any escape schemes, then we need to clone the whole
// MStack, since the escape scheme cache logic requires an MStack. We
// reallyjust need the top for cloning for suspensions, but that
// requires changes to how the escape schema cache is accessed, which
// isn't a trivial change.
val esClone = new MStackOfMaybe[EscapeSchemeUnparserHelper]()
esClone.copyFrom(escapeSchemeEVCache)
Maybe(esClone)
} else {
Nope
}
val ds =
if (!delimiterStack.isEmpty) {
// If there are any delimiters, then we need to clone them all since
// they may be needed for escaping
val dsClone = new MStackOf[DelimiterStackUnparseNode]()
dsClone.copyFrom(delimiterStack)
Maybe(dsClone)
} else {
Nope
}
val clone = new UStateForSuspension(
this,
suspendedDOS,
variableBox,
currentInfosetNodeStack.top.get, // only need the to of the stack, not the whole thing
arrayIndexStack.top, // only need the top of the stack, not the whole thing
es,
ds,
tunable,
areDebugging)
clone.setProcessor(processor)
clone
}
override lazy val withUnparserDataInputStream = new LocalStack[StringDataInputStreamForUnparse](new StringDataInputStreamForUnparse)
override lazy val withByteArrayOutputStream = new LocalStack[(ByteArrayOutputStream, DirectOrBufferedDataOutputStream)](
{
val baos = new ByteArrayOutputStream() // TODO: PERFORMANCE: Allocates new object. Can reuse one from an onStack/pool via reset()
val dos = DirectOrBufferedDataOutputStream(
baos,
null,
false,
tunable.outputStreamChunkSizeInBytes,
tunable.maxByteArrayOutputStreamBufferSizeInBytes,
tunable.tempFilePath)
(baos, dos)
},
pair => pair match {
case (baos, dos) =>
baos.reset()
dos.resetAllBitPos()
})
override def advance: Boolean = inputter.advance
override def advanceAccessor: InfosetAccessor = inputter.advanceAccessor
override def inspect: Boolean = inputter.inspect
override def inspectAccessor: InfosetAccessor = inputter.inspectAccessor
override def fini: Unit = { inputter.fini }
/**
* Use this so if there isn't an event we get a clean diagnostic message saying
* that is what has gone wrong.
*/
override def inspectOrError = {
val m = inspectMaybe
if (m.isEmpty) Assert.invariantFailed("An InfosetEvent was required for unparsing, but no InfosetEvent was available.")
m.get
}
override def advanceOrError = {
val m = advanceMaybe
if (m.isEmpty) Assert.invariantFailed("An InfosetEvent was required for unparsing, but no InfosetEvent was available.")
m.get
}
override def isInspectArrayEnd = {
if (!inspect) false
else {
val p = inspectAccessor
val res = p match {
case e if e.isEnd && e.isArray => true
case _ => false
}
res
}
}
def currentInfosetNode: DINode =
if (currentInfosetNodeMaybe.isEmpty) null
else currentInfosetNodeMaybe.get
def currentInfosetNodeMaybe: Maybe[DINode] =
if (currentInfosetNodeStack.isEmpty) Nope
else currentInfosetNodeStack.top
override val currentInfosetNodeStack = new MStackOfMaybe[DINode]
override val arrayIndexStack = MStackOfLong()
arrayIndexStack.push(1L)
override def moveOverOneArrayIndexOnly() = arrayIndexStack.push(arrayIndexStack.pop + 1)
override def arrayPos = arrayIndexStack.top
override val groupIndexStack = MStackOfLong()
groupIndexStack.push(1L)
override def moveOverOneGroupIndexOnly() = groupIndexStack.push(groupIndexStack.pop + 1)
override def groupPos = groupIndexStack.top
// TODO: it doesn't look anything is actually reading the value of childindex
// stack. Can we get rid of it?
override val childIndexStack = MStackOfLong()
childIndexStack.push(1L)
override def moveOverOneElementChildOnly() = childIndexStack.push(childIndexStack.pop + 1)
override def childPos = childIndexStack.top
override lazy val escapeSchemeEVCache = new MStackOfMaybe[EscapeSchemeUnparserHelper]
val delimiterStack = new MStackOf[DelimiterStackUnparseNode]()
override def pushDelimiters(node: DelimiterStackUnparseNode) = delimiterStack.push(node)
override def popDelimiters() = delimiterStack.pop
override def localDelimiters = delimiterStack.top
override def allTerminatingMarkup = {
delimiterStack.iterator.flatMap { dnode =>
dnode.separator ++ dnode.terminator
}.toList
}
override def setVariables(newVariableMap: VariableMap) = {
setVariableMap(newVariableMap)
}
/**
* For outputValueCalc we accumulate the suspendables here.
*
* Note: only the primary UState (the initial one) will use this.
* All the other clones used for outputValueCalc, those never
* need to add any.
*/
private val suspensionTracker =
new SuspensionTracker(
tunable.unparseSuspensionWaitYoung,
tunable.unparseSuspensionWaitOld)
def addSuspension(se: Suspension): Unit = {
suspensionTracker.trackSuspension(se)
}
def evalSuspensions(isFinal: Boolean): Unit = {
suspensionTracker.evalSuspensions()
if (isFinal) suspensionTracker.requireFinal()
}
final override def pushTRD(trd: TermRuntimeData) =
inputter.pushTRD(trd)
final override def maybeTopTRD(): Maybe[TermRuntimeData] =
inputter.maybeTopTRD()
final override def popTRD(trd: TermRuntimeData) = {
val poppedTRD = inputter.popTRD()
if (poppedTRD ne trd)
Assert.invariantFailed("TRDs do not match. Expected: " + trd + " got " + poppedTRD)
poppedTRD
}
final override def documentElement = inputter.documentElement
override def toString = {
val elt = if (this.currentInfosetNodeMaybe.isDefined) "node=" + this.currentInfosetNode.toString else ""
val hidden = if (withinHiddenNest) " hidden" else ""
"UState(" + elt + hidden + " DOS=" + dataOutputStream.toString() + ")"
}
}
object UState {
def createInitialUState(
outStream: java.io.OutputStream,
dataProc: DFDL.DataProcessor,
inputter: InfosetInputter,
areDebugging: Boolean): UStateMain = {
Assert.invariant(inputter.isInitialized)
/**
* This is a full deep copy as variableMap is mutable. Reusing
* dataProc.VariableMap without a copy would not be thread safe.
*/
val variables = dataProc.variableMap.copy
val diagnostics = Nil
val newState = new UStateMain(
inputter,
outStream,
variables,
diagnostics,
dataProc.asInstanceOf[DataProcessor],
dataProc.getTunables(),
areDebugging)
newState
}
}