diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/unparsers/runtime1/StringLengthUnparsers.scala b/daffodil-core/src/main/scala/org/apache/daffodil/unparsers/runtime1/StringLengthUnparsers.scala index 49e1b31afc..0b2be365bc 100644 --- a/daffodil-core/src/main/scala/org/apache/daffodil/unparsers/runtime1/StringLengthUnparsers.scala +++ b/daffodil-core/src/main/scala/org/apache/daffodil/unparsers/runtime1/StringLengthUnparsers.scala @@ -26,6 +26,7 @@ import org.apache.daffodil.runtime1.processors.CharsetEv import org.apache.daffodil.runtime1.processors.ElementRuntimeData import org.apache.daffodil.runtime1.processors.Evaluatable import org.apache.daffodil.runtime1.processors.LengthEv +import org.apache.daffodil.runtime1.processors.SuspendableOperation import org.apache.daffodil.runtime1.processors.TextTruncationType import org.apache.daffodil.runtime1.processors.UnparseTargetLengthInBitsEv import org.apache.daffodil.runtime1.processors.unparsers.* @@ -70,10 +71,26 @@ class StringNoTruncateUnparser(erd: ElementRuntimeData) sealed abstract class StringSpecifiedLengthUnparserTruncateBase( stringTruncationType: TextTruncationType.Type, erd: ElementRuntimeData -) extends StringSpecifiedLengthUnparserBase(erd) { +) extends StringSpecifiedLengthUnparserBase(erd) + with SuspendableUnparser { Assert.usage(stringTruncationType ne TextTruncationType.None) + /** + * The target length the string is truncated to. It can depend on an + * outputValueCalc element that has not been computed yet. + */ + protected[runtime1] def targetLengthEv: Evaluatable[AnyRef] + + /** + * Reads the target length without retrying, so it must only be called + * once the target length can be evaluated. + */ + protected[runtime1] def unparseString(state: UState): Unit + + override protected def suspendableOperation = + new StringTruncationSuspendableOperation(erd, this) + /** * We only truncate strings, and only if textStringJustification is left or * right, and only if truncateSpecifiedLengthString is yes. @@ -122,6 +139,8 @@ class StringMaybeTruncateBitsUnparser( override val runtimeDependencies = Array(targetLengthInBitsEv, charsetEv) + override protected[runtime1] def targetLengthEv = targetLengthInBitsEv + private def getLengthInBits(str: String, state: UState): (Long, Long) = { val cs = charsetEv.evaluate(state) val mfw = cs.maybeFixedWidth @@ -171,7 +190,7 @@ class StringMaybeTruncateBitsUnparser( res } - override def unparse(state: UState): Unit = { + override protected[runtime1] def unparseString(state: UState): Unit = { // // We have to stage the bits of the value just so as to be able to count them @@ -259,7 +278,9 @@ class StringMaybeTruncateCharactersUnparser( override val runtimeDependencies = Array(lengthInCharactersEv) - override def unparse(state: UState): Unit = { + override protected[runtime1] def targetLengthEv = lengthInCharactersEv + + override protected[runtime1] def unparseString(state: UState): Unit = { val dos = state.getDataOutputStream val valueString = contentString(state) val targetLengthInCharacters = @@ -285,3 +306,27 @@ class StringMaybeTruncateCharactersUnparser( Assert.invariant(nCharsWritten == valueToWrite.length) } } + +/** + * Suspends until the target length can be evaluated, then unparses the + * string, because the truncating string unparsers read the target length + * without retrying. + */ +class StringTruncationSuspendableOperation( + override val rd: ElementRuntimeData, + unparser: StringSpecifiedLengthUnparserTruncateBase +) extends SuspendableOperation { + + override def toString = "string truncation for " + rd.diagnosticDebugName + + // Evaluating throws a RetryableException while the length is unavailable, + // which blocks this operation. + override protected def test(ustate: UState): Boolean = { + unparser.targetLengthEv.evaluate(ustate) + true + } + + override protected def continuation(ustate: UState): Unit = { + unparser.unparseString(ustate) + } +} diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/section00/general/testUnparserGeneral.tdml b/daffodil-test/src/test/resources/org/apache/daffodil/section00/general/testUnparserGeneral.tdml index 549cef2799..4940801eb0 100644 --- a/daffodil-test/src/test/resources/org/apache/daffodil/section00/general/testUnparserGeneral.tdml +++ b/daffodil-test/src/test/resources/org/apache/daffodil/section00/general/testUnparserGeneral.tdml @@ -1023,8 +1023,7 @@ - not have a value - circular dependency + circularly deadlocked @@ -1055,10 +1054,7 @@ - - not have a value - circular dependency - + 5ABCDE @@ -1113,7 +1109,7 @@ - Value length unknown + circularly deadlocked