org.apache.daffodil.unparsers.runtime1.StreamSplitterMixin.scala Maven / Gradle / Ivy
/*
* 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.unparsers.runtime1
import scala.collection.mutable.Buffer
import org.apache.daffodil.io.DataOutputStream
import org.apache.daffodil.lib.exceptions.Assert
import org.apache.daffodil.lib.util.Maybe
import org.apache.daffodil.runtime1.processors.Processor
import org.apache.daffodil.runtime1.processors.SuspendableOperation
import org.apache.daffodil.runtime1.processors.TermRuntimeData
import org.apache.daffodil.runtime1.processors.unparsers._
trait StreamSplitter {
/**
* Given two DataOutputStream, determine the set of data output streams starting from the first,
* and in chain until (and including) we reach the second.
*
* If the two are the same DataOutputStream, we get back a sequence of just the one DOS.
*/
def getDOSFromAtoB(
beforeDOS: DataOutputStream,
afterDOS: DataOutputStream
): Seq[DataOutputStream] = {
val buf = Buffer[DataOutputStream]()
var maybeNext = Maybe(beforeDOS)
while (maybeNext.isDefined && (maybeNext.get ne afterDOS)) {
val thisOne = maybeNext.get
buf += thisOne
maybeNext = thisOne.maybeNextInChain
}
Assert.invariant(maybeNext.isDefined) // we MUST find a path to the afterDOS.
Assert.invariant(maybeNext.get eq afterDOS)
// we include the final afterDOS data output stream in the list
buf += afterDOS
val res = buf.toSeq
res
}
}
/**
* We need to isolate the regions that we have to test for ZL/notZL
* for separator suppression. So we want these regions to not share
* data output streams with anything outside them.
*
* To achieve that we split the data output stream using its buffering
* capabilities. Some of the splits are natural. They are one side of
* the (potentially suppressed) separator itself. So creating that
* suspendable unparser for the separator gives us a split.
*
* The other side of these regions is however, an artificial split.
*
* To avoid duplication of complex code paths, we create this
* suspendable unparser that exists purely to split the underlying DOS
* but maintain all the invariants of the underlying DOS code, i.e.,
* the streams still get finalized (and therefore collapsed) the regular
* way.
*
* We are counting on the fact that these objects live on the garbage
* collected heap, so have unbounded lifetimes, i.e., they aren't recycled
* in a pool or anything.
*
* Performance Note: These do not need to capture state like a normal
* suspension does (e.g., for dfdl:outputValueCalc), as they're not actually
* suspending any actual unparsing behavior. Just forcing a boundary in the
* data output streams so that we can use the data output streams to measure the
* length (zero/non-zero) of unparsed data.
*/
class RegionSplitUnparser(override val context: TermRuntimeData)
extends PrimUnparser
with SuspendableUnparser {
override val childProcessors: Vector[Processor] = Vector()
override def runtimeDependencies = Vector()
override lazy val suspendableOperation = new RegionSplitSuspendableOperation(context)
lazy val dataOutputStream = suspendableOperation.savedUstate.dataOutputStream
}
final class RegionSplitSuspendableOperation(override val rd: TermRuntimeData)
extends SuspendableOperation {
private var secondTime = false
/**
* Suspends once, since test fails the first time.
* When retried, the test succeeds.
*/
override def test(ustate: UState): Boolean = {
if (secondTime) true
else {
secondTime = true
false
}
}
override def continuation(ustate: UState): Unit = {
// do nothing.
//
// The underlying suspension system will take care of
// finishing the DOS so everything gets unblocked.
}
}
object RegionSplitUnparser {
def apply(trd: TermRuntimeData) = {
val unp = new RegionSplitUnparser(trd)
Processor.initialize(unp)
unp
}
}