blob: 8f521969c4380897aea1c3ac38a495258738348b [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 scala.xml.Node
import org.apache.daffodil.exceptions.Assert
import org.apache.daffodil.ExecutionMode
import org.apache.daffodil.xml.XMLUtils
import org.apache.daffodil.xml.NS
import org.apache.daffodil.equality._
import org.apache.daffodil.schema.annotation.props.PropertyLookupResult
import org.apache.daffodil.schema.annotation.props.NotFound
import org.apache.daffodil.schema.annotation.props.Found
import org.apache.daffodil.schema.annotation.props.FindPropertyMixin
/**
* Only objects from which we generate processors (parsers/unparsers)
* can lookup property values.
*
* This avoids the possibility of a property being resolved incorrectly by
* not looking at the complete chain of schema components contributing to the
* property resolution.
*
* The only objects that should resolve properties are
* ElementRef, Root, LocalElementDecl, Sequence, Choice, SequenceRef, ChoiceRef
*
* These are all the "real" terms. Everything else is just contributing
* properties to the mix, but they are not points where properties are
* used to generate processors.
*/
trait ResolvesProperties
extends FindPropertyMixin { self: AnnotatedSchemaComponent =>
/** Returns Term corresponding to this object. */
def term: Term
private def findNonDefaultProperty(pname: String): PropertyLookupResult = {
val result = findDefaultOrNonDefaultProperty(pname, nonDefaultPropertySources)
result match {
case f: Found => f
case NotFound(nd, d, _) =>
Assert.invariant(d.isEmpty)
}
result
}
private def findDefaultProperty(pname: String): PropertyLookupResult = {
val result = findDefaultOrNonDefaultProperty(pname, defaultPropertySources)
val fixup = result match {
case Found(value, loc, pname, _) =>
// found as a default property.
// supply constructor's last arg is boolean indicating it's a default property
Found(value, loc, pname, true)
case NotFound(nd, d, pn) =>
Assert.invariant(d.isEmpty)
NotFound(Seq(), nd, pn) // we want the places we searched shown as default locations searched
}
fixup
}
/**
* Does lookup of property using DFDL scoping rules, checking first non-default
* properties, then default property locations.
*/
override def findPropertyOption(pname: String): PropertyLookupResult = {
ExecutionMode.requireCompilerMode
// first try in regular properties
val regularResult = resolver.findNonDefaultProperty(pname)
regularResult match {
case f: Found => f
case NotFound(nonDefaultLocsTried1, defaultLocsTried1, _) => {
Assert.invariant(defaultLocsTried1.isEmpty)
val defaultResult = resolver.findDefaultProperty(pname)
defaultResult match {
case f: Found => f
case NotFound(nonDefaultLocsTried2, defaultLocsTried2, _) => {
Assert.invariant(nonDefaultLocsTried2.isEmpty)
// did not find it at all. Return a NotFound with all the places we
// looked non-default and default.
val nonDefaultPlaces = nonDefaultLocsTried1
val defaultPlaces = defaultLocsTried2
NotFound(nonDefaultPlaces, defaultPlaces, pname)
}
}
}
}
}
}
/** Convenience class for implemening AnnotatedSchemaComponent trait */
abstract class AnnotatedSchemaComponentImpl( final override val xml: Node,
final override val parent: SchemaComponent)
extends AnnotatedSchemaComponent
/**
* Shared characteristics of any annotated schema component.
*
* Not all components can carry DFDL annotations.
*/
trait AnnotatedSchemaComponent
extends SchemaComponent
with AnnotatedMixin
with OverlapCheckMixin {
requiredEvaluations(annotationObjs)
requiredEvaluations(nonDefaultPropertySources)
requiredEvaluations(defaultPropertySources)
/** Returns the Term corresponding to this component. */
final lazy val term: Term = this match {
case gr: GroupRef => gr.asModelGroup
// case mg: ModelGroup => mg.parent match {
// case ggd: GlobalGroupDef => {
// // this is the model group that is the definition of
// // a global group, so our term is the group reference referring to this.
// ggd.groupRef.asModelGroup
// }
// case _ => mg
// }
case t: Term => t
case ged: GlobalElementDecl => ged.elementRef
case ty: SimpleTypeDefBase => ty.elementBase
case ty: ComplexTypeBase => ty.elementBase
case ggd: GlobalGroupDef => ggd.groupRef.asModelGroup
case sd: SchemaDocument =>
Assert.usageError("not to be called for schema documents")
}
/** Returns the property resolver for this component. */
final lazy val resolver: ResolvesProperties = {
val res = this match {
case sd: SchemaDocument => sd
case _ => term
}
res
}
/**
* Since validation of extra attributes on XML Schema elements is
* normally lax validation, we can't count on validation of DFDL schemas
* to tell us whether short-form annotations are correct or not.
*
* So, we have to do this check ourselves.
*
* TBD: change properties code generator to output the various lists of
* properties that we have to check against. (Might already be there...?)
*
*/
// TODO: Implement this - DFDL-598, DFDL-1512
// private def areShortFormAnnotationsValid: Boolean = true
/**
* For property combining only. E.g., doesn't refer from an element
* to its complex type because we don't combine properties with that
* in DFDL v1.0. (I consider that a language design bug in DFDL v1.0, but
* that is the way it's defined.)
*/
final protected def refersToForPropertyCombining: Option[AnnotatedSchemaComponent] = optReferredToComponent
protected def optReferredToComponent: Option[AnnotatedSchemaComponent] // override in ref objects
final protected lazy val nonDefaultPropertySources: Seq[ChainPropProvider] = LV('nonDefaultPropertySources) {
this match {
case sd: SchemaDocument => Nil
case _ => {
val refTo = refersToForPropertyCombining
val chainFromReferredTo = refTo.map { c =>
val ndps = c.nonDefaultPropertySources
ndps
}.toSeq.flatten
val myNDFC = nonDefaultFormatChain
val completeNonDefaultFormatChain =
myNDFC +: chainFromReferredTo
val seq = completeNonDefaultFormatChain.distinct
checkNonOverlap(seq)
seq
}
}
}.value
final protected lazy val defaultPropertySources: Seq[ChainPropProvider] = LV('defaultPropertySources) {
val refTo = refersToForPropertyCombining
val chainFromReferredTo = refTo.toSeq.map { _.defaultPropertySources }.distinct.flatten
val completeDefaultFormatChain =
defaultFormatChain +: chainFromReferredTo
val seq = completeDefaultFormatChain.distinct
seq
}.value
final protected lazy val nonDefaultFormatChain: ChainPropProvider = {
val fa = formatAnnotation
val fc = fa.formatChain
fc
}
final protected lazy val defaultFormatChain: ChainPropProvider = {
val res = schemaDocument.formatAnnotation.formatChain
res
}
protected final def findDefaultOrNonDefaultProperty(
pname: String,
sources: Seq[ChainPropProvider]): PropertyLookupResult = {
//
// Important - use of stream here insures we don't lookup
// properties down the chain once we have them here.
//
val str = sources.toStream.map { _.chainFindProperty(pname) }
val optFound = str.collectFirst { case found: Found => found }
val result = optFound match {
case Some(f: Found) => f
case None => {
val seq = str.toSeq
// merge all the NotFound stuff.
val nonDefaults = seq.flatMap {
case NotFound(nd, d, _) => nd
case _: Found => Assert.invariantFailed()
}
val defaults = seq.flatMap {
case NotFound(nd, d, _) => d
case _: Found => Assert.invariantFailed()
}
Assert.invariant(defaults.isEmpty)
val nf = NotFound(nonDefaults, defaults, pname)
nf
}
}
result
}
/**
* Use this when you want to know if a property is defined exactly on a
* component. This ignores any default properties or properties defined on
* element references. For example, if you want to know if a property was
* defined on a global element decl rather than an element reference to that
* decl.
*/
final def findPropertyOptionThisComponentOnly(pname: String): PropertyLookupResult = {
val result = findDefaultOrNonDefaultProperty(pname, Seq(nonDefaultFormatChain))
result
}
}
/**
* Every component that can be annotated.
* Review Note:
* It's no longer clear that this separation is strictly speaking needed.
* It's possible that this could be collapsed back into AnnotatedSchemaComponent
* or made smaller anyway.
*
*/
trait AnnotatedMixin
extends EscapeSchemeRefMixin { self: AnnotatedSchemaComponent =>
/**
* Anything annotated must be able to construct the
* appropriate DFDLAnnotation object from the xml.
*/
protected def annotationFactory(node: Node): Option[DFDLAnnotation]
private lazy val annotationNode = {
val ann = xml \ "annotation"
ann
}
lazy val dfdlAppInfos = {
val ais = (annotationNode \ "appinfo")
val dais = ais.filter { ai =>
{
ai.attribute("source") match {
case None => {
this.SDW("""xs:appinfo without source attribute. Is source="http://www.ogf.org/dfdl/" missing?""")
false
}
case Some(n) => {
val sourceNS = NS(n.text)
//
// Keep in mind. As the DFDL standard evolves, and new versions
// come out, this code may change to tolerate different source
// attributes that call out distinct versions of the standard.
//
val officialAppinfoSourceAttributeNS = XMLUtils.dfdlAppinfoSource
//
// Note: use of the strongly typed =:= operator below.
//
// I got sick of mysterious behavior where turns out we are
// comparing two things of different types.
//
// This fixes a bug where we were comparing a string to a NS
// and getting false, where the types should have been the same.
//
val hasRightSource = (sourceNS =:= officialAppinfoSourceAttributeNS)
val isAcceptable = sourceNS.toString.contains("ogf") && sourceNS.toString.contains("dfdl")
schemaDefinitionWarningWhen(!hasRightSource && isAcceptable,
"The xs:appinfo source attribute value '%s' should be '%s'.", sourceNS, officialAppinfoSourceAttributeNS)
(hasRightSource || isAcceptable)
}
}
}
}
dais
}
/**
* The DFDL annotations on the component, as objects
* that are subtypes of DFDLAnnotation.
*/
final lazy val annotationObjs = {
val objs = dfdlAppInfos.flatMap { dai =>
{
val children = dai.child
val res = children.filter { _.isInstanceOf[scala.xml.Elem] }.flatMap { child =>
{
annotationFactory(child)
}
}
res
}
}
objs
}
/**
* Here we establish an invariant which is that every annotatable schema component has, definitely, has an
* annotation object. It may have no properties on it, but it will be there. Hence, we can
* delegate various property-related attribute calculations to it.
*
* To realize this, every concrete class must implement (or inherit) an implementation of
* emptyFormatFactory, which constructs an empty format annotation,
* and isMyFormatAnnotation which tests if an annotation is the corresponding kind.
*
* Given that, formatAnnotation then either finds the right annotation, or constructs one, but our invariant
* is imposed. There *is* a formatAnnotation.
*/
protected def emptyFormatFactory: DFDLFormatAnnotation
protected def isMyFormatAnnotation(a: DFDLAnnotation): Boolean
final lazy val formatAnnotation = LV('formatAnnotation) {
val format = annotationObjs.collect { case fa: DFDLFormatAnnotation if isMyFormatAnnotation(fa) => fa }
val res = format match {
case Seq() => emptyFormatFactory // does make things with the right namespace scopes attached!
case Seq(fa) => fa
case _ => schemaDefinitionError("Only one format annotation is allowed at each annotation point.")
}
res
}.value
}