| /* |
| * 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 |
| |
| } |