blob: 0559d984b45f55ee0c1b5919ab7687bb79574845 [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.util._
import org.apache.daffodil.util.Logging
import org.apache.daffodil.schema.annotation.props.LookupLocation
import org.apache.daffodil.schema.annotation.props.PropertyLookupResult
import org.apache.daffodil.schema.annotation.props.PropTypes
import org.apache.daffodil.schema.annotation.props.NotFound
import org.apache.daffodil.schema.annotation.props.Found
/**
* Property lookup uses ChainPropProviders containing LeafPropProviders.
*
* Each ChainPropProvider is at its core a Seq[LeafPropProvider] created
* by following the dfdl:ref attribute 'chain' of a DFDLFormatAnnotation from
* one format annotation to another by way of a named define format.
*
* A LeafPropProvider corresponds directly to a DFDLFormatAnnotation. It
* can supply properties from the short, long, and element form property bindings
* found on that object alone. It insures they are non-overlapping.
*
* Ultimately, a schema component will have two sequences of ChainPropProviders.
* One for non-default properties (these must be tested to be sure they are
* non-overlapping), and one for default properties. Those are assembed by
* each schema component in a component-specific manner.
*/
/**
* A single annotation which combines short form, long form,
* and element form property bindings together.
*
* From this perspective, there are no ref chains connecting
* format annotations together.
*/
trait LeafPropProvider
extends LookupLocation with PropTypes with Logging {
/**
* for debug/test only
*/
final lazy val properties: PropMap = justThisOneProperties
//TODO: optimize by having this object actually check if there
// are any property bindings. if not, this should get removed
// from any Seq[LeafPropProvider] it is put into.
def diagnosticDebugName: String
/**
* properties for just this object, but they can be
* short, long, or element forms.
*/
def justThisOneProperties: PropMap
/**
* Just the properties as string pairs, without any location information.
*
* Intended for use comparing to see if two prop providers are equivalent
* in terms of providing the same property values.
*/
final lazy val justThisOnePropertyPairsSet = justThisOneProperties.map { case (s1, (s2, _)) => (s1, s2) }.toSet
final def leafFindProperty(pname: String): PropertyLookupResult = {
val mine = justThisOneProperties.get(pname)
val res = mine match {
case Some((value, loc)) => {
//
// One downside of the ConstructingParser for XML - we lose the
// schema-aware processing that was trimming off whitespace
// from properties/attributes declared as xs:tokens automatically.
//
// Instead we have to implement our own trim logic on a
// case by case basis. (which means not here)
// Ex: textNumberPattern spaces are significant and important to preserve
// AND tend to be leading or trailing in the string value. So if anyone
// does string.trim on that property value, they will be clobbering it.
//
Found(value, loc, pname, false)
}
case None => NotFound(List(this), Nil, pname)
}
res
}
}
/**
* flat chain of format annotations connected by dfdl:ref (short form)
* or ref (long form) references to named defined formats.
*
* Could be the nonDefault formats being chained together, or could
* be the default formats being chained together.
*/
final class ChainPropProvider(leafProvidersArg: Seq[LeafPropProvider], forAnnotation: String)
extends Logging with PropTypes {
override def toString() = Misc.getNameFromClass(this) + "(" + forAnnotation + ")"
/**
* This is a sequence of sets basically for debug/maintenance reasons.
* Conceptually it could be flattened to just a set, but this way the contents of each set
* may be visibly meaningful when debugging, as the first set comes from the
* first leaf provider, second from the second (which comes from first dfdl:ref
* reference), etc.
*/
final lazy val propertyPairsSets: Seq[Set[(String, String)]] =
leafProviders.map { _.justThisOnePropertyPairsSet }
/**
* for debug/test only
*/
final lazy val properties: PropMap = leafProviders.flatMap { _.properties.toSeq }.toMap
final lazy val leafProviders = leafProvidersArg
final def chainFindProperty(pname: String): PropertyLookupResult = {
lookupPropertyInSources(leafProviders, pname)
}
/*
* The Override algorithm - first property source with a hit wins.
*/
private def lookupPropertyInSources(sources: Seq[LeafPropProvider], pname: String): PropertyLookupResult = {
val allNotFound =
for { source <- sources } yield {
val res = source.leafFindProperty(pname)
res match {
case _: Found => return res //found it! return right now.
case nf @ NotFound(_, _, _) => nf
}
}
// didn't find it. Compile complete list of everywhere we looked.
val allLocalPlacesSearched = allNotFound.flatMap { _.localWhereLooked }.toSeq
NotFound(allLocalPlacesSearched, Seq(), pname)
}
}
// TODO: Check for circularity if schema validation doesn't.
// That is, we check for circularity in the ref chain from a
// format annotation to a defineFormat.
//
// What we may need to check is for recursive circularity of
// the chains from say, simpleTypes to their bases.
//
// There are other funny circularities possible also. E.g,
// a sequence contains a group ref, to a group def, which contains
// the original sequence.
// Element refs and elements can set up the same sort of
// nesting cycle.
// A ComplexType can contain an element which has that same
// ComplexType.
trait OverlapCheckMixin {
def schemaDefinitionErrorButContinue(str: String, args: Any*): Unit
/**
* check for overlap.
*/
protected final def checkNonOverlap(providers: Seq[ChainPropProvider]) = {
/**
* given a list, take the first, make sure it is non-overlapping with
* each of the rest.
*
* So if we have the whole list, that tells us the first doesn't overlap with
* any.
*
* Then we need the first list tail, and checking that will tell us that the
* second does not overlap with any of the rest.
*
* And so on for second list tail to check third against rest,
*
* So to do this, we need to process the whole list and each of its tails
* I.e, given a list x, we want List(x, x.tail, x.tail.tail, ....)
*
*/
def allTails[T](list: List[T]): List[List[T]] = {
list match {
case Nil => Nil
case _ :: r => list :: allTails(r)
}
}
// Detail: let's reverse the list. So it will detect overlaps deep in the
// SimpleTypeDef base chains first, then elementDecl to SimpleTypeDef chain, then
// finally ElementRef to ElementDecl and SimpleTypeDef chain.
val tails = allTails(providers.toList).reverse
tails.foreach { checkNonOverlap1(_) }
}
/**
* Check first of the list for overlap against each of the 2nd through last
*/
private def checkNonOverlap1(list: List[ChainPropProvider]): Unit = {
list match {
case Nil => // ok
case List(_) => // ok. One thing can't overlap with anything.
case fCPP :: rList => {
checkNonOverlap2(fCPP, rList)
checkNonOverlap1(rList)
}
}
}
/**
* For each in the b argument list, check that the a arg doesn't overlap
* with it.
*/
private def checkNonOverlap2(a: ChainPropProvider, b: List[ChainPropProvider]) = {
b.foreach { bCPP => checkNonOverlap3(a, bCPP) }
}
/**
* iterate through all properties of the a-arg, checking each for
* whether it appears in the b-arg. If so, SDE.
*/
private def checkNonOverlap3(a: ChainPropProvider, b: ChainPropProvider): Unit = {
val aLeaves = a.leafProviders
aLeaves.foreach { aLeaf =>
val propMap = aLeaf.justThisOneProperties
propMap.foreach {
case (propName, (_, aLoc)) =>
b.chainFindProperty(propName) match {
case _: NotFound => // ok
case Found(_, bLoc, _, _) => {
schemaDefinitionErrorButContinue(
"Overlapping properties: %1$s overlaps between %2$s and %3$s. Overlap is not allowed.",
propName, aLoc, bLoc)
}
}
}
}
}
}