blob: ab9ee3251a487a9b0455bb2ea0fab2fd300e4eec [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.dsom
import org.apache.daffodil.dsom.walker.ChoiceView
import scala.xml.Node
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.exceptions.Assert
import org.apache.daffodil.schema.annotation.props.gen.YesNo
import org.apache.daffodil.schema.annotation.props.gen.ChoiceKeyKindType
/**
*
* Captures concepts common to all choice definitions, which includes both
* local and global choice definitions, but not choice group references.
*
* Choices are a bit complicated.
*
* They can have initiators and terminators. This is most easily thought of as wrapping each choice
* inside a sequence having only the choice inside it, and moving the initiator and terminator specification to that
* sequence.
*
* That sequence is then the term replacing the choice wherever the choice is.
*
* Choices can have children which are scalar elements. In this case, that scalar element behaves as if it were
* a child of the enclosing sequence (which could be the one we just injected above the choice.
*
* Choices can have children which are recurring elements. In this case, the behavior is as if the recurring child was
* placed inside a sequence which has no initiator nor terminator, but repeats the separator specification from
* the sequence context that encloses the choice.
*
* All that, and the complexities of separator suppression too.
*
* There's also issues like this:
*
* <choice>
* <element .../>
* <sequence/>
* </choice>
*
* in the above, one alternative is an empty sequence. So this choice may produce an element which takes up
* a child position, and potentially requires separation, or it may produce nothing at all.
*
*/
trait ChoiceDefMixin
extends AnnotatedSchemaComponent
with GroupDefLike {
protected def isMyFormatAnnotation(a: DFDLAnnotation) = a.isInstanceOf[DFDLChoice]
protected override def annotationFactory(node: Node): Option[DFDLAnnotation] = {
node match {
case <dfdl:choice>{ contents @ _* }</dfdl:choice> => Some(new DFDLChoice(node, this))
case _ => annotationFactoryForDFDLStatement(node, this)
}
}
protected def emptyFormatFactory: DFDLFormatAnnotation = new DFDLChoice(newDFDLAnnotationXML("choice"), this)
lazy val xmlChildren = xml match {
case <choice>{ c @ _* }</choice> => c
case <group>{ _* }</group> => {
val ch = this match {
case cgd: GlobalChoiceGroupDef => cgd.xml \ "choice"
}
val <choice>{ c @ _* }</choice> = ch(0)
c
}
}
}
abstract class ChoiceTermBase(
final override val xml: Node,
final override val optLexicalParent: Option[SchemaComponent],
final override val position: Int)
extends ModelGroup(position)
with Choice_AnnotationMixin
with RawDelimitedRuntimeValuedPropertiesMixin // initiator and terminator (not separator)
with ChoiceGrammarMixin
with ChoiceAGMixin
with HasOptRepTypeMixinImpl {
requiredEvaluationsIfActivated(noBranchesFound)
requiredEvaluationsIfActivated(branchesAreNonOptional)
requiredEvaluationsIfActivated(branchesAreNotIVCElements)
final protected lazy val optionChoiceDispatchKeyRaw = findPropertyOption("choiceDispatchKey", expressionAllowed = true)
final protected lazy val choiceDispatchKeyRaw = requireProperty(optionChoiceDispatchKeyRaw)
lazy val optionChoiceDispatchKeyKindRaw = findPropertyOption("choiceDispatchKeyKind")
lazy val defaultableChoiceDispatchKeyKind =
if (tunable.requireChoiceDispatchKeyKindProperty) {
choiceDispatchKeyKind
} else {
val asString = optionChoiceDispatchKeyKindRaw.toOption.getOrElse("implicit")
ChoiceKeyKindType(asString, this)
}
lazy val optionChoiceBranchKeyKindRaw = findPropertyOption("choiceBranchKeyKind")
lazy val defaultableChoiceBranchKeyKind =
if (tunable.requireChoiceBranchKeyKindProperty) {
choiceDispatchKeyKind
} else {
val asString = optionChoiceBranchKeyKindRaw.toOption.getOrElse("implicit")
ChoiceKeyKindType(asString, this)
}
final lazy val isDirectDispatch = {
val isDD: Boolean = defaultableChoiceDispatchKeyKind match {
case ChoiceKeyKindType.ByType => true
case ChoiceKeyKindType.Explicit => true
case ChoiceKeyKindType.Implicit => optionChoiceDispatchKeyRaw.isDefined
case ChoiceKeyKindType.Speculative => false
}
if (isDD && initiatedContent == YesNo.Yes) {
SDE("dfdl:initiatedContent must not equal 'yes' when dfdl:choiceDispatchKey is defined")
}
isDD
}
// If choiceDispatchKeyKind is byType, verify that all our children share a repType,
// and use that. Otherwise, there is no need to associate a repType with this choice
override final lazy val optRepType: Option[SimpleTypeDefBase] = defaultableChoiceDispatchKeyKind match {
case ChoiceKeyKindType.ByType => {
val branchReptypes: Seq[SimpleTypeDefBase] = groupMembers.map(term => {
term match {
case e: ElementDeclMixin => e.typeDef match {
case t: SimpleTypeDefBase => t.optRepTypeDef match {
case None => SDE("When <xs:choice> has choiceBranchKey=\"byType\", all branches must have a type which defines a repType")
case Some(x) => x
}
case _: SimpleTypeBase => SDE("When <xs:choice> has choiceBranchKey=\"byType\", no branch can have a primitive xsd type")
case _ => SDE("When <xs:choice> has choiceBranchKey=\"byType\", all branches must be a simple type")
}
case _ => SDE("When <xs:choice> has choiceBranchKey=\"byType\", all branches must be a simple element")
}
})
val ans = branchReptypes.reduce((a, b) => {
/*
* We tolerate type objects being copies or the same object because we compare the QNames.
*/
if (a.namedQName != b.namedQName) {
SDE("All children of an <xs:choice> with choiceDispatchKeyKind=byType must have the same reptype")
}
a
})
Some(ans)
}
case ChoiceKeyKindType.Speculative => None
case ChoiceKeyKindType.Explicit => None
case ChoiceKeyKindType.Implicit => None
}
/*
* When choiceBranchKeyKind=byType or choiceDispatchKeyKind=byType, we are associated with one or more repTypes (corresponding to
* the reptypes of our choices).
* Since the repValueSet represents the set of repValues that we expect to be able to be associated with an element, it does, in theory,
* make sense to assign a repValueSet to an entire choiceGroup. For instance, in the common case of choiceBranchKeyKind=byType and choiceDispatchKeyKind=byType,
* this would just be the union of the repValueSets of all of the individual choices.
* However, figuring this out becomes more complicated when we start having to deal with choiceBranchKeyKind=explicit
* There is no indication yet that this problem is particuarly intractable, however it is non-trivial. Since we never
* actually need to know the optRepValueSet of an entire choiceGroup, we can simple avoid thinking about it until such a time that we do.
*/
override final lazy val optRepValueSet = Assert.invariantFailed("We shouldn't need to compute the optRepValueSet of a choiceGroup")
final override lazy val hasKnownRequiredSyntax = LV('hasKnownRequiredSyntax) {
if (hasFraming) true
// else if (isKnownToBeAligned) true //TODO: Alignment may not occur; hence, cannot be part of determining whether there is known syntax.
else {
val res =
// For a group, we only have known required syntax if all branches do.
// If even one branch doesn't, then we don't have "known required" because the data could match
// that branch, meaning we wouldn't, in that case, have syntax.
groupMembers.forall { member =>
val res =
member.hasKnownRequiredSyntax
res
}
res
}
}.value
final lazy val noBranchesFound = LV('noBranchesFound) {
if (groupMembers.size == 0) {
SDE("choice element must contain one or more branches")
}
}.value
/**
* The DFDL Spec as of this writing 2015-06-02 says that branches cannot be optional, but then
* specifies that what it means is that if a choice branch root is an element/element-ref, it
* cannot have minOccurs="0". This is a different notion of optional.
*
* The DFDL Working group has Action 280 which is about whether this should be corrected/modified.
* There are two notions of optionality in DFDL. First is optional meaning 0 or 1 occurrence of an
* element. The second is "not required", and that is defined in terms of dfdl:occursCountKind.
*
* For the time being, the definition of non-optional used here is that is not minOccurs="0" if an
* element, and that unless it is a calculated element, it must have some footprint syntactically
* from the data value or framing.
*
* Open issues:
* 1) Is alignment or leading/trailing skip to be considered syntax. Alignment might not be there.
* 2) What about an empty sequence that only carries statement annotations such as dfdl:assert or
* dfdl:setVariable
*
* This latter need to be allowed, because while they do not have known required syntax they do
* have to be executed for side-effect.
*/
final lazy val branchesAreNonOptional = LV('branchesAreNonOptional) {
val branchesOk = groupMembers map { branch =>
val realBranch = branch match {
case impliedSeq: ChoiceBranchImpliedSequence => impliedSeq.groupMembers(0)
case regular => regular
}
if (realBranch.isOptional) {
schemaDefinitionErrorButContinue("Branch of choice %s must be non-optional.".format(realBranch.path))
false
} else true
}
assuming(branchesOk.forall { x => x })
}.value
final lazy val branchesAreNotIVCElements = LV('branchesAreNotIVCElements) {
val branchesOk = groupMembers map { branch =>
if (!branch.isRepresented) {
schemaDefinitionErrorButContinue("Branch of choice %s cannot have the dfdl:inputValueCalc property.".format(branch.path))
false
} else true
}
assuming(branchesOk.forall { x => x })
}.value
}
object Choice {
def apply(xmlArg: Node, lexicalParent: SchemaComponent, position: Int) = {
val c = new Choice(xmlArg, lexicalParent, position)
c.initialize()
c
}
}
final class Choice private (xmlArg: Node, lexicalParent: SchemaComponent, position: Int)
extends ChoiceTermBase(xmlArg, Option(lexicalParent), position)
with ChoiceDefMixin
with ChoiceView {
override lazy val optReferredToComponent = None
override protected def computeGroupMembers(): Seq[Term] = {
val firstCut = super.computeGroupMembers()
val withImpliedSequences =
firstCut.map { term =>
val finalTerm =
if (term.isScalar) term
else {
/**
* If this choice branch is a non-scalar, then we need to encapsulate
* it with a ChoiceBranchImpliedSequence, which is a kind of Sequence
* base. When compiling this this choice branch, Daffodil can then
* depend on the invariant that every recurring element is contained
* inside a sequence, and that sequence describes everything about how
* that elements occurrences are separated.
*/
ChoiceBranchImpliedSequence(term)
}
finalTerm
}
withImpliedSequences
}
}