package net.sf.saxon.expr;
import net.sf.saxon.Err;
import net.sf.saxon.om.Item;
import net.sf.saxon.om.NamePool;
import net.sf.saxon.trans.DynamicError;
import net.sf.saxon.trans.StaticError;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.type.AtomicType;
import net.sf.saxon.type.ItemType;
import net.sf.saxon.type.Type;
import net.sf.saxon.type.TypeHierarchy;
import net.sf.saxon.value.*;
/**
* Arithmetic Expression: an expression using one of the operators
* plus, minus, multiply, div, idiv, mod.
*/
class ArithmeticExpression extends BinaryExpression {
private static final class Signature {
ItemType operand0;
ItemType operand1;
int operation;
ItemType resultType;
Signature(ItemType t0, ItemType t1, int op, ItemType r) {
operand0 = t0;
operand1 = t1;
operation = op;
resultType = r;
}
}
private static final int NUMERIC_ARITHMETIC = 0;
private static final int DATE_AND_DURATION = 1;
private static final int DATE_DIFFERENCE = 2;
private static final int DURATION_ADDITION = 3;
private static final int DURATION_MULTIPLICATION = 4;
private static final int DURATION_DIVISION = 5;
private static final int UNKNOWN = -1;
private static final int UNKNOWN_10 = -2;
private static final Signature[] plusTable = {
new Signature(Type.NUMBER_TYPE, Type.NUMBER_TYPE, NUMERIC_ARITHMETIC, Type.NUMBER_TYPE),
new Signature(Type.NUMBER_TYPE, Type.UNTYPED_ATOMIC_TYPE, NUMERIC_ARITHMETIC, Type.NUMBER_TYPE),
new Signature(Type.UNTYPED_ATOMIC_TYPE, Type.NUMBER_TYPE, NUMERIC_ARITHMETIC, Type.NUMBER_TYPE),
new Signature(Type.UNTYPED_ATOMIC_TYPE, Type.UNTYPED_ATOMIC_TYPE, NUMERIC_ARITHMETIC, Type.NUMBER_TYPE),
new Signature(Type.DATE_TYPE, Type.DURATION_TYPE, DATE_AND_DURATION, Type.DATE_TYPE),
new Signature(Type.DURATION_TYPE, Type.DATE_TYPE, DATE_AND_DURATION, Type.DATE_TYPE),
new Signature(Type.TIME_TYPE, Type.DURATION_TYPE, DATE_AND_DURATION, Type.TIME_TYPE),
new Signature(Type.DURATION_TYPE, Type.TIME_TYPE, DATE_AND_DURATION, Type.TIME_TYPE),
new Signature(Type.DATE_TIME_TYPE, Type.DURATION_TYPE, DATE_AND_DURATION, Type.DATE_TIME_TYPE),
new Signature(Type.DURATION_TYPE, Type.DATE_TIME_TYPE, DATE_AND_DURATION, Type.DATE_TIME_TYPE),
new Signature(Type.DURATION_TYPE, Type.DURATION_TYPE, DURATION_ADDITION, Type.DURATION_TYPE)
};
private static final Signature[] minusTable = {
new Signature(Type.NUMBER_TYPE, Type.NUMBER_TYPE, NUMERIC_ARITHMETIC, Type.NUMBER_TYPE),
new Signature(Type.NUMBER_TYPE, Type.UNTYPED_ATOMIC_TYPE, NUMERIC_ARITHMETIC, Type.NUMBER_TYPE),
new Signature(Type.UNTYPED_ATOMIC_TYPE, Type.NUMBER_TYPE, NUMERIC_ARITHMETIC, Type.NUMBER_TYPE),
new Signature(Type.UNTYPED_ATOMIC_TYPE, Type.UNTYPED_ATOMIC_TYPE, NUMERIC_ARITHMETIC, Type.NUMBER_TYPE),
new Signature(Type.DATE_TYPE, Type.DATE_TYPE, DATE_DIFFERENCE, Type.DAY_TIME_DURATION_TYPE),
new Signature(Type.DATE_TYPE, Type.DURATION_TYPE, DATE_AND_DURATION, Type.DATE_TYPE),
new Signature(Type.TIME_TYPE, Type.TIME_TYPE, DATE_DIFFERENCE, Type.DAY_TIME_DURATION_TYPE),
new Signature(Type.TIME_TYPE, Type.DURATION_TYPE, DATE_AND_DURATION, Type.TIME_TYPE),
new Signature(Type.DATE_TIME_TYPE, Type.DATE_TIME_TYPE, DATE_DIFFERENCE, Type.DAY_TIME_DURATION_TYPE),
new Signature(Type.DATE_TIME_TYPE, Type.DURATION_TYPE, DATE_AND_DURATION, Type.DATE_TIME_TYPE),
new Signature(Type.DURATION_TYPE, Type.DURATION_TYPE, DURATION_ADDITION, Type.DURATION_TYPE)
};
private static final Signature[] multiplyTable = {
new Signature(Type.NUMBER_TYPE, Type.NUMBER_TYPE, NUMERIC_ARITHMETIC, Type.NUMBER_TYPE),
new Signature(Type.NUMBER_TYPE, Type.UNTYPED_ATOMIC_TYPE, NUMERIC_ARITHMETIC, Type.NUMBER_TYPE),
new Signature(Type.UNTYPED_ATOMIC_TYPE, Type.NUMBER_TYPE, NUMERIC_ARITHMETIC, Type.NUMBER_TYPE),
new Signature(Type.UNTYPED_ATOMIC_TYPE, Type.UNTYPED_ATOMIC_TYPE, NUMERIC_ARITHMETIC, Type.NUMBER_TYPE),
new Signature(Type.NUMBER_TYPE, Type.DURATION_TYPE, DURATION_MULTIPLICATION, Type.DURATION_TYPE),
new Signature(Type.DURATION_TYPE, Type.NUMBER_TYPE, DURATION_MULTIPLICATION, Type.DURATION_TYPE)
};
private static final Signature[] divideTable = {
new Signature(Type.NUMBER_TYPE, Type.NUMBER_TYPE, NUMERIC_ARITHMETIC, Type.NUMBER_TYPE),
new Signature(Type.NUMBER_TYPE, Type.UNTYPED_ATOMIC_TYPE, NUMERIC_ARITHMETIC, Type.NUMBER_TYPE),
new Signature(Type.UNTYPED_ATOMIC_TYPE, Type.NUMBER_TYPE, NUMERIC_ARITHMETIC, Type.NUMBER_TYPE),
new Signature(Type.UNTYPED_ATOMIC_TYPE, Type.UNTYPED_ATOMIC_TYPE, NUMERIC_ARITHMETIC, Type.NUMBER_TYPE),
new Signature(Type.DURATION_TYPE, Type.NUMBER_TYPE, DURATION_MULTIPLICATION, Type.DURATION_TYPE),
new Signature(Type.DURATION_TYPE, Type.DURATION_TYPE, DURATION_DIVISION, Type.NUMBER_TYPE)
};
private static final Signature[] idivTable = {
new Signature(Type.NUMBER_TYPE, Type.NUMBER_TYPE, NUMERIC_ARITHMETIC, Type.INTEGER_TYPE),
new Signature(Type.NUMBER_TYPE, Type.UNTYPED_ATOMIC_TYPE, NUMERIC_ARITHMETIC, Type.INTEGER_TYPE),
new Signature(Type.UNTYPED_ATOMIC_TYPE, Type.NUMBER_TYPE, NUMERIC_ARITHMETIC, Type.INTEGER_TYPE),
new Signature(Type.UNTYPED_ATOMIC_TYPE, Type.UNTYPED_ATOMIC_TYPE, NUMERIC_ARITHMETIC, Type.INTEGER_TYPE)
};
private static final Signature[] modTable = {
new Signature(Type.NUMBER_TYPE, Type.NUMBER_TYPE, NUMERIC_ARITHMETIC, Type.NUMBER_TYPE),
new Signature(Type.NUMBER_TYPE, Type.UNTYPED_ATOMIC_TYPE, NUMERIC_ARITHMETIC, Type.NUMBER_TYPE),
new Signature(Type.UNTYPED_ATOMIC_TYPE, Type.NUMBER_TYPE, NUMERIC_ARITHMETIC, Type.NUMBER_TYPE),
new Signature(Type.UNTYPED_ATOMIC_TYPE, Type.UNTYPED_ATOMIC_TYPE, NUMERIC_ARITHMETIC, Type.NUMBER_TYPE)
};
private boolean backwardsCompatible = false;
public ArithmeticExpression(Expression p1, int operator, Expression p2) {
super(p1, operator, p2);
}
/**
* Type-check the expression statically. We try to work out which particular
* arithmetic function to use if the types of operands are known an compile time.
*/
public Expression typeCheck(StaticContext env, ItemType contextItemType) throws XPathException {
final TypeHierarchy th = env.getNamePool().getTypeHierarchy();
backwardsCompatible = env.isInBackwardsCompatibleMode();
operand0 = operand0.typeCheck(env, contextItemType);
operand1 = operand1.typeCheck(env, contextItemType);
SequenceType atomicType = SequenceType.OPTIONAL_ATOMIC;
RoleLocator role0 = new RoleLocator(RoleLocator.BINARY_EXPR, Token.tokens[operator], 0, null);
role0.setSourceLocator(this);
operand0 = TypeChecker.staticTypeCheck(operand0, atomicType, backwardsCompatible, role0, env);
// System.err.println("First operand"); operand0.display(10);
RoleLocator role1 = new RoleLocator(RoleLocator.BINARY_EXPR, Token.tokens[operator], 1, null);
role1.setSourceLocator(this);
operand1 = TypeChecker.staticTypeCheck(operand1, atomicType, backwardsCompatible, role1, env);
// System.err.println("Second operand"); operand1.display(10);
if (backwardsCompatible) {
Expression exp = new Arithmetic10(operand0, operator, operand1);
return exp.simplify(env).typeCheck(env, contextItemType);
}
Expression e = super.typeCheck(env, contextItemType);
if (e instanceof ArithmeticExpression) {
AtomicType type0 = (AtomicType)operand0.getItemType(th).getPrimitiveItemType();
AtomicType type1 = (AtomicType)operand1.getItemType(th).getPrimitiveItemType();
final int action = getAction(type0, operator, type1, backwardsCompatible);
switch (action) {
case NUMERIC_ARITHMETIC:
e = new NumericArithmetic(operand0, operator, operand1);
break;
case DURATION_ADDITION:
e = new DurationAddition(operand0, operator, operand1);
break;
case DURATION_MULTIPLICATION:
e = new DurationMultiplication(operand0, operator, operand1, env.getNamePool());
break;
case DURATION_DIVISION:
e = new DurationDivision(operand0, operator, operand1);
break;
case DATE_AND_DURATION:
e = new DateAndDuration(operand0, operator, operand1, env.getNamePool());
break;
case DATE_DIFFERENCE:
e = new DateDifference(operand0, operator, operand1);
break;
case UNKNOWN_10:
e = new Arithmetic10(operand0, operator, operand1);
break;
case UNKNOWN:
// either the types are not known yet, or they are wrong
if (type0.isAtomicType() &&
type0 != Type.UNTYPED_ATOMIC_TYPE &&
type0 != Type.ANY_ATOMIC_TYPE &&
type1.isAtomicType() &&
type1 != Type.UNTYPED_ATOMIC_TYPE &&
type1 != Type.ANY_ATOMIC_TYPE) {
StaticError err = new StaticError("Unsuitable operands for arithmetic operation (" +
type0.toString(env.getNamePool()) + ", " +
type1.toString(env.getNamePool()) + ')');
err.setErrorCode("XPTY0004");
err.setIsTypeError(true);
err.setLocator(ExpressionTool.getLocator(this));
throw err;
}
return e;
}
ExpressionTool.copyLocationInfo(this, e);
if (e instanceof ComputedExpression) {
((ComputedExpression)e).setParentExpression(getParentExpression());
}
try {
if (operand0 instanceof Value && operand1 instanceof Value) {
return ExpressionTool.eagerEvaluate(e, env.makeEarlyEvaluationContext());
}
} catch (DynamicError err) {
// Defer any error reporting until run-time
}
}
return e;
}
/**
* Decide what action is required based on types of operands
*/
private static int getAction(AtomicType type1, int operator, AtomicType type2, boolean backwardsCompatible) {
if (type1.getFingerprint() == Type.DAY_TIME_DURATION) {
type1 = Type.DURATION_TYPE;
} else if (type1.getFingerprint() == Type.YEAR_MONTH_DURATION) {
type1 = Type.DURATION_TYPE;
}
if (type2.getFingerprint() == Type.DAY_TIME_DURATION) {
type2 = Type.DURATION_TYPE;
} else if (type2.getFingerprint() == Type.YEAR_MONTH_DURATION) {
type2 = Type.DURATION_TYPE;
}
Signature[] table = getOperatorTable(operator);
int entry = getEntry(table, type1, type2);
if (entry < 0) {
return (backwardsCompatible ? UNKNOWN_10 : UNKNOWN);
}
return table[entry].operation;
}
private static Signature[] getOperatorTable(int operator) {
switch (operator) {
case Token.PLUS:
return plusTable;
case Token.MINUS:
case Token.NEGATE:
return minusTable;
case Token.MULT:
return multiplyTable;
case Token.DIV:
return divideTable;
case Token.IDIV:
return idivTable;
case Token.MOD:
return modTable;
default:
throw new IllegalArgumentException("Unknown arithmetic operator");
}
}
private static int getEntry(Signature[] table, ItemType type1, ItemType type2) {
if (Type.isNumericPrimitiveType(type1)) {
type1 = Type.NUMBER_TYPE;
}
if (Type.isNumericPrimitiveType(type2)) {
type2 = Type.NUMBER_TYPE;
}
for (int i = 0; i < table.length; i++) {
if (type1.equals(table[i].operand0) &&
type2.equals(table[i].operand1)) {
return i;
}
}
return -1;
}
/**
* Determine the data type of the expression, if this is known statically
* @param th
*/
public ItemType getItemType(TypeHierarchy th) {
final ItemType t1 = operand0.getItemType(th);
final ItemType pt1 = t1.getPrimitiveItemType();
final ItemType t2 = operand1.getItemType(th);
final ItemType pt2 = t2.getPrimitiveItemType();
final Signature[] table = getOperatorTable(operator);
final int entry = getEntry(table, pt1, pt2);
if (entry < 0) {
return Type.ANY_ATOMIC_TYPE; // type is not known statically
}
ItemType resultType = table[entry].resultType;
if (resultType == Type.NUMBER_TYPE) {
resultType = NumericValue.promote(pt1, pt2, th);
if (operator == Token.DIV && resultType == Type.INTEGER_TYPE) {
// exception: integer div integer => decimal
resultType = Type.DECIMAL_TYPE;
} else {
resultType = NumericValue.promote(pt1, pt2, th);
}
} else if (resultType == Type.DURATION_TYPE) {
// if one of the operands is a subtype of duration, then the result will be the same subtype
// (this isn't captured in the table because these types are not primitive).
if (th.isSubType(t1, Type.DAY_TIME_DURATION_TYPE)) {
resultType = Type.DAY_TIME_DURATION_TYPE;
} else if (th.isSubType(t2, Type.DAY_TIME_DURATION_TYPE)) {
resultType = Type.DAY_TIME_DURATION_TYPE;
} else if (th.isSubType(t1, Type.YEAR_MONTH_DURATION_TYPE)) {
resultType = Type.YEAR_MONTH_DURATION_TYPE;
} else if (th.isSubType(t2, Type.YEAR_MONTH_DURATION_TYPE)) {
resultType = Type.YEAR_MONTH_DURATION_TYPE;
}
}
return resultType;
}
/**
* Evaluate the expression. We only take this path if the type could not be determined
* statically.
*/
public Item evaluateItem(XPathContext context) throws XPathException {
AtomicValue v1 = (AtomicValue)operand0.evaluateItem(context);
if (v1 == null) {
return null;
}
v1 = v1.getPrimitiveValue();
AtomicValue v2 = (AtomicValue)operand1.evaluateItem(context);
if (v2 == null) {
return null;
}
v2 = v2.getPrimitiveValue();
final NamePool namePool = context.getNamePool();
final TypeHierarchy th = namePool.getTypeHierarchy();
int action = getAction((AtomicType)v1.getItemType(th).getPrimitiveItemType(),
operator,
(AtomicType)v2.getItemType(th).getPrimitiveItemType(),
backwardsCompatible);
Expression e;
switch (action) {
case NUMERIC_ARITHMETIC:
e = new NumericArithmetic(v1, operator, v2);
break;
case DURATION_ADDITION:
e = new DurationAddition(v1, operator, v2);
break;
case DURATION_MULTIPLICATION:
e = new DurationMultiplication(v1, operator, v2, namePool);
break;
case DURATION_DIVISION:
e = new DurationDivision(v1, operator, v2);
break;
case DATE_AND_DURATION:
e = new DateAndDuration(v1, operator, v2, namePool);
break;
case DATE_DIFFERENCE:
e = new DateDifference(v1, operator, v2);
break;
default:
// types are not known yet. Force to double if in 1.0 mode
if (backwardsCompatible) {
NumericValue nv1;
NumericValue nv2;
try {
nv1 = (NumericValue)v1.convert(Type.DOUBLE, context);
nv2 = (NumericValue)v2.convert(Type.DOUBLE, context);
} catch (XPathException err) {
typeError("Unsuitable operands for arithmetic operation (" +
v1.getItemType(th) + ", " +
v2.getItemType(th) + ')', "XPTY0004", context);
return null;
}
e = new NumericArithmetic(nv1, operator, nv2);
} else {
typeError("Unsuitable operands for arithmetic operation (" +
v1.getItemType(th) + ", " +
v2.getItemType(th) + ')', "XPTY0004", context);
return null;
}
}
ExpressionTool.copyLocationInfo(this, e);
if (e instanceof ComputedExpression) {
((ComputedExpression)e).setParentExpression(getParentExpression());
}
return e.evaluateItem(context);
}
/**
* Inner class to handle 1.0 backwards compatible arithmetic
*/
private static class Arithmetic10 extends BinaryExpression {
public boolean backwardsCompatible;
public Arithmetic10(Expression p1, int operator, Expression p2) {
super(p1, operator, p2);
}
public Expression typeCheck(StaticContext env, ItemType contextItemType) throws XPathException {
final TypeHierarchy th = env.getNamePool().getTypeHierarchy();
SequenceType atomicType = SequenceType.OPTIONAL_ATOMIC;
RoleLocator role0 = new RoleLocator(RoleLocator.BINARY_EXPR, Token.tokens[operator], 0, null);
role0.setSourceLocator(this);
operand0 = TypeChecker.staticTypeCheck(operand0, atomicType, backwardsCompatible, role0, env);
RoleLocator role1 = new RoleLocator(RoleLocator.BINARY_EXPR, Token.tokens[operator], 1, null);
role1.setSourceLocator(this);
operand1 = TypeChecker.staticTypeCheck(operand1, atomicType, backwardsCompatible, role1, env);
Expression e = super.typeCheck(env, contextItemType);
if (e instanceof ArithmeticExpression) {
AtomicType type0 = (AtomicType)operand0.getItemType(th).getPrimitiveItemType();
AtomicType type1 = (AtomicType)operand1.getItemType(th).getPrimitiveItemType();
if (backwardsCompatible) {
if (type0 == Type.BOOLEAN_TYPE) {
type0 = Type.NUMBER_TYPE;
} else if (type0 == Type.STRING_TYPE) {
type0 = Type.NUMBER_TYPE;
}
if (type1 == Type.BOOLEAN_TYPE) {
type1 = Type.NUMBER_TYPE;
} else if (type1 == Type.STRING_TYPE) {
type1 = Type.NUMBER_TYPE;
}
}
final int action = getAction(type0, operator, type1, backwardsCompatible);
switch (action) {
case NUMERIC_ARITHMETIC:
e = new NumericArithmetic(operand0, operator, operand1);
((NumericArithmetic)e).setBackwardsCompatible(backwardsCompatible);
break;
case DURATION_ADDITION:
e = new DurationAddition(operand0, operator, operand1);
break;
case DURATION_MULTIPLICATION:
e = new DurationMultiplication(operand0, operator, operand1, env.getNamePool());
break;
case DURATION_DIVISION:
e = new DurationDivision(operand0, operator, operand1);
break;
case DATE_AND_DURATION:
e = new DateAndDuration(operand0, operator, operand1, env.getNamePool());
break;
case DATE_DIFFERENCE:
e = new DateDifference(operand0, operator, operand1);
break;
case UNKNOWN_10:
e = new Arithmetic10(operand0, operator, operand1);
break;
case UNKNOWN:
// either the types are not known yet, or they are wrong
if (type0.isAtomicType() &&
type0 != Type.UNTYPED_ATOMIC_TYPE &&
type0 != Type.ANY_ATOMIC_TYPE &&
type1.isAtomicType() &&
type1 != Type.UNTYPED_ATOMIC_TYPE &&
type1 != Type.ANY_ATOMIC_TYPE) {
StaticError err = new StaticError("Unsuitable operands for arithmetic operation (" +
type0.toString(env.getNamePool()) + ", " +
type1.toString(env.getNamePool()) + ')');
err.setErrorCode("XPTY0004");
err.setIsTypeError(true);
throw err;
}
return e;
}
ExpressionTool.copyLocationInfo(this, e);
if (e instanceof ComputedExpression) {
((ComputedExpression)e).setParentExpression(getParentExpression());
}
try {
if (operand0 instanceof Value && operand1 instanceof Value) {
return ExpressionTool.eagerEvaluate(e, env.makeEarlyEvaluationContext());
}
} catch (DynamicError err) {
// Defer any error reporting until run-time
}
}
return e;
}
/**
* Determine the data type of the expression, if possible. All expression return
* sequences, in general; this method determines the type of the items within the
* sequence, assuming that (a) this is known in advance, and (b) it is the same for
* all items in the sequence.
* <p/>
* <p>This method should always return a result, though it may be the best approximation
* that is available at the time.</p>
*
* @return a value such as Type.STRING, Type.BOOLEAN, Type.NUMBER,
* Type.NODE, or Type.ITEM (meaning not known at compile time)
* @param th
*/
public ItemType getItemType(TypeHierarchy th) {
return Type.ANY_ATOMIC_TYPE;
}
/**
* Evaluate the expression.
*/
public Item evaluateItem(XPathContext context) throws XPathException {
AtomicValue v1 = (AtomicValue)operand0.evaluateItem(context);
if (v1 == null) {
return DoubleValue.NaN;
}
v1 = v1.getPrimitiveValue();
if (v1 instanceof BooleanValue || v1 instanceof StringValue || v1 instanceof NumericValue) {
try {
v1 = v1.convert(Type.DOUBLE, context);
} catch (XPathException e) {
return DoubleValue.NaN;
}
}
AtomicValue v2 = (AtomicValue)operand1.evaluateItem(context);
if (v2 == null) {
return DoubleValue.NaN;
}
v2 = v2.getPrimitiveValue();
if (v2 instanceof BooleanValue || v2 instanceof StringValue || v2 instanceof NumericValue) {
try {
v2 = v2.convert(Type.DOUBLE, context);
} catch (XPathException e) {
return DoubleValue.NaN;
}
}
final TypeHierarchy th = context.getNamePool().getTypeHierarchy();
int action = getAction((AtomicType)v1.getItemType(th).getPrimitiveItemType(),
operator,
(AtomicType)v2.getItemType(th).getPrimitiveItemType(),
backwardsCompatible);
switch (action) {
case NUMERIC_ARITHMETIC:
try {
return NumericArithmetic.doArithmetic(v1, operator, v2, context, backwardsCompatible);
} catch (XPathException ex) {
if (ex.getLocator() == null) {
ex.setLocator(this);
}
throw ex;
}
case DURATION_ADDITION:
try {
return DurationAddition.doArithmetic(v1, operator, v2, context);
} catch (XPathException ex) {
if (ex.getLocator() == null) {
ex.setLocator(this);
}
throw ex;
}
case DURATION_MULTIPLICATION:
try {
return DurationMultiplication.doArithmetic(v1, operator, v2, context);
} catch (XPathException ex) {
if (ex.getLocator() == null) {
ex.setLocator(this);
}
throw ex;
}
case DURATION_DIVISION:
try {
return DurationDivision.doArithmetic(v1, v2, context);
} catch (XPathException ex) {
if (ex.getLocator() == null) {
ex.setLocator(this);
}
throw ex;
}
case DATE_AND_DURATION:
try {
return DateAndDuration.doArithmetic(v1, operator, v2, context);
} catch (XPathException ex) {
if (ex.getLocator() == null) {
ex.setLocator(this);
}
throw ex;
}
case DATE_DIFFERENCE:
try {
return DateDifference.doArithmetic(v1, v2, context);
} catch (XPathException ex) {
if (ex.getLocator() == null) {
ex.setLocator(this);
}
throw ex;
}
default:
typeError("Unsuitable operands for arithmetic operation (" +
v1.getItemType(th) + ", " +
v2.getItemType(th) + ')', "XPTY0004", context);
return null;
}
}
}
/**
* Inner class to handle numeric arithmetic expressions
*/
public static class NumericArithmetic extends ArithmeticExpression {
boolean backwardsCompatible = false;
public NumericArithmetic(Expression p1, int operator, Expression p2) {
super(p1, operator, p2);
}
public void setBackwardsCompatible(boolean flag) {
backwardsCompatible = flag;
}
/**
* Evaluate the expression.
*/
public Item evaluateItem(XPathContext context) throws XPathException {
try {
return doArithmetic(operand0, operator, operand1, context, backwardsCompatible);
} catch (XPathException err) {
if (err.getLocator() == null) {
err.setLocator(this);
}
throw err;
}
}
public static Item doArithmetic(Expression operand0, int operator, Expression operand1,
XPathContext context, boolean backwardsCompatible)
throws XPathException {
AtomicValue v1 = ((AtomicValue)operand0.evaluateItem(context));
if (v1 == null) {
return null;
}
if (v1 instanceof UntypedAtomicValue) {
try {
v1 = new DoubleValue(Value.stringToNumber(v1.getStringValueCS()));
} catch (NumberFormatException e) {
if (backwardsCompatible) {
v1 = DoubleValue.NaN;
} else {
DynamicError err = new DynamicError("Failure converting untyped value " +
Err.wrap(v1.getStringValueCS(), Err.VALUE) + " to a number");
err.setErrorCode("FORG0001");
err.setXPathContext(context);
throw err;
}
}
} else {
v1 = v1.getPrimitiveValue();
}
AtomicValue v2 = ((AtomicValue)operand1.evaluateItem(context));
if (v2 == null) {
return null;
}
if (v2 instanceof UntypedAtomicValue) {
try {
v2 = new DoubleValue(Value.stringToNumber(v2.getStringValueCS()));
} catch (NumberFormatException e) {
if (backwardsCompatible) {
v2 = DoubleValue.NaN;
} else {
DynamicError err = new DynamicError("Failure converting untyped value " +
Err.wrap(v2.getStringValueCS(), Err.VALUE) + " to a number");
err.setErrorCode("FORG0001");
err.setXPathContext(context);
throw err;
}
}
} else {
v2 = v2.getPrimitiveValue();
}
if (operator == Token.NEGATE) {
return ((NumericValue)v2).negate();
} else {
try {
return ((NumericValue)v1).arithmetic(operator, (NumericValue)v2, context);
} catch (DynamicError err) {
if (err.getXPathContext() == null) {
err.setXPathContext(context);
}
throw err;
} catch (ArithmeticException err) {
DynamicError e = new DynamicError("Arithmetic exception: " + err.getMessage());
e.setXPathContext(context);
throw e;
}
}
}
}
/**
* Inner class to handle addition and subtraction of two durations
*/
public static class DurationAddition extends ArithmeticExpression {
public DurationAddition(Expression p1, int operator, Expression p2) {
super(p1, operator, p2);
}
/**
* Evaluate the expression.
*/
public Item evaluateItem(XPathContext context) throws XPathException {
try {
return doArithmetic(operand0, operator, operand1, context);
} catch (XPathException err) {
if (err.getLocator() == null) {
err.setLocator(this);
}
throw err;
}
}
public static Item doArithmetic(Expression operand0, int operator, Expression operand1, XPathContext context)
throws XPathException {
AtomicValue av1 = (AtomicValue)operand0.evaluateItem(context);
if (av1 == null) {
return null;
}
DurationValue v1 = (DurationValue)av1.getPrimitiveValue();
AtomicValue av2 = (AtomicValue)operand1.evaluateItem(context);
if (av2 == null) {
return null;
}
DurationValue v2 = (DurationValue)av2.getPrimitiveValue();
if (operator == Token.PLUS) {
return v1.add(v2, context);
} else if (operator == Token.MINUS) {
return v1.subtract(v2, context);
} else {
throw new AssertionError("Unknown operation on durations");
}
}
}
/**
* Inner class to handle multiplication (or division) of a duration by a number
*/
public static class DurationMultiplication extends ArithmeticExpression {
public DurationMultiplication(Expression p1, int operator, Expression p2, NamePool pool) {
// by the time we get here, we know that one of the operands is a duration,
// but it might be either one. We make it the first.
super(p1, operator, p2);
final TypeHierarchy th = pool.getTypeHierarchy();
if (th.isSubType(p2.getItemType(th), Type.DURATION_TYPE)) {
operand0 = p2;
operand1 = p1;
}
}
/**
* Evaluate the expression.
*/
public Item evaluateItem(XPathContext context) throws XPathException {
try {
return doArithmetic(operand0, operator, operand1, context);
} catch (XPathException err) {
if (err.getLocator() == null) {
err.setLocator(this);
}
throw err;
}
}
public static Item doArithmetic(Expression operand0, int operator, Expression operand1, XPathContext context)
throws XPathException {
AtomicValue av1 = (AtomicValue)operand0.evaluateItem(context);
if (av1 == null) {
return null;
}
DurationValue v1 = (DurationValue)av1.getPrimitiveValue();
AtomicValue av2 = (AtomicValue)operand1.evaluateItem(context);
if (av2 == null) {
return null;
}
NumericValue v2 = (NumericValue)av2.getPrimitiveValue();
double d = v2.getDoubleValue();
if (operator == Token.DIV) {
d = 1.0 / d;
}
return v1.multiply(d, context);
}
}
/**
* Inner class to handle division of two durations to give a number
*/
public static class DurationDivision extends ArithmeticExpression {
public DurationDivision(Expression p1, int operator, Expression p2) {
super(p1, operator, p2);
}
/**
* Evaluate the expression.
*/
public Item evaluateItem(XPathContext context) throws XPathException {
try {
return doArithmetic(operand0, operand1, context);
} catch (XPathException err) {
if (err.getLocator() == null) {
err.setLocator(this);
}
throw err;
}
}
public static Item doArithmetic(Expression operand0, Expression operand1, XPathContext context)
throws XPathException {
AtomicValue av1 = (AtomicValue)operand0.evaluateItem(context);
if (av1 == null) {
return null;
}
DurationValue v1 = (DurationValue)av1.getPrimitiveValue();
AtomicValue av2 = (AtomicValue)operand1.evaluateItem(context);
if (av2 == null) {
return null;
}
DurationValue v2 = (DurationValue)av2.getPrimitiveValue();
return v1.divide(v2, context);
}
}
/**
* Inner class to handle addition or subtraction of a Date (or Time, or DateTime) and a Duration
*/
public static class DateAndDuration extends ArithmeticExpression {
public DateAndDuration(Expression p1, int operator, Expression p2, NamePool pool) {
// by the time we get here, we know that one of the operands is a duration,
// but it might be either one. We make it the second.
super(p1, operator, p2);
final TypeHierarchy th = pool.getTypeHierarchy();
if (th.isSubType(p1.getItemType(th), Type.DURATION_TYPE)) {
operand0 = p2;
operand1 = p1;
}
}
/**
* Evaluate the expression.
*/
public Item evaluateItem(XPathContext context) throws XPathException {
try {
return doArithmetic(operand0, operator, operand1, context);
} catch (XPathException err) {
if (err.getLocator() == null) {
err.setLocator(this);
}
throw err;
}
}
public static Item doArithmetic(Expression operand0, int operator, Expression operand1, XPathContext context)
throws XPathException {
AtomicValue av1 = (AtomicValue)operand0.evaluateItem(context);
if (av1 == null) {
return null;
}
CalendarValue v1 = (CalendarValue)av1.getPrimitiveValue();
AtomicValue av2 = (AtomicValue)operand1.evaluateItem(context);
if (av2 == null) {
return null;
}
DurationValue v2 = (DurationValue)av2.getPrimitiveValue();
if (operator == Token.MINUS) {
v2 = v2.multiply(-1.0, context);
}
return v1.add(v2);
}
}
/**
* Inner class to handle subtraction of a Date (or Time, or DateTime) from another, to return a Duration
*/
public static class DateDifference extends ArithmeticExpression {
public DateDifference(Expression p1, int operator, Expression p2) {
super(p1, operator, p2);
}
/**
* Evaluate the expression.
*/
public Item evaluateItem(XPathContext context) throws XPathException {
try {
return doArithmetic(operand0, operand1, context);
} catch (XPathException err) {
if (err.getLocator() == null) {
err.setLocator(this);
}
throw err;
}
}
public static Item doArithmetic(Expression operand0, Expression operand1, XPathContext context)
throws XPathException {
AtomicValue av1 = (AtomicValue)operand0.evaluateItem(context);
if (av1 == null) {
return null;
}
CalendarValue v1 = (CalendarValue)av1.getPrimitiveValue();
AtomicValue av2 = (AtomicValue)operand1.evaluateItem(context);
if (av2 == null) {
return null;
}
CalendarValue v2 = (CalendarValue)av2.getPrimitiveValue();
return v1.subtract(v2, context);
}
}
}
//
// 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.
//
|