package net.sf.saxon.expr;
import net.sf.saxon.om.Item;
import net.sf.saxon.om.NamePool;
import net.sf.saxon.sort.IntHashMap;
import net.sf.saxon.trans.DynamicError;
import net.sf.saxon.trans.StaticError;
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.
*/
public final class CastExpression extends UnaryExpression {
static IntHashMap castingTable = new IntHashMap(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 = Type.UNTYPED_ATOMIC;
final int str = Type.STRING;
final int flt = Type.FLOAT;
final int dbl = Type.DOUBLE;
final int dec = Type.DECIMAL;
final int ing = Type.INTEGER;
final int dur = Type.DURATION;
final int ymd = Type.YEAR_MONTH_DURATION;
final int dtd = Type.DAY_TIME_DURATION;
final int dtm = Type.DATE_TIME;
final int tim = Type.TIME;
final int dat = Type.DATE;
final int gym = Type.G_YEAR_MONTH;
final int gyr = Type.G_YEAR;
final int gmd = Type.G_MONTH_DAY;
final int gdy = Type.G_DAY;
final int gmo = Type.G_MONTH;
final int boo = Type.BOOLEAN;
final int b64 = Type.BASE64_BINARY;
final int hxb = Type.HEX_BINARY;
final int uri = Type.ANY_URI;
final int qnm = Type.QNAME;
final int not = Type.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, not};
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);
final int[] t17 = {uat, str, not};
addAllowedCasts(not, t17);
}
/**
* 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 == Type.ANY_ATOMIC || source == Type.EMPTY) {
return true;
}
int[] targets = (int[])castingTable.get(source);
if (targets == null) {
return false;
}
for (int i=0; i<targets.length; i++) {
if (targets[i] == target) {
return true;
}
}
return false;
}
private AtomicType targetType;
private AtomicType targetPrimitiveType;
private boolean allowEmpty = false;
private boolean derived = false;
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);
}
/**
* Handle a cast to QName or NOTATION. The argument must be a string literal.
*/
public AtomicValue doQNameCast(StaticContext env) throws XPathException {
if (!(operand instanceof StringValue)) {
throw new StaticError("The argument of a QName or NOTATION constructor must be a string literal");
}
return QNameValue.castToQName((StringValue)operand, targetType, env);
}
/**
* Simplify the expression
* @return the simplified expression
*/
public Expression simplify(StaticContext env) throws XPathException {
if ((targetType instanceof BuiltInAtomicType) && !env.isAllowedBuiltInType(targetType)) {
// this is checked here because the ConstructorFunctionLibrary doesn't have access to the static
// context at bind time
StaticError err = new StaticError("The type " + targetType.getDisplayName() +
" is not recognized by a Basic XSLT Processor", this);
err.setErrorCode("XPST0080");
throw err;
}
operand = operand.simplify(env);
if (operand instanceof AtomicValue) {
return typeCheck(env, Type.ITEM_TYPE);
}
return this;
}
/**
* Type-check the expression
*/
public Expression typeCheck(StaticContext env, ItemType contextItemType) throws XPathException {
operand = operand.typeCheck(env, contextItemType);
SequenceType atomicType = SequenceType.makeSequenceType(Type.ANY_ATOMIC_TYPE, getCardinality());
RoleLocator role = new RoleLocator(RoleLocator.TYPE_OP, "cast as", 0, null);
role.setSourceLocator(this);
operand = TypeChecker.staticTypeCheck(operand, atomicType, false, role, env);
final TypeHierarchy th = env.getNamePool().getTypeHierarchy();
ItemType sourceType = operand.getItemType(th);
if (th.isSubType(sourceType, targetType)) {
return operand;
// It's not entirely clear that the spec permits this. Perhaps we should change the type label?
// On the other hand, it's generally true that any expression defined to return an X
// is allowed to return a subtype of X.
}
if (targetType.isNamespaceSensitive()) {
return new CastAsQName(operand, targetType).analyze(env, contextItemType);
}
if (operand instanceof AtomicValue) {
return (AtomicValue)evaluateItem(env.makeEarlyEvaluationContext());
}
if (operand instanceof EmptySequence) {
if (allowEmpty) {
return operand;
} else {
StaticError err = new StaticError("Cast can never succeed: the operand must not be an empty sequence");
err.setErrorCode("XPTY0004");
err.setIsTypeError(true);
throw err;
}
}
int p = sourceType.getPrimitiveType();
if (!isPossibleCast(p, targetType.getPrimitiveType())) {
StaticError err = new StaticError("Casting from " + sourceType + " to " + targetType +
" can never succeed");
err.setErrorCode("XPTY0004");
err.setIsTypeError(true);
throw err;
}
return this;
}
/**
* Get the static cardinality of the expression
*/
public int computeCardinality() {
return (allowEmpty ? StaticProperty.ALLOWS_ZERO_OR_ONE : StaticProperty.EXACTLY_ONE);
}
/**
* Get the static type of the expression
* @param th
*/
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 {
DynamicError e = new DynamicError("Cast does not allow an empty sequence");
e.setXPathContext(context);
throw e;
}
}
AtomicValue result = value.convert(targetPrimitiveType, context, true);
if (result instanceof ValidationErrorValue) {
XPathException err = ((ValidationErrorValue)result).getException();
String code = err.getErrorCodeLocalPart();
dynamicError(err.getMessage(), code, context);
}
if (derived) {
result = result.convert(targetType, context, true);
if (result instanceof ValidationErrorValue) {
XPathException err = ((ValidationErrorValue)result).getException();
String code = err.getErrorCodeLocalPart();
dynamicError(err.getMessage(), code, context);
}
}
return 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;
}
/**
* Give a string representation of the operator for use in diagnostics
* @return the operator, as a string
*/
protected String displayOperator(NamePool pool) {
return "cast as " + targetType.toString(pool);
}
}
//
// The contents of this file are subject to the Mozilla Public License Version 1.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.mozilla.org/MPL/
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied.
// See the License for the specific language governing rights and limitations under the License.
//
// The Original Code is: all this file.
//
// The Initial Developer of the Original Code is Michael H. Kay
//
// Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
//
// Contributor(s): none.
//
|