blob: 970a4e34ef9d23ed728e08d0fdcf915be93372ff [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.processors
import scala.collection.immutable.HashMap
import scala.collection.immutable.HashSet
import org.apache.daffodil.dpath.DState
import org.apache.daffodil.dpath.NodeInfo
import org.apache.daffodil.dsom.CompiledExpression
import org.apache.daffodil.exceptions.Assert
import org.apache.daffodil.processors.parsers.PState
import org.apache.daffodil.processors.parsers.ParseError
import org.apache.daffodil.processors.unparsers.UState
import org.apache.daffodil.util.Maybe
import org.apache.daffodil.util.Maybe.One
import org.apache.daffodil.util.PreSerialization
import org.apache.daffodil.util.RangeBound
import org.apache.daffodil.util.TransientParam
import org.apache.daffodil.xml.QNameBase
abstract class TypeCalculator[A <: AnyRef, B <: AnyRef](val srcType: NodeInfo.Kind, val dstType: NodeInfo.Kind)
extends PreSerialization {
type Error = String
override def preSerialization: Any = {
super.preSerialization
}
/*
* We can be used from both a parser directly, and as part of a DPath expression.
* There are 2 main differences that require handling these cases seperatly
*
* 1) A (un)parser expects errors to be reported via state.setFailed, while DPath expects subexpressions to report
* error by throwing
* 2) DPathCompiledExpressions provides a top level interface to initiate evaluation which accepts a ParseOrUnparseState,
* and a second interface to evaluate subexpressions which accepts a DState
*/
def inputTypeCalc(x: A, xType: NodeInfo.Kind): (Maybe[B], Maybe[Error])
def outputTypeCalc(x: B, xType: NodeInfo.Kind): (Maybe[A], Maybe[Error])
def inputTypeCalcParse(pstate: PState, context: RuntimeData, x: A, xType: NodeInfo.Kind): Maybe[B] = {
val (ans, err) = inputTypeCalc(x, xType)
Assert.invariant(ans.isDefined ^ err.isDefined)
if (err.isDefined) {
val diag = new ParseError(Maybe(context.schemaFileLocation), Maybe(pstate.currentLocation), err.get)
pstate.setFailed(diag)
}
//In event of an error, we still want to return Maybe.Nope, which happens
//to be what ans would have
ans
}
def outputTypeCalcUnparse(ustate: UState, context: RuntimeData, x: B, xType: NodeInfo.Kind): Maybe[A] = {
val (ans, err) = outputTypeCalc(x, xType)
Assert.invariant(ans.isDefined ^ err.isDefined)
if (err.isDefined) {
val diag = new ParseError(Maybe(context.schemaFileLocation), Maybe(ustate.currentLocation), err.get)
ustate.setFailed(diag)
}
//In event of an error, we still want to return Maybe.Nope, which happens
//to be what ans would have
ans
}
/*
* It appears that dpath expressions actually throw errors.
* See the definition of DAFError
*/
def inputTypeCalcRun(dstate: DState, x: A, xType: NodeInfo.Kind): Unit = {
val context = dstate.runtimeData.get
val (ans, err) = inputTypeCalc(x, xType)
Assert.invariant(ans.isDefined ^ err.isDefined)
if (err.isDefined) {
val diag = new ParseError(Maybe(context.schemaFileLocation), dstate.contextLocation, err.get)
throw diag
// throw FNErrorFunctionException(Maybe(context.schemaFileLocation), dstate.contextLocation, err.get)
}
dstate.setCurrentValue(ans.get)
}
def outputTypeCalcRun(dstate: DState, x: B, xType: NodeInfo.Kind): Unit = {
val context = dstate.runtimeData.get
val (ans, err) = outputTypeCalc(x, xType)
Assert.invariant(ans.isDefined ^ err.isDefined)
if (err.isDefined) {
val diag = new ParseError(Maybe(context.schemaFileLocation), dstate.contextLocation, err.get)
throw diag
}
dstate.setCurrentValue(ans.get)
}
def supportsParse: Boolean = true
def supportsUnparse: Boolean = true
@throws(classOf[java.io.IOException])
final private def writeObject(out: java.io.ObjectOutputStream): Unit = serializeObject(out)
}
class KeysetValueTypeCalculatorOrdered[A <: AnyRef, B <: AnyRef](valueMap: HashMap[A, B], rangeTable: Seq[(RangeBound[A], RangeBound[A], B)], unparseMap: HashMap[B, A],
srcType: NodeInfo.Kind, dstType: NodeInfo.Kind)
extends TypeCalculator[A, B](srcType, dstType) {
override def inputTypeCalc(x: A, xType: NodeInfo.Kind): (Maybe[B], Maybe[Error]) = {
if (valueMap.contains(x)) {
(One(valueMap.get(x).get), Maybe.Nope)
} else {
val ans1: Option[(RangeBound[A], RangeBound[A], B)] = rangeTable.find({
case (min, max, _) => {
min.testAsLower(x) && max.testAsUpper(x)
}
})
ans1 match {
case None => {
(Maybe.Nope, One(s"Key ${x} not found in keyset-value mapping"))
}
case Some((_, _, v)) => (Maybe(v), Maybe.Nope)
}
}
}
override def outputTypeCalc(x: B, xType: NodeInfo.Kind): (Maybe[A], Maybe[Error]) = {
unparseMap.get(x) match {
case Some(v) => (Maybe(v), Maybe.Nope)
case None => (Maybe.Nope, One(s"Value ${x} not found in keyset-value mapping"))
}
}
}
class KeysetValueTypeCalculatorUnordered[A <: AnyRef, B <: AnyRef](valueMap: HashMap[A, B], unparseMap: HashMap[B, A], srcType: NodeInfo.Kind, dstType: NodeInfo.Kind)
extends TypeCalculator[A, B](srcType, dstType) {
override def inputTypeCalc(x: A, xType: NodeInfo.Kind): (Maybe[B], Maybe[Error]) = {
if (valueMap.contains(x)) {
valueMap.get(x) match {
case Some(a) => (Maybe(a), Maybe.Nope)
case None => (Maybe.Nope, One(s"Value ${x} not found in keyset-value mapping"))
}
} else {
(Maybe.Nope, One(s"Key ${x} not found in keyset-value mapping"))
}
}
override def outputTypeCalc(x: B, xType: NodeInfo.Kind): (Maybe[A], Maybe[Error]) = {
unparseMap.get(x) match {
case Some(v) => (Maybe(v), Maybe.Nope)
case None => (Maybe.Nope, One(s"Value ${x} not found in keyset-value mapping"))
}
}
}
class ExpressionTypeCalculator[A <: AnyRef, B <: AnyRef](
@TransientParam maybeInputTypeCalcArg: => Maybe[ CompiledExpression[B]],
@TransientParam maybeOutputTypeCalcArg: => Maybe[CompiledExpression[A]],
srcType: NodeInfo.Kind, dstType: NodeInfo.Kind)
extends TypeCalculator[A, B](srcType, dstType) {
override def supportsParse = maybeInputTypeCalcArg.isDefined
override def supportsUnparse = maybeOutputTypeCalcArg.isDefined
/*
* Compiling DPath expressions may need to evaluate typeCalculators in order to lookup their srcType and dstType.
* To prevent circular dependencies, this means that the underlying expressions must be lazy.
*
* Since these fields must be lazy, we cannot use them to determine supportsParse or supportUnparse
*/
lazy val maybeInputTypeCalc = maybeInputTypeCalcArg
lazy val maybeOutputTypeCalc = maybeOutputTypeCalcArg
override def preSerialization: Any = {
super.preSerialization
maybeInputTypeCalc
maybeOutputTypeCalc
}
//The class TypeValueCalc will verify that supports(Un)Parse is true when nessasary
//Therefore, if we ever call the below functions, we know that the relevent Maybe object is defined.
//TODO, pass x into DPath state
override def inputTypeCalc(x: A, xType: NodeInfo.Kind): (Maybe[B], Maybe[Error]) =
Assert.invariantFailed("inputTypeCalc not implemented on ExpressionTypeCalculator. Call the more specialized forms directly")
override def outputTypeCalc(x: B, xType: NodeInfo.Kind): (Maybe[A], Maybe[Error]) =
Assert.invariantFailed("outputTypeCalc not implemented on ExpressionTypeCalculator. Call the more specialized forms directly")
override def inputTypeCalcParse(state: PState, context: RuntimeData, x: A, xType: NodeInfo.Kind): Maybe[B] = {
val dstate = state.dState
val oldRepValue = dstate.repValue
val oldLogicalValue = dstate.logicalValue
dstate.repValue = One(x)
dstate.logicalValue = Maybe.Nope
val ans = Maybe(maybeInputTypeCalc.get.evaluate(state))
dstate.repValue = oldRepValue
dstate.logicalValue = oldLogicalValue
ans
}
override def outputTypeCalcUnparse(state: UState, context: RuntimeData, x: B, xType: NodeInfo.Kind): Maybe[A] = {
val dstate = state.dState
val oldRepValue = dstate.repValue
val oldLogicalValue = dstate.logicalValue
dstate.repValue = Maybe.Nope
dstate.logicalValue = One(x)
val ans = Maybe(maybeOutputTypeCalc.get.evaluate(state))
dstate.repValue = oldRepValue
dstate.logicalValue = oldLogicalValue
ans
}
override def inputTypeCalcRun(dstate: DState, x: A, xType: NodeInfo.Kind): Unit = {
val oldRepValue = dstate.repValue
val oldLogicalValue = dstate.logicalValue
dstate.repValue = One(x)
dstate.logicalValue = Maybe.Nope
maybeInputTypeCalc.get.run(dstate)
dstate.repValue = oldRepValue
dstate.logicalValue = oldLogicalValue
}
override def outputTypeCalcRun(dstate: DState, x: B, xType: NodeInfo.Kind): Unit = {
val oldRepValue = dstate.repValue
val oldLogicalValue = dstate.logicalValue
dstate.repValue = Maybe.Nope
dstate.logicalValue = One(x)
maybeOutputTypeCalc.get.run(dstate)
dstate.repValue = oldRepValue
dstate.logicalValue = oldLogicalValue
}
}
class IdentifyTypeCalculator[A <: AnyRef](srcType: NodeInfo.Kind) extends TypeCalculator[A, A](srcType, srcType) {
override def inputTypeCalc(x: A, xType: NodeInfo.Kind): (Maybe[A], Maybe[Error]) = (Maybe(x), Maybe.Nope)
override def outputTypeCalc(x: A, xType: NodeInfo.Kind): (Maybe[A], Maybe[Error]) = (Maybe(x), Maybe.Nope)
}
class UnionTypeCalculator[A <: AnyRef, B <: AnyRef](subCalculators: Seq[(RepValueSet[A], RepValueSet[B], TypeCalculator[A, B])], srcType: NodeInfo.Kind, dstType: NodeInfo.Kind)
extends TypeCalculator[A, B](srcType, dstType) {
//TODO, it may be worth it to pre-compute a hash table for direct dispatch,
//Similar to how keyset-value works
override def inputTypeCalc(x: A, xType: NodeInfo.Kind): (Maybe[B], Maybe[Error]) = {
val subCalcSeq = subCalculators.filter(sub => sub._1.contains(x))
Assert.invariant(subCalcSeq.length <= 1)
if (subCalcSeq.isEmpty) {
(Maybe.Nope, One(s"Key ${x} does not match any component of this simpleType union"))
} else {
val subCalc = subCalcSeq.head._3
subCalc.inputTypeCalc(x, xType)
}
}
override def outputTypeCalc(x: B, xType: NodeInfo.Kind): (Maybe[A], Maybe[Error]) = {
val subCalcSeq = subCalculators.filter(sub => sub._2.contains(x))
Assert.invariant(subCalcSeq.length <= 1)
if (subCalcSeq.isEmpty) {
(Maybe.Nope, One(s"Key ${x} does not match the logical values from any component of this union."))
} else {
val subCalc = subCalcSeq.head._3
subCalc.outputTypeCalc(x, xType)
}
}
}
/*
* Since we can inherit the restriction from xsd facets, we also need to be able to support an
* aribitrary subset of: minInclusive, minExclusive, maxInclusive, and maxExclusive
*/
class RepValueSet[A <: AnyRef](val valueSet: HashSet[A], val valueRanges: Set[(RangeBound[A], RangeBound[A])]) extends Serializable {
def contains(x: A): Boolean = {
val ans1 = valueSet.contains(x)
if (ans1) {
ans1
} else {
valueRanges.map({
case (min, max) =>
min.testAsLower(x) && max.testAsUpper(x)
}).fold(false)(_ || _)
}
}
def merge(other: RepValueSet[A]): RepValueSet[A] = {
val valueSet_ = valueSet ++ other.valueSet
val valueRanges_ = valueRanges ++ other.valueRanges
new RepValueSet(valueSet_, valueRanges_)
}
lazy val isEmpty: Boolean = valueSet.isEmpty && valueRanges.isEmpty
}
//TODO, many of the key/values we receive will be BigInt.
//We should check if we can safely convert them to Long
object RepValueSetCompiler {
def compile[A <: AnyRef](valueSet: Seq[A], valuesRanges: Seq[(RangeBound[A], RangeBound[A])]): RepValueSet[A] = {
val hashSet = HashSet.empty ++ valueSet
val rangeSet = Set.empty ++ valuesRanges.filter(x => x._1.isDefined || x._2.isDefined)
new RepValueSet(hashSet, rangeSet)
}
def empty[A <: AnyRef]: RepValueSet[A] = compile(Seq.empty, Seq.empty)
}
object TypeCalculatorCompiler {
type TypeCalcMap = Map[QNameBase, TypeCalculator[AnyRef, AnyRef]]
// mappings: [(keySet, canonicalKey, value)]
def compileKeysetValue[A <: AnyRef, B <: AnyRef](mappings: Seq[(RepValueSet[A], A, B)], srcType: NodeInfo.Kind, dstType: NodeInfo.Kind): TypeCalculator[A, B] = {
Assert.invariant(!mappings.isEmpty)
/*
* We need to cast to HashMap, because the type of HashMap.++ returns a generic Map
* HashMap.+ returns a HashMap, so we can avoid the case by doing the fold ourself
*/
val valueMap: HashMap[A, B] = (HashMap.empty ++ mappings.flatMap(x => {
val (keySet, _, value) = x
keySet.valueSet.map((_, value))
})).asInstanceOf[HashMap[A, B]]
val rangeTable: Seq[(RangeBound[A], RangeBound[A], B)] = mappings.flatMap(x => {
val (keySet, _, value) = x
keySet.valueRanges.map({ case (min, max) => (min, max, value) })
})
val unparseMap: HashMap[B, A] = (HashMap.empty ++ mappings.map(x => {
val (_, canonicalKey, value) = x
(value, canonicalKey)
})).asInstanceOf[HashMap[B, A]]
/*
* Type erasure makes dispatching based on if we have an ordered keyset or not difficult
* Really, the problem is that we should not be using AnyRef at all,
* but since the DPath library is based around AnyRef, we are stuck with it
*/
rangeTable match {
case Seq() => new KeysetValueTypeCalculatorUnordered(valueMap, unparseMap, srcType, dstType)
case _ => {
new KeysetValueTypeCalculatorOrdered(valueMap, rangeTable, unparseMap, srcType, dstType)
}
}
}
/*
* compileExpression is already a highly overloaded name from the DPath expression compiler.
* While this technically overload that function, to avoid confusion, we are giving it a different
* name entirely.
*/
def compileTypeCalculatorFromExpression[A <: AnyRef, B <: AnyRef](
optInputTypeCalc: => Option[CompiledExpression[B]],
optOutputTypeCalc: => Option[CompiledExpression[A]],
srcType: NodeInfo.Kind, dstType: NodeInfo.Kind): ExpressionTypeCalculator[A, B] = {
lazy val maybeInputType: Maybe[CompiledExpression[B]] = optInputTypeCalc.map(Maybe(_)).getOrElse(Maybe.Nope)
lazy val maybeOutputType: Maybe[CompiledExpression[A]] = optOutputTypeCalc.map(Maybe(_)).getOrElse(Maybe.Nope)
new ExpressionTypeCalculator(maybeInputType, maybeOutputType, srcType, dstType)
}
def compileIdentity[A <: AnyRef](srcType: NodeInfo.Kind): TypeCalculator[A, A] = new IdentifyTypeCalculator(srcType)
//subCalculators: Seq[(repValues, logicalValues, subCalc)]
def compileUnion[A <: AnyRef, B <: AnyRef](subCalculators: Seq[(RepValueSet[A], RepValueSet[B], TypeCalculator[A, B])]): TypeCalculator[A, B] = {
//TODO, in some cases, it may be possible to merge some subCalculators
//It may also be possible to compute a direct dispatch table
val types = subCalculators.map(x => (x._3.srcType, x._3.dstType))
val srcTypes = types.map(_._1)
val dstTypes = types.map(_._2)
val srcType = srcTypes.head
val dstType = dstTypes.head
Assert.invariant(srcTypes.map(_ == srcType).reduce(_ == _))
Assert.invariant(dstTypes.map(_ == dstType).reduce(_ == _))
new UnionTypeCalculator(subCalculators, srcType, dstType)
}
}