/* eXist Open Source Native XML Database * Copyright (C) 2000-03, Wolfgang M. Meier (meier@ifs.tu-darmstadt.de) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * $Id$ */ package org.exist.xquery; import java.util.HashMap; import java.util.Map; import java.util.TreeMap; import org.exist.dom.persistent.NodeSet; import org.exist.storage.DBBroker; import org.exist.xquery.Constants.ArithmeticOperator; import org.exist.xquery.util.ExpressionDumper; import org.exist.xquery.value.ComputableValue; import org.exist.xquery.value.Item; import org.exist.xquery.value.NumericValue; import org.exist.xquery.value.Sequence; import org.exist.xquery.value.Type; /** * numeric operation on two operands by +, -, *, div, mod etc.. * */ public class OpNumeric extends BinaryOp { protected final ArithmeticOperator operator; protected int returnType = Type.ATOMIC; protected NodeSet temp = null; protected DBBroker broker; public OpNumeric(XQueryContext context, ArithmeticOperator operator) { super(context); this.operator = operator; } public OpNumeric(XQueryContext context, Expression left, Expression right, ArithmeticOperator operator) { super(context); this.operator = operator; int ltype = left.returnsType(); int rtype = right.returnsType(); if (Type.subTypeOf(ltype, Type.NUMBER) && Type.subTypeOf(rtype, Type.NUMBER)) { if (ltype > rtype) { right = new UntypedValueCheck(context, ltype, right); } else if (rtype > ltype) { left = new UntypedValueCheck(context, rtype, left); } if (operator == ArithmeticOperator.DIVISION && ltype == Type.INTEGER && rtype == Type.INTEGER) { returnType = Type.DECIMAL; } else if (operator == ArithmeticOperator.DIVISION_INTEGER) { returnType = Type.INTEGER; } else { returnType = Math.max(ltype, rtype); } } else { if (Type.subTypeOf(ltype, Type.NUMBER)) {ltype = Type.NUMBER;} if (Type.subTypeOf(rtype, Type.NUMBER)) {rtype = Type.NUMBER;} final OpEntry entry = OP_TYPES.get(new OpEntry(operator, ltype, rtype)); if (entry != null) { returnType = entry.typeResult; } else if (ltype == Type.NUMBER || rtype == Type.NUMBER) { // if one of both operands returns a number, we can safely assume // the return type of the whole expression will be a number returnType = Type.NUMBER; } } add(left); add(right); } @Override public int getDependencies() { return getLeft().getDependencies() | getRight().getDependencies(); } public int returnsType() { return returnType; } public void analyze(AnalyzeContextInfo contextInfo) throws XPathException { super.analyze(contextInfo); contextInfo.setStaticReturnType(returnType); } public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathException { if (context.getProfiler().isEnabled()) { context.getProfiler().start(this); context.getProfiler().message(this, Profiler.DEPENDENCIES, "DEPENDENCIES", Dependency.getDependenciesName(this.getDependencies())); if (contextSequence != null) {context.getProfiler().message(this, Profiler.START_SEQUENCES, "CONTEXT SEQUENCE", contextSequence);} if (contextItem != null) {context.getProfiler().message(this, Profiler.START_SEQUENCES, "CONTEXT ITEM", contextItem.toSequence());} } final Sequence lseq = Atomize.atomize(getLeft().eval(contextSequence, contextItem)); final Sequence rseq = Atomize.atomize(getRight().eval(contextSequence, contextItem)); if (lseq.hasMany()) {throw new XPathException(this, ErrorCodes.XPTY0004, "Too many operands at the left of " + operator.symbol);} if (rseq.hasMany()) {throw new XPathException(this, ErrorCodes.XPTY0004, "Too many operands at the right of " + operator.symbol);} Sequence result; if (rseq.isEmpty()) {result = Sequence.EMPTY_SEQUENCE;} else if (lseq.isEmpty()) {result = Sequence.EMPTY_SEQUENCE;} else { Item lvalue = lseq.itemAt(0); Item rvalue = rseq.itemAt(0); try { if (lvalue.getType() == Type.UNTYPED_ATOMIC || lvalue.getType() == Type.ATOMIC) {lvalue = lvalue.convertTo(Type.NUMBER);} if (rvalue.getType() == Type.UNTYPED_ATOMIC || rvalue.getType() == Type.ATOMIC) {rvalue = rvalue.convertTo(Type.NUMBER);} if (!(lvalue instanceof ComputableValue)) {throw new XPathException(this, ErrorCodes.XPTY0004, "'" + Type.getTypeName(lvalue.getType()) + "(" + lvalue + ")' can not be an operand for " + operator.symbol);} if (!(rvalue instanceof ComputableValue)) {throw new XPathException(this, ErrorCodes.XPTY0004, "'" + Type.getTypeName(rvalue.getType()) + "(" + rvalue + ")' can not be an operand for " + operator.symbol);} //TODO : move to implementations if (operator == ArithmeticOperator.DIVISION_INTEGER) { if (!Type.subTypeOf(lvalue.getType(), Type.NUMBER)) {throw new XPathException(this, ErrorCodes.XPTY0004, "'" + Type.getTypeName(lvalue.getType()) + "(" + lvalue + ")' can not be an operand for " + operator.symbol);} if (!Type.subTypeOf(rvalue.getType(), Type.NUMBER)) {throw new XPathException(this, ErrorCodes.XPTY0004, "'" + Type.getTypeName(rvalue.getType()) + "(" + rvalue + ")' can not be an operand for " + operator.symbol);} //If the divisor is (positive or negative) zero, then an error is raised [err:FOAR0001] if (((NumericValue)rvalue).isZero()) {throw new XPathException(this, ErrorCodes.FOAR0001, "Division by zero");} //If either operand is NaN then an error is raised [err:FOAR0002]. if (((NumericValue)lvalue).isNaN()) {throw new XPathException(this, ErrorCodes.FOAR0002, "Division of " + Type.getTypeName(lvalue.getType()) + "(" + lvalue + ")'");} //If either operand is NaN then an error is raised [err:FOAR0002]. if (((NumericValue)rvalue).isNaN()) {throw new XPathException(this, ErrorCodes.FOAR0002, "Division of " + Type.getTypeName(rvalue.getType()) + "(" + rvalue + ")'");} //If $arg1 is INF or -INF then an error is raised [err:FOAR0002]. if (((NumericValue)lvalue).isInfinite()) {throw new XPathException(this, ErrorCodes.FOAR0002, "Division of " + Type.getTypeName(lvalue.getType()) + "(" + lvalue + ")'");} result = ((NumericValue) lvalue).idiv((NumericValue) rvalue); } else { result = applyOperator((ComputableValue) lvalue, (ComputableValue) rvalue); } //TODO : type-checks on MOD operator : maybe the same ones than above -pb } catch (final XPathException e) { e.setLocation(line, column); throw e; } } if (context.getProfiler().isEnabled()) {context.getProfiler().end(this, "", result);} //Sets the return type if not already set if (returnType == Type.ATOMIC) //TODO : refine previously set type ? -pb {returnType = result.getItemType();} return result; } public ComputableValue applyOperator(ComputableValue left, ComputableValue right) throws XPathException { switch (operator) { case SUBTRACTION: return left.minus(right); case ADDITION: return left.plus(right); case MULTIPLICATION: return left.mult(right); case DIVISION: return left.div(right); case MODULUS: { if (!Type.subTypeOf(left.getType(), Type.NUMBER)) {throw new XPathException(this, ErrorCodes.XPTY0004, "'" + Type.getTypeName(left.getType()) + "(" + left + ")' is not numeric");} if (!Type.subTypeOf(right.getType(), Type.NUMBER)) {throw new XPathException(this, ErrorCodes.XPTY0004, "'" + Type.getTypeName(right.getType()) + "(" + right + ")' is not numeric");} return ((NumericValue) left).mod((NumericValue) right); } default: throw new RuntimeException("Unknown numeric operator " + operator); } } @Override public void dump(ExpressionDumper dumper) { getLeft().dump(dumper); dumper.display(' ').display(operator.symbol).display(' '); getRight().dump(dumper); } @Override public String toString() { return getLeft().toString() + ' ' + operator.symbol + ' ' + getRight(); } // excerpt from operator mapping table in XQuery 1.0 section B.2 // http://www.w3.org/TR/xquery/#mapping private static final OpEntry[] OP_TABLE = { new OpEntry(ArithmeticOperator.ADDITION, Type.NUMBER, Type.NUMBER, Type.NUMBER), new OpEntry(ArithmeticOperator.ADDITION, Type.DATE, Type.YEAR_MONTH_DURATION, Type.DATE), new OpEntry(ArithmeticOperator.ADDITION, Type.YEAR_MONTH_DURATION, Type.DATE, Type.DATE), new OpEntry(ArithmeticOperator.ADDITION, Type.DATE, Type.DAY_TIME_DURATION, Type.DATE), new OpEntry(ArithmeticOperator.ADDITION, Type.DAY_TIME_DURATION, Type.DATE, Type.DATE), new OpEntry(ArithmeticOperator.ADDITION, Type.TIME, Type.DAY_TIME_DURATION, Type.TIME), new OpEntry(ArithmeticOperator.ADDITION, Type.DAY_TIME_DURATION, Type.TIME, Type.TIME), new OpEntry(ArithmeticOperator.ADDITION, Type.DATE_TIME, Type.YEAR_MONTH_DURATION, Type.DATE_TIME), new OpEntry(ArithmeticOperator.ADDITION, Type.YEAR_MONTH_DURATION, Type.DATE_TIME, Type.DATE_TIME), new OpEntry(ArithmeticOperator.ADDITION, Type.DATE_TIME, Type.DAY_TIME_DURATION, Type.DATE_TIME), new OpEntry(ArithmeticOperator.ADDITION, Type.DAY_TIME_DURATION, Type.DATE_TIME, Type.DATE_TIME), new OpEntry(ArithmeticOperator.ADDITION, Type.YEAR_MONTH_DURATION, Type.YEAR_MONTH_DURATION, Type.YEAR_MONTH_DURATION), new OpEntry(ArithmeticOperator.ADDITION, Type.DAY_TIME_DURATION, Type.DAY_TIME_DURATION, Type.DAY_TIME_DURATION), new OpEntry(ArithmeticOperator.SUBTRACTION, Type.NUMBER, Type.NUMBER, Type.NUMBER), new OpEntry(ArithmeticOperator.SUBTRACTION, Type.DATE, Type.DATE, Type.DAY_TIME_DURATION), new OpEntry(ArithmeticOperator.SUBTRACTION, Type.DATE, Type.YEAR_MONTH_DURATION, Type.DATE), new OpEntry(ArithmeticOperator.SUBTRACTION, Type.DATE, Type.DAY_TIME_DURATION, Type.DATE), new OpEntry(ArithmeticOperator.SUBTRACTION, Type.TIME, Type.TIME, Type.DAY_TIME_DURATION), new OpEntry(ArithmeticOperator.SUBTRACTION, Type.TIME, Type.DAY_TIME_DURATION, Type.TIME), new OpEntry(ArithmeticOperator.SUBTRACTION, Type.DATE_TIME, Type.DATE_TIME, Type.DAY_TIME_DURATION), new OpEntry(ArithmeticOperator.SUBTRACTION, Type.DATE_TIME, Type.YEAR_MONTH_DURATION, Type.DATE_TIME), new OpEntry(ArithmeticOperator.SUBTRACTION, Type.DATE_TIME, Type.DAY_TIME_DURATION, Type.DATE_TIME), new OpEntry(ArithmeticOperator.SUBTRACTION, Type.YEAR_MONTH_DURATION, Type.YEAR_MONTH_DURATION, Type.YEAR_MONTH_DURATION), new OpEntry(ArithmeticOperator.SUBTRACTION, Type.DAY_TIME_DURATION, Type.DAY_TIME_DURATION, Type.DAY_TIME_DURATION), new OpEntry(ArithmeticOperator.MULTIPLICATION, Type.NUMBER, Type.NUMBER, Type.NUMBER), new OpEntry(ArithmeticOperator.MULTIPLICATION, Type.YEAR_MONTH_DURATION, Type.NUMBER, Type.YEAR_MONTH_DURATION), new OpEntry(ArithmeticOperator.MULTIPLICATION, Type.NUMBER, Type.YEAR_MONTH_DURATION, Type.YEAR_MONTH_DURATION), new OpEntry(ArithmeticOperator.MULTIPLICATION, Type.DAY_TIME_DURATION, Type.NUMBER, Type.DAY_TIME_DURATION), new OpEntry(ArithmeticOperator.MULTIPLICATION, Type.NUMBER, Type.DAY_TIME_DURATION, Type.DAY_TIME_DURATION), new OpEntry(ArithmeticOperator.DIVISION_INTEGER, Type.NUMBER, Type.NUMBER, Type.INTEGER), new OpEntry(ArithmeticOperator.DIVISION, Type.NUMBER, Type.NUMBER, Type.NUMBER), // except for integer -> decimal new OpEntry(ArithmeticOperator.DIVISION, Type.YEAR_MONTH_DURATION, Type.NUMBER, Type.YEAR_MONTH_DURATION), new OpEntry(ArithmeticOperator.DIVISION, Type.DAY_TIME_DURATION, Type.NUMBER, Type.DAY_TIME_DURATION), new OpEntry(ArithmeticOperator.DIVISION, Type.YEAR_MONTH_DURATION, Type.YEAR_MONTH_DURATION, Type.DECIMAL), new OpEntry(ArithmeticOperator.DIVISION, Type.DAY_TIME_DURATION, Type.DAY_TIME_DURATION, Type.DECIMAL), new OpEntry(ArithmeticOperator.MODULUS, Type.NUMBER, Type.NUMBER, Type.NUMBER) }; private static class OpEntry implements Comparable<OpEntry> { public final ArithmeticOperator op; public final int typeA, typeB, typeResult; public OpEntry(final ArithmeticOperator op, final int typeA, final int typeB) { this(op, typeA, typeB, Type.ATOMIC); } public OpEntry(final ArithmeticOperator op, final int typeA, final int typeB, final int typeResult) { this.op = op; this.typeA = typeA; this.typeB = typeB; this.typeResult = typeResult; } @Override public int compareTo(final OpEntry that) { if (this.op != that.op) { return this.op.ordinal() - that.op.ordinal(); } else if (this.typeA != that.typeA) { return this.typeA - that.typeA; } else if (this.typeB != that.typeB) { return this.typeB - that.typeB; } else { return 0; } } @Override public boolean equals(final Object o) { try { final OpEntry that = (OpEntry) o; return this.op == that.op && this.typeA == that.typeA && this.typeB == that.typeB; } catch (final ClassCastException e) { return false; } } } private static final Map<OpEntry, OpEntry> OP_TYPES = new TreeMap<>(); static { for (final OpEntry entry : OP_TABLE) { OP_TYPES.put(entry, entry); } } }