| /* Copyright (c) 2012-2015 Tresys Technology, LLC. All rights reserved. |
| * |
| * Developed by: Tresys Technology, LLC |
| * http://www.tresys.com |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a copy of |
| * this software and associated documentation files (the "Software"), to deal with |
| * the Software without restriction, including without limitation the rights to |
| * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies |
| * of the Software, and to permit persons to whom the Software is furnished to do |
| * so, subject to the following conditions: |
| * |
| * 1. Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimers. |
| * |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimers in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * 3. Neither the names of Tresys Technology, nor the names of its contributors |
| * may be used to endorse or promote products derived from this Software |
| * without specific prior written permission. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| * CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE |
| * SOFTWARE. |
| */ |
| |
| package edu.illinois.ncsa.daffodil.dpath |
| |
| import edu.illinois.ncsa.daffodil.exceptions.ThrowsSDE |
| import java.text.ParsePosition |
| import com.ibm.icu.util.Calendar |
| import com.ibm.icu.text.SimpleDateFormat |
| import java.text.ParseException |
| import edu.illinois.ncsa.daffodil.calendar.DFDLDateTime |
| import edu.illinois.ncsa.daffodil.calendar.DFDLTime |
| import edu.illinois.ncsa.daffodil.calendar.DFDLDate |
| |
| /* |
| * Casting chart taken from http://www.w3.org/TR/xpath-functions/#casting, with |
| * types DFDL does not allow removed. |
| * |
| * S\T bool dat dbl dec dT flt hxB int str tim |
| * bool Y N Y Y N Y N Y Y N |
| * dat N Y N N Y N N N Y N |
| * dbl Y N Y M N Y N M Y N |
| * dec Y N Y Y N Y N Y Y N |
| * dT N Y N N Y N N N Y Y |
| * flt Y N Y M N Y N M Y N |
| * hxB N N N N N N Y N Y N |
| * int Y N Y Y N Y N Y Y N |
| * str M M M M M M M M Y M |
| * tim N N N N N N N N Y Y |
| */ |
| |
| object Conversion { |
| |
| /** |
| * Computes additional recipe ops to be added to the end of an argument Recipe |
| * to convert the type. ST is the start type, the type the recipe has as its |
| * output type. TT is the target type desired. |
| */ |
| def conversionOps(st: NodeInfo.Kind, tt: NodeInfo.Kind, context: ThrowsSDE): List[RecipeOp] = { |
| import NodeInfo._ |
| val ops: List[RecipeOp] = (st, tt) match { |
| case (_, Array) => Nil |
| case (x, y) if (x == y) => Nil |
| case (x, Nillable) => { |
| // we don't have enough information here to check whether the |
| // item under scrutiny is nillable or not. |
| Nil |
| } |
| case (Nothing, x) => Nil |
| case (x, AnyType) => Nil |
| case (x: AnySimpleType.Kind, AnySimpleExists) => Nil |
| case (x: AnyAtomic.Kind, AnyAtomic) => Nil |
| case (x: Numeric.Kind, String) => List(NumericToString) |
| |
| case (Numeric, _) => NumericToString +: conversionOps(String, tt, context) |
| // |
| // Because of round-half-to-even, we need to know what the type of the original |
| // argument is. Doing these auto-conversions to Decimal hide this. |
| // |
| // Valid Numeric types are xs:float, xs:double, xs:integer and any sub-types. |
| // |
| // case (Float, n: Numeric.Kind) => FloatToDouble +: conversionOps(Double, tt, context) |
| // case (Integer, n: Numeric.Kind) => IntegerToDecimal +: conversionOps(Decimal, tt, context) |
| // case (Double, Numeric) => List(DoubleToDecimal) |
| case (n: NodeInfo.Numeric.Kind, Numeric) => Nil |
| |
| case (x: AnyDateTime.Kind, String) => List(DateTimeToString) |
| case (HexBinary, String) => List(HexBinaryToString) |
| case (String, NonEmptyString) => List(StringToNonEmptyString) |
| case (x, NonEmptyString) => conversionOps(st, String, context) :+ StringToNonEmptyString |
| case (x, ArrayIndex) => conversionOps(st, Long, context) ++ List(LongToArrayIndex) |
| case (Boolean, Double) => BooleanToLong +: conversionOps(Long, tt, context) |
| case (Boolean, Decimal) => BooleanToLong +: conversionOps(Long, tt, context) |
| case (Boolean, Float) => BooleanToLong +: conversionOps(Long, tt, context) |
| case (Boolean, Integer) => BooleanToLong +: conversionOps(Long, tt, context) |
| case (Boolean, NonNegativeInteger) => BooleanToLong +: conversionOps(Long, tt, context) |
| case (Boolean, Long) => List(BooleanToLong) |
| case (Boolean, UnsignedLong) => List(BooleanToLong) // no need to range check. |
| case (Boolean, Int) => BooleanToLong +: conversionOps(Long, tt, context) |
| case (Boolean, UnsignedInt) => BooleanToLong +: conversionOps(Long, tt, context) |
| case (Boolean, Short) => BooleanToLong +: conversionOps(Long, tt, context) |
| case (Boolean, UnsignedShort) => BooleanToLong +: conversionOps(Long, tt, context) |
| case (Boolean, Byte) => BooleanToLong +: conversionOps(Long, tt, context) |
| case (Boolean, UnsignedByte) => BooleanToLong +: conversionOps(Long, tt, context) |
| case (Boolean, String) => List(BooleanToString) |
| case (Date, DateTime) => List(DateToDateTime) |
| case (Double, Boolean) => DoubleToLong +: conversionOps(Long, tt, context) |
| case (Double, Decimal) => List(DoubleToDecimal) |
| case (Double, Float) => List(DoubleToFloat) |
| case (Double, Integer) => DoubleToDecimal +: conversionOps(Decimal, tt, context) |
| case (Double, NonNegativeInteger) => DoubleToDecimal +: conversionOps(Decimal, tt, context) |
| case (Double, Long) => List(DoubleToLong) |
| case (Double, UnsignedLong) => List(DoubleToUnsignedLong) |
| case (Double, i: Int.Kind) => DoubleToLong +: conversionOps(Long, tt, context) |
| case (Double, ui: UnsignedInt.Kind) => DoubleToUnsignedLong +: conversionOps(UnsignedLong, tt, context) |
| |
| case (Float, n: Numeric.Kind) => FloatToDouble +: conversionOps(Double, tt, context) |
| case (Float, Boolean) => FloatToDouble +: conversionOps(Double, tt, context) |
| |
| case (Decimal, Boolean) => DecimalToLong +: conversionOps(Long, tt, context) |
| case (Decimal, Integer) => List(DecimalToInteger) |
| case (Decimal, NonNegativeInteger) => List(DecimalToNonNegativeInteger) |
| case (Decimal, Double) => List(DecimalToDouble) |
| case (Decimal, Float) => DecimalToDouble +: conversionOps(Double, tt, context) |
| case (Decimal, Long) => List(DecimalToLong) |
| case (Decimal, UnsignedLong) => List(DecimalToUnsignedLong) |
| case (Decimal, Int) => List(DecimalToLong, LongToInt) |
| case (Decimal, UnsignedInt) => List(DecimalToLong, LongToUnsignedInt) |
| case (Decimal, Short) => List(DecimalToLong, LongToShort) |
| case (Decimal, UnsignedShort) => List(DecimalToLong, LongToUnsignedShort) |
| case (Decimal, Byte) => List(DecimalToLong, LongToByte) |
| case (Decimal, UnsignedByte) => List(DecimalToLong, LongToUnsignedByte) |
| |
| case (Integer, n: NodeInfo.Numeric.Kind) => IntegerToDecimal +: conversionOps(Decimal, tt, context) |
| |
| case (NonNegativeInteger, n: Numeric.Kind) => IntegerToDecimal +: conversionOps(Decimal, tt, context) |
| // Commented these out because these are illegal conversions for XPath functions. |
| // I cannot pass a DateTime as a Date argument to an XPath function. |
| // |
| //case (DateTime, Date) => List(DateTimeToDate) |
| //case (DateTime, Time) => List(DateTimeToTime) |
| //case (Time, DateTime) => List(TimeToDateTime) |
| case (Integer, Boolean) => IntegerToDecimal +: conversionOps(Decimal, tt, context) |
| case (String, Double) => List(StringToDouble) |
| case (String, Decimal) => List(StringToDecimal) |
| case (String, Float) => List(StringToDouble, DoubleToFloat) |
| case (String, Integer) => List(StringToDecimal, DecimalToInteger) |
| case (String, NonNegativeInteger) => List(StringToDecimal, DecimalToNonNegativeInteger) |
| case (String, Long) => List(StringToLong) |
| case (String, UnsignedLong) => List(StringToUnsignedLong) |
| case (String, Int) => List(StringToLong, LongToInt) |
| case (String, UnsignedInt) => List(StringToLong, LongToUnsignedInt) |
| case (String, Short) => List(StringToLong, LongToShort) |
| case (String, UnsignedShort) => List(StringToLong, LongToUnsignedShort) |
| case (String, Byte) => List(StringToLong, LongToByte) |
| case (String, UnsignedByte) => List(StringToLong, LongToUnsignedByte) |
| case (String, HexBinary) => List(XSHexBinary.asInstanceOf[RecipeOp]) // TODO: figure out why I had to put this cast in place. |
| case (String, Date) => List(StringToDate) |
| case (String, Time) => List(StringToTime) |
| case (String, DateTime) => List(StringToDateTime) |
| case (String, Boolean) => List(StringToBoolean) |
| case (Byte, Long) => List(ByteToLong) |
| |
| case (Long, Boolean) => List(LongToBoolean) |
| case (x: Long.Kind, Boolean) => conversionOps(st, Long, context) ++ conversionOps(Long, tt, context) |
| case (Long, Integer) => List(LongToInteger) |
| case (Long, NonNegativeInteger) => List(LongToNonNegativeInteger) |
| case (Long, Decimal) => List(LongToDecimal) |
| case (Long, UnsignedLong) => List(LongToUnsignedLong) |
| case (Long, Int) => List(LongToInt) |
| case (Long, UnsignedInt) => List(LongToUnsignedInt) |
| case (Long, Short) => List(LongToShort) |
| case (Long, UnsignedShort) => List(LongToUnsignedShort) |
| case (Long, Byte) => List(LongToByte) |
| case (Long, UnsignedByte) => List(LongToUnsignedByte) |
| case (Long, Double) => List(LongToDouble) |
| case (Long, Float) => LongToDouble +: conversionOps(Double, tt, context) |
| |
| case (Int, Double) => IntToLong +: conversionOps(Long, tt, context) |
| case (Int, Decimal) => IntToLong +: conversionOps(Long, tt, context) |
| case (Int, Float) => IntToLong +: conversionOps(Long, tt, context) |
| case (Int, Integer) => IntToLong +: conversionOps(Long, tt, context) |
| case (Int, NonNegativeInteger) => IntToLong +: conversionOps(Long, tt, context) |
| case (Int, Long) => List(IntToLong) |
| case (Int, UnsignedLong) => IntToLong +: conversionOps(Long, tt, context) |
| case (Int, UnsignedInt) => IntToLong +: conversionOps(Long, tt, context) |
| case (Int, Short) => IntToLong +: conversionOps(Long, tt, context) |
| case (Int, UnsignedShort) => IntToLong +: conversionOps(Long, tt, context) |
| case (Int, Byte) => IntToLong +: conversionOps(Long, tt, context) |
| case (Int, UnsignedByte) => IntToLong +: conversionOps(Long, tt, context) |
| |
| case (UnsignedInt, Double) => UnsignedIntToLong +: conversionOps(Long, tt, context) |
| case (UnsignedInt, Decimal) => UnsignedIntToLong +: conversionOps(Long, tt, context) |
| case (UnsignedInt, Float) => UnsignedIntToLong +: conversionOps(Long, tt, context) |
| case (UnsignedInt, Integer) => UnsignedIntToLong +: conversionOps(Long, tt, context) |
| case (UnsignedInt, NonNegativeInteger) => UnsignedIntToLong +: conversionOps(Long, tt, context) |
| case (UnsignedInt, Long) => List(UnsignedIntToLong) |
| case (UnsignedInt, UnsignedLong) => List(UnsignedIntToLong) // no need to range check. |
| case (UnsignedInt, Int) => UnsignedIntToLong +: conversionOps(Long, tt, context) |
| case (UnsignedInt, Short) => UnsignedIntToLong +: conversionOps(Long, tt, context) |
| case (UnsignedInt, UnsignedShort) => UnsignedIntToLong +: conversionOps(Long, tt, context) |
| case (UnsignedInt, Byte) => UnsignedIntToLong +: conversionOps(Long, tt, context) |
| case (UnsignedInt, UnsignedByte) => UnsignedIntToLong +: conversionOps(Long, tt, context) |
| |
| case (UnsignedLong, Double) => UnsignedLongToLong +: conversionOps(Long, tt, context) |
| case (UnsignedLong, Decimal) => UnsignedLongToLong +: conversionOps(Long, tt, context) |
| case (UnsignedLong, Float) => UnsignedLongToLong +: conversionOps(Long, tt, context) |
| case (UnsignedLong, Integer) => UnsignedLongToLong +: conversionOps(Long, tt, context) |
| case (UnsignedLong, NonNegativeInteger) => UnsignedLongToLong +: conversionOps(Long, tt, context) |
| case (UnsignedLong, Long) => List(UnsignedLongToLong) |
| case (UnsignedLong, Int) => UnsignedLongToLong +: conversionOps(Long, tt, context) |
| case (UnsignedLong, Short) => UnsignedLongToLong +: conversionOps(Long, tt, context) |
| case (UnsignedLong, UnsignedShort) => UnsignedLongToLong +: conversionOps(Long, tt, context) |
| case (UnsignedLong, Byte) => UnsignedLongToLong +: conversionOps(Long, tt, context) |
| case (UnsignedLong, UnsignedByte) => UnsignedLongToLong +: conversionOps(Long, tt, context) |
| |
| case (ArrayIndex, Double) => ArrayIndexToLong +: conversionOps(Long, tt, context) |
| case (ArrayIndex, Decimal) => ArrayIndexToLong +: conversionOps(Long, tt, context) |
| case (ArrayIndex, Float) => ArrayIndexToLong +: conversionOps(Long, tt, context) |
| case (ArrayIndex, Integer) => ArrayIndexToLong +: conversionOps(Long, tt, context) |
| case (ArrayIndex, NonNegativeInteger) => ArrayIndexToLong +: conversionOps(Long, tt, context) |
| case (ArrayIndex, Long) => List(ArrayIndexToLong) |
| case (ArrayIndex, UnsignedLong) => List(ArrayIndexToLong) // no need to range check. |
| case (ArrayIndex, Int) => ArrayIndexToLong +: conversionOps(Long, tt, context) |
| case (ArrayIndex, UnsignedInt) => ArrayIndexToLong +: conversionOps(Long, tt, context) |
| case (ArrayIndex, Short) => ArrayIndexToLong +: conversionOps(Long, tt, context) |
| case (ArrayIndex, UnsignedShort) => ArrayIndexToLong +: conversionOps(Long, tt, context) |
| case (ArrayIndex, Byte) => ArrayIndexToLong +: conversionOps(Long, tt, context) |
| case (ArrayIndex, UnsignedByte) => ArrayIndexToLong +: conversionOps(Long, tt, context) |
| |
| case (Short, Double) => ShortToLong +: conversionOps(Long, tt, context) |
| case (Short, Decimal) => ShortToLong +: conversionOps(Long, tt, context) |
| case (Short, Float) => ShortToLong +: conversionOps(Long, tt, context) |
| case (Short, Integer) => ShortToLong +: conversionOps(Long, tt, context) |
| case (Short, NonNegativeInteger) => ShortToLong +: conversionOps(Long, tt, context) |
| case (Short, Long) => List(ShortToLong) |
| case (Short, UnsignedLong) => List(ShortToLong) // no need to range check. |
| case (Short, Int) => ShortToLong +: conversionOps(Long, tt, context) |
| case (Short, UnsignedInt) => ShortToLong +: conversionOps(Long, tt, context) |
| case (Short, UnsignedShort) => ShortToLong +: conversionOps(Long, tt, context) |
| case (Short, Byte) => ShortToLong +: conversionOps(Long, tt, context) |
| case (Short, UnsignedByte) => ShortToLong +: conversionOps(Long, tt, context) |
| |
| case (UnsignedShort, Double) => UnsignedShortToLong +: conversionOps(Long, tt, context) |
| case (UnsignedShort, Decimal) => UnsignedShortToLong +: conversionOps(Long, tt, context) |
| case (UnsignedShort, Float) => UnsignedShortToLong +: conversionOps(Long, tt, context) |
| case (UnsignedShort, Integer) => UnsignedShortToLong +: conversionOps(Long, tt, context) |
| case (UnsignedShort, NonNegativeInteger) => UnsignedShortToLong +: conversionOps(Long, tt, context) |
| case (UnsignedShort, Long) => List(UnsignedShortToLong) |
| case (UnsignedShort, UnsignedLong) => List(UnsignedShortToLong) // no need to range check. |
| case (UnsignedShort, Int) => UnsignedShortToLong +: conversionOps(Long, tt, context) |
| case (UnsignedShort, UnsignedInt) => UnsignedShortToLong +: conversionOps(Long, tt, context) |
| case (UnsignedShort, Short) => UnsignedShortToLong +: conversionOps(Long, tt, context) |
| case (UnsignedShort, Byte) => UnsignedShortToLong +: conversionOps(Long, tt, context) |
| case (UnsignedShort, UnsignedByte) => UnsignedShortToLong +: conversionOps(Long, tt, context) |
| |
| case (Byte, Double) => ByteToLong +: conversionOps(Long, tt, context) |
| case (Byte, Decimal) => ByteToLong +: conversionOps(Long, tt, context) |
| case (Byte, Float) => ByteToLong +: conversionOps(Long, tt, context) |
| case (Byte, Integer) => ByteToLong +: conversionOps(Long, tt, context) |
| case (Byte, NonNegativeInteger) => ByteToLong +: conversionOps(Long, tt, context) |
| case (Byte, UnsignedLong) => List(ByteToLong) // no need to range check. |
| case (Byte, Int) => ByteToLong +: conversionOps(Long, tt, context) |
| case (Byte, UnsignedInt) => ByteToLong +: conversionOps(Long, tt, context) |
| case (Byte, Short) => ByteToLong +: conversionOps(Long, tt, context) |
| case (Byte, UnsignedShort) => ByteToLong +: conversionOps(Long, tt, context) |
| case (Byte, Byte) => ByteToLong +: conversionOps(Long, tt, context) |
| case (Byte, UnsignedByte) => ByteToLong +: conversionOps(Long, tt, context) |
| |
| case (UnsignedByte, Double) => UnsignedByteToLong +: conversionOps(Long, tt, context) |
| case (UnsignedByte, Decimal) => UnsignedByteToLong +: conversionOps(Long, tt, context) |
| case (UnsignedByte, Float) => UnsignedByteToLong +: conversionOps(Long, tt, context) |
| case (UnsignedByte, Integer) => UnsignedByteToLong +: conversionOps(Long, tt, context) |
| case (UnsignedByte, NonNegativeInteger) => UnsignedByteToLong +: conversionOps(Long, tt, context) |
| case (UnsignedByte, Long) => List(UnsignedByteToLong) |
| case (UnsignedByte, UnsignedLong) => List(UnsignedByteToLong) // no need to range check. |
| case (UnsignedByte, Int) => UnsignedByteToLong +: conversionOps(Long, tt, context) |
| case (UnsignedByte, UnsignedInt) => UnsignedByteToLong +: conversionOps(Long, tt, context) |
| case (UnsignedByte, Short) => UnsignedByteToLong +: conversionOps(Long, tt, context) |
| case (UnsignedByte, UnsignedShort) => UnsignedByteToLong +: conversionOps(Long, tt, context) |
| case (UnsignedByte, Byte) => UnsignedByteToLong +: conversionOps(Long, tt, context) |
| |
| // Note: These are explicitly not allowed |
| // case (AnyAtomic, Time) => AnyAtomicToString +: conversionOps(String, tt, context) |
| // case (AnyAtomic, Date) => AnyAtomicToString +: conversionOps(String, tt, context) |
| // case (AnyAtomic, DateTime) => AnyAtomicToString +: conversionOps(String, tt, context) |
| |
| // TODO: Is this a valid solution for DFDL-1074 and the like? |
| // Essentially, all of the 'types' should fall under AnyAtomic and there |
| // is no need to conver to an AnyAtomic. So anything converted to AnyAtomic |
| // should be itself. |
| // |
| case (_, AnyAtomic) => Nil // is this correct? |
| |
| case (_, Exists) => Nil |
| case (_, other) => context.SDE("The type %s cannot be converted to %s.", st.name, tt.name) |
| } |
| ops |
| } |
| |
| protected def constructCalendar(value: String, inFormat: SimpleDateFormat, fncName: String, toType: String): Calendar = { |
| val isLenient = inFormat.isLenient() |
| |
| try { |
| inFormat.setLenient(false) |
| inFormat.parse(value) |
| true |
| } catch { |
| case e: ParseException => { |
| inFormat.setLenient(isLenient) |
| throw new java.lang.IllegalArgumentException(String.format("Conversion Error: %s failed to convert \"%s\" to %s. Due to %s", |
| fncName, value.toString, toType, "Failed to match format.")) |
| } |
| } |
| |
| val calendar = inFormat.getCalendar() |
| val pos = new ParsePosition(0) |
| |
| inFormat.parse(value.toString, calendar, pos) |
| |
| try { |
| calendar.getTime() |
| } catch { |
| case ex: java.lang.IllegalArgumentException => { |
| inFormat.setLenient(isLenient) |
| throw new java.lang.IllegalArgumentException(String.format("Conversion Error: %s failed to convert \"%s\" to %s. Due to %s", |
| fncName, value.toString, toType, ex.getMessage())) |
| } |
| } |
| |
| inFormat.setLenient(isLenient) |
| |
| if (pos.getIndex() == 0 || pos.getErrorIndex() != -1) { |
| throw new java.lang.IllegalArgumentException(String.format("Conversion Error: %s failed to convert \"%s\" to %s due to a parse error.", |
| fncName, value.toString, toType)) |
| } |
| if (pos.getIndex() != (value.length())) { |
| throw new java.lang.IllegalArgumentException(String.format("Conversion Error: %s failed to convert \"%s\" to %s. Failed to use up all characters in the parse. Stopped at %s.", |
| fncName, value.toString, toType, pos.getIndex().toString)) |
| } |
| calendar |
| } |
| |
| def stringToDFDLDateTime(value: String, inFormat: SimpleDateFormat, expectsTZ: Boolean, fncName: String, toType: String): DFDLDateTime = { |
| val calendar = constructCalendar(value, inFormat, fncName, toType) |
| DFDLDateTime(calendar, expectsTZ) |
| } |
| |
| def stringToDFDLDate(value: String, inFormat: SimpleDateFormat, expectsTZ: Boolean, fncName: String, toType: String): DFDLDate = { |
| val calendar = constructCalendar(value, inFormat, fncName, toType) |
| DFDLDate(calendar, expectsTZ) |
| } |
| |
| def stringToDFDLTime(value: String, inFormat: SimpleDateFormat, expectsTZ: Boolean, fncName: String, toType: String): DFDLTime = { |
| val calendar = constructCalendar(value, inFormat, fncName, toType) |
| DFDLTime(calendar, expectsTZ) |
| } |
| } |