blob: da3c3bf82d086e478d3487396aa31cb0ec7b2f76 [file] [log] [blame]
package edu.illinois.ncsa.daffodil.dsom
/* Copyright (c) 2012-2013 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.
*/
import java.math.BigInteger
import scala.collection.mutable.Queue
import scala.math.BigDecimal.javaBigDecimal2bigDecimal
import scala.util.matching.Regex
import scala.xml.Node
import com.ibm.icu.text.SimpleDateFormat
import com.ibm.icu.util.GregorianCalendar
import com.ibm.icu.util.TimeZone
import edu.illinois.ncsa.daffodil.dsom.oolag.OOLAG.HasIsError
import edu.illinois.ncsa.daffodil.exceptions.Assert
import edu.illinois.ncsa.daffodil.exceptions.ThrowsSDE
import edu.illinois.ncsa.daffodil.exceptions.UnsuppressableException
import edu.illinois.ncsa.daffodil.util.Enum
import edu.illinois.ncsa.daffodil.util.Misc
import edu.illinois.ncsa.daffodil.util.SchemaUtils
import edu.illinois.ncsa.daffodil.xml.NS
import edu.illinois.ncsa.daffodil.xml.XMLUtils
import edu.illinois.ncsa.daffodil.processors.VariableMap
import edu.illinois.ncsa.daffodil.api.DFDL.{ Input, Output, DataProcessor, ParseResult, UnparseResult }
import edu.illinois.ncsa.daffodil.processors.EmptyVariableMap
import edu.illinois.ncsa.daffodil.api.ValidationMode
import edu.illinois.ncsa.daffodil.api.Diagnostic
import edu.illinois.ncsa.daffodil.externalvars.Binding
import java.io.File
/////////////////////////////////////////////////////////////////
// Type System
/////////////////////////////////////////////////////////////////
trait TypeBase
extends HasIsError
sealed trait SimpleTypeBase
extends TypeBase
with TypeChecks {
def context: SchemaComponent
def primitiveType: PrimitiveType
}
trait TypeConversions extends TypeChecks {
private def tryCatch[T](context: ThrowsSDE, msg: String, args: Any*)(f: => T): T = {
try { f } catch {
case u: UnsuppressableException => throw u
case n: Exception => context.SDE(msg, args: _*) // TODO: Internationalization
}
}
def convertToLong(s: Any, context: ThrowsSDE) =
tryCatch(context, "Cannot convert %s to Long.", s) {
val (isSuccess, theValue) = this.checkRangeReturnsValue(s.toString, PrimType.Long, context)
if (!isSuccess) context.SDE("Cannot convert %s to Long. Failed range checks.", s.toString)
theValue match {
case Some(bd) => bd.toLong
case None => Assert.impossibleCase
}
}
def convertToDouble(s: Any, context: ThrowsSDE) =
tryCatch(context, "Cannot convert %s to Double.", s) {
val (isSuccess, theValue) = this.checkRangeReturnsValue(s.toString, PrimType.Double, context)
if (!isSuccess) context.SDE("Cannot convert %s to Double. Failed range checks.", s.toString)
theValue match {
case Some(bd) => bd.toDouble
case None => Assert.impossibleCase
}
}
def convertToBoolean(s: Any, context: ThrowsSDE) =
tryCatch(context, "Cannot convert %s to Boolean.", s) {
val (isSuccess, theValue) = this.checkRangeReturnsValue(s.toString, PrimType.Boolean, context)
if (!isSuccess) context.SDE("Cannot convert %s to Boolean.", s.toString)
theValue match {
case Some(bd) => bd.toInt match {
case 0 => false
case 1 => true
case _ => Assert.impossibleCase
}
case None => Assert.impossibleCase
}
}
def convertToByte(s: Any, context: ThrowsSDE) =
tryCatch(context, "Cannot convert %s to Byte.", s) {
val (isSuccess, theValue) = this.checkRangeReturnsValue(s.toString, PrimType.Byte, context)
if (!isSuccess) context.SDE("Cannot convert %s to Byte. Failed range checks.", s.toString)
theValue match {
case Some(bd) => bd.toByte
case None => Assert.impossibleCase
}
}
def convertToShort(s: Any, context: ThrowsSDE) =
tryCatch(context, "Cannot convert %s to Short.", s) {
val (isSuccess, theValue) = this.checkRangeReturnsValue(s.toString, PrimType.Short, context)
if (!isSuccess) context.SDE("Cannot convert %s to Short. Failed range checks.", s.toString)
theValue match {
case Some(bd) => bd.toShort
case None => Assert.impossibleCase
}
}
def convertToInt(s: Any, context: ThrowsSDE) =
tryCatch(context, "Cannot convert %s to Int.", s) {
val (isSuccess, theValue) = this.checkRangeReturnsValue(s.toString, PrimType.Int, context)
if (!isSuccess) context.SDE("Cannot convert %s to UnsignedInt. Failed range checks.", s.toString)
theValue match {
case Some(bd) => bd.toInt
case None => Assert.impossibleCase
}
}
def convertToUByte(s: Any, context: ThrowsSDE) =
tryCatch(context, "Cannot convert %s to UnsignedByte.", s) {
val (isSuccess, theValue) = this.checkRangeReturnsValue(s.toString, PrimType.UByte, context)
if (!isSuccess) context.SDE("Cannot convert %s to UnsignedByte. Failed range checks.", s.toString)
theValue match {
case Some(bd) => bd.toShort
case None => Assert.impossibleCase
}
}
def convertToUShort(s: Any, context: ThrowsSDE) =
tryCatch(context, "Cannot convert %s to UnsignedShort.", s) {
val (isSuccess, theValue) = this.checkRangeReturnsValue(s.toString, PrimType.UShort, context)
if (!isSuccess) context.SDE("Cannot convert %s to UnsignedShort. Failed range checks.", s.toString)
theValue match {
case Some(bd) => bd.toInt
case None => Assert.impossibleCase
}
}
def convertToUInt(s: Any, context: ThrowsSDE) =
tryCatch(context, "Cannot convert %s to UnsignedInt.", s) {
val (isSuccess, theValue) = this.checkRangeReturnsValue(s.toString, PrimType.UInt, context)
if (!isSuccess) context.SDE("Cannot convert %s to UnsignedInt. Failed range checks.", s.toString)
theValue match {
case Some(bd) => bd.toLong
case None => Assert.impossibleCase
}
}
def convertToULong(s: Any, context: ThrowsSDE) =
tryCatch(context, "Cannot convert %s to UnsignedLong.", s) {
val (isSuccess, theValue) = this.checkRangeReturnsValue(s.toString, PrimType.ULong, context)
if (!isSuccess) context.SDE("Cannot convert %s to UnsignedLong. Failed range checks.", s.toString)
theValue match {
case Some(bd) => bd
case None => Assert.impossibleCase
}
}
def convertToInteger(s: Any, context: ThrowsSDE) =
tryCatch(context, "Cannot convert %s to Integer (Unbounded).", s) {
val (isSuccess, theValue) = this.checkRangeReturnsValue(s.toString, PrimType.Integer, context)
if (!isSuccess) context.SDE("Cannot convert %s to Integer (Unbounded). Failed range checks.", s.toString)
theValue match {
case Some(bd) => bd
case None => Assert.impossibleCase
}
}
def convertToNonNegativeInteger(s: Any, context: ThrowsSDE) =
tryCatch(context, "Cannot convert %s to NonNegativeInteger.", s) {
val (isSuccess, theValue) = this.checkRangeReturnsValue(s.toString, PrimType.NonNegativeInteger, context)
if (!isSuccess) context.SDE("Cannot convert %s to NonNegativeInteger. Failed range checks.", s.toString)
theValue match {
case Some(bd) => bd
case None => Assert.impossibleCase
}
}
def convertToDecimal(s: Any, context: ThrowsSDE) =
tryCatch(context, "Cannot convert %s to Decimal.", s) {
val value = BigDecimal(s.toString)
value
}
}
trait TypeChecks {
protected def dateToBigDecimal(date: String, format: String, dateType: String, context: ThrowsSDE): java.math.BigDecimal = {
val df = new SimpleDateFormat(format)
df.setCalendar(new GregorianCalendar())
df.setTimeZone(TimeZone.GMT_ZONE)
val bd = try {
val dt = df.parse(date)
new java.math.BigDecimal(dt.getTime())
} catch {
case e: Exception => {
try {
// Could already be a BigDecimal
new java.math.BigDecimal(date)
} catch {
case e: Exception => context.SDE("Failed to parse (%s) to %s (%s)", date, dateType, format)
}
}
}
bd
}
private def convertStringToBigDecimal(value: String, primitiveType: PrimType.Type, context: ThrowsSDE): java.math.BigDecimal = {
primitiveType match {
case PrimType.DateTime => dateToBigDecimal(value, "uuuu-MM-dd'T'HH:mm:ss.SSSSSSxxx", PrimType.DateTime.toString(), context)
case PrimType.Date => dateToBigDecimal(value, "uuuu-MM-ddxxx", PrimType.Date.toString(), context)
case PrimType.Time => dateToBigDecimal(value, "HH:mm:ss.SSSSSSxxx", PrimType.Time.toString(), context)
case _ => new java.math.BigDecimal(value)
}
}
def checkRangeReturnsValue(value: String, primitiveType: PrimType.Type, theContext: ThrowsSDE): (Boolean, Option[BigDecimal]) = {
// EmptyString is only valid for hexBinary and String
if ((value == null | value.length() == 0)) {
return primitiveType match {
case PrimType.HexBinary | PrimType.String => (true, None)
case _ => (false, None)
}
}
// Don't need to range check String or HexBinary
// no point attempting a conversion to BigDecimal so
// return early here.
primitiveType match {
case PrimType.String | PrimType.HexBinary => return (true, None)
case _ => /* Continue on below */
}
// Check Boolean, and the Numeric types.
(value.toLowerCase(), primitiveType) match {
case ("true", PrimType.Boolean) => (true, Some(BigDecimal(1)))
case ("false", PrimType.Boolean) => (true, Some(BigDecimal(0)))
case (x, PrimType.Boolean) => theContext.SDE("%s is not a valid Boolean value. Expected 'true' or 'false'.", x)
case (_, _) => {
// Perform conversions once
val theValue = convertStringToBigDecimal(value, primitiveType, theContext)
// Here we're just doing range checking for the
// specified primitive type
val res: Boolean = primitiveType match {
case PrimType.Int => isInIntRange(theValue)
case PrimType.Byte => isInByteRange(theValue)
case PrimType.Short => isInShortRange(theValue)
case PrimType.Long => isInLongRange(theValue)
case PrimType.Integer => true // Unbounded Integer
case PrimType.UInt => isInUnsignedIntRange(theValue)
case PrimType.UByte => isInUnsignedByteRange(theValue)
case PrimType.UShort => isInUnsignedShortRange(theValue)
case PrimType.ULong => isInUnsignedLongRange(theValue)
case PrimType.Double => isInDoubleRange(theValue)
case PrimType.Float => isInFloatRange(theValue)
case PrimType.DateTime => true
case PrimType.Date => true
case PrimType.Time => true
case PrimType.Boolean => Assert.impossibleCase // Handled earlier, shouldn't get here
case PrimType.Decimal => true // Unbounded Decimal
case PrimType.HexBinary => Assert.impossibleCase // Handled earlier, shouldn't get here
case PrimType.String => Assert.impossibleCase // Handled earlier, shouldn't get here
case PrimType.NonNegativeInteger => isInNonNegativeIntegerRange(theValue)
}
val isValueWhole = {
val IsWholeRegex = """^[^.]*(\.0*)?$""".r
value match {
case IsWholeRegex(_) => true
case _ => false
}
}
primitiveType match {
case PrimType.Int | PrimType.Byte | PrimType.Short | PrimType.Long |
PrimType.Integer | PrimType.UInt | PrimType.UByte | PrimType.UShort |
PrimType.ULong => if (!isValueWhole) theContext.SDE("checkRange - Value (%s) must be a whole number.", value)
case _ => // OK
}
(res, Some(theValue))
}
}
}
def checkRange(value: String, primitiveType: PrimType.Type, theContext: ThrowsSDE): Boolean = {
val (boolResult, _) = checkRangeReturnsValue(value, primitiveType, theContext)
boolResult
}
protected def isNumInRange(num: java.math.BigDecimal, min: java.math.BigDecimal,
max: java.math.BigDecimal): Boolean = {
val checkMin = num.compareTo(min)
if (checkMin < 0) { return false } // num less than min
val checkMax = num.compareTo(max)
if (checkMax > 0) { return false } // num greater than max
true
}
protected def isInByteRange(value: java.math.BigDecimal): Boolean = {
val min = new java.math.BigDecimal(Byte.MinValue.toLong.toString())
val max = new java.math.BigDecimal(Byte.MaxValue.toLong.toString())
isNumInRange(value, min, max)
}
protected def isInShortRange(value: java.math.BigDecimal): Boolean = {
val min = new java.math.BigDecimal(Short.MinValue.toLong.toString())
val max = new java.math.BigDecimal(Short.MaxValue.toLong.toString())
isNumInRange(value, min, max)
}
protected def isInIntRange(value: java.math.BigDecimal): Boolean = {
val min = new java.math.BigDecimal(Int.MinValue.toString())
val max = new java.math.BigDecimal(Int.MaxValue.toString())
isNumInRange(value, min, max)
}
protected def isInIntegerRange(value: java.math.BigDecimal): Boolean = {
val min = new java.math.BigDecimal(Int.MinValue.toString())
// Unbounded Integer
val checkMin = value.compareTo(min)
if (checkMin < 0) { return false } // num less than min
true
}
protected def isInLongRange(value: java.math.BigDecimal): Boolean = {
val min = new java.math.BigDecimal(Long.MinValue.toString())
val max = new java.math.BigDecimal(Long.MaxValue.toString())
isNumInRange(value, min, max)
}
protected def isInDoubleRange(value: java.math.BigDecimal): Boolean = {
val min = new java.math.BigDecimal(Double.MinValue.toString())
val max = new java.math.BigDecimal(Double.MaxValue.toString())
isNumInRange(value, min, max)
}
protected def isInFloatRange(value: java.math.BigDecimal): Boolean = {
val min = new java.math.BigDecimal(Float.MinValue.toString())
val max = new java.math.BigDecimal(Float.MaxValue.toString())
isNumInRange(value, min, max)
}
protected def isInDecimalRange(value: java.math.BigDecimal): Boolean = {
// BigDecimal is unbounded? So nothing outside of its range?
true
}
protected def isInNegativeIntegerRange(value: java.math.BigDecimal, context: ThrowsSDE): Boolean = {
// TODO: NegativeInteger not supported in DFDL v1.0
val min = new java.math.BigDecimal(Int.MinValue.toString())
val max = new java.math.BigDecimal(Int.MaxValue.toString())
val isNegative = value.signum == -1
if (!isNegative) context.SDE("Expected a negative integer for this value.")
val checkMin = value.compareTo(min)
if (checkMin < 0) context.SDE("Value (%s) was found to be more negative than allowed by Int.MinValue.", value.intValue())
true
}
protected def isInNonNegativeIntegerRange(value: java.math.BigDecimal): Boolean = {
// Should be treated as unsigned Integer (unbounded)
val min = java.math.BigDecimal.ZERO
val isNegative = value.signum == -1
if (isNegative) return false
true
}
protected def isInUnsignedXXXRange(value: java.math.BigDecimal, numBits: Int, typeName: String): Boolean = {
Assert.usage(numBits <= 64, "isInUnsignedXXXRange: numBits must be <= 64.")
val min = java.math.BigDecimal.ZERO
val max = new java.math.BigDecimal(BigInteger.ONE.shiftLeft(numBits)).subtract(new java.math.BigDecimal(1))
val isNegative = value.signum == -1
if (isNegative) return false
val checkMax = value.compareTo(max)
if (checkMax > 0) return false
true
}
protected def isInUnsignedLongRange(value: java.math.BigDecimal): Boolean =
isInUnsignedXXXRange(value, 64, "ulong")
protected def isInUnsignedIntRange(value: java.math.BigDecimal): Boolean =
isInUnsignedXXXRange(value, 32, "uint")
protected def isInUnsignedShortRange(value: java.math.BigDecimal): Boolean =
isInUnsignedXXXRange(value, 16, "ushort")
protected def isInUnsignedByteRange(value: java.math.BigDecimal): Boolean =
isInUnsignedXXXRange(value, 8, "ubyte")
}
trait Facets { self: SimpleTypeDefBase =>
import Facet._
import edu.illinois.ncsa.daffodil.dsom.FacetTypes._
def retrieveFacetValueFromRestrictionBase(xml: Node, facetName: Facet.Type): String = {
val res = xml \\ "restriction" \ facetName.toString() \ "@value"
if (res.length > 0) res.head.text else ""
}
def retrieveFacetValuesFromRestrictionBase(xml: Node, facetName: Facet.Type): Seq[String] = {
val res = xml \\ "restriction" \ facetName.toString() \\ "@value"
if (res.length > 0) res.map(n => n.text).toList else List.empty
}
def enumeration(xml: Node): Seq[String] = { retrieveFacetValuesFromRestrictionBase(xml, Facet.enumeration) }
def fractionDigits(xml: Node): String = { retrieveFacetValueFromRestrictionBase(xml, Facet.fractionDigits) }
def maxExclusive(xml: Node): String = { retrieveFacetValueFromRestrictionBase(xml, Facet.maxExclusive) }
def maxInclusive(xml: Node): String = { retrieveFacetValueFromRestrictionBase(xml, Facet.maxInclusive) }
def maxLength(xml: Node): String = { retrieveFacetValueFromRestrictionBase(xml, Facet.maxLength) }
def minExclusive(xml: Node): String = { retrieveFacetValueFromRestrictionBase(xml, Facet.minExclusive) }
def minInclusive(xml: Node): String = { retrieveFacetValueFromRestrictionBase(xml, Facet.minInclusive) }
def minLength(xml: Node): String = { retrieveFacetValueFromRestrictionBase(xml, Facet.minLength) }
def pattern(xml: Node): Seq[String] = {
// Patterns are OR'd locally, AND'd remotely
retrieveFacetValuesFromRestrictionBase(xml, Facet.pattern).map(p => p)
}
def totalDigits(xml: Node): String = { retrieveFacetValueFromRestrictionBase(xml, Facet.totalDigits) }
def whitespace(xml: Node): String = { retrieveFacetValueFromRestrictionBase(xml, Facet.whiteSpace) }
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("|")
}
lazy val localMinInclusiveValue: String = minInclusive(xml)
lazy val localMaxInclusiveValue: String = maxInclusive(xml)
lazy val localMinExclusiveValue: String = minExclusive(xml)
lazy val localMaxExclusiveValue: String = maxExclusive(xml)
lazy val localMinLengthValue: String = minLength(xml)
lazy val localMaxLengthValue: String = maxLength(xml)
lazy val localTotalDigitsValue: String = totalDigits(xml)
lazy val localFractionDigitsValue: String = fractionDigits(xml)
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) context.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("|")
}
lazy val localWhitespaceValue: String = {
whitespace(xml)
context.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()
}
lazy val hasEnumeration: Boolean = (localEnumerationValue.length > 0) || (getRemoteFacetValues(Facet.enumeration).size > 0)
lazy val hasPattern: Boolean = (localPatternValue.length > 0) || (getRemoteFacetValues(Facet.pattern).size > 0)
lazy val hasMinLength: Boolean = (localMinLengthValue != "") || (getRemoteFacetValues(Facet.minLength).size > 0)
lazy val hasMaxLength: Boolean = (localMaxLengthValue != "") || (getRemoteFacetValues(Facet.maxLength).size > 0)
lazy val hasMinInclusive: Boolean = (localMinInclusiveValue != "") || (getRemoteFacetValues(Facet.minInclusive).size > 0)
lazy val hasMaxInclusive: Boolean = (localMaxInclusiveValue != "") || (getRemoteFacetValues(Facet.maxInclusive).size > 0)
lazy val hasMinExclusive: Boolean = (localMinExclusiveValue != "") || (getRemoteFacetValues(Facet.minExclusive).size > 0)
lazy val hasMaxExclusive: Boolean = (localMaxExclusiveValue != "") || (getRemoteFacetValues(Facet.maxExclusive).size > 0)
lazy val hasTotalDigits: Boolean = (localTotalDigitsValue != "") || (getRemoteFacetValues(Facet.totalDigits).size > 0)
lazy val hasFractionDigits: Boolean = (localFractionDigitsValue != "") || (getRemoteFacetValues(Facet.fractionDigits).size > 0)
lazy val patternValues: Seq[FacetValueR] = {
val values = combinedBaseFacets.filter { case (f, _) => f == Facet.pattern }
if (values.size > 0) {
val res: Seq[FacetValueR] = values.map { case (f, v) => (f, v.r) }
res
} else Seq.empty
}
lazy val enumerationValues: 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)
value
} else context.SDE("Enumeration was not found in this context.")
}
// TODO: Tidy up. Can likely replace getFacetValue with a similar call to combinedBaseFacets
// as combinedBaseFacets should contain the 'narrowed' values.
//
lazy val minLengthValue: java.math.BigDecimal = getFacetValue(localMinLengthValue, Facet.minLength, hasMinLength)
lazy val maxLengthValue: java.math.BigDecimal = getFacetValue(localMaxLengthValue, Facet.maxLength, hasMaxLength)
lazy val minInclusiveValue: java.math.BigDecimal = getFacetValue(localMinInclusiveValue, Facet.minInclusive, hasMinInclusive)
lazy val maxInclusiveValue: java.math.BigDecimal = getFacetValue(localMaxInclusiveValue, Facet.maxInclusive, hasMaxInclusive)
lazy val minExclusiveValue: java.math.BigDecimal = getFacetValue(localMinExclusiveValue, Facet.minExclusive, hasMinExclusive)
lazy val maxExclusiveValue: java.math.BigDecimal = getFacetValue(localMaxExclusiveValue, Facet.maxExclusive, hasMaxExclusive)
lazy val totalDigitsValue: java.math.BigDecimal = getFacetValue(localTotalDigitsValue, Facet.totalDigits, hasTotalDigits)
lazy val fractionDigitsValue: java.math.BigDecimal = getFacetValue(localFractionDigitsValue, Facet.fractionDigits, hasFractionDigits)
private def errorOnLocalLessThanBaseFacet(local: Long, base: Long, theFacetType: Facet.Type) = {
if (local < base) context.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) context.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) context.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) context.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) context.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) context.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 evaluateFacet(check: (Long, Long, Facet.Type) => Unit, theFacetType: Facet.Type, theLocalFacet: Long) = {
val remoteFacetValues = getRemoteFacetValues(theFacetType)
if (remoteFacetValues.size > 0) {
val (_, remoteValues) = remoteFacetValues(0)
val theRemoteFacet = remoteValues(0).toLong
check(theLocalFacet, theRemoteFacet, theFacetType)
}
theLocalFacet
}
private def getFacetValue(theLocalValue: String, theRemoteValue: String, theType: Facet.Type, exists: Boolean): java.math.BigDecimal = {
if (!exists) context.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) context.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) context.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)) context.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.primitiveType match {
case PrimType.DateTime => dateToBigDecimal(facet, "uuuu-MM-dd'T'HH:mm:ss.SSSSSSxxx", PrimType.DateTime.toString(), context)
case PrimType.Date => dateToBigDecimal(facet, "uuuu-MM-ddxxx", PrimType.Date.toString(), context)
case PrimType.Time => dateToBigDecimal(facet, "HH:mm:ss.SSSSSSxxx", PrimType.Time.toString(), context)
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 = new java.math.BigDecimal(localFacet)
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
primitiveType match {
case PrimType.Int => {
if (!isFacetInIntRange(theLocalFacet)) {
context.SDE("%s facet value (%s) was found to be outside of Int range.",
facetType, localFacet)
}
}
case PrimType.Byte => {
if (!isFacetInByteRange(theLocalFacet)) {
context.SDE("%s facet value (%s) was found to be outside of Byte range.",
facetType, localFacet)
}
}
case PrimType.Short => {
if (!isFacetInShortRange(theLocalFacet)) {
context.SDE("%s facet value (%s) was found to be outside of Short range.",
facetType, localFacet)
}
}
case PrimType.Long => {
if (!isFacetInLongRange(theLocalFacet)) {
context.SDE("%s facet value (%s) was found to be outside of Long range.",
facetType, localFacet)
}
}
case PrimType.Integer => {
// Unbounded integer
if (!isFacetInIntegerRange(theLocalFacet)) {
context.SDE("%s facet value (%s) was found to be outside of Integer range.",
facetType, localFacet)
}
}
case PrimType.UInt => {
if (!isFacetInUnsignedIntRange(theLocalFacet)) {
context.SDE("%s facet value (%s) was found to be outside of unsigned int range.",
facetType, localFacet)
}
}
case PrimType.UByte => {
if (!isFacetInUnsignedByteRange(theLocalFacet)) {
context.SDE("%s facet value (%s) was found to be outside of unsigned byte range.",
facetType, localFacet)
}
}
case PrimType.UShort => {
if (!isFacetInUnsignedShortRange(theLocalFacet)) {
context.SDE("%s facet value (%s) was found to be outside of unsigned short range.",
facetType, localFacet)
}
}
case PrimType.ULong => {
if (!isFacetInUnsignedLongRange(theLocalFacet)) {
context.SDE("%s facet value (%s) was found to be outside of unsigned long range.",
facetType, localFacet)
}
}
case PrimType.Double => {
if (!isFacetInDoubleRange(theLocalFacet)) {
context.SDE("%s facet value (%s) was found to be outside of Double range.",
facetType, localFacet)
}
}
case PrimType.Float => {
if (!isFacetInFloatRange(theLocalFacet)) {
context.SDE("%s facet value (%s) was found to be outside of Float range.",
facetType, localFacet)
}
}
case PrimType.NonNegativeInteger => {
// Unsigned Unbounded Integer
if (!isFacetInNonNegativeIntegerRange(theLocalFacet)) {
context.SDE("%s facet value (%s) was found to be outside of NonNegativeInteger range.",
facetType, localFacet)
}
}
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 _ => schemaDefinitionError("checkValueSpaceFacetRange - Unrecognized primitive type: %s", primitiveType.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) //new java.math.BigDecimal(remoteFacet)
val theLocalFacet = checkValueSpaceFacetRange(localFacet, facetType)
(theLocalFacet, theRemoteFacet)
}
// private def isNumInRange(num: java.math.BigDecimal, min: java.math.BigDecimal,
// max: java.math.BigDecimal): Boolean = {
// val checkMin = num.compareTo(min)
// if (checkMin < 0) { return false } // num less than min
// val checkMax = num.compareTo(max)
// if (checkMax > 0) { return false } // num greater than max
// true
// }
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 def getCombinedValueEnum = {
val lValue = getLocalValue(Facet.enumeration)
val rValue = getRemoteFacetValue(Facet.enumeration)
lValue.foreach(e => {
if (rValue.length() > 0 && !rValue.contains(e)) context.SDE("Local enumerations must be a subset of base enumerations.")
})
if (lValue.length() > 0) { lValue }
else { rValue }
}
}
object Facet extends Enum {
sealed abstract trait Type extends EnumValueType
case object enumeration extends Type
case object fractionDigits extends Type
case object maxExclusive extends Type
case object maxInclusive extends Type
case object maxLength extends Type
case object minExclusive extends Type
case object minInclusive extends Type
case object minLength extends Type
case object pattern extends Type
case object totalDigits extends Type
case object whiteSpace extends Type
}
object FacetTypes {
// These were defined to make life simpler
// TODO: Should we modify these to also include the name of the simpleType?
type Values = String
type ValuesR = Regex
type FacetValue = (Facet.Type, Values)
type FacetValueR = (Facet.Type, ValuesR)
type ElemFacets = Seq[FacetValue]
type ElemFacetsR = Seq[FacetValueR]
}
class SimpleTypeNode(name: String, parent: SimpleTypeNode, childrenArg: => List[SimpleTypeNode]) {
// Eliminated a var here. Doing functional graph construction now below.
lazy val children = childrenArg
lazy val isHead: Boolean = parent == null
lazy val lcaseName = name.toLowerCase()
// names in lower case
lazy val parentList: List[String] = {
if (isHead) {
List.empty
} else {
lcaseName :: parent.parentList
}
}
def doesParentListContain(typeName: String): Boolean = {
val list = parentList.filter(n => n == typeName.toLowerCase())
list.size > 0
}
}
trait SimpleTypeDerivation {
lazy val simpleTypes = buildStructure
def getSimpleTypeNode(name: String) = {
simpleTypes.find(stn => stn.lcaseName == name.toLowerCase())
}
def isXDerivedFromY(nameX: String, nameY: String): Boolean = {
getSimpleTypeNode(nameX) match {
case Some(stn) => {
stn.doesParentListContain(nameY)
}
case None => false
}
}
private def buildStructure = {
// This is how you construct a graph in functional programming.
// These structures are recursive, but it all works out in the end.
lazy val anySimpleType: SimpleTypeNode = new SimpleTypeNode("anySimpleType", null, List(string, float, double, decimal, boolean, hexBinary))
lazy val string = new SimpleTypeNode("string", anySimpleType, Nil)
lazy val float = new SimpleTypeNode("float", anySimpleType, Nil)
lazy val double = new SimpleTypeNode("double", anySimpleType, Nil)
lazy val decimal = new SimpleTypeNode("decimal", anySimpleType, List(integer))
lazy val boolean = new SimpleTypeNode("boolean", anySimpleType, Nil)
lazy val hexBinary = new SimpleTypeNode("hexBinary", anySimpleType, Nil)
lazy val integer: SimpleTypeNode = new SimpleTypeNode("integer", decimal, List(long, nonNegativeInteger))
lazy val long = new SimpleTypeNode("long", integer, List(int))
lazy val nonNegativeInteger = new SimpleTypeNode("nonNegativeInteger", integer, List(unsignedLong))
lazy val int: SimpleTypeNode = new SimpleTypeNode("int", long, List(short))
lazy val short: SimpleTypeNode = new SimpleTypeNode("short", int, List(byte))
lazy val byte = new SimpleTypeNode("byte", short, Nil)
lazy val unsignedLong: SimpleTypeNode = new SimpleTypeNode("unsignedLong", nonNegativeInteger, List(unsignedInt))
lazy val unsignedInt = new SimpleTypeNode("unsignedInt", unsignedLong, List(unsignedShort))
lazy val unsignedShort: SimpleTypeNode = new SimpleTypeNode("unsignedShort", unsignedInt, List(unsignedByte))
lazy val unsignedByte = new SimpleTypeNode("unsignedByte", unsignedShort, Nil)
List(anySimpleType, string, float, double, decimal, boolean, hexBinary, integer, long,
nonNegativeInteger, int, short, byte, unsignedLong, unsignedInt, unsignedShort, unsignedByte)
}
}
abstract class SimpleTypeDefBase(xmlArg: Node, parent: SchemaComponent)
extends AnnotatedSchemaComponent(xmlArg, parent)
with SimpleTypeBase
with DFDLStatementMixin
with Facets
with TypeChecks
with SimpleTypeDerivation
with OverlapCheckMixin {
requiredEvaluations(myBaseTypeList)
lazy val bases: Seq[SimpleTypeDefBase] =
myBaseDef match {
case None => Nil
case Some(st: SimpleTypeDefBase) => st +: st.bases
case _ => Nil
}
lazy val sTypeNonDefault: Seq[ChainPropProvider] = bases.map { _.nonDefaultFormatChain }
lazy val sTypeDefault: Seq[ChainPropProvider] = bases.map { _.defaultFormatChain }
// want a QueueSet i.e., fifo order if iterated, but duplicates
// kept out of the set. Will simulate by calling distinct.
lazy val nonDefaultPropertySources = nonDefaultPropertySources_.value
private val nonDefaultPropertySources_ = LV('nonDefaultPropertySources) {
val seq = (this.nonDefaultFormatChain +: sTypeNonDefault).distinct
checkNonOverlap(seq)
seq
}
lazy val defaultPropertySources = defaultPropertySources_.value
private val defaultPropertySources_ = LV('defaultPropertySources) {
val seq = (this.defaultFormatChain +: sTypeDefault).distinct
seq
}
import edu.illinois.ncsa.daffodil.dsom.FacetTypes._
def emptyFormatFactory = new DFDLSimpleType(newDFDLAnnotationXML("simpleType"), this)
def isMyFormatAnnotation(a: DFDLAnnotation) = a.isInstanceOf[DFDLSimpleType]
def annotationFactory(node: Node): DFDLAnnotation = {
node match {
case <dfdl:simpleType>{ contents @ _* }</dfdl:simpleType> => new DFDLSimpleType(node, this)
case _ => annotationFactoryForDFDLStatement(node, this)
}
}
// Returns name of base class in the form of QName
//
lazy val restrictionBase: String = {
val rsb = xml \\ "restriction" \ "@base"
if (rsb.length != 1) {
context.SDE("Restriction base was not found.")
}
rsb.head.text
}
lazy val optPrimitiveType = {
val (nsURI, localName) = baseTypeQName
if (nsURI == XMLUtils.XSD_NAMESPACE) {
// XSD namespace
val prim = schemaDocument.schemaSet.getPrimitiveType(nsURI, localName)
schemaDefinitionUnless(prim != None,
"Type {%s}%s is not an XSD primitive type.", nsURI, localName)
prim
} else None
}
lazy val myBaseDef = myBaseType match {
case st: SimpleTypeDefBase => Some(st)
case _ => None
}
lazy val myBaseTypeFactory = {
Assert.invariant(restrictionBase.length() != 0)
val (nsURI, localName) = baseTypeQName
Assert.invariant(optPrimitiveType == None)
val factory = schemaDocument.schemaSet.getGlobalSimpleTypeDef(nsURI, localName)
factory
}
/**
* Follows all indirections to get you the ultimate primitive
* built-in simple type that must underlie all simple types
* eventually.
*/
lazy val primitiveType = {
myBaseType.primitiveType
}
lazy val baseTypeQName = XMLUtils.QName(xml, restrictionBase, schemaDocument)
lazy val myBaseType: SimpleTypeBase = {
optPrimitiveType match {
case Some(pt) => pt
case None => {
val bt = myBaseTypeFactory.map { _.forDerivedType(this) }
bt match {
case None => schemaDefinitionError("No type found for base: " + baseTypeQName)
case Some(bt) => bt
}
}
}
}
lazy val myBaseTypeList = List(myBaseType)
lazy val localBaseFacets: ElemFacets = {
val myFacets: Queue[FacetValue] = Queue.empty // val not var - it's a mutable collection
if (localPatternValue.length > 0) { myFacets.enqueue((Facet.pattern, localPatternValue)) }
if (localMinLengthValue.length > 0) { myFacets.enqueue((Facet.minLength, localMinLengthValue)) }
if (localMaxLengthValue.length > 0) { myFacets.enqueue((Facet.maxLength, localMaxLengthValue)) }
if (localMinInclusiveValue.length > 0) { myFacets.enqueue((Facet.minInclusive, localMinInclusiveValue)) }
if (localMaxInclusiveValue.length > 0) { myFacets.enqueue((Facet.maxInclusive, localMaxInclusiveValue)) }
if (localMinExclusiveValue.length > 0) { myFacets.enqueue((Facet.minExclusive, localMinExclusiveValue)) }
if (localMaxExclusiveValue.length > 0) { myFacets.enqueue((Facet.maxExclusive, localMaxExclusiveValue)) }
if (localTotalDigitsValue.length > 0) { myFacets.enqueue((Facet.totalDigits, localTotalDigitsValue)) }
if (localFractionDigitsValue.length > 0) { myFacets.enqueue((Facet.fractionDigits, localFractionDigitsValue)) }
if (localEnumerationValue.length > 0) { myFacets.enqueue((Facet.enumeration, localEnumerationValue)) }
val res: ElemFacets = myFacets.toSeq
res
}
lazy val combinedBaseFacets: Seq[FacetValue] = {
val localF = localBaseFacets
val remoteF = remoteBaseFacets
val combined: Queue[FacetValue] = Queue.empty
if (hasEnumeration) {
val enumVal = getCombinedValueEnum
combined.enqueue((Facet.enumeration, enumVal))
}
if (hasPattern) {
val lPattern = localBaseFacets.filter { case (f, v) => f == Facet.pattern }
val rPattern = remoteBaseFacets.filter { case (f, v) => f == Facet.pattern }
val cPattern = lPattern.union(rPattern)
cPattern.foreach(x => combined.enqueue(x))
}
if (hasMinLength) {
val cValue = getCombinedValue(Facet.minLength)
combined.enqueue((Facet.minLength, cValue.toString()))
}
if (hasMaxLength) {
val cValue = getCombinedValue(Facet.maxLength)
combined.enqueue((Facet.maxLength, cValue.toString()))
}
if (hasMaxInclusive) {
val cValue = getCombinedValue(Facet.maxInclusive)
combined.enqueue((Facet.maxInclusive, cValue.toString()))
}
if (hasMaxExclusive) {
val cValue = getCombinedValue(Facet.maxExclusive)
combined.enqueue((Facet.maxExclusive, cValue.toString()))
}
if (hasMinInclusive) {
val cValue = getCombinedValue(Facet.minInclusive)
combined.enqueue((Facet.minInclusive, cValue.toString()))
}
if (hasMinExclusive) {
val cValue = getCombinedValue(Facet.minExclusive)
combined.enqueue((Facet.minExclusive, cValue.toString()))
}
if (hasTotalDigits) {
val cValue = getCombinedValue(Facet.totalDigits)
combined.enqueue((Facet.totalDigits, cValue.toString()))
}
if (hasFractionDigits) {
val cValue = getCombinedValue(Facet.fractionDigits)
combined.enqueue((Facet.fractionDigits, cValue.toString()))
}
combined.toSeq
}
lazy val remoteBaseFacets = remoteBaseFacets_.value
private val remoteBaseFacets_ = LV('remoteBaseFacets) {
myBaseType match {
case gstd: GlobalSimpleTypeDef => gstd.combinedBaseFacets
case prim: PrimitiveType => Nil
case _ => Assert.impossible()
}
}
/**
* Combine our statements with those of our base def (if there is one)
*
* The order is important here. I.e., we FIRST put in each list those from our base. Then our own local ones.
*/
lazy val statements: Seq[DFDLStatement] = myBaseDef.map { _.statements }.getOrElse(Nil) ++ localStatements
// TODO: refactor into shared code for combining all the annotations in the resolved set of annotations
// for a particular annotation point, checking that there is only one format annotation, that
// asserts and discriminators are properly excluding each-other, etc.
// Code should be sharable for many kinds of annotation points, perhaps specialized for groups, complex type
// elements, and simple type elements.
//
// See JIRA issue DFDL-481
lazy val newVariableInstanceStatements: Seq[DFDLNewVariableInstance] =
myBaseDef.map { _.newVariableInstanceStatements }.getOrElse(Nil) ++ localNewVariableInstanceStatements
lazy val (discriminatorStatements, assertStatements) = checkDiscriminatorsAssertsDisjoint(combinedDiscrims, combinedAsserts)
private lazy val combinedAsserts: Seq[DFDLAssert] = myBaseDef.map { _.assertStatements }.getOrElse(Nil) ++ localAssertStatements
private lazy val combinedDiscrims: Seq[DFDLDiscriminator] = myBaseDef.map { _.discriminatorStatements }.getOrElse(Nil) ++ localDiscriminatorStatements
lazy val setVariableStatements: Seq[DFDLSetVariable] = {
val combinedSvs = myBaseDef.map { _.setVariableStatements }.getOrElse(Nil) ++ localSetVariableStatements
checkDistinctVariableNames(combinedSvs)
}
}
class LocalSimpleTypeDef(xmlArg: Node, parent: ElementBase)
extends SimpleTypeDefBase(xmlArg, parent)
with LocalComponentMixin {
lazy val baseName = (xml \ "restriction" \ "@base").text
lazy val baseType = {
val res = if (baseName == "") None
else notYetImplemented("local simpleType with base attribute.") // should go find the global simple type here
}
}
/**
* We need a schema document and such for unit testing, also our PrimitiveType
* needs a dummy schema document also so that our invariant, that *everything*
* has a schema document, schema, and schema set
* holds true even when we're not building up a "real" schema.
*/
object Fakes {
lazy val sch = SchemaUtils.dfdlTestSchema(
<dfdl:format ref="tns:daffodilTest1"/>,
<xs:element name="fake" type="xs:string" dfdl:lengthKind="delimited"/>
<xs:element name="fake2" type="tns:fakeCT"/>
<xs:complexType name="fakeCT">
<xs:sequence>
<xs:group ref="tns:fakeGroup"/>
<xs:element ref="tns:fake"/>
</xs:sequence>
</xs:complexType>
<xs:group name="fakeGroup">
<xs:choice>
<xs:sequence/>
</xs:choice>
</xs:group>)
val DummyPrimitiveFactory = null
lazy val xsd_sset = new SchemaSet(DummyPrimitiveFactory, sch, "http://example.com", "fake")
lazy val xsd_schema = xsd_sset.getSchema(NS("http://example.com")).get
lazy val fakeSD = xsd_schema.schemaDocuments(0)
lazy val fakeElem = fakeSD.getGlobalElementDecl("fake").get.forRoot()
lazy val fakeCT = fakeSD.getGlobalElementDecl("fake2").get.forRoot().typeDef.asInstanceOf[GlobalComplexTypeDef]
lazy val fakeSequence = fakeCT.modelGroup.asInstanceOf[Sequence]
lazy val Seq(fs1, fs2) = fakeSequence.groupMembers
lazy val fakeGroupRef = fs1.asInstanceOf[GroupRef]
class FakeDataProcessor extends DataProcessor {
def setValidationMode(mode: ValidationMode.Type): Unit = {}
def getValidationMode(): ValidationMode.Type = { ValidationMode.Full }
def save(output: Output): Unit = {}
def setExternalVariables(extVars: Map[String, String]): Unit = {}
def setExternalVariables(extVars: Seq[Binding]): Unit = {}
def setExternalVariables(extVars: File): Unit = {}
def getVariables(): VariableMap = EmptyVariableMap
def unparse(output: Output, node: scala.xml.Node): UnparseResult = null
def parse(input: Input, lengthLimitInBits: Long = -1): ParseResult = null
def getDiagnostics: Seq[Diagnostic] = Seq.empty
//final lazy val canProceed: Boolean = !isError
def isError: Boolean = false
}
lazy val fakeDP = new FakeDataProcessor
}
// Primitives are not "global" because they don't appear in any schema document
sealed abstract class PrimitiveType
extends SchemaComponent(<primitive/>, null)
with SimpleTypeBase
with NamedMixin {
/**
* When class name is isomorphic to the type name, compute automatically.
*/
lazy val pname = {
val cname = Misc.getNameFromClass(this)
val first = cname(0).toLower
val rest = cname.substring(1)
first + rest
}
override lazy val namespace = XMLUtils.XSD_NAMESPACE
override lazy val prefix = "xsd"
import PrimType._
override lazy val enclosingComponent = None // Shouldn't be used anyway.
override lazy val fileDescription = "" // no file, no file description
lazy val primitiveType = this
override def toString = "PrimitiveType(" + prettyName + ")"
override lazy val name = pname
override def prettyName = pname
// override val xml = Assert.invariantFailed("Primitives don't have xml definitions.")
override lazy val schemaDocument = Fakes.fakeSD
}
object PrimType {
type Type = PrimitiveType
case object String extends Type
case object Int extends Type
case object Byte extends Type
case object Short extends Type
case object Long extends Type
case object Integer extends Type
case object Decimal extends Type
case object UInt extends Type { override lazy val pname = "unsignedInt" }
case object UByte extends Type { override lazy val pname = "unsignedByte" }
case object UShort extends Type { override lazy val pname = "unsignedShort" }
case object ULong extends Type { override lazy val pname = "unsignedLong" }
case object NonNegativeInteger extends Type
case object Double extends Type
case object Float extends Type
case object HexBinary extends Type
case object Boolean extends Type
case object DateTime extends Type
case object Date extends Type
case object Time extends Type
lazy val allPrimitiveTypes: Seq[PrimitiveType] = List(
String,
Int,
Byte,
Short,
Long,
Integer,
Decimal,
UInt,
UByte,
UShort,
ULong,
NonNegativeInteger,
Double,
Float,
HexBinary,
Boolean,
DateTime,
Date,
Time)
}
/**
* The factory is sharable even though the global object it creates cannot
* be shared.
*
* Call forElement(element) and supply the element referring
* to the global type, then you get back an instance that is one-to-one with the
* element.
*
* This then allows attributes of the type to refer to the element in deciding things.
* I.e., the context is clear and kept separate for each place a global type is used.
*/
class GlobalSimpleTypeDefFactory(xmlArg: Node, schemaDocumentArg: SchemaDocument)
extends SchemaComponent(xmlArg, schemaDocumentArg) with NamedMixin {
// def forRoot() = new GlobalSimpleTypeDef(xml, schemaDocument, None)
/**
* Create a private instance for this element's use.
*/
def forElement(element: ElementBase) = new GlobalSimpleTypeDef(None, xml, schemaDocument, Some(element))
def forDerivedType(derivedType: SimpleTypeDefBase) = new GlobalSimpleTypeDef(Some(derivedType), xml, schemaDocument, None)
}
/**
* The instance type for global simple type definitions.
*/
class GlobalSimpleTypeDef(derivedType: Option[SimpleTypeDefBase], xmlArg: Node, schemaDocumentArg: SchemaDocument, val element: Option[ElementBase])
extends SimpleTypeDefBase(xmlArg, schemaDocumentArg)
with GlobalComponentMixin {
override lazy val referringComponent = (derivedType, element) match {
case (Some(dt), None) => derivedType
case (None, Some(elem)) => element
case _ => Assert.impossible("SimpleType must either have a derivedType or an element. Not both.")
}
override def prettyName = "simpleType." + name
}
abstract class ComplexTypeBase(xmlArg: Node, parent: SchemaComponent)
extends SchemaComponent(xmlArg, parent)
with TypeBase
with ComplexTypeBaseGrammarMixin {
requiredEvaluations(modelGroup)
def element: ElementBase
lazy val <complexType>{ xmlChildren @ _* }</complexType> = xml
lazy val Seq(modelGroup) = {
val s = smg.value
schemaDefinitionUnless(s.length == 1, "A complex type must have exactly one model-group element child which is a sequence, choice, or group reference.")
s
}
private val smg = LV('smg) {
xmlChildren.flatMap {
xmlChild =>
{
val g = GroupFactory(xmlChild, this, 1) // discards unwanted text nodes also.
g
}
}
}
// provides needed polymorphism across unannotated complex types, and
// the annotated objects.
lazy val localAndFormatRefProperties: Map[String, String] = {
Map.empty[String, String]
}
lazy val isScannable: Boolean = {
val parentElem: ElementBase = enclosingComponent.get.asInstanceOf[ElementBase]
val unScannableChildren = modelGroup.group.groupMembers.filterNot { child =>
(child.knownEncodingCharset == parentElem.knownEncodingCharset) && child.isScannable
}
unScannableChildren.length == 0
}
lazy val alignmentValueInBits: Int = {
val children = modelGroup.group.groupMembers.sortBy(m => -m.alignmentValueInBits)
children.headOption match {
case Some(child) => child.alignmentValueInBits
case None => 0
}
}
}
class GlobalComplexTypeDefFactory(xmlArg: Node, schemaDocumentArg: SchemaDocument)
extends SchemaComponent(xmlArg, schemaDocumentArg) with NamedMixin {
def forElement(element: ElementBase) = new GlobalComplexTypeDef(xml, schemaDocument, element)
}
class GlobalComplexTypeDef(xmlArg: Node, schemaDocumentArg: SchemaDocument, val element: ElementBase)
extends ComplexTypeBase(xmlArg, schemaDocumentArg)
with GlobalComponentMixin {
lazy val referringComponent = Some(element)
}
class LocalComplexTypeDef(xmlArg: Node, val element: ElementBase)
extends ComplexTypeBase(xmlArg, element)
with LocalComponentMixin {
//nothing
}