/* 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.Map; import java.util.TreeMap; import org.exist.dom.NodeSet; import org.exist.storage.DBBroker; 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 int operator; protected int returnType = Type.ATOMIC; protected NodeSet temp = null; protected DBBroker broker; public OpNumeric(XQueryContext context, int operator) { super(context); this.operator = operator; } public OpNumeric(XQueryContext context, Expression left, Expression right, int operator) { super(context); this.operator = operator; left = atomizeIfNecessary(left); right = atomizeIfNecessary(right); 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 == Constants.DIV && ltype == Type.INTEGER && rtype == Type.INTEGER) { returnType = Type.DECIMAL; } else if (operator == Constants.IDIV) { 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; OpEntry entry = (OpEntry) OP_TYPES.get(new OpEntry(operator, ltype, rtype)); if (entry != null) returnType = entry.typeResult; } add(left); add(right); } private Expression atomizeIfNecessary(Expression x) { return Type.subTypeOf(x.returnsType(), Type.ATOMIC) ? x : new Atomize(context, x); } 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()); } Sequence lseq = getLeft().eval(contextSequence, contextItem); Sequence rseq = getRight().eval(contextSequence, contextItem); if (lseq.hasMany()) throw new XPathException(this, "XPTY0004: too many operands at the left of " + Constants.OPS[operator]); if (rseq.hasMany()) throw new XPathException(this, "XPTY0004: too many operands at the right of " + Constants.OPS[operator]); 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, "XPTY0004: '" + Type.getTypeName(lvalue.getType()) + "(" + lvalue + ")' can not be an operand for " + Constants.OPS[operator]); if (!(rvalue instanceof ComputableValue)) throw new XPathException(this, "XPTY0004: '" + Type.getTypeName(rvalue.getType()) + "(" + rvalue + ")' can not be an operand for " + Constants.OPS[operator]); //TODO : move to implementations if (operator == Constants.IDIV) { if (!Type.subTypeOf(lvalue.getType(), Type.NUMBER)) throw new XPathException(this, "XPTY0004: '" + Type.getTypeName(lvalue.getType()) + "(" + lvalue + ")' can not be an operand for " + Constants.OPS[operator]); if (!Type.subTypeOf(rvalue.getType(), Type.NUMBER)) throw new XPathException(this, "XPTY0004: '" + Type.getTypeName(rvalue.getType()) + "(" + rvalue + ")' can not be an operand for " + Constants.OPS[operator]); //If the divisor is (positive or negative) zero, then an error is raised [err:FOAR0001] if (((NumericValue)rvalue).isZero()) throw new XPathException(this, "FOAR0001: division by zero"); //If either operand is NaN then an error is raised [err:FOAR0002]. if (((NumericValue)lvalue).isNaN()) throw new XPathException(this, "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, "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, "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 (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 Constants.MINUS: return left.minus(right); case Constants.PLUS: return left.plus(right); case Constants.MULT: return left.mult(right); case Constants.DIV: return left.div(right); case Constants.MOD: { if (!Type.subTypeOf(left.getType(), Type.NUMBER)) throw new XPathException(this, "XPTY0004: '" + Type.getTypeName(left.getType()) + "(" + left + ")' is not numeric"); if (!Type.subTypeOf(right.getType(), Type.NUMBER)) throw new XPathException(this, "XPTY0004: '" + Type.getTypeName(right.getType()) + "(" + right + ")' is not numeric"); return ((NumericValue) left).mod((NumericValue) right); } default: throw new RuntimeException("unknown numeric operator " + operator); } } /* (non-Javadoc) * @see org.exist.xquery.PathExpr#dump(org.exist.xquery.util.ExpressionDumper) */ public void dump(ExpressionDumper dumper) { getLeft().dump(dumper); dumper.display(' ').display(Constants.OPS[operator]).display(' '); getRight().dump(dumper); } public String toString() { StringBuilder result = new StringBuilder(); result.append(getLeft().toString()); result.append(' ').append(Constants.OPS[operator]).append(' '); result.append(getRight()); return result.toString(); } // excerpt from operator mapping table in XQuery 1.0 section B.2 // http://www.w3.org/TR/xquery/#mapping private static final int[] OP_TABLE = { Constants.PLUS, Type.NUMBER, Type.NUMBER, Type.NUMBER, Constants.PLUS, Type.DATE, Type.YEAR_MONTH_DURATION, Type.DATE, Constants.PLUS, Type.YEAR_MONTH_DURATION, Type.DATE, Type.DATE, Constants.PLUS, Type.DATE, Type.DAY_TIME_DURATION, Type.DATE, Constants.PLUS, Type.DAY_TIME_DURATION, Type.DATE, Type.DATE, Constants.PLUS, Type.TIME, Type.DAY_TIME_DURATION, Type.TIME, Constants.PLUS, Type.DAY_TIME_DURATION, Type.TIME, Type.TIME, Constants.PLUS, Type.DATE_TIME, Type.YEAR_MONTH_DURATION, Type.DATE_TIME, Constants.PLUS, Type.YEAR_MONTH_DURATION, Type.DATE_TIME, Type.DATE_TIME, Constants.PLUS, Type.DATE_TIME, Type.DAY_TIME_DURATION, Type.DATE_TIME, Constants.PLUS, Type.DAY_TIME_DURATION, Type.DATE_TIME, Type.DATE_TIME, Constants.PLUS, Type.YEAR_MONTH_DURATION, Type.YEAR_MONTH_DURATION, Type.YEAR_MONTH_DURATION, Constants.PLUS, Type.DAY_TIME_DURATION, Type.DAY_TIME_DURATION, Type.DAY_TIME_DURATION, Constants.MINUS, Type.NUMBER, Type.NUMBER, Type.NUMBER, Constants.MINUS, Type.DATE, Type.DATE, Type.DAY_TIME_DURATION, Constants.MINUS, Type.DATE, Type.YEAR_MONTH_DURATION, Type.DATE, Constants.MINUS, Type.DATE, Type.DAY_TIME_DURATION, Type.DATE, Constants.MINUS, Type.TIME, Type.TIME, Type.DAY_TIME_DURATION, Constants.MINUS, Type.TIME, Type.DAY_TIME_DURATION, Type.TIME, Constants.MINUS, Type.DATE_TIME, Type.DATE_TIME, Type.DAY_TIME_DURATION, Constants.MINUS, Type.DATE_TIME, Type.YEAR_MONTH_DURATION, Type.DATE_TIME, Constants.MINUS, Type.DATE_TIME, Type.DAY_TIME_DURATION, Type.DATE_TIME, Constants.MINUS, Type.YEAR_MONTH_DURATION, Type.YEAR_MONTH_DURATION, Type.YEAR_MONTH_DURATION, Constants.MINUS, Type.DAY_TIME_DURATION, Type.DAY_TIME_DURATION, Type.DAY_TIME_DURATION, Constants.MULT, Type.NUMBER, Type.NUMBER, Type.NUMBER, Constants.MULT, Type.YEAR_MONTH_DURATION, Type.NUMBER, Type.YEAR_MONTH_DURATION, Constants.MULT, Type.NUMBER, Type.YEAR_MONTH_DURATION, Type.YEAR_MONTH_DURATION, Constants.MULT, Type.DAY_TIME_DURATION, Type.NUMBER, Type.DAY_TIME_DURATION, Constants.MULT, Type.NUMBER, Type.DAY_TIME_DURATION, Type.DAY_TIME_DURATION, Constants.IDIV, Type.NUMBER, Type.NUMBER, Type.INTEGER, Constants.DIV, Type.NUMBER, Type.NUMBER, Type.NUMBER, // except for integer -> decimal Constants.DIV, Type.YEAR_MONTH_DURATION, Type.NUMBER, Type.YEAR_MONTH_DURATION, Constants.DIV, Type.DAY_TIME_DURATION, Type.NUMBER, Type.DAY_TIME_DURATION, Constants.DIV, Type.YEAR_MONTH_DURATION, Type.YEAR_MONTH_DURATION, Type.DECIMAL, Constants.DIV, Type.DAY_TIME_DURATION, Type.DAY_TIME_DURATION, Type.DECIMAL, Constants.MOD, Type.NUMBER, Type.NUMBER, Type.NUMBER, }; private static class OpEntry implements Comparable { public final int op, typeA, typeB, typeResult; public OpEntry(int op, int typeA, int typeB) { this(op, typeA, typeB, Type.ATOMIC); } public OpEntry(int op, int typeA, int typeB, int typeResult) { this.op = op; this.typeA = typeA; this.typeB = typeB; this.typeResult = typeResult; } public int compareTo(Object o) { OpEntry that = (OpEntry) o; if (this.op != that.op) return this.op - that.op; 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; } public boolean equals(Object o) { try { OpEntry that = (OpEntry) o; return this.op == that.op && this.typeA == that.typeA && this.typeB == that.typeB; } catch (ClassCastException e) { return false; } } // TODO: implement hashcode, if needed } private static final Map OP_TYPES = new TreeMap(); static { for (int i=0; i<OP_TABLE.length; i+=4) { OpEntry entry = new OpEntry(OP_TABLE[i], OP_TABLE[i+1], OP_TABLE[i+2], OP_TABLE[i+3]); OP_TYPES.put(entry, entry); } } }