net.sf.saxon.expr.CastExpression Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of saxon-he Show documentation
Show all versions of saxon-he Show documentation
An OSGi bundle for Saxon-HE
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2013 Saxonica Limited.
// 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.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
package net.sf.saxon.expr;
import net.sf.saxon.expr.parser.ExpressionVisitor;
import net.sf.saxon.expr.parser.RoleLocator;
import net.sf.saxon.expr.parser.TypeChecker;
import net.sf.saxon.functions.StringFn;
import net.sf.saxon.lib.ConversionRules;
import net.sf.saxon.om.GroundedValue;
import net.sf.saxon.om.StandardNames;
import net.sf.saxon.trace.ExpressionPresenter;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.type.*;
import net.sf.saxon.value.*;
/**
* 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.
*
* This expression class does not handle casting to a union type.
*/
public class CastExpression extends CastingExpression {
/**
* 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, target, allowEmpty);
}
/**
* Type-check the expression
*/
/*@NotNull*/
public Expression typeCheck(ExpressionVisitor visitor, ExpressionVisitor.ContextItemType contextItemType) throws XPathException {
operand = visitor.typeCheck(operand, contextItemType);
SequenceType atomicType = SequenceType.makeSequenceType(BuiltInAtomicType.ANY_ATOMIC, getCardinality());
final TypeHierarchy th = visitor.getConfiguration().getTypeHierarchy();
RoleLocator role = new RoleLocator(RoleLocator.TYPE_OP, "cast as", 0);
ItemType sourceItemType = null;
// if (getTargetType().isNamespaceSensitive()) {
// int rel = th.relationship(operand.getItemType(th), AnyNodeTest.getInstance());
// if (rel == TypeHierarchy.SAME_TYPE || rel == TypeHierarchy.SUBSUMED_BY) {
// // See spec bug 11964
// XPathException err = new XPathException("Atomization is not allowed when casting to QName or NOTATION");
// err.setErrorCode("XPTY0004");
// err.setLocator(this);
// err.setIsTypeError(true);
// throw err;
// } else if (rel != TypeHierarchy.DISJOINT) {
// operand = new ItemChecker(operand, BuiltInAtomicType.ANY_ATOMIC, role);
// }
// sourceItemType = BuiltInAtomicType.ANY_ATOMIC;
// } else {
operand = TypeChecker.staticTypeCheck(operand, atomicType, false, role, visitor);
sourceItemType = operand.getItemType(th);
// }
if (sourceItemType instanceof ErrorType) {
if (allowsEmpty()) {
return Literal.makeEmptySequence();
} else {
XPathException err = new XPathException("Cast does not allow an empty sequence as input");
err.setErrorCode("XPTY0004");
err.setLocator(this);
err.setIsTypeError(true);
throw err;
}
}
AtomicType sourceType = (AtomicType) sourceItemType;
int r = th.relationship(sourceType, getTargetType());
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.
converter = new Converter.UpCastingConverter(getTargetType());
} else {
ConversionRules rules = visitor.getConfiguration().getConversionRules();
if (!(sourceType == BuiltInAtomicType.ANY_ATOMIC || sourceType.getFingerprint() == Type.EMPTY)) {
converter = rules.getConverter(sourceType, getTargetType());
if (converter == null) {
XPathException err = new XPathException("Casting from " + sourceType + " to " + getTargetType() +
" can never succeed");
err.setErrorCode("XPTY0004");
err.setLocator(this);
err.setIsTypeError(true);
throw err;
} else {
if (getTargetType().isNamespaceSensitive()) {
converter.setNamespaceResolver(nsResolver);
}
}
if (converter.isXPath30Conversion() && !visitor.getStaticContext().getXPathLanguageLevel().equals(DecimalValue.THREE)
&& !(operand instanceof Literal && getTargetType().getPrimitiveType() == StandardNames.XS_QNAME)) {
XPathException err = new XPathException("Casting from " + sourceType + " to " + getTargetType() +
" requires XPath 3.0 functionality to be enabled");
err.setErrorCode("XPTY0004");
err.setLocator(this);
err.setIsTypeError(true);
throw err;
}
}
}
if (operand instanceof Literal) {
return preEvaluate();
}
return this;
}
/**
* Perform optimisation of an expression and its subexpressions.
*
* 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.
* @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 net.sf.saxon.type.Type#ITEM_TYPE}
* @return the original expression, rewritten if appropriate to optimize execution
* @throws net.sf.saxon.trans.XPathException
* if an error is discovered during this phase
* (typically a type error)
*/
/*@NotNull*/
public Expression optimize(ExpressionVisitor visitor, ExpressionVisitor.ContextItemType 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 (getTargetType() == 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;
}
} else if (operand instanceof CastExpression) {
if (((CastExpression)operand).getTargetType() == BuiltInAtomicType.UNTYPED_ATOMIC) {
return operand;
} else if (((CastExpression)operand).getTargetType() == BuiltInAtomicType.STRING) {
((CastExpression)operand).setTargetType(BuiltInAtomicType.UNTYPED_ATOMIC);
return operand;
}
} else if (operand instanceof AtomicSequenceConverter) {
if (operand.getItemType(th) == BuiltInAtomicType.UNTYPED_ATOMIC) {
return operand;
} else if (operand.getItemType(th) == BuiltInAtomicType.STRING) {
AtomicSequenceConverter old = (AtomicSequenceConverter)operand;
AtomicSequenceConverter asc = new AtomicSequenceConverter(
old.getBaseExpression(),
BuiltInAtomicType.UNTYPED_ATOMIC
);
return asc.typeCheck(visitor, contextItemType)
.optimize(visitor, contextItemType);
}
}
}
// 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, getTargetType())) {
return e;
}
}
// avoid converting anything to untypedAtomic and then back again
if (operand instanceof CastExpression) {
ItemType it = ((CastExpression)operand).getTargetType();
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, getTargetType())) {
return e;
}
}
}
if (operand instanceof AtomicSequenceConverter) {
ItemType it = operand.getItemType(th);
if (th.isSubType(it, BuiltInAtomicType.STRING) || th.isSubType(it, BuiltInAtomicType.UNTYPED_ATOMIC)) {
Expression e = ((AtomicSequenceConverter)operand).getBaseExpression();
ItemType et = e.getItemType(th);
if (et instanceof AtomicType &&
e.getCardinality() == StaticProperty.EXACTLY_ONE &&
th.isSubType(et, getTargetType())) {
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())) {
setAllowEmpty(false);
resetLocalStaticProperties();
}
if (operand instanceof Literal) {
return preEvaluate();
}
return this;
}
/**
* Perform early (compile-time) evaluation
*/
protected Expression preEvaluate() throws XPathException {
GroundedValue literalOperand = ((Literal)operand).getValue();
if (literalOperand instanceof AtomicValue && converter != null) {
if (converter.isXPath30Conversion() && !getExecutable().isAllowXPath30() &&
!(converter instanceof StringConverter.StringToQName && isOperandIsStringLiteral())) {
XPathException e = new XPathException("Casting from " + ((AtomicValue)literalOperand).getPrimitiveType() + " to " + getTargetType() + " requires XPath 3.0 to be enabled");
e.setLocator(this);
e.setErrorCode("XPTY0004");
throw e;
}
ConversionResult result = converter.convert((AtomicValue)literalOperand);
if (result instanceof ValidationFailure) {
ValidationFailure err = (ValidationFailure)result;
String code = err.getErrorCode();
if (code == null) {
code = "FORG0001";
}
XPathException xpe = new XPathException(err.getMessage(), this);
xpe.setErrorCode(code);
throw xpe;
} else {
return Literal.makeLiteral((AtomicValue)result);
}
}
if (literalOperand instanceof EmptySequence) {
if (allowsEmpty()) {
return operand;
} else {
XPathException err = new XPathException("Cast can never succeed: the operand must not be an empty sequence");
err.setErrorCode("XPTY0004");
err.setLocator(this);
err.setIsTypeError(true);
throw err;
}
}
return this;
}
/**
* Get the static cardinality of the expression
*/
public int computeCardinality() {
return (allowsEmpty() && 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
*/
/*@NotNull*/
public ItemType getItemType(TypeHierarchy th) {
return getTargetType();
}
/**
* For an expression that returns an integer or a sequence of integers, get
* a lower and upper bound on the values of the integers that may be returned, from
* static analysis. The default implementation returns null, meaning "unknown" or
* "not applicable". Other implementations return an array of two IntegerValue objects,
* representing the lower and upper bounds respectively. The values
* UNBOUNDED_LOWER and UNBOUNDED_UPPER are used by convention to indicate that
* the value may be arbitrarily large. The values MAX_STRING_LENGTH and MAX_SEQUENCE_LENGTH
* are used to indicate values limited by the size of a string or the size of a sequence.
*
* @return the lower and upper bounds of integer values in the result, or null to indicate
* unknown or not applicable.
*/
/*@Nullable*/@Override
public IntegerValue[] getIntegerBounds() {
if (converter == Converter.BOOLEAN_TO_INTEGER) {
return new IntegerValue[]{Int64Value.ZERO, Int64Value.PLUS_ONE};
} else {
return null;
}
}
/**
* Copy an expression. This makes a deep copy.
*
* @return the copy of the original expression
*/
/*@NotNull*/
public Expression copy() {
CastExpression c2 = new CastExpression(getBaseExpression().copy(), getTargetType(), allowsEmpty());
c2.converter = converter;
c2.nsResolver = nsResolver;
c2.setOperandIsStringLiteral(isOperandIsStringLiteral());
return c2;
}
/**
* Evaluate the expression
*/
/*@Nullable*/ public AtomicValue evaluateItem(XPathContext context) throws XPathException {
AtomicValue value = (AtomicValue)operand.evaluateItem(context);
if (value==null) {
if (allowsEmpty()) {
return null;
} else {
XPathException e = new XPathException("Cast does not allow an empty sequence");
e.setXPathContext(context);
e.setLocator(this);
e.setErrorCode("XPTY0004");
throw e;
}
}
Converter converter = this.converter;
if (converter == null) {
ConversionRules rules = context.getConfiguration().getConversionRules();
converter = rules.getConverter(value.getPrimitiveType(), getTargetType());
if (converter == null) {
XPathException e = new XPathException("Casting from " + value.getPrimitiveType() + " to " + getTargetType() + " is not permitted");
e.setXPathContext(context);
e.setLocator(this);
e.setErrorCode("XPTY0004");
throw e;
}
if (converter.isXPath30Conversion() && !getExecutable().isAllowXPath30() &&
!(converter instanceof StringConverter.StringToQName && isOperandIsStringLiteral())) {
XPathException e = new XPathException("Casting from " + value.getPrimitiveType() + " to " + getTargetType() + " requires XPath 3.0 to be enabled");
e.setXPathContext(context);
e.setLocator(this);
e.setErrorCode("XPTY0004");
throw e;
}
if (nsResolver != null) {
converter.setNamespaceResolver(nsResolver);
}
}
ConversionResult result = converter.convert(value);
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 other instanceof CastExpression &&
operand.equals(((CastExpression)other).operand) &&
getTargetType() == ((CastExpression)other).getTargetType() &&
getTargetType() == ((CastExpression)other).getTargetType();
}
/**
* 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() ^ getTargetType().hashCode();
}
/**
* Represent the expression as a string. The resulting string will be a valid XPath 3.0 expression
* with no dependencies on namespace bindings.
* @return the expression as a string in XPath 3.0 syntax
*/
public String toString() {
return getTargetType().getEQName() + "(" + operand.toString() + ")";
}
/**
* Diagnostic print of expression structure. The abstract expression tree
* is written to the supplied output destination.
*/
public void explain(ExpressionPresenter out) {
out.startElement("cast");
out.emitAttribute("as", getTargetType().getEQName());
operand.explain(out);
out.endElement();
}
}