package client.net.sf.saxon.ce.expr; import client.net.sf.saxon.ce.functions.StringFn; import client.net.sf.saxon.ce.om.*; import client.net.sf.saxon.ce.trans.XPathException; import client.net.sf.saxon.ce.type.*; import client.net.sf.saxon.ce.value.*; import client.net.sf.saxon.ce.value.StringValue; import java.util.HashMap; /** * Cast Expression: implements "cast as data-type ( expression )". It also allows an internal * cast, which has the same semantics as a user-requested cast, but maps an empty sequence to * an empty sequence. */ public final class CastExpression extends UnaryExpression { private AtomicType targetType; private AtomicType targetPrimitiveType; private boolean allowEmpty = false; private boolean derived = false; private boolean upcast = false; static HashMap<Integer, int[]> castingTable = new HashMap(25); static void addAllowedCasts(int source, int[] target) { castingTable.put(source, target); } /** * The following data represents all the "Y" and "M" entries in section 17.1 of the F+O spec. */ static { final int uat = StandardNames.XS_UNTYPED_ATOMIC; final int str = StandardNames.XS_STRING; final int flt = StandardNames.XS_FLOAT; final int dbl = StandardNames.XS_DOUBLE; final int dec = StandardNames.XS_DECIMAL; final int ing = StandardNames.XS_INTEGER; final int dur = StandardNames.XS_DURATION; final int ymd = StandardNames.XS_YEAR_MONTH_DURATION; final int dtd = StandardNames.XS_DAY_TIME_DURATION; final int dtm = StandardNames.XS_DATE_TIME; final int tim = StandardNames.XS_TIME; final int dat = StandardNames.XS_DATE; final int gym = StandardNames.XS_G_YEAR_MONTH; final int gyr = StandardNames.XS_G_YEAR; final int gmd = StandardNames.XS_G_MONTH_DAY; final int gdy = StandardNames.XS_G_DAY; final int gmo = StandardNames.XS_G_MONTH; final int boo = StandardNames.XS_BOOLEAN; final int b64 = StandardNames.XS_BASE64_BINARY; final int hxb = StandardNames.XS_HEX_BINARY; final int uri = StandardNames.XS_ANY_URI; final int qnm = StandardNames.XS_QNAME; //final int not = StandardNames.XS_NOTATION; final int[] t01 = {uat, str, flt, dbl, dec, ing, dur, ymd, dtd, dtm, tim, dat, gym, gyr, gmd, gdy, gmo, boo, b64, hxb, uri}; addAllowedCasts(uat, t01); final int[] t02 = {uat, str, flt, dbl, dec, ing, dur, ymd, dtd, dtm, tim, dat, gym, gyr, gmd, gdy, gmo, boo, b64, hxb, uri, qnm}; addAllowedCasts(str, t02); final int[] t03 = {uat, str, flt, dbl, dec, ing, boo}; addAllowedCasts(flt, t03); addAllowedCasts(dbl, t03); addAllowedCasts(dec, t03); addAllowedCasts(ing, t03); final int[] t04 = {uat, str, dur, ymd, dtd}; addAllowedCasts(dur, t04); addAllowedCasts(ymd, t04); addAllowedCasts(dtd, t04); final int[] t05 = {uat, str, dtm, tim, dat, gym, gyr, gmd, gdy, gmo}; addAllowedCasts(dtm, t05); final int[] t06 = {uat, str, tim}; addAllowedCasts(tim, t06); final int[] t07 = {uat, str, dtm, dat, gym, gyr, gmd, gdy, gmo}; addAllowedCasts(dat, t07); final int[] t08 = {uat, str, gym}; addAllowedCasts(gym, t08); final int[] t09 = {uat, str, gyr}; addAllowedCasts(gyr, t09); final int[] t10 = {uat, str, gmd}; addAllowedCasts(gmd, t10); final int[] t11 = {uat, str, gdy}; addAllowedCasts(gdy, t11); final int[] t12 = {uat, str, gmo}; addAllowedCasts(gmo, t12); final int[] t13 = {uat, str, flt, dbl, dec, ing, boo}; addAllowedCasts(boo, t13); final int[] t14 = {uat, str, b64, hxb}; addAllowedCasts(b64, t14); addAllowedCasts(hxb, t14); final int[] t15 = {uat, str, uri}; addAllowedCasts(uri, t15); final int[] t16 = {uat, str, qnm}; addAllowedCasts(qnm, t16); } /** * Determine whether casting from a source type to a target type is possible * @param source a primitive type (one that has an entry in the casting table) * @param target another primitive type * @return true if the entry in the casting table is either "Y" (casting always succeeds) * or "M" (casting allowed but may fail for some values) */ public static boolean isPossibleCast(int source, int target) { if (source == StandardNames.XS_ANY_ATOMIC_TYPE || source == Type.EMPTY) { return true; } if (source == StandardNames.XS_NUMERIC) { source = StandardNames.XS_DOUBLE; } int[] targets = castingTable.get(source); if (targets == null) { return false; } for (int i=0; i<targets.length; i++) { if (targets[i] == target) { return true; } } return false; } /** * Create a cast expression * @param source expression giving the value to be converted * @param target the type to which the value is to be converted * @param allowEmpty true if the expression allows an empty sequence as input, producing * an empty sequence as output. If false, an empty sequence is a type error. */ public CastExpression(Expression source, AtomicType target, boolean allowEmpty) { super(source); this.allowEmpty = allowEmpty; targetType = target; targetPrimitiveType = (AtomicType)target.getPrimitiveItemType(); derived = (targetType.getFingerprint() != targetPrimitiveType.getFingerprint()); adoptChildExpression(source); } /** * Get the target type (the result type) * @return the target type */ public AtomicType getTargetType() { return targetType; } /** * Simplify the expression * @return the simplified expression * @param visitor an expression visitor */ public Expression simplify(ExpressionVisitor visitor) throws XPathException { operand = visitor.simplify(operand); if (Literal.isAtomic(operand)) { return typeCheck(visitor, Type.ITEM_TYPE); } return this; } /** * Type-check the expression */ public Expression typeCheck(ExpressionVisitor visitor, ItemType contextItemType) throws XPathException { operand = visitor.typeCheck(operand, contextItemType); SequenceType atomicType = SequenceType.makeSequenceType(BuiltInAtomicType.ANY_ATOMIC, getCardinality()); RoleLocator role = new RoleLocator(RoleLocator.TYPE_OP, "cast as", 0); //role.setSourceLocator(this); operand = TypeChecker.staticTypeCheck(operand, atomicType, false, role, visitor); final TypeHierarchy th = visitor.getConfiguration().getTypeHierarchy(); ItemType sourceType = operand.getItemType(th); int r = th.relationship(sourceType, targetType); if (r == TypeHierarchy.SAME_TYPE) { return operand; } else if (r == TypeHierarchy.SUBSUMED_BY) { // It's generally true that any expression defined to return an X is allowed to return a subtype of X. // However, people seem to get upset if we treat the cast as a no-op. upcast = true; return this; } if (operand instanceof Literal) { Value literalOperand = ((Literal)operand).getValue(); if (literalOperand instanceof AtomicValue) { AtomicValue av = ((AtomicValue)evaluateItem(visitor.getStaticContext().makeEarlyEvaluationContext())); if (av instanceof StringValue) { return new StringLiteral((StringValue)av); } else { return new Literal(av); } } if (literalOperand instanceof EmptySequence) { if (allowEmpty) { return operand; } else { typeError("Cast can never succeed: the operand must not be an empty sequence", "XPTY0004", null); } } } int p = sourceType.getPrimitiveType(); if (!isPossibleCast(p, targetType.getPrimitiveType())) { typeError("Casting from " + sourceType + " to " + targetType + " can never succeed", "XPTY0004", null); } return this; } /** * Perform optimisation of an expression and its subexpressions. * <p/> * <p>This method is called after all references to functions and variables have been resolved * to the declaration of the function or variable, and after all type checking has been done.</p> * @param visitor an expression visitor * @param contextItemType the static type of "." at the point where this expression is invoked. * The parameter is set to null if it is known statically that the context item will be undefined. * If the type of the context item is not known statically, the argument is set to * {@link client.net.sf.saxon.ce.type.Type#ITEM_TYPE} * @return the original expression, rewritten if appropriate to optimize execution * @throws client.net.sf.saxon.ce.trans.XPathException * if an error is discovered during this phase * (typically a type error) */ public Expression optimize(ExpressionVisitor visitor, ItemType contextItemType) throws XPathException { final TypeHierarchy th = visitor.getConfiguration().getTypeHierarchy(); Expression e2 = super.optimize(visitor, contextItemType); if (e2 != this) { return e2; } // Eliminate pointless casting between untypedAtomic and string if (targetType == BuiltInAtomicType.UNTYPED_ATOMIC) { if (operand instanceof StringFn) { Expression e = ((StringFn)operand).getArguments()[0]; if (e.getItemType(th) instanceof AtomicType && e.getCardinality() == StaticProperty.EXACTLY_ONE) { operand = e; } } } // avoid converting anything to a string and then back again if (operand instanceof StringFn) { Expression e = ((StringFn)operand).getArguments()[0]; ItemType et = e.getItemType(th); if (et instanceof AtomicType && e.getCardinality() == StaticProperty.EXACTLY_ONE && th.isSubType(et, targetType)) { return e; } } // avoid converting anything to untypedAtomic and then back again if (operand instanceof CastExpression) { ItemType it = ((CastExpression)operand).targetType; if (th.isSubType(it, BuiltInAtomicType.STRING) || th.isSubType(it, BuiltInAtomicType.UNTYPED_ATOMIC)) { Expression e = ((CastExpression)operand).getBaseExpression(); ItemType et = e.getItemType(th); if (et instanceof AtomicType && e.getCardinality() == StaticProperty.EXACTLY_ONE && th.isSubType(et, targetType)) { return e; } } } // if the operand can't be empty, then set allowEmpty to false to provide more information for analysis if (!Cardinality.allowsZero(operand.getCardinality())) { allowEmpty = false; resetLocalStaticProperties(); } return this; } /** * Get the static cardinality of the expression */ public int computeCardinality() { return (allowEmpty && Cardinality.allowsZero(operand.getCardinality()) ? StaticProperty.ALLOWS_ZERO_OR_ONE : StaticProperty.EXACTLY_ONE); } /** * Get the static type of the expression * @param th the type hierarchy cache */ public ItemType getItemType(TypeHierarchy th) { return targetType; } /** * Determine the special properties of this expression * @return {@link StaticProperty#NON_CREATIVE}. */ public int computeSpecialProperties() { int p = super.computeSpecialProperties(); return p | StaticProperty.NON_CREATIVE; } /** * Evaluate the expression */ public Item evaluateItem(XPathContext context) throws XPathException { AtomicValue value = (AtomicValue)operand.evaluateItem(context); if (value==null) { if (allowEmpty) { return null; } else { XPathException e = new XPathException("Cast does not allow an empty sequence"); e.setXPathContext(context); e.setLocator(getSourceLocator()); e.setErrorCode("XPTY0004"); throw e; } } if (upcast) { // When casting to a supertype of the original type, we can bypass validation AtomicValue result = (AtomicValue)value.convert(targetPrimitiveType, false); if (derived) { result = (AtomicValue)result.convert(targetType, false); } return result; } ConversionResult result = value.convert(targetType, true); if (result instanceof ValidationFailure) { ValidationFailure err = (ValidationFailure)result; String code = err.getErrorCode(); if (code == null) { code = "FORG0001"; } dynamicError(err.getMessage(), code, context); return null; } return (AtomicValue)result; } /** * Is this expression the same as another expression? */ public boolean equals(Object other) { return super.equals(other) && targetType == ((CastExpression)other).targetType && allowEmpty == ((CastExpression)other).allowEmpty; } /** * get HashCode for comparing two expressions. Note that this hashcode gives the same * result for (A op B) and for (B op A), whether or not the operator is commutative. */ @Override public int hashCode() { return super.hashCode() ^ targetType.hashCode(); } /** * The toString() method for an expression attempts to give a representation of the expression * in an XPath-like form, but there is no guarantee that the syntax will actually be true XPath. * In the case of XSLT instructions, the toString() method gives an abstracted view of the syntax */ public String toString() { try { NamePool pool = getExecutable().getConfiguration().getNamePool(); return targetType.toString(pool) + "(" + operand.toString() + ")"; } catch (Exception err) { return targetType.toString() + "(" + operand.toString() + ")"; } } /** * Evaluate the "pseudo-cast" of a string literal to a QName or NOTATION value. This can only happen * at compile time * @param operand the value to be converted * @param targetType the type to which it is to be converted * @param env the static context * @return the QName or NOTATION value that results from casting the string to a QName. * This will either be a QNameValue or a derived AtomicValue derived from QName or NOTATION */ public static AtomicValue castStringToQName( CharSequence operand, AtomicType targetType, StaticContext env) throws XPathException { try { CharSequence arg = Whitespace.trimWhitespace(operand); String parts[] = NameChecker.getQNameParts(arg); String uri; if (parts[0].length() == 0) { uri = env.getDefaultElementNamespace(); } else { try { uri = env.getURIForPrefix(parts[0]); } catch (XPathException e) { uri = null; } if (uri == null) { XPathException e = new XPathException("Prefix '" + parts[0] + "' has not been declared"); e.setErrorCode("FONS0004"); throw e; } } return new QNameValue(parts[0], uri, parts[1], BuiltInAtomicType.QNAME, true); } catch (XPathException err) { if (err.getErrorCodeQName() == null) { err.setErrorCode("FONS0004"); } throw err; } catch (QNameException err) { XPathException e = new XPathException(err); e.setErrorCode("FORG0001"); throw e; } } } // This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. // If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. // This Source Code Form is “Incompatible With Secondary Licenses”, as defined by the Mozilla Public License, v. 2.0.