stevedlawrence commented on a change in pull request #214: Sequences and Separators Refactoring and Rewrite URL: https://github.com/apache/incubator-daffodil/pull/214#discussion_r285202168
########## File path: daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/SuppressableSeparatorUnparser.scala ########## @@ -0,0 +1,268 @@ +/* + * 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 org.apache.daffodil.processors.SuspendableOperation +import org.apache.daffodil.util.LogLevel +import org.apache.daffodil.processors.TermRuntimeData +import org.apache.daffodil.exceptions.Assert +import org.apache.daffodil.util.Maybe +import org.apache.daffodil.util.Maybe._ +import org.apache.daffodil.io.DataOutputStream +import org.apache.daffodil.io.ZeroLengthStatus +import scala.collection.mutable.Buffer +import org.apache.daffodil.processors.Processor +import org.apache.daffodil.io.DirectOrBufferedDataOutputStream + +final class SuppressableSeparatorUnparserSuspendableOperation(sepUnparser: Unparser, + override val rd: TermRuntimeData) + extends SuspendableOperation { + + private var zlStatus_ : ZeroLengthStatus = ZeroLengthStatus.Unknown + + private var maybeDOSAfterSeparatorRegion: Maybe[DataOutputStream] = Maybe.Nope + private var maybeDOSForStartOfSeparatedRegionBeforePostfixSeparator: Maybe[DataOutputStream] = Maybe.Nope + private var maybeDOSForEndOfSeparatedRegionBeforePostfixSeparator: Maybe[DataOutputStream] = Maybe.Nope + + def captureStateAtEndOfPotentiallyZeroLengthRegionFollowingTheSeparator(s: UState): Unit = { + val splitter = SuspressionRegionSplitUnparser(rd) + splitter.unparse(s) // splits the DOS so all the potentially ZL stuff is isolated. + maybeDOSAfterSeparatorRegion = Maybe(splitter.dataOutputStream) + } + + /** + * Needed for the corner case of suppressing a postfix separator. + * + * Here we need to know whether two regions of data are zero length + * when dealing with trailingEmpty suppression. The data before the + * postfix separator must be ZL, and the data to the end of sequence must + * be ZL. So we have the ability to capture two ranges of buffered data + * from unparsing. + */ + def captureDOSForStartOfSeparatedRegionBeforePostfixSeparator(s: UState): Unit = { + val splitter = SuspressionRegionSplitUnparser(rd) + splitter.unparse(s) // splits the DOS so all the potentially ZL stuff is isolated. + maybeDOSForStartOfSeparatedRegionBeforePostfixSeparator = Maybe(splitter.dataOutputStream.maybeNextInChain.get) + } + + def captureDOSForEndOfSeparatedRegionBeforePostfixSeparator(s: UState): Unit = { + val splitter = SuspressionRegionSplitUnparser(rd) + splitter.unparse(s) // splits the DOS so all the potentially ZL stuff is isolated. + maybeDOSForEndOfSeparatedRegionBeforePostfixSeparator = Maybe(splitter.dataOutputStream) + } + + /** + * Get list of DOSs that we need to check to see if we know + * they are nonZL or not. + */ + private lazy val dosToCheck_ = { + Assert.usage(maybeDOSAfterSeparatorRegion.isDefined) + val dosForStartOfSeparatedRegion = savedUstate.dataOutputStream.maybeNextInChain.get + val dosForEndOfSeparatedRegion = maybeDOSAfterSeparatorRegion.get + val primaryDOSList = getDOSFromAtoB(dosForStartOfSeparatedRegion, + dosForEndOfSeparatedRegion) + val secondaryDOSList = + if (maybeDOSForStartOfSeparatedRegionBeforePostfixSeparator.isDefined) { + Assert.usage(maybeDOSForEndOfSeparatedRegionBeforePostfixSeparator.isDefined) + getDOSFromAtoB(maybeDOSForStartOfSeparatedRegionBeforePostfixSeparator.get, + maybeDOSForEndOfSeparatedRegionBeforePostfixSeparator.get) + } else + Seq() + val res = primaryDOSList ++ secondaryDOSList + res + } + + /** + * Determine if the decision about whether to suppress or not can be taken. + * + * A trailing separator may be suppressed if the things after it are zero-length + * to the end of the sequence. + * + * An anyEmpty separator may be suppressed if the separated thing is zero-length. + * + * This must be determined by examining the buffers of unparsed data. Such time as their + * length is known to be greater than zero the test will return true. If they are + * finished and length is still zero the test will also return true. Otherwise they + * are possibly just temporarily of length zero, so we don't know so we return false + * and the suspension will be retried later. + */ + override def test(ustate: UState): Boolean = { + if (zlStatus_ ne ZeroLengthStatus.Unknown) + true + else if (maybeDOSAfterSeparatorRegion.isEmpty) + false + else { + Assert.invariant(maybeDOSAfterSeparatorRegion.isDefined) + if (dosToCheck_.exists { dos => + val dosZLStatus = dos.zeroLengthStatus + dosZLStatus eq ZeroLengthStatus.NonZero + }) { + zlStatus_ = ZeroLengthStatus.NonZero + true + } else if (dosToCheck_.forall { dos => + val dosZLStatus = dos.zeroLengthStatus + dosZLStatus eq ZeroLengthStatus.Zero + }) { + zlStatus_ = ZeroLengthStatus.Zero + true + } else { + Assert.invariant(zlStatus_ eq ZeroLengthStatus.Unknown) + false + } + } + } + + /** + * 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. + */ + private 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 + } + if (maybeNext.isDefined && (maybeNext.get eq afterDOS)) { Review comment: I think this statement is always true, right? We must always find afterDOS so maybeNext must always be defined. And we will always stop the above while loop when we find afterDOS, so maybeNext.get must always equal afterDOS. In fact, all the isDefined checks are probably unnecessary? ---------------------------------------------------------------- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. For queries about this service, please contact Infrastructure at: [email protected] With regards, Apache Git Services
