blob: a8f78075569b9ec9dcbc9d172163513d538fd97c [file] [log] [blame]
/* Copyright (c) 2012-2015 Tresys Technology, LLC. All rights reserved.
*
* Developed by: Tresys Technology, LLC
* http://www.tresys.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal with
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimers.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimers in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the names of Tresys Technology, nor the names of its contributors
* may be used to endorse or promote products derived from this Software
* without specific prior written permission.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
* SOFTWARE.
*/
package edu.illinois.ncsa.daffodil.dsom
import edu.illinois.ncsa.daffodil.api.LocationInSchemaFile
/**
* This file is classes and traits to implement
* property lookups, diagnostics about them, and the
* lexical scoping of properties from the schema-document level.
*
* The ultimate data structure is two sequences, one for nonDefault,
* one for default. Each such sequence is a sequence of ChainRropProviders.
* Each of those is a sequence of LeafPropProviders
*/
/**
* Result of searching for a property.
*
* Includes location information so you can issue a diagnostic
* about where a conflicting property
* definition was found, or all the places where one searched
* but did not find a property definition.
*
* One must also have the location object because it must
* be used if the property value contains a QName (or is an expression
* containing QNames). That is, you can't interpret a QName without
* the scope from the XML where it was written.
*/
sealed abstract class PropertyLookupResult extends Serializable
case class Found(value: String, location: LookupLocation) extends PropertyLookupResult
case class NotFound(localWhereLooked: Seq[LookupLocation], defaultWhereLooked: Seq[LookupLocation])
extends PropertyLookupResult
/**
* A lookup location is where we indicate a property binding
* resides. This can be an annotation object (ex: a dfdl:sequence or a dfdl:format)
* or in the case of short-form properties it could be the
* annotated schema component itself.
*
* The point is to get the file and line number information right
* so we point the user at the right place.
*
* It also resolves QNames in the right way. So given a property value that
* contains a QName, the associated LookupLocation can be used to resolve that
* QName to a namespace and a local name.
*/
trait LookupLocation
extends ResolvesQNames with LocationInSchemaFile
trait PropTypes {
/**
* type of a map entry which maps a property name as key, to a property value,
* and along side it is a LookupLocation object telling us where we found that
* property value.
*
* The property maps/lists in DFDLFormatAnnotation have been enhanced to have
* this LookupLocation thing along their side to allow issuing
* better diagnostic messages about conflicting property definitions
*/
type PropItem = (String, (String, LookupLocation))
type PropMap = Map[String, (String, LookupLocation)]
val emptyPropMap = Map.empty.asInstanceOf[PropMap]
}
trait FindPropertyMixin extends PropTypes {
/**
* Implemented by users of the mixin so that we can
* report a property not found error.
*/
def SDE(str: String, args: Any*): Nothing
/**
* Implemented in various ways by users of the mixin.
*/
def findPropertyOption(pname: String): PropertyLookupResult
/**
* Call this to find/get a property.
*
* Property values are non-optional in DFDL. If they're not
* there but a format requires them, then it's always an error.
*
* Note also that DFDL doesn't have default values for properties. That means
* that most use of properties is unconditional. Get the property value, and
* it must be there, or its an error. There are very few exceptions to this
* rule.
*/
final def findProperty(pname: String): Found = {
val prop = findPropertyOption(pname)
val res = prop match {
case f: Found => f
//
// TODO: Internationalization - should not be assembling error messages in English like this.
// All this has to be delegated to a layer that uses the english string as a key to find
// the translation.
//
// Hence, we need a way to explicitly get the possibly translated version of a
// literal english string when that string is not the direct argument of a SDE call.
//
case nf: NotFound => requiredButNotFound(pname, nf)
}
res
}
private def requiredButNotFound(pname: String, nf: NotFound) = {
val NotFound(nonDefaultLocs, defaultLocs) = nf
val ndListText = nonDefaultLocs.map { _.locationDescription }.mkString("\n")
val dListText = defaultLocs.map { _.locationDescription }.mkString("\n")
val nonDefDescription =
if (nonDefaultLocs.length > 0)
"\nNon-default properties were combined from these locations:\n" + ndListText + "\n"
else ""
val defLocsDescription =
if (defaultLocs.length > 0)
"\nDefault properties were taken from these locations:\n" + dListText + "\n"
else "\nThere were no default properties.\n"
SDE("Property %s is not defined.%s%s", pname, nonDefDescription, defLocsDescription)
}
/**
* It is ok to use getProperty if the resulting property value cannot ever contain
* a QName that would have to be resolved.
*/
final def getProperty(pname: String): String = {
val Found(res, _) = findProperty(pname)
res
}
/**
* Don't use this much. If an SDE needs to be reported, you won't have
* the source of the property to put into the message. Use findPropertyOption
* instead. That returns the value and the LookupLocation where it was
* found for use in diagnostics.
*
* Also, don't use if the property value could ever contain a QName, because
* one needs that LookupLocation to resolve QNames properly.
*
* Note: any expression can contain QNames, so no expression-valued property or
* one that could be a value or an expression, should ever use this or getProperty.
*
* See JIRA DFDL-506.
*/
final def getPropertyOption(pname: String): Option[String] = {
val lookupRes = findPropertyOption(pname)
val res = lookupRes match {
case Found(v, _) => Some(v)
case _ => None
}
res
}
/**
* For unit testing convenience
*/
final def verifyPropValue(key: String, value: String): Boolean = {
findPropertyOption(key) match {
case Found(`value`, _) => true
case Found(_, _) => false
case NotFound(_, _) => false
}
}
private val propCache = new scala.collection.mutable.LinkedHashMap[String, PropertyLookupResult]
protected final def cachePropertyOption(name: String): PropertyLookupResult = {
val propCacheResult = propCache.get(name)
val propRes =
propCacheResult match {
case Some(res) => res
case None => {
val lr = findPropertyOption(name)
propCache.put(name, lr)
lr
}
}
propRes
}
protected final def cacheProperty(name: String): Found = {
val propCacheResult = cachePropertyOption(name)
propCacheResult match {
case f: Found => f
case nf: NotFound => requiredButNotFound(name, nf)
}
}
protected final def cacheGetPropertyOption(name: String): Option[String] = {
val pOpt = cachePropertyOption(name)
pOpt match {
case Found(v, l) => Some(v)
case _ => None
}
}
}