blob: 473f26c72ac9fd117944aa4e2e3f5b7d9d082f42 [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 java.math.BigInteger
import scala.xml.Node
import org.apache.daffodil.exceptions.Assert
import org.apache.daffodil.dpath.NodeInfo.PrimType
import org.apache.daffodil.xml.XMLUtils
trait Facets { self: Restriction =>
import org.apache.daffodil.dsom.FacetTypes._
private def retrieveFacetValueFromRestrictionBase(xml: Node, facetName: Facet.Type): String = {
val res = xml \\ "restriction" \ facetName.toString() \ "@value"
if (res.length > 0) res.head.text else ""
}
private def retrieveFacetValuesFromRestrictionBase(xml: Node, facetName: Facet.Type): Seq[String] = {
val res = xml \\ "restriction" \ facetName.toString() \\ "@value"
val ret = if (res.length > 0) res.map(n => n.text).toList else List.empty
ret
}
private def enumeration(xml: Node): Seq[String] = { retrieveFacetValuesFromRestrictionBase(xml, Facet.enumeration) }
private def fractionDigits(xml: Node): String = { retrieveFacetValueFromRestrictionBase(xml, Facet.fractionDigits) }
private def maxExclusive(xml: Node): String = { retrieveFacetValueFromRestrictionBase(xml, Facet.maxExclusive) }
private def maxInclusive(xml: Node): String = { retrieveFacetValueFromRestrictionBase(xml, Facet.maxInclusive) }
private def maxLength(xml: Node): String = { retrieveFacetValueFromRestrictionBase(xml, Facet.maxLength) }
private def minExclusive(xml: Node): String = { retrieveFacetValueFromRestrictionBase(xml, Facet.minExclusive) }
private def minInclusive(xml: Node): String = { retrieveFacetValueFromRestrictionBase(xml, Facet.minInclusive) }
private def minLength(xml: Node): String = { retrieveFacetValueFromRestrictionBase(xml, Facet.minLength) }
private def pattern(xml: Node): Seq[String] = {
// Patterns are OR'd locally, AND'd remotely
val res = retrieveFacetValuesFromRestrictionBase(xml, Facet.pattern).map(p => p)
res
}
private def totalDigits(xml: Node): String = { retrieveFacetValueFromRestrictionBase(xml, Facet.totalDigits) }
private def whitespace(xml: Node): String = { retrieveFacetValueFromRestrictionBase(xml, Facet.whiteSpace) }
final lazy val localPatternValue: String = {
// Patterns within a type are OR'd
// per http://www.xfront.com/XML-Schema-library/papers/Algorithm-for-Merging-a-simpleType-Dependency-Chain.pdf
//
// Assumed to be valid RegEx
val patterns = pattern(xml)
patterns.mkString("|")
}
final lazy val localMinInclusiveValue: String = minInclusive(xml)
final lazy val localMaxInclusiveValue: String = maxInclusive(xml)
final lazy val localMinExclusiveValue: String = minExclusive(xml)
final lazy val localMaxExclusiveValue: String = maxExclusive(xml)
final lazy val localMinLengthValue: String = minLength(xml)
final lazy val localMaxLengthValue: String = maxLength(xml)
final lazy val localTotalDigitsValue: String = totalDigits(xml)
final lazy val localFractionDigitsValue: String = fractionDigits(xml)
final lazy val localEnumerationValue: String = {
// Enumerations are OR'd
// May be empty string
// Must be unique
val enumerations = enumeration(xml)
val distinctEnums = enumerations.distinct
if (enumerations.size != distinctEnums.size) SDE("Enumerations must be unique!")
// Not a regular expression, but we plan to use it as one
// so we must escape characters that can be interpreted as RegEx
enumerations.map(s => escapeForRegex(s)).mkString("|")
}
final lazy val localWhitespaceValue: String = {
whitespace(xml)
SDE("whitespaceValue is not implemented for DFDL v1.0 schemas but reserved for future use.")
}
private def escapeForRegex(s: String): String = {
val sb = new StringBuilder
s.foreach(c => {
c match {
case '[' => sb.append("\\[")
case '\\' => sb.append("\\\\")
case '^' => sb.append("\\^")
case '$' => sb.append("\\$")
case '.' => sb.append("\\.")
case '|' => sb.append("\\|")
case '?' => sb.append("\\?")
case '*' => sb.append("\\*")
case '+' => sb.append("\\+")
case '(' => sb.append("\\(")
case ')' => sb.append("\\)")
case '{' => sb.append("\\{")
case '}' => sb.append("\\}")
case x => sb.append(x)
}
})
sb.toString()
}
final lazy val hasEnumeration: Boolean = (localEnumerationValue.length > 0) || (getRemoteFacetValues(Facet.enumeration).size > 0)
final lazy val hasPattern: Boolean = (localPatternValue.length > 0) || (getRemoteFacetValues(Facet.pattern).size > 0)
final lazy val hasMinLength: Boolean = (localMinLengthValue != "") || (getRemoteFacetValues(Facet.minLength).size > 0)
final lazy val hasMaxLength: Boolean = (localMaxLengthValue != "") || (getRemoteFacetValues(Facet.maxLength).size > 0)
final lazy val hasMinInclusive: Boolean = (localMinInclusiveValue != "") || (getRemoteFacetValues(Facet.minInclusive).size > 0)
final lazy val hasMaxInclusive: Boolean = (localMaxInclusiveValue != "") || (getRemoteFacetValues(Facet.maxInclusive).size > 0)
final lazy val hasMinExclusive: Boolean = (localMinExclusiveValue != "") || (getRemoteFacetValues(Facet.minExclusive).size > 0)
final lazy val hasMaxExclusive: Boolean = (localMaxExclusiveValue != "") || (getRemoteFacetValues(Facet.maxExclusive).size > 0)
final lazy val hasTotalDigits: Boolean = (localTotalDigitsValue != "") || (getRemoteFacetValues(Facet.totalDigits).size > 0)
final lazy val hasFractionDigits: Boolean = (localFractionDigitsValue != "") || (getRemoteFacetValues(Facet.fractionDigits).size > 0)
final lazy val patternValues: Seq[FacetValueR] = {
val values: Seq[(Facet.Type, Values)] = combinedBaseFacets.filter { case (f, _) => f == Facet.pattern }
if (values.size > 0) {
val res: Seq[FacetValueR] = values.map {
case (f, v: String) => {
//
// The DFDL Infoset can contain strings which hold characters
// that are not allowed in XML at all.
//
// In order to talk about these characters in a XSD pattern facet
// we use a remapping of such characters into the Unicode
// Private Use Area, so as to have XML-legal characters.
//
// See the section titled "XML Illegal Characters" on this web page:
// https://daffodil.apache.org/infoset/
//
// Before processing a regex of these characters in Daffodil's pattern facet
// validation, we must remap these PUA characters back to the originally
// intended code points, since that's what the Infoset strings will contain.
//
// Consider the character code 0xB. This is illegal in XML v1.0 documents.
// A DFDL Schema is an XML Schema, which is an XML document; hence, we cannot
// use the character with code 0xB directly, nor can we use an XML numeric
// character entity like  for it. The character is simply disallowed in
// XML, including DFDL schemas. Hence, we mention 0xB by using a remapping of it
// to the PUA area character 0xE00B, which we express by 
// Hence a pattern regex like "[ 0-9a-zA-Z]" will match
// the character with char code 0xB (remapped from E00B), as well as spaces,
// and alphanumeric characters.
//
// The XSD numeric character entity  can be used to match ASCII NUL
// (char code 0).
//
val remapped: String = XMLUtils.remapPUAToXMLIllegalCharacters(v)
(f, remapped.r)
}
}
res
} else
Seq.empty
}
final lazy val enumerationValues: Option[String] = {
// Should only ever have one set per SimpleType
val values = combinedBaseFacets.filter { case (f, _) => f == Facet.enumeration }
if (values.size > 0) {
val (_, value) = values(0)
Some(value)
} else None
}
// TODO: Tidy up. Can likely replace getFacetValue with a similar call to combinedBaseFacets
// as combinedBaseFacets should contain the 'narrowed' values.
//
final lazy val minLengthValue: java.math.BigDecimal = getFacetValue(localMinLengthValue, Facet.minLength, hasMinLength)
final lazy val maxLengthValue: java.math.BigDecimal = getFacetValue(localMaxLengthValue, Facet.maxLength, hasMaxLength)
final lazy val minInclusiveValue: java.math.BigDecimal = getFacetValue(localMinInclusiveValue, Facet.minInclusive, hasMinInclusive)
final lazy val maxInclusiveValue: java.math.BigDecimal = getFacetValue(localMaxInclusiveValue, Facet.maxInclusive, hasMaxInclusive)
final lazy val minExclusiveValue: java.math.BigDecimal = getFacetValue(localMinExclusiveValue, Facet.minExclusive, hasMinExclusive)
final lazy val maxExclusiveValue: java.math.BigDecimal = getFacetValue(localMaxExclusiveValue, Facet.maxExclusive, hasMaxExclusive)
final lazy val totalDigitsValue: java.math.BigDecimal = getFacetValue(localTotalDigitsValue, Facet.totalDigits, hasTotalDigits)
final lazy val fractionDigitsValue: java.math.BigDecimal = getFacetValue(localFractionDigitsValue, Facet.fractionDigits, hasFractionDigits)
// private def errorOnLocalLessThanBaseFacet(local: Long, base: Long, theFacetType: Facet.Type) = {
// if (local < base) SDE("SimpleTypes: The local %s (%s) was less than the base %s (%s) ", theFacetType, local, theFacetType, base)
// }
// private def errorOnLocalGreaterThanBaseFacet(local: Long, base: Long, theFacetType: Facet.Type) = {
// if (local > base) SDE("SimpleTypes: The local %s (%s) was greater than the base %s (%s) ", theFacetType, local, theFacetType, base)
// }
private def errorOnLocalLessThanBaseFacet(
local: BigInteger,
base: BigInteger, theFacetType: Facet.Type) = {
val res = local.compareTo(base)
if (res < 0) SDE(
"SimpleTypes: The local %s (%s) was less than the base %s (%s) ",
theFacetType, local, theFacetType, base)
}
private def errorOnLocalGreaterThanBaseFacet(
local: BigInteger,
base: BigInteger, theFacetType: Facet.Type) = {
val res = local.compareTo(base)
if (res > 0) SDE(
"SimpleTypes: The local %s (%s) was greater than the base %s (%s) ",
theFacetType, local, theFacetType, base)
}
private def errorOnLocalLessThanBaseFacet(
local: java.math.BigDecimal,
base: java.math.BigDecimal, theFacetType: Facet.Type) = {
val res = local.compareTo(base)
if (res < 0) SDE(
"SimpleTypes: The local %s (%s) was less than the base %s (%s) ",
theFacetType, local, theFacetType, base)
}
private def errorOnLocalGreaterThanBaseFacet(
local: java.math.BigDecimal,
base: java.math.BigDecimal, theFacetType: Facet.Type) = {
val res = local.compareTo(base)
if (res > 0) SDE(
"SimpleTypes: The local %s (%s) was greater than the base %s (%s) ",
theFacetType, local, theFacetType, base)
}
// private def getRemoteFacets(theFacetType: Facet.Type): Seq[FacetValueR] = {
// val remoteValues = remoteBaseFacets.filter { case (f, _) => f == theFacetType }
// if (remoteValues.size > 0) {
// val res: Seq[FacetValueR] = remoteValues.map { case (f, v) => (f, v.r) }
// res
// } else Seq.empty
// }
private def getRemoteFacetValues(theFacetType: Facet.Type): Seq[FacetValue] = {
val res = remoteBaseFacets.filter { case (f, _) => f == theFacetType }
res
}
private def getRemoteFacetValue(theFacetType: Facet.Type): String = {
// Filtering works more appropriately here
val res = remoteBaseFacets.filter { case (f, v) => f == theFacetType }
if (res.size > 0) {
val (_, theFacetValue) = res(0)
return theFacetValue
}
"" // Indicates the facet doesn't exist
}
private def getFacetValue(theLocalValue: String, theRemoteValue: String, theType: Facet.Type, exists: Boolean): java.math.BigDecimal = {
if (!exists) SDE("The facet %s was not found.", theType)
else if (theLocalValue != "" && theRemoteValue != "") {
val resFacet = doNumericFacetNarrowing(theLocalValue, theRemoteValue, theType)
new java.math.BigDecimal(resFacet)
} else if (theLocalValue != "") {
checkValueSpaceFacetRange(theLocalValue, theType)
} else {
checkValueSpaceFacetRange(theRemoteValue, theType)
}
}
private def getFacetValue(theLocalValue: String, theType: Facet.Type, exists: Boolean): java.math.BigDecimal = {
val remoteFacets = getRemoteFacetValues(theType)
if (!exists) SDE("The facet %s was not found.", theType)
else if (theLocalValue != "" && remoteFacets.size > 0) {
val (_, remoteValue) = getRemoteFacetValues(theType)(0)
val resFacet = doNumericFacetNarrowing(theLocalValue, remoteValue, theType)
new java.math.BigDecimal(resFacet)
} else if (theLocalValue != "") {
checkValueSpaceFacetRange(theLocalValue, theType)
} else {
val (_, remoteValue) = remoteFacets(0)
checkValueSpaceFacetRange(remoteValue, theType)
}
}
private def narrowNonNegativeFacets(localFacet: String, remoteFacet: String, facetType: Facet.Type): String = {
val theLocalFacet = new BigInteger(localFacet)
val theRemoteFacet = new BigInteger(remoteFacet)
if (theLocalFacet.signum() != 1) SDE("The %s facet must be a non-negative integer.", facetType)
facetType match {
case Facet.minLength => {
errorOnLocalLessThanBaseFacet(theLocalFacet, theRemoteFacet, facetType)
localFacet
}
case Facet.maxLength | Facet.fractionDigits => {
errorOnLocalGreaterThanBaseFacet(theLocalFacet, theRemoteFacet, facetType)
localFacet
}
case _ => {
val errMsg = "narrowNonNegativeFacets is not valid for %s facet".format(facetType)
Assert.usageError(errMsg)
}
}
}
private def narrowPositiveIntegerFacets(localFacet: String, remoteFacet: String, facetType: Facet.Type): String = {
val theLocalFacet = new BigInteger(localFacet)
val theRemoteFacet = new BigInteger(remoteFacet)
if ((theLocalFacet.signum() != 1) || (theLocalFacet.compareTo(BigInteger.ZERO) == 0)) SDE("The %s facet must be a positive integer.", facetType)
facetType match {
case Facet.totalDigits => {
errorOnLocalGreaterThanBaseFacet(theLocalFacet, theRemoteFacet, facetType)
localFacet
}
case _ => {
val errMsg = "narrowPositiveIntegerFacets is not valid for %s facet".format(facetType)
Assert.usageError(errMsg)
}
}
}
private def narrowValueSpaceFacets(localFacet: String, remoteFacet: String, facetType: Facet.Type) = {
val (theLocalFacet, theRemoteFacet) = checkValueSpaceFacetRange(localFacet, remoteFacet, facetType)
// Made it here so range checks were successful
// Now just validate/compare local and base/remote facet
facetType match {
case Facet.minInclusive => { errorOnLocalLessThanBaseFacet(theLocalFacet, theRemoteFacet, facetType) }
case Facet.maxInclusive => { errorOnLocalGreaterThanBaseFacet(theLocalFacet, theRemoteFacet, facetType) }
case Facet.minExclusive => { errorOnLocalLessThanBaseFacet(theLocalFacet, theRemoteFacet, facetType) }
case Facet.maxExclusive => { errorOnLocalGreaterThanBaseFacet(theLocalFacet, theRemoteFacet, facetType) }
case _ => {
val errMsg = "Unrecognized facet type (%s) for narrowing of value-space facets.".format(facetType)
Assert.usageError(errMsg)
}
}
localFacet
}
private def convertFacetToBigDecimal(facet: String): java.math.BigDecimal = {
self.primType match {
case PrimType.DateTime => dateToBigDecimal(facet, "uuuu-MM-dd'T'HH:mm:ss.SSSSSSxxx", PrimType.DateTime.toString(), this)
case PrimType.Date => dateToBigDecimal(facet, "uuuu-MM-ddxxx", PrimType.Date.toString(), this)
case PrimType.Time => dateToBigDecimal(facet, "HH:mm:ss.SSSSSSxxx", PrimType.Time.toString(), this)
case _ => new java.math.BigDecimal(facet)
}
}
private def checkValueSpaceFacetRange(localFacet: String, facetType: Facet.Type): java.math.BigDecimal = {
// Necessary for min/max Inclusive/Exclusive Facets
// Perform conversions once
val theLocalFacet = convertFacetToBigDecimal(localFacet)
facetType match {
case Facet.maxExclusive | Facet.maxInclusive |
Facet.minExclusive | Facet.minInclusive | Facet.enumeration => {
// Here we're just doing range checking for the
// specified primitive type
primType match {
case PrimType.Int => {
if (!isFacetInIntRange(theLocalFacet)) {
SDE(
"%s facet value (%s) was found to be outside of Int range.",
facetType, localFacet)
}
}
case PrimType.Byte => {
if (!isFacetInByteRange(theLocalFacet)) {
SDE(
"%s facet value (%s) was found to be outside of Byte range.",
facetType, localFacet)
}
}
case PrimType.Short => {
if (!isFacetInShortRange(theLocalFacet)) {
SDE(
"%s facet value (%s) was found to be outside of Short range.",
facetType, localFacet)
}
}
case PrimType.Long => {
if (!isFacetInLongRange(theLocalFacet)) {
SDE(
"%s facet value (%s) was found to be outside of Long range.",
facetType, localFacet)
}
}
case PrimType.Integer => {
// Unbounded integer
if (!isFacetInIntegerRange(theLocalFacet)) {
SDE(
"%s facet value (%s) was found to be outside of Integer range.",
facetType, localFacet)
}
}
case PrimType.UnsignedInt => {
if (!isFacetInUnsignedIntRange(theLocalFacet)) {
SDE(
"%s facet value (%s) was found to be outside of unsigned int range.",
facetType, localFacet)
}
}
case PrimType.UnsignedByte => {
if (!isFacetInUnsignedByteRange(theLocalFacet)) {
SDE(
"%s facet value (%s) was found to be outside of unsigned byte range.",
facetType, localFacet)
}
}
case PrimType.UnsignedShort => {
if (!isFacetInUnsignedShortRange(theLocalFacet)) {
SDE(
"%s facet value (%s) was found to be outside of unsigned short range.",
facetType, localFacet)
}
}
case PrimType.UnsignedLong => {
if (!isFacetInUnsignedLongRange(theLocalFacet)) {
SDE(
"%s facet value (%s) was found to be outside of unsigned long range.",
facetType, localFacet)
}
}
case PrimType.Double => {
if (!isFacetInDoubleRange(theLocalFacet)) {
SDE(
"%s facet value (%s) was found to be outside of Double range.",
facetType, localFacet)
}
}
case PrimType.Float => {
if (!isFacetInFloatRange(theLocalFacet)) {
SDE(
"%s facet value (%s) was found to be outside of Float range.",
facetType, localFacet)
}
}
case PrimType.NonNegativeInteger => {
// Unsigned Unbounded Integer
if (!isFacetInNonNegativeIntegerRange(theLocalFacet)) {
SDE(
"%s facet value (%s) was found to be outside of NonNegativeInteger range.",
facetType, localFacet)
}
}
case PrimType.Decimal => { /* Nothing to do here */ }
case PrimType.DateTime => { /* Nothing to do here */ }
case PrimType.Date => { /* Nothing to do here */ }
case PrimType.Time => { /* Nothing to do here */ }
case PrimType.Boolean => notYetImplemented("checkValueSpaceFacetRange - Boolean")
case PrimType.HexBinary => { /* Nothing to do here */ }
case PrimType.String => { /* Nothing to do here */ }
case _ => Assert.usageError("checkValueSpaceFacetRange - Unrecognized primitive type: " + primType.name)
}
}
case _ => { /* Nothing to do */ }
}
theLocalFacet
}
private def checkValueSpaceFacetRange(
localFacet: String,
remoteFacet: String, facetType: Facet.Type): (java.math.BigDecimal, java.math.BigDecimal) = {
// Neccessary for min/max Inclusive/Exclusive Facets
// TODO: I think the performance here can be improved.
//
// Consider storing the remoteBaseFacets as their actual evaluated values
// rather than just as String. This would prevent us from having to perform
// the checkValueSpaceFacetRange on the remoteFacet here as it would've already
// been done in the base. --TRW
// Perform conversions once
val theRemoteFacet = checkValueSpaceFacetRange(remoteFacet, facetType)
val theLocalFacet = checkValueSpaceFacetRange(localFacet, facetType)
(theLocalFacet, theRemoteFacet)
}
private def isFacetInByteRange(facet: java.math.BigDecimal): Boolean = self.isInByteRange(facet)
private def isFacetInShortRange(facet: java.math.BigDecimal): Boolean = self.isInShortRange(facet)
private def isFacetInIntRange(facet: java.math.BigDecimal): Boolean = self.isInIntRange(facet)
private def isFacetInIntegerRange(facet: java.math.BigDecimal): Boolean = self.isInIntegerRange(facet)
private def isFacetInLongRange(facet: java.math.BigDecimal): Boolean = self.isInLongRange(facet)
private def isFacetInDoubleRange(facet: java.math.BigDecimal): Boolean = self.isInDoubleRange(facet)
private def isFacetInFloatRange(facet: java.math.BigDecimal): Boolean = self.isInFloatRange(facet)
// private def isFacetInDecimalRange(facet: java.math.BigDecimal): Boolean = {
// BigDecimal is unbounded? So nothing outside of its range?
// true
// }
// private def isFacetInNegativeIntegerRange(facet: java.math.BigDecimal): Boolean = self.isInNonNegativeIntegerRange(facet)
private def isFacetInNonNegativeIntegerRange(facet: java.math.BigDecimal): Boolean = self.isInNonNegativeIntegerRange(facet)
private def isFacetInUnsignedLongRange(facet: java.math.BigDecimal): Boolean =
isInUnsignedLongRange(facet)
private def isFacetInUnsignedIntRange(facet: java.math.BigDecimal): Boolean =
isInUnsignedIntRange(facet)
private def isFacetInUnsignedShortRange(facet: java.math.BigDecimal): Boolean =
isInUnsignedShortRange(facet)
private def isFacetInUnsignedByteRange(facet: java.math.BigDecimal): Boolean =
isInUnsignedByteRange(facet)
protected def doNumericFacetNarrowing(localFacet: String, remoteFacet: String, facetType: Facet.Type) = {
// Assumes both local and remote facets exist
// Only for Numeric facets
//
// Can likely do narrowing checks here
//
// BigInt use compareTo
// a negative number, zero, or a positive number as this BigInteger is numerically less than,
// equal to, or greater than o, which must be a BigInteger.
facetType match {
case Facet.minLength | Facet.maxLength | Facet.fractionDigits => {
// Non-negative Integers. BigInt
narrowNonNegativeFacets(localFacet, remoteFacet, facetType)
}
case Facet.minInclusive | Facet.maxInclusive | Facet.minExclusive | Facet.maxExclusive => {
// In value-space of base type. BigDecimal?
narrowValueSpaceFacets(localFacet, remoteFacet, facetType)
}
case Facet.totalDigits => {
// Positive Integer (value greater than 0). BigInt
narrowPositiveIntegerFacets(localFacet, remoteFacet, facetType)
}
case _ => Assert.usageError("Call to 'doNumericFacetNarrowing' only valid for Numeric Facets.")
}
}
private def getLocalValue(theType: Facet.Type) = {
val res = localBaseFacets.filter { case (f, v) => f == theType }
if (res.length > 0) {
val (_, theFacetValue) = res(0)
theFacetValue
} else ""
}
protected def getCombinedValue(theType: Facet.Type) = {
val lValue = getLocalValue(theType)
val rValue = getRemoteFacetValue(theType)
val cValue = getFacetValue(lValue, rValue, theType, true)
cValue
}
protected lazy val getCombinedValueEnum = {
val lValue = getLocalValue(Facet.enumeration)
val rValue = getRemoteFacetValue(Facet.enumeration)
lValue.foreach(e => {
if (rValue.length() > 0 && !rValue.contains(e)) SDE("Local enumerations must be a subset of base enumerations.")
})
if (lValue.length() > 0) { lValue }
else { rValue }
}
}