You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@daffodil.apache.org by ja...@apache.org on 2019/06/07 17:07:13 UTC

[incubator-daffodil] branch master updated: Implement dfdl:choiceLength='explicit'

This is an automated email from the ASF dual-hosted git repository.

jadams pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-daffodil.git


The following commit(s) were added to refs/heads/master by this push:
     new 2ec2bee  Implement dfdl:choiceLength='explicit'
2ec2bee is described below

commit 2ec2bee17c7d0d85eaffefc268a974d1844d265b
Author: Josh Adams <ja...@tresys.com>
AuthorDate: Mon May 13 08:23:39 2019 -0400

    Implement dfdl:choiceLength='explicit'
    
    DAFFODIL-640
---
 .../org/apache/daffodil/dsom/ChoiceGroup.scala     |   2 +
 .../RawCommonRuntimeValuedPropertiesMixin.scala    |   2 +
 .../daffodil/dsom/RuntimePropertyMixins.scala      |   2 +
 .../daffodil/grammar/ChoiceGrammarMixin.scala      |   4 +
 .../grammar/primitives/ChoiceCombinator.scala      |  20 +-
 .../unparsers/ChoiceAndOtherVariousUnparsers.scala |  20 +-
 .../processors/unparsers/SpecifiedLength2.scala    | 131 ++++++-
 .../unparsers/SpecifiedLengthUnparsers.scala       |   3 +
 .../processors/unparsers/StreamSplitterMixin.scala | 125 +++++++
 .../unparsers/SuppressableSeparatorUnparser.scala  | 104 +-----
 .../org/apache/daffodil/processors/EvElement.scala |  11 +-
 .../daffodil/processors/parsers/Parser.scala       |   2 +
 .../parsers/SpecifiedLengthParsers.scala           |  14 +-
 .../choice_groups/ChoiceLengthExplicit.tdml        | 377 +++++++++++++++++++++
 .../section15/choice_groups/TestChoice.scala       |  19 ++
 15 files changed, 710 insertions(+), 126 deletions(-)

diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/ChoiceGroup.scala b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/ChoiceGroup.scala
index 83a7cf9..b9d6114 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/ChoiceGroup.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/ChoiceGroup.scala
@@ -20,6 +20,7 @@ package org.apache.daffodil.dsom
 import scala.xml.Node
 import scala.xml._
 import org.apache.daffodil.schema.annotation.props.gen.Choice_AnnotationMixin
+import org.apache.daffodil.schema.annotation.props.gen.ChoiceAGMixin
 import org.apache.daffodil.grammar.ChoiceGrammarMixin
 import org.apache.daffodil.processors.RuntimeData
 import org.apache.daffodil.processors.ChoiceRuntimeData
@@ -106,6 +107,7 @@ abstract class ChoiceTermBase(
   with Choice_AnnotationMixin
   with RawDelimitedRuntimeValuedPropertiesMixin // initiator and terminator (not separator)
   with ChoiceGrammarMixin
+  with ChoiceAGMixin
   with HasOptRepTypeMixinImpl {
 
   requiredEvaluations(branchesAreNonOptional)
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/RawCommonRuntimeValuedPropertiesMixin.scala b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/RawCommonRuntimeValuedPropertiesMixin.scala
index 1957ea4..1985c96 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/RawCommonRuntimeValuedPropertiesMixin.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/RawCommonRuntimeValuedPropertiesMixin.scala
@@ -39,6 +39,8 @@ trait RawDelimitedRuntimeValuedPropertiesMixin
   protected final lazy val initiatorRaw = requireProperty(optionInitiatorRaw)
   protected final lazy val optionTerminatorRaw = findPropertyOption("terminator")
   protected final lazy val terminatorRaw = requireProperty(optionTerminatorRaw)
+  protected final lazy val optionChoiceLengthRaw = findPropertyOption("choiceLength")
+  protected final lazy val choiceLengthRaw = requireProperty(optionChoiceLengthRaw)
 }
 
 trait RawElementRuntimeValuedPropertiesMixin
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/RuntimePropertyMixins.scala b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/RuntimePropertyMixins.scala
index ba00b6c..8b2065c 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/RuntimePropertyMixins.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/RuntimePropertyMixins.scala
@@ -46,6 +46,7 @@ import org.apache.daffodil.schema.annotation.props.gen.DFDLBaseTypeMixin
 import org.apache.daffodil.schema.annotation.props.gen.DFDLSimpleTypeMixin
 import org.apache.daffodil.schema.annotation.props.gen.LengthAGMixin
 import org.apache.daffodil.schema.annotation.props.gen.LengthKind
+import org.apache.daffodil.schema.annotation.props.gen.ChoiceAGMixin
 import org.apache.daffodil.schema.annotation.props.gen.OccursAGMixin
 import org.apache.daffodil.schema.annotation.props.gen.Representation
 import org.apache.daffodil.schema.annotation.props.gen.Sequence_AnnotationMixin
@@ -84,6 +85,7 @@ import org.apache.daffodil.schema.annotation.props.Found
 trait TermRuntimeValuedPropertiesMixin
   extends DFDLBaseTypeMixin
   with PropertyReferencedElementInfosMixin
+  with ChoiceAGMixin
   with RawCommonRuntimeValuedPropertiesMixin { decl: Term =>
 
   private lazy val encodingExpr = LV('encoding) {
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/grammar/ChoiceGrammarMixin.scala b/daffodil-core/src/main/scala/org/apache/daffodil/grammar/ChoiceGrammarMixin.scala
index 37375c5..9851928 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/grammar/ChoiceGrammarMixin.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/grammar/ChoiceGrammarMixin.scala
@@ -19,6 +19,10 @@ package org.apache.daffodil.grammar
 
 import org.apache.daffodil.dsom.ChoiceTermBase
 import org.apache.daffodil.grammar.primitives.ChoiceCombinator
+import org.apache.daffodil.processors.parsers.NadaParser
+import org.apache.daffodil.processors.unparsers.Unparser
+import org.apache.daffodil.processors.unparsers.ChoiceUnusedUnparser
+import org.apache.daffodil.util.Maybe
 
 trait ChoiceGrammarMixin extends GrammarMixin { self: ChoiceTermBase =>
 
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/grammar/primitives/ChoiceCombinator.scala b/daffodil-core/src/main/scala/org/apache/daffodil/grammar/primitives/ChoiceCombinator.scala
index 4661042..1eaed1d 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/grammar/primitives/ChoiceCombinator.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/grammar/primitives/ChoiceCombinator.scala
@@ -26,6 +26,7 @@ import org.apache.daffodil.exceptions.Assert
 import org.apache.daffodil.cookers.ChoiceBranchKeyCooker
 import org.apache.daffodil.api.WarnID
 import org.apache.daffodil.equality._
+import org.apache.daffodil.schema.annotation.props.gen.ChoiceLengthKind
 import org.apache.daffodil.schema.annotation.props.gen.ChoiceKeyKindType
 import org.apache.daffodil.dsom.ChoiceTermBase
 import scala.util.Try
@@ -35,6 +36,7 @@ import scala.math.BigInt
 import org.apache.daffodil.cookers.RepValueCooker
 import org.apache.daffodil.processors.RangeBound
 import org.apache.daffodil.util.Maybe
+import org.apache.daffodil.util.MaybeInt
 
 /*
  * The purpose of the ChoiceCombinator (and the parsers it creates) is to
@@ -67,9 +69,19 @@ case class ChoiceCombinator(ch: ChoiceTermBase, alternatives: Seq[Gram])
 
   lazy val optChoiceDispatchKeyKind = Some(ch.choiceDispatchKeyKind)
 
+  // dfdl:choiceLength is always specified in bytes
+  private lazy val choiceLengthInBits: MaybeInt = ch.choiceLengthKind match {
+    case ChoiceLengthKind.Explicit => MaybeInt(ch.choiceLength * 8)
+    case ChoiceLengthKind.Implicit => MaybeInt.Nope
+  }
+
   lazy val parser: Parser = {
     if (!ch.isDirectDispatch) {
-      new ChoiceParser(ch.termRuntimeData, parsers.toVector)
+      val cp = new ChoiceParser(ch.termRuntimeData, parsers.toVector)
+      ch.choiceLengthKind match {
+        case ChoiceLengthKind.Implicit => cp
+        case ChoiceLengthKind.Explicit => new SpecifiedLengthChoiceParser(cp, ch.choiceRuntimeData, choiceLengthInBits.get)
+      }
     } else {
       val dispatchBranchKeyValueTuples: Seq[(String, Gram)] = alternatives.flatMap { alt =>
         val keyTerm = alt.context.asInstanceOf[Term]
@@ -176,7 +188,6 @@ case class ChoiceCombinator(ch: ChoiceTermBase, alternatives: Seq[Gram])
           new ChoiceDispatchCombinatorParser(ch.termRuntimeData, ch.choiceDispatchKeyEv, serializableMap, serializableKeyRangeMap)
         case ChoiceKeyKindType.Speculative => Assert.invariantFailed("ChoiceKeyKindType==speculative while isDirectDispatch==true")
       }
-
     }
   }
 
@@ -199,8 +210,9 @@ case class ChoiceCombinator(ch: ChoiceTermBase, alternatives: Seq[Gram])
       val mapValues = eventUnparserMap.map { case (k, v) => v }.toSeq.filterNot(_.isEmpty)
       if (mapValues.isEmpty)
         new NadaUnparser(null)
-      else
-        new ChoiceCombinatorUnparser(ch.modelGroupRuntimeData, eventUnparserMap)
+      else {
+        new ChoiceCombinatorUnparser(ch.modelGroupRuntimeData, eventUnparserMap, choiceLengthInBits)
+      }
     } else {
       // Choices inside a hidden group ref are slightly different because we
       // will never see events for any of the branches. Instead, we will just
diff --git a/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/ChoiceAndOtherVariousUnparsers.scala b/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/ChoiceAndOtherVariousUnparsers.scala
index 0a9f432..dd36f7b 100644
--- a/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/ChoiceAndOtherVariousUnparsers.scala
+++ b/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/ChoiceAndOtherVariousUnparsers.scala
@@ -21,8 +21,10 @@ import org.apache.daffodil.processors._
 import org.apache.daffodil.infoset._
 import org.apache.daffodil.processors.RuntimeData
 import org.apache.daffodil.processors.dfa.DFADelimiter
+import org.apache.daffodil.schema.annotation.props.gen.ChoiceLengthKind
 import org.apache.daffodil.util.Maybe._
 import org.apache.daffodil.util.Maybe
+import org.apache.daffodil.util.MaybeInt
 import org.apache.daffodil.util.Maybe._
 
 class ComplexTypeUnparser(rd: RuntimeData, bodyUnparser: Unparser)
@@ -43,7 +45,10 @@ class ComplexTypeUnparser(rd: RuntimeData, bodyUnparser: Unparser)
   }
 }
 
-class ChoiceCombinatorUnparser(mgrd: ModelGroupRuntimeData, eventUnparserMap: Map[ChoiceBranchEvent, Unparser])
+class ChoiceCombinatorUnparser(
+  mgrd: ModelGroupRuntimeData,
+  eventUnparserMap: Map[ChoiceBranchEvent, Unparser],
+  choiceLengthInBits: MaybeInt)
   extends CombinatorUnparser(mgrd)
   with ToBriefXMLImpl {
   override def nom = "Choice"
@@ -71,7 +76,18 @@ class ChoiceCombinatorUnparser(mgrd: ModelGroupRuntimeData, eventUnparserMap: Ma
       UnparseError(One(mgrd.schemaFileLocation), One(state.currentLocation), "Encountered event %s. Expected one of %s.",
         key, eventUnparserMap.keys.mkString(", "))
     }
-    childUnparser.unparse1(state)
+
+    if (choiceLengthInBits.isDefined) {
+      val suspendableOp = new ChoiceUnusedUnparserSuspendableOperation(mgrd, choiceLengthInBits.get)
+      val choiceUnusedUnparser = new ChoiceUnusedUnparser(mgrd, choiceLengthInBits.get, suspendableOp)
+
+      suspendableOp.captureDOSStartForChoiceUnused(state)
+      childUnparser.unparse1(state)
+      suspendableOp.captureDOSEndForChoiceUnused(state)
+      choiceUnusedUnparser.unparse(state)
+    } else {
+      childUnparser.unparse1(state)
+    }
   }
 }
 
diff --git a/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/SpecifiedLength2.scala b/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/SpecifiedLength2.scala
index db20809..b6860dc 100644
--- a/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/SpecifiedLength2.scala
+++ b/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/SpecifiedLength2.scala
@@ -22,6 +22,8 @@ import org.apache.daffodil.infoset.DIComplex
 import org.apache.daffodil.infoset.DIElement
 import org.apache.daffodil.infoset.DISimple
 import org.apache.daffodil.processors.ElementRuntimeData
+import org.apache.daffodil.processors.ModelGroupRuntimeData
+import org.apache.daffodil.processors.RuntimeData
 import org.apache.daffodil.processors.SuspendableOperation
 import org.apache.daffodil.processors.UnparseTargetLengthInBitsEv
 import org.apache.daffodil.processors.charset.BitsCharset
@@ -37,6 +39,8 @@ import org.apache.daffodil.processors.LengthEv
 import org.apache.daffodil.processors.Evaluatable
 import org.apache.daffodil.util.MaybeJULong
 import org.apache.daffodil.io.DirectOrBufferedDataOutputStream
+import org.apache.daffodil.io.ZeroLengthStatus
+import org.apache.daffodil.io.DataOutputStream
 import passera.unsigned.ULong
 
 import java.nio.charset.MalformedInputException
@@ -405,6 +409,26 @@ sealed trait NeedValueAndTargetLengthMixin {
   }
 }
 
+trait SkipTheBits { self: SuspendableOperation =>
+
+  protected val rd: RuntimeData
+
+  protected final def skipTheBits(ustate: UState, skipInBits: Long) {
+    if (skipInBits > 0) {
+      val dos = ustate.dataOutputStream
+      if (!dos.skip(skipInBits, ustate))
+        UE(ustate, "Unable to skip %s(bits).", skipInBits)
+    }
+    if (skipInBits == 0) {
+      log(LogLevel.Debug, "%s no fill for %s DOS %s.",
+        Misc.getNameFromClass(this), rd.diagnosticDebugName, ustate.dataOutputStream)
+    } else {
+      log(LogLevel.Debug, "%s filled %s bits for %s DOS %s.",
+        Misc.getNameFromClass(this), skipInBits, rd.diagnosticDebugName, ustate.dataOutputStream)
+    }
+  }
+}
+
 class ElementUnusedUnparserSuspendableOperation(
   override val rd: ElementRuntimeData,
   override val targetLengthEv: UnparseTargetLengthInBitsEv,
@@ -412,6 +436,7 @@ class ElementUnusedUnparserSuspendableOperation(
   override val maybeCharsetEv: Maybe[CharsetEv],
   override val maybeLiteralNilEv: Maybe[NilStringLiteralForUnparserEv])
   extends SuspendableOperation
+  with SkipTheBits
   with NeedValueAndTargetLengthMixin {
 
   /**
@@ -426,21 +451,6 @@ class ElementUnusedUnparserSuspendableOperation(
     skipTheBits(ustate, skipInBits)
   }
 
-  protected final def skipTheBits(ustate: UState, skipInBits: Long) {
-    if (skipInBits > 0) {
-      val dos = ustate.dataOutputStream
-      if (!dos.skip(skipInBits, ustate))
-        UE(ustate, "Unable to skip %s(bits).", skipInBits)
-    }
-    if (skipInBits == 0) {
-      log(LogLevel.Debug, "%s no fill for %s DOS %s.",
-        Misc.getNameFromClass(this), rd.diagnosticDebugName, ustate.dataOutputStream)
-    } else {
-      log(LogLevel.Debug, "%s filled %s bits for %s DOS %s.",
-        Misc.getNameFromClass(this), skipInBits, rd.diagnosticDebugName, ustate.dataOutputStream)
-    }
-  }
-
 }
 
 class ElementUnusedUnparser(
@@ -460,6 +470,97 @@ class ElementUnusedUnparser(
 
 }
 
+class ChoiceUnusedUnparserSuspendableOperation(
+  override val rd: ModelGroupRuntimeData,
+  targetLengthInBits: Long)
+  extends SuspendableOperation
+  with StreamSplitter
+  with SkipTheBits {
+
+  private var zlStatus_ : ZeroLengthStatus = ZeroLengthStatus.Unknown
+
+  private var maybeDOSStart : Maybe[DataOutputStream] = Maybe.Nope
+  private var maybeDOSEnd : Maybe[DataOutputStream] = Maybe.Nope
+
+
+  def captureDOSStartForChoiceUnused(state: UState): Unit = {
+    val splitter = new RegionSplitUnparser(rd)
+    splitter.unparse(state)
+    maybeDOSStart = Maybe(splitter.dataOutputStream)
+  }
+
+  def captureDOSEndForChoiceUnused(state: UState): Unit = {
+    val splitter = new RegionSplitUnparser(rd)
+    splitter.unparse(state)
+    maybeDOSEnd = Maybe(splitter.dataOutputStream)
+  }
+
+  private lazy val dosToCheck_ = {
+    Assert.usage(maybeDOSStart.isDefined)
+    val dosForStart = maybeDOSStart.get
+    val dosForEnd = maybeDOSEnd.get
+    val primaryDOSList = getDOSFromAtoB(
+      dosForStart,
+      dosForEnd)
+
+    primaryDOSList
+  }
+
+  override def test(ustate: UState): Boolean = {
+    if (zlStatus_ ne ZeroLengthStatus.Unknown)
+      true
+    else if (maybeDOSStart.isEmpty)
+      false
+    else {
+      Assert.invariant(maybeDOSStart.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
+      }
+    }
+  }
+
+
+  /**
+   * determine delta between value length and target length
+   *
+   * and skip that many bits.
+   */
+  override def continuation(ustate: UState) {
+    val startPos0b = dosToCheck_(0).relBitPos0b
+    val endPos0b = dosToCheck_.last.relBitPos0b + startPos0b
+    val vl = (endPos0b - startPos0b).toLong
+    val skipInBits = targetLengthInBits - vl
+    if (skipInBits < 0)
+      UE(ustate, "Data too long for dfdl:choiceLength (%s bits) by %s bits. Unable to truncate.", targetLengthInBits, -skipInBits)
+    skipTheBits(ustate, skipInBits)
+  }
+}
+
+class ChoiceUnusedUnparser(
+  override val context: ModelGroupRuntimeData,
+  targetLengthInBits: Long,
+  suspendableOp: SuspendableOperation)
+  extends PrimUnparser
+  with SuspendableUnparser {
+
+  override lazy val runtimeDependencies = Vector()
+
+  override def suspendableOperation = suspendableOp
+}
+
 trait PaddingUnparserMixin
   extends NeedValueAndTargetLengthMixin { self: SuspendableOperation =>
 
diff --git a/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/SpecifiedLengthUnparsers.scala b/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/SpecifiedLengthUnparsers.scala
index 3a790f3..1803851 100644
--- a/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/SpecifiedLengthUnparsers.scala
+++ b/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/SpecifiedLengthUnparsers.scala
@@ -26,8 +26,11 @@ import org.apache.daffodil.infoset.DISimple
 import org.apache.daffodil.infoset.Infoset
 import org.apache.daffodil.infoset.RetryableException
 import org.apache.daffodil.io.DataOutputStream
+import org.apache.daffodil.io.ZeroLengthStatus
 import org.apache.daffodil.processors.CharsetEv
 import org.apache.daffodil.processors.ElementRuntimeData
+import org.apache.daffodil.processors.TermRuntimeData
+import org.apache.daffodil.processors.ChoiceRuntimeData
 import org.apache.daffodil.processors.Evaluatable
 import org.apache.daffodil.processors.ParseOrUnparseState
 import org.apache.daffodil.processors.Success
diff --git a/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/StreamSplitterMixin.scala b/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/StreamSplitterMixin.scala
new file mode 100644
index 0000000..bb88524
--- /dev/null
+++ b/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/StreamSplitterMixin.scala
@@ -0,0 +1,125 @@
+/*
+ * 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.io.DataOutputStream
+import org.apache.daffodil.io.ZeroLengthStatus
+import org.apache.daffodil.io.DirectOrBufferedDataOutputStream
+import org.apache.daffodil.processors.TermRuntimeData
+import org.apache.daffodil.processors.Processor
+import org.apache.daffodil.processors.SuspendableOperation
+import org.apache.daffodil.exceptions.Assert
+import org.apache.daffodil.util.Maybe
+import scala.collection.mutable.Buffer
+
+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
+  }
+}
diff --git a/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/SuppressableSeparatorUnparser.scala b/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/SuppressableSeparatorUnparser.scala
index 99c4ce9..96f7e2e 100644
--- a/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/SuppressableSeparatorUnparser.scala
+++ b/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/SuppressableSeparatorUnparser.scala
@@ -38,7 +38,8 @@ import org.apache.daffodil.io.DirectOrBufferedDataOutputStream
 final class SuppressableSeparatorUnparserSuspendableOperation(
   sepUnparser: Unparser,
   override val rd: TermRuntimeData)
-  extends SuspendableOperation {
+  extends SuspendableOperation
+  with StreamSplitter {
 
   private var zlStatus_ : ZeroLengthStatus = ZeroLengthStatus.Unknown
 
@@ -47,7 +48,7 @@ final class SuppressableSeparatorUnparserSuspendableOperation(
   private var maybeDOSForEndOfSeparatedRegionBeforePostfixSeparator: Maybe[DataOutputStream] = Maybe.Nope
 
   def captureStateAtEndOfPotentiallyZeroLengthRegionFollowingTheSeparator(s: UState): Unit = {
-    val splitter = SuspressionRegionSplitUnparser(rd)
+    val splitter = RegionSplitUnparser(rd)
     splitter.unparse(s) // splits the DOS so all the potentially ZL stuff is isolated.
     maybeDOSAfterSeparatorRegion = Maybe(splitter.dataOutputStream)
   }
@@ -62,13 +63,13 @@ final class SuppressableSeparatorUnparserSuspendableOperation(
    * from unparsing.
    */
   def captureDOSForStartOfSeparatedRegionBeforePostfixSeparator(s: UState): Unit = {
-    val splitter = SuspressionRegionSplitUnparser(rd)
+    val splitter = RegionSplitUnparser(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)
+    val splitter = RegionSplitUnparser(rd)
     splitter.unparse(s) // splits the DOS so all the potentially ZL stuff is isolated.
     maybeDOSForEndOfSeparatedRegionBeforePostfixSeparator = Maybe(splitter.dataOutputStream)
   }
@@ -137,28 +138,6 @@ final class SuppressableSeparatorUnparserSuspendableOperation(
   }
 
   /**
-   * 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
-    }
-    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
-  }
-
-  /**
    * Once we know whether the length is zero/non-zero, then we decide to unparse the
    * separator or not.
    *
@@ -210,76 +189,3 @@ object SuppressableSeparatorUnparser {
 
 }
 
-/**
- * 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.
- */
-final class SuspressionRegionSplitUnparser private (override val context: TermRuntimeData)
-  extends PrimUnparser
-  with SuspendableUnparser {
-
-  override val childProcessors: Vector[Processor] = Vector()
-
-  override def runtimeDependencies = Vector()
-
-  override lazy val suspendableOperation = new SuppressionRegionSplitSuspendableOperation(context)
-
-  lazy val dataOutputStream = suspendableOperation.savedUstate.dataOutputStream
-}
-
-final class SuppressionRegionSplitSuspendableOperation(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 SuspressionRegionSplitUnparser {
-  def apply(trd: TermRuntimeData) = {
-    val unp = new SuspressionRegionSplitUnparser(trd)
-    Processor.initialize(unp)
-    unp
-  }
-}
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/EvElement.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/EvElement.scala
index b7e08ed..008693a 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/EvElement.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/EvElement.scala
@@ -36,12 +36,13 @@ import org.apache.daffodil.processors.unparsers.UState
 import org.apache.daffodil.schema.annotation.props.gen.LengthKind
 import org.apache.daffodil.schema.annotation.props.gen.LengthUnits
 import org.apache.daffodil.util.Maybe
+import org.apache.daffodil.util.Maybe.Nope
 import org.apache.daffodil.util.MaybeJULong
 import org.apache.daffodil.util.Numbers
 
 sealed trait LengthEv extends Evaluatable[JLong]
 
-class ExplicitLengthEv(expr: CompiledExpression[JLong], rd: ElementRuntimeData)
+class ExplicitLengthEv(expr: CompiledExpression[JLong], rd: RuntimeData)
   extends EvaluatableExpression[JLong](
     expr,
     rd)
@@ -124,7 +125,7 @@ class ImplicitLengthEv(lengthValue: Long, rd: ElementRuntimeData)
  * In that case, the access to the infoset throws particular exceptions
  * descended from the RetryableException trait.
  */
-sealed abstract class LengthInBitsEvBase(rd: ElementRuntimeData,
+sealed abstract class LengthInBitsEvBase(rd: TermRuntimeData,
   val lengthUnits: LengthUnits,
   val lengthKind: LengthKind)
   extends Evaluatable[MaybeJULong](rd)
@@ -167,7 +168,7 @@ class LengthInBitsEv(lengthUnits: LengthUnits,
   lengthKind: LengthKind,
   override val maybeCharsetEv: Maybe[CharsetEv],
   val lengthEv: LengthEv,
-  rd: ElementRuntimeData)
+  rd: TermRuntimeData)
   extends LengthInBitsEvBase(rd, lengthUnits, lengthKind) {
 
   override lazy val runtimeDependencies = maybeCharsetEv.toList :+ lengthEv
@@ -187,7 +188,7 @@ class LengthInBitsEv(lengthUnits: LengthUnits,
 class MinLengthInBitsEv(lengthUnits: LengthUnits,
   lengthKind: LengthKind,
   override val maybeCharsetEv: Maybe[CharsetEv],
-  minLen: Long, rd: ElementRuntimeData)
+  minLen: Long, rd: TermRuntimeData)
   extends LengthInBitsEvBase(rd, lengthUnits, lengthKind) {
 
   override lazy val runtimeDependencies = maybeCharsetEv.toList
@@ -209,7 +210,7 @@ class MinLengthInBitsEv(lengthUnits: LengthUnits,
 class UnparseTargetLengthInBitsEv(
   val lengthInBitsEv: LengthInBitsEv,
   minLengthInBitsEv: MinLengthInBitsEv,
-  rd: ElementRuntimeData)
+  rd: RuntimeData)
   extends Evaluatable[MaybeJULong](rd)
   with InfosetCachedEvaluatable[MaybeJULong] {
 
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/Parser.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/Parser.scala
index 961e639..e7d80a8 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/Parser.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/Parser.scala
@@ -19,6 +19,7 @@ package org.apache.daffodil.processors.parsers
 
 import java.io.StringWriter
 import java.io.PrintWriter
+import java.lang.{ Long => JLong }
 
 import org.apache.daffodil.api.Diagnostic
 import org.apache.daffodil.dsom.RuntimeSchemaDefinitionError
@@ -29,6 +30,7 @@ import org.apache.daffodil.processors.ElementRuntimeData
 import org.apache.daffodil.processors.Processor
 import org.apache.daffodil.processors.RuntimeData
 import org.apache.daffodil.processors.Success
+import org.apache.daffodil.processors.Evaluatable
 import org.apache.daffodil.util.LogLevel
 import org.apache.daffodil.util.Maybe.One
 import org.apache.daffodil.util.MaybeULong
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/SpecifiedLengthParsers.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/SpecifiedLengthParsers.scala
index 94f73f4..b0078b3 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/SpecifiedLengthParsers.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/SpecifiedLengthParsers.scala
@@ -25,6 +25,7 @@ import passera.unsigned.ULong
 import org.apache.daffodil.equality._
 import org.apache.daffodil.exceptions.Assert
 import org.apache.daffodil.processors.ElementRuntimeData
+import org.apache.daffodil.processors.RuntimeData
 import org.apache.daffodil.processors.Evaluatable
 import org.apache.daffodil.processors.Success
 import org.apache.daffodil.schema.annotation.props.gen.LengthUnits
@@ -35,7 +36,7 @@ import org.apache.daffodil.util.OnStack
 
 sealed abstract class SpecifiedLengthParserBase(
   eParser: Parser,
-  erd: ElementRuntimeData)
+  erd: RuntimeData)
   extends CombinatorParser(erd)
   with CaptureParsingValueLength {
 
@@ -132,6 +133,17 @@ class SpecifiedLengthExplicitParser(
   }
 }
 
+class SpecifiedLengthChoiceParser(
+  eParser: Parser,
+  erd: RuntimeData,
+  choiceLengthInBits: JLong)
+  extends SpecifiedLengthParserBase(eParser, erd) {
+
+  final override def getBitLength(s: PState): MaybeULong = {
+    MaybeULong(choiceLengthInBits)
+  }
+}
+
 class SpecifiedLengthImplicitParser(
   eParser: Parser,
   erd: ElementRuntimeData,
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/section15/choice_groups/ChoiceLengthExplicit.tdml b/daffodil-test/src/test/resources/org/apache/daffodil/section15/choice_groups/ChoiceLengthExplicit.tdml
new file mode 100644
index 0000000..95b6077
--- /dev/null
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/section15/choice_groups/ChoiceLengthExplicit.tdml
@@ -0,0 +1,377 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+
+<tdml:testSuite suiteName="choiceExplicit" description="Tests for choice construct with explicit length"
+  xmlns:tdml="http://www.ibm.com/xmlns/dfdl/testData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xmlns:dfdl="http://www.ogf.org/dfdl/dfdl-1.0/" xmlns:xs="http://www.w3.org/2001/XMLSchema"
+  xmlns:ct="http://w3.ibm.com/xmlns/dfdl/ctInfoset" xmlns:ex="http://example.com"
+  xmlns:fn="http://www.w3.org/2005/xpath-functions"
+  xmlns="http://example.com">
+
+  <tdml:defineSchema name="basic">
+    <xs:include schemaLocation="org/apache/daffodil/xsd/DFDLGeneralFormat.dfdl.xsd"/>
+    <dfdl:format ref="ex:GeneralFormat"/>
+
+    <xs:element name="root">
+      <xs:complexType>
+        <xs:sequence dfdl:separator=";">
+           <xs:choice>
+             <xs:element name="int" type="xs:int" dfdl:lengthKind="delimited" />
+             <xs:element name="flt" type="xs:float" dfdl:lengthKind="delimited" />
+             <xs:element name="str" type="xs:string" dfdl:lengthKind="delimited" />
+           </xs:choice>
+        </xs:sequence>
+      </xs:complexType>
+    </xs:element>
+
+    <xs:element name="ch1">
+      <xs:complexType>
+        <xs:sequence>
+          <xs:choice dfdl:choiceLengthKind="explicit" dfdl:choiceLength="2">
+            <xs:element name="inty" type="xs:int"
+              dfdl:lengthKind="explicit" dfdl:length="1" />
+            <xs:element name="stringy" type="xs:string"
+              dfdl:lengthKind="explicit" dfdl:length="2" />
+          </xs:choice>
+        </xs:sequence>
+      </xs:complexType>
+    </xs:element>
+
+    <xs:element name="ch2">
+      <xs:complexType>
+        <xs:sequence>
+          <xs:choice dfdl:choiceLengthKind="explicit" dfdl:choiceLength="2">
+            <xs:element name="inty" type="xs:int"
+              dfdl:lengthKind="explicit" dfdl:length="1" />
+            <xs:element name="stringy" type="xs:string"
+              dfdl:lengthKind="explicit" dfdl:length="3" />
+          </xs:choice>
+        </xs:sequence>
+      </xs:complexType>
+    </xs:element>
+
+    <xs:element name="ch3">
+      <xs:complexType>
+        <xs:sequence>
+          <xs:choice dfdl:choiceLengthKind="explicit" dfdl:choiceLength="2">
+            <xs:element name="int1" type="xs:int"
+              dfdl:lengthKind="explicit" dfdl:length="1" />
+            <xs:element name="string1" type="xs:string"
+              dfdl:lengthKind="explicit" dfdl:length="2" />
+          </xs:choice>
+          <xs:choice dfdl:choiceLengthKind="explicit" dfdl:choiceLength="4">
+            <xs:element name="int2" type="xs:int"
+              dfdl:lengthKind="explicit" dfdl:length="1" />
+            <xs:element name="string2" type="xs:string"
+              dfdl:lengthKind="explicit" dfdl:length="4" />
+          </xs:choice>
+        </xs:sequence>
+      </xs:complexType>
+    </xs:element>
+
+  </tdml:defineSchema>
+
+  <tdml:parserTestCase name="explicit_01" root="ch1"
+    model="basic" roundTrip="true"
+    description="simplest imaginable test of choice. No asserts, no discriminators - DFDL-15-001R.">
+
+    <tdml:document><![CDATA[AB]]></tdml:document>
+
+    <tdml:infoset>
+      <tdml:dfdlInfoset xmlns:ex="http://example.com">
+        <ex:ch1>
+          <ex:stringy>AB</ex:stringy>
+        </ex:ch1>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="explicit_02" root="ch1"
+    model="basic" roundTrip="true"
+    description="simplest imaginable test of choice. No asserts, no discriminators - DFDL-15-001R.">
+
+    <tdml:document><![CDATA[1 ]]></tdml:document>
+
+    <tdml:infoset>
+      <tdml:dfdlInfoset xmlns:ex="http://example.com">
+        <ex:ch1>
+          <ex:inty>1</ex:inty>
+        </ex:ch1>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="explicit_03" root="ch2"
+    model="basic"
+    description="simplest imaginable test of choice. No asserts, no discriminators - DFDL-15-001R.">
+
+    <tdml:document><![CDATA[ABC]]></tdml:document>
+
+    <tdml:errors>
+      <tdml:error>Parse Error</tdml:error>
+      <tdml:error>All choice alternatives failed</tdml:error>
+    </tdml:errors>
+
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="explicit_multiple_choices" root="ch3"
+    model="basic" roundTrip="true"
+    description="simplest imaginable test of choice. No asserts, no discriminators - DFDL-15-001R.">
+
+    <tdml:document><![CDATA[1 TEST]]></tdml:document>
+
+    <tdml:infoset>
+      <tdml:dfdlInfoset xmlns:ex="http://example.com">
+        <ex:ch3>
+          <ex:int1>1</ex:int1>
+          <ex:string2>TEST</ex:string2>
+        </ex:ch3>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+
+  </tdml:parserTestCase>
+
+
+  <tdml:unparserTestCase name="explicit_unparse_01" root="ch1" model="basic">
+    <tdml:infoset>
+      <tdml:dfdlInfoset xmlns:ex="http://example.com">
+        <ex:ch1>
+          <ex:inty>1</ex:inty>
+        </ex:ch1>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+    <tdml:document><![CDATA[1 ]]></tdml:document>
+  </tdml:unparserTestCase>
+
+  <tdml:unparserTestCase name="explicit_unparse_02" root="ch1" model="basic">
+    <tdml:infoset>
+      <tdml:dfdlInfoset xmlns:ex="http://example.com">
+        <ex:ch1>
+          <ex:stringy>A</ex:stringy>
+        </ex:ch1>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+    <tdml:document><![CDATA[A ]]></tdml:document>
+  </tdml:unparserTestCase>
+
+  <tdml:defineSchema name="choice2">
+    <xs:include schemaLocation="org/apache/daffodil/xsd/DFDLGeneralFormat.dfdl.xsd"/>
+    <dfdl:format ref="ex:GeneralFormat" />
+
+    <xs:group name="simpleGroup">
+      <xs:sequence dfdl:separator=".">
+        <xs:element name="string" type="xs:string" dfdl:lengthKind="delimited"/>
+        <xs:element name="int" type="xs:int" dfdl:lengthKind="delimited"/>
+      </xs:sequence>
+    </xs:group>
+    <xs:group name="simpleGroup2">
+      <xs:sequence dfdl:separator=",">
+        <xs:element name="string" type="xs:string" dfdl:lengthKind="delimited"/>
+        <xs:element name="int" type="xs:int" dfdl:lengthKind="delimited" />
+      </xs:sequence>
+    </xs:group>
+
+    <xs:complexType name="ctype">
+      <xs:choice dfdl:choiceLengthKind="explicit" dfdl:choiceLength="3">
+        <xs:element name="inty" type="xs:int"
+          dfdl:lengthKind="explicit" dfdl:length="{ 3 }" />
+        <xs:element name="floaty" type="xs:float"
+          dfdl:lengthKind="explicit" dfdl:length="{ 3 }" />
+        <xs:element name="stringy" type="xs:string"
+          dfdl:lengthKind="explicit" dfdl:length="{ 3 }" />
+      </xs:choice>
+    </xs:complexType>
+
+    <xs:element name="ctype2">
+      <xs:complexType>
+        <xs:choice dfdl:choiceLengthKind="explicit" dfdl:choiceLength="10">
+          <xs:element name="a">
+            <xs:complexType>
+              <xs:group ref="ex:simpleGroup" />
+            </xs:complexType>
+          </xs:element>
+          <xs:element name="b">
+            <xs:complexType>
+              <xs:group ref="ex:simpleGroup2" />
+            </xs:complexType>
+          </xs:element>
+        </xs:choice>
+      </xs:complexType>
+    </xs:element>
+
+    <xs:element name="ch1">
+      <xs:complexType>
+        <xs:sequence dfdl:separator="">
+          <xs:element name="a" type="ex:ctype"
+            dfdl:lengthKind="implicit" maxOccurs="unbounded" minOccurs="0"
+            dfdl:occursCountKind="parsed" />
+        </xs:sequence>
+      </xs:complexType>
+    </xs:element>
+
+  </tdml:defineSchema>
+
+<!--
+     Test Name: explicit_04
+        Schema: choice2
+          Root: ctype2
+       Purpose: This test demonstrates that you can have a choice with group references as branches
+-->
+
+  <tdml:parserTestCase name="explicit_04" root="ctype2"
+    model="choice2"
+    description="Section 15 - Choice Groups - group refs as branches- DFDL-15-005R.">
+
+    <tdml:document><![CDATA[string.147]]></tdml:document>
+
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <ctype2>
+          <a>
+            <string>string</string>
+            <int>147</int>
+          </a>
+        </ctype2>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="explicit_05" root="ch1"
+    model="choice2"
+    description="Many choices one after another. 3 way branches - DFDL-15-001R.">
+
+    <tdml:document><![CDATA[3.5AAA]]></tdml:document>
+
+    <tdml:infoset>
+      <tdml:dfdlInfoset xmlns:ex="http://example.com">
+        <ex:ch1>
+          <ex:a>
+            <ex:floaty>3.5</ex:floaty>
+          </ex:a>
+          <ex:a>
+            <ex:stringy>AAA</ex:stringy>
+          </ex:a>
+
+        </ex:ch1>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="explicit_06" root="ch1"
+    model="choice2"
+    description="Many choices one after another. 3 way branches - DFDL-15-001R.">
+
+    <tdml:document><![CDATA[999888777]]></tdml:document>
+
+    <tdml:infoset>
+      <tdml:dfdlInfoset xmlns:ex="http://example.com">
+        <ex:ch1>
+          <ex:a>
+            <ex:inty>999</ex:inty>
+          </ex:a>
+          <ex:a>
+            <ex:inty>888</ex:inty>
+          </ex:a>
+          <ex:a>
+            <ex:inty>777</ex:inty>
+          </ex:a>
+        </ex:ch1>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="explicit_07" root="ch1"
+    model="choice2"
+    description="Many choices one after another. 3 way branches - DFDL-15-001R.">
+
+    <tdml:document><![CDATA[3.54.65.7]]></tdml:document>
+
+    <tdml:infoset>
+      <tdml:dfdlInfoset xmlns:ex="http://example.com">
+        <ex:ch1>
+          <ex:a>
+            <ex:floaty>3.5</ex:floaty>
+          </ex:a>
+          <ex:a>
+            <ex:floaty>4.6</ex:floaty>
+          </ex:a>
+          <ex:a>
+            <ex:floaty>5.7</ex:floaty>
+          </ex:a>
+        </ex:ch1>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="explicit_08" root="ch1"
+    model="choice2"
+    description="Many choices one after another. 3 way branches - DFDL-15-001R.">
+
+    <tdml:document><![CDATA[AAABBBCCC]]></tdml:document>
+
+    <tdml:infoset>
+      <tdml:dfdlInfoset xmlns:ex="http://example.com">
+        <ex:ch1>
+          <ex:a>
+            <ex:stringy>AAA</ex:stringy>
+          </ex:a>
+          <ex:a>
+            <ex:stringy>BBB</ex:stringy>
+          </ex:a>
+          <ex:a>
+            <ex:stringy>CCC</ex:stringy>
+          </ex:a>
+        </ex:ch1>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+
+  </tdml:parserTestCase>
+
+
+  <tdml:parserTestCase name="explicit_09" root="ch1"
+    model="choice2"
+    description="Many choices one after another. 3 way branches - DFDL-15-001R.">
+
+    <tdml:document><![CDATA[999AAA777]]></tdml:document>
+
+    <tdml:infoset>
+      <tdml:dfdlInfoset xmlns:ex="http://example.com">
+        <ex:ch1>
+          <ex:a>
+            <ex:inty>999</ex:inty>
+          </ex:a>
+          <ex:a>
+            <ex:stringy>AAA</ex:stringy>
+          </ex:a>
+          <ex:a>
+            <ex:inty>777</ex:inty>
+          </ex:a>
+        </ex:ch1>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+
+  </tdml:parserTestCase>
+
+
+</tdml:testSuite>
diff --git a/daffodil-test/src/test/scala/org/apache/daffodil/section15/choice_groups/TestChoice.scala b/daffodil-test/src/test/scala/org/apache/daffodil/section15/choice_groups/TestChoice.scala
index 86d5dbd..3f8e1ee 100644
--- a/daffodil-test/src/test/scala/org/apache/daffodil/section15/choice_groups/TestChoice.scala
+++ b/daffodil-test/src/test/scala/org/apache/daffodil/section15/choice_groups/TestChoice.scala
@@ -25,9 +25,11 @@ object TestChoice {
   val testDir = "/org/apache/daffodil/section15/choice_groups/"
 
   val runnerCH = Runner(testDir, "choice.tdml")
+  val runnerCE = Runner(testDir, "ChoiceLengthExplicit.tdml")
 
   @AfterClass def shutDown {
     runnerCH.reset
+    runnerCE.reset
   }
 
 }
@@ -121,4 +123,21 @@ class TestChoice {
   @Test def test_direct_dispatch_16() { runnerCH.runOneTest("direct_dispatch_16") }
 
   //@Test def test_choice_noBranch() { runnerCH.runOneTest("choice_noBranch") } - Test consumes no data, which causes a TDMLError
+
+
+  @Test def test_explicit_01() { runnerCE.runOneTest("explicit_01") }
+  @Test def test_explicit_02() { runnerCE.runOneTest("explicit_02") }
+  @Test def test_explicit_03() { runnerCE.runOneTest("explicit_03") }
+  @Test def test_explicit_04() { runnerCE.runOneTest("explicit_04") }
+  @Test def test_explicit_05() { runnerCE.runOneTest("explicit_05") }
+  @Test def test_explicit_06() { runnerCE.runOneTest("explicit_06") }
+  @Test def test_explicit_07() { runnerCE.runOneTest("explicit_07") }
+  @Test def test_explicit_08() { runnerCE.runOneTest("explicit_08") }
+  @Test def test_explicit_09() { runnerCE.runOneTest("explicit_09") }
+
+  @Test def test_explicit_multiple_choices() { runnerCE.runOneTest("explicit_multiple_choices") }
+
+  @Test def test_explicit_unparse_01() { runnerCE.runOneTest("explicit_unparse_01") }
+  @Test def test_explicit_unparse_02() { runnerCE.runOneTest("explicit_unparse_02") }
+
 }