001: package net.sf.saxon.expr;
002:
003: import net.sf.saxon.functions.*;
004: import net.sf.saxon.om.Item;
005: import net.sf.saxon.sort.AtomicComparer;
006: import net.sf.saxon.sort.CodepointCollator;
007: import net.sf.saxon.trans.DynamicError;
008: import net.sf.saxon.trans.StaticError;
009: import net.sf.saxon.trans.XPathException;
010: import net.sf.saxon.type.AtomicType;
011: import net.sf.saxon.type.ItemType;
012: import net.sf.saxon.type.Type;
013: import net.sf.saxon.type.TypeHierarchy;
014: import net.sf.saxon.value.*;
015:
016: import java.util.Comparator;
017:
018: /**
019: * ValueComparison: a boolean expression that compares two atomic values
020: * for equals, not-equals, greater-than or less-than. Implements the operators
021: * eq, ne, lt, le, gt, ge
022: */
023:
024: public final class ValueComparison extends BinaryExpression {
025:
026: private AtomicComparer comparer;
027:
028: /**
029: * Create a relational expression identifying the two operands and the operator
030: * @param p1 the left-hand operand
031: * @param op the operator, as a token returned by the Tokenizer (e.g. Token.LT)
032: * @param p2 the right-hand operand
033: */
034:
035: public ValueComparison(Expression p1, int op, Expression p2) {
036: super (p1, op, p2);
037: }
038:
039: /**
040: * Type-check the expression
041: */
042:
043: public Expression typeCheck(StaticContext env,
044: ItemType contextItemType) throws XPathException {
045:
046: operand0 = operand0.typeCheck(env, contextItemType);
047: if (operand0 instanceof EmptySequence) {
048: return operand0;
049: }
050: operand1 = operand1.typeCheck(env, contextItemType);
051: if (operand1 instanceof EmptySequence) {
052: return operand1;
053: }
054:
055: final SequenceType optionalAtomic = SequenceType.OPTIONAL_ATOMIC;
056: final TypeHierarchy th = env.getNamePool().getTypeHierarchy();
057:
058: RoleLocator role0 = new RoleLocator(RoleLocator.BINARY_EXPR,
059: Token.tokens[operator], 0, null);
060: role0.setSourceLocator(this );
061: operand0 = TypeChecker.staticTypeCheck(operand0,
062: optionalAtomic, false, role0, env);
063:
064: RoleLocator role1 = new RoleLocator(RoleLocator.BINARY_EXPR,
065: Token.tokens[operator], 1, null);
066: role1.setSourceLocator(this );
067: operand1 = TypeChecker.staticTypeCheck(operand1,
068: optionalAtomic, false, role1, env);
069:
070: AtomicType t1 = operand0.getItemType(th).getAtomizedItemType();
071: AtomicType t2 = operand1.getItemType(th).getAtomizedItemType();
072:
073: int p1 = t1.getPrimitiveType();
074: if (p1 == Type.UNTYPED_ATOMIC) {
075: p1 = Type.STRING;
076: }
077: int p2 = t2.getPrimitiveType();
078: if (p2 == Type.UNTYPED_ATOMIC) {
079: p2 = Type.STRING;
080: }
081:
082: if (!Type.isComparable(p1, p2)) {
083: boolean opt0 = Cardinality.allowsZero(operand0
084: .getCardinality());
085: boolean opt1 = Cardinality.allowsZero(operand1
086: .getCardinality());
087: if (opt0 || opt1) {
088: // This is a comparison such as (xs:integer? eq xs:date?). This is almost
089: // certainly an error, but we need to let it through because it will work if
090: // one of the operands is an empty sequence.
091:
092: String which = null;
093: if (opt0)
094: which = "the first operand is";
095: if (opt1)
096: which = "the second operand is";
097: if (opt0 && opt1)
098: which = "one or both operands are";
099: env.issueWarning("Comparison of "
100: + t1.toString(env.getNamePool())
101: + (opt0 ? "?" : "") + " to "
102: + t2.toString(env.getNamePool())
103: + (opt1 ? "?" : "") + " will fail unless "
104: + which + " empty", this );
105:
106: } else {
107: StaticError err = new StaticError("Cannot compare "
108: + t1.toString(env.getNamePool()) + " to "
109: + t2.toString(env.getNamePool()));
110: err.setIsTypeError(true);
111: err.setErrorCode("XPTY0004");
112: throw err;
113: }
114: }
115: if (!(operator == Token.FEQ || operator == Token.FNE)) {
116: if (!Type.isOrdered(p1)) {
117: StaticError err = new StaticError("Type "
118: + t1.toString(env.getNamePool())
119: + " is not an ordered type");
120: err.setErrorCode("XPTY0004");
121: err.setIsTypeError(true);
122: throw err;
123: }
124: if (!Type.isOrdered(p2)) {
125: StaticError err = new StaticError("Type "
126: + t2.toString(env.getNamePool())
127: + " is not an ordered type");
128: err.setErrorCode("XPTY0004");
129: err.setIsTypeError(true);
130: throw err;
131: }
132: }
133:
134: Comparator comp = env.getCollation(env
135: .getDefaultCollationName());
136: if (comp == null)
137: comp = CodepointCollator.getInstance();
138: comparer = new AtomicComparer(comp, env.getConfiguration());
139: return this ;
140: }
141:
142: /**
143: * Perform optimisation of an expression and its subexpressions.
144: * <p/>
145: * <p>This method is called after all references to functions and variables have been resolved
146: * to the declaration of the function or variable, and after all type checking has been done.</p>
147: *
148: * @param opt the optimizer in use. This provides access to supporting functions; it also allows
149: * different optimization strategies to be used in different circumstances.
150: * @param env the static context of the expression
151: * @param contextItemType the static type of "." at the point where this expression is invoked.
152: * The parameter is set to null if it is known statically that the context item will be undefined.
153: * If the type of the context item is not known statically, the argument is set to
154: * {@link net.sf.saxon.type.Type#ITEM_TYPE}
155: * @return the original expression, rewritten if appropriate to optimize execution
156: * @throws net.sf.saxon.trans.StaticError if an error is discovered during this phase
157: * (typically a type error)
158: */
159:
160: public Expression optimize(Optimizer opt, StaticContext env,
161: ItemType contextItemType) throws XPathException {
162:
163: operand0 = operand0.optimize(opt, env, contextItemType);
164: operand1 = operand1.optimize(opt, env, contextItemType);
165: // optimise count(x) eq 0 (or gt 0, ne 0, eq 0, etc)
166:
167: if (Aggregate.isCountFunction(operand0)
168: && operand1 instanceof AtomicValue) {
169: if (isZero(operand1)) {
170: if (operator == Token.FEQ || operator == Token.FLE) {
171: // rewrite count(x)=0 as empty(x)
172: FunctionCall fn = SystemFunction
173: .makeSystemFunction("empty", 1, env
174: .getNamePool());
175: Expression[] args = new Expression[1];
176: args[0] = ((FunctionCall) operand0).argument[0];
177: fn.setArguments(args);
178: return fn;
179: } else if (operator == Token.FNE
180: || operator == Token.FGT) {
181: // rewrite count(x)!=0, count(x)>0 as exists(x)
182: FunctionCall fn = SystemFunction
183: .makeSystemFunction("exists", 1, env
184: .getNamePool());
185: Expression[] args = new Expression[1];
186: args[0] = ExpressionTool.unsorted(opt,
187: ((FunctionCall) operand0).argument[0],
188: false);
189: fn.setArguments(args);
190: return fn;
191: } else if (operator == Token.FGE) {
192: // rewrite count(x)>=0 as true()
193: return BooleanValue.TRUE;
194: } else { // singletonOperator == Token.FLT
195: // rewrite count(x)<0 as false()
196: return BooleanValue.FALSE;
197: }
198: } else if (operand1 instanceof IntegerValue
199: && (operator == Token.FGT || operator == Token.FGE)) {
200: // rewrite count(x) gt n as exists(x[n+1])
201: // and count(x) ge n as exists(x[n])
202: long val = ((IntegerValue) operand1).longValue();
203: if (operator == Token.FGT) {
204: val++;
205: }
206: FunctionCall fn = SystemFunction.makeSystemFunction(
207: "exists", 1, env.getNamePool());
208: Expression[] args = new Expression[1];
209: FilterExpression filter = new FilterExpression(
210: ((FunctionCall) operand0).argument[0],
211: new IntegerValue(val), env);
212: args[0] = filter;
213: fn.setArguments(args);
214: return fn;
215: }
216: }
217:
218: // optimise (0 eq count(x)), etc
219:
220: if (Aggregate.isCountFunction(operand1) && isZero(operand0)) {
221: Expression s = new ValueComparison(operand1, Token
222: .inverse(operator), operand0).typeCheck(env,
223: contextItemType);
224: //((ValueComparison)s).defaultCollation = defaultCollation;
225: return s.optimize(opt, env, contextItemType);
226: }
227:
228: // optimise string-length(x) = 0, >0, !=0 etc
229:
230: if ((operand0 instanceof StringLength)
231: && (((StringLength) operand0).getNumberOfArguments() == 1)
232: && isZero(operand1)) {
233: ((StringLength) operand0).setShortcut();
234: }
235:
236: // optimise (0 = string-length(x)), etc
237:
238: if ((operand1 instanceof StringLength)
239: && (((StringLength) operand1).getNumberOfArguments() == 1)
240: && isZero(operand0)) {
241: ((StringLength) operand1).setShortcut();
242: }
243:
244: // optimise [position()=last()] etc
245:
246: if ((operand0 instanceof Position)
247: && (operand1 instanceof Last)) {
248: switch (operator) {
249: case Token.FEQ:
250: case Token.FGE:
251: return new IsLastExpression(true);
252: case Token.FNE:
253: case Token.FLT:
254: return new IsLastExpression(false);
255: case Token.FGT:
256: return BooleanValue.FALSE;
257: case Token.FLE:
258: return BooleanValue.TRUE;
259: }
260: }
261: if ((operand0 instanceof Last)
262: && (operand1 instanceof Position)) {
263: switch (operator) {
264: case Token.FEQ:
265: case Token.FLE:
266: return new IsLastExpression(true);
267: case Token.FNE:
268: case Token.FGT:
269: return new IsLastExpression(false);
270: case Token.FLT:
271: return BooleanValue.FALSE;
272: case Token.FGE:
273: return BooleanValue.TRUE;
274: }
275: }
276:
277: // optimise [position() < n] etc
278:
279: if (operand0 instanceof Position) {
280: boolean isInteger = (operand1 instanceof IntegerValue);
281: int pos = 0;
282: if (isInteger) {
283: pos = (int) ((IntegerValue) operand1).longValue();
284: if (pos < 0) {
285: pos = 0;
286: }
287: }
288: switch (operator) {
289: case Token.FEQ:
290: return new PositionRange(operand1, operand1);
291: case Token.FGE:
292: return new PositionRange(operand1, null);
293: case Token.FNE:
294: if (isInteger && pos == 1) {
295: return new PositionRange(2, Integer.MAX_VALUE);
296: } else {
297: break;
298: }
299: case Token.FLT:
300: if (isInteger) {
301: return new PositionRange(1, pos - 1);
302: } else {
303: break;
304: }
305: case Token.FGT:
306: if (isInteger) {
307: return new PositionRange(pos + 1, Integer.MAX_VALUE);
308: } else {
309: break;
310: }
311: case Token.FLE:
312: if (isInteger) {
313: return new PositionRange(1, pos);
314: } else {
315: break;
316: }
317: }
318: }
319:
320: if (operand1 instanceof Position) {
321: int pos = 0;
322: boolean isInteger = (operand0 instanceof IntegerValue);
323: if (isInteger) {
324: pos = (int) ((IntegerValue) operand0).longValue();
325: if (pos < 0) {
326: pos = 0;
327: }
328: }
329:
330: switch (operator) {
331: case Token.FEQ:
332: return new PositionRange(operand0, operand0);
333: case Token.FLE:
334: return new PositionRange(operand0, null);
335: case Token.FNE:
336: if (isInteger && pos == 1) {
337: return new PositionRange(2, Integer.MAX_VALUE);
338: } else {
339: break;
340: }
341: case Token.FGT:
342: if (isInteger) {
343: return new PositionRange(1, pos - 1);
344: } else {
345: break;
346: }
347: case Token.FLT:
348: if (isInteger) {
349: return new PositionRange(pos + 1, Integer.MAX_VALUE);
350: } else {
351: break;
352: }
353: case Token.FGE:
354: if (isInteger) {
355: return new PositionRange(1, pos);
356: } else {
357: break;
358: }
359: }
360: }
361:
362: // optimize generate-id(X) = generate-id(Y) as "X is Y"
363: // This construct is often used in XSLT 1.0 stylesheets.
364: // Only do this if we know the arguments are singletons, because "is" doesn't
365: // do first-value extraction.
366:
367: if (NamePart.isGenerateIdFunction(operand0)
368: && NamePart.isGenerateIdFunction(operand1)) {
369: FunctionCall f0 = (FunctionCall) operand0;
370: FunctionCall f1 = (FunctionCall) operand1;
371: if (!Cardinality
372: .allowsMany(f0.argument[0].getCardinality())
373: && !Cardinality.allowsMany(f1.argument[0]
374: .getCardinality())
375: && (operator == Token.FEQ)) {
376: IdentityComparison id = new IdentityComparison(
377: f0.argument[0], Token.IS, f1.argument[0]);
378: id.setGenerateIdEmulation(true);
379: return id.simplify(env).typeCheck(env, contextItemType)
380: .optimize(opt, env, contextItemType);
381: }
382: }
383:
384: // evaluate the expression now if both arguments are constant
385:
386: if ((operand0 instanceof Value) && (operand1 instanceof Value)) {
387: return (AtomicValue) evaluateItem(env
388: .makeEarlyEvaluationContext());
389: }
390:
391: return this ;
392: }
393:
394: /**
395: * Return the negation of this value comparison: that is, a value comparison that returns true()
396: * if and only if the original returns false()
397: */
398:
399: public ValueComparison negate() {
400: ValueComparison vc = new ValueComparison(operand0, Token
401: .negate(operator), operand1);
402: vc.comparer = comparer;
403: return vc;
404: }
405:
406: /**
407: * Test whether an expression is constant zero
408: */
409:
410: private static boolean isZero(Expression exp) {
411: try {
412: if (!(exp instanceof AtomicValue))
413: return false;
414: if (exp instanceof IntegerValue) {
415: return ((IntegerValue) exp).longValue() == 0;
416: }
417: if (exp instanceof BigIntegerValue) {
418: return ((BigIntegerValue) exp)
419: .compareTo(BigIntegerValue.ZERO) == 0;
420: }
421:
422: Value val = ((AtomicValue) exp).convert(Type.INTEGER, null);
423: return isZero(val);
424: } catch (XPathException err) {
425: return false;
426: }
427: }
428:
429: /**
430: * Evaluate the expression in a boolean context
431: * @param context the given context for evaluation
432: * @return a boolean representing the result of the numeric comparison of the two operands
433: */
434:
435: public boolean effectiveBooleanValue(XPathContext context)
436: throws XPathException {
437: try {
438: AtomicValue v1 = ((AtomicValue) operand0
439: .evaluateItem(context));
440: if (v1 == null)
441: return false;
442: if (v1 instanceof UntypedAtomicValue) {
443: v1 = v1.convert(Type.STRING, context);
444: }
445: AtomicValue v2 = ((AtomicValue) operand1
446: .evaluateItem(context));
447: if (v2 == null)
448: return false;
449: if (v2 instanceof UntypedAtomicValue) {
450: v2 = v2.convert(Type.STRING, context);
451: }
452: return compare(v1, operator, v2, comparer);
453: } catch (DynamicError e) {
454: // re-throw the exception with location information added
455: if (e.getXPathContext() == null) {
456: e.setXPathContext(context);
457: }
458: if (e.getLocator() == null) {
459: e.setLocator(this );
460: }
461: throw e;
462: }
463: }
464:
465: /**
466: * Compare two atomic values, using a specified operator and collation
467: * @param v1 the first operand
468: * @param op the operator, as defined by constants such as {@link Token#FEQ} or
469: * {@link Token#FLT}
470: * @param v2 the second operand
471: * @param collator the Collator to be used when comparing strings
472: * @throws net.sf.saxon.trans.DynamicError if the values are not comparable
473: */
474:
475: static boolean compare(AtomicValue v1, int op, AtomicValue v2,
476: AtomicComparer collator) throws DynamicError {
477: if (v1 instanceof NumericValue && ((NumericValue) v1).isNaN()) {
478: return false;
479: }
480: if (v2 instanceof NumericValue && ((NumericValue) v2).isNaN()) {
481: return false;
482: }
483: try {
484: switch (op) {
485: case Token.FEQ:
486: return collator.comparesEqual(v1, v2);
487: case Token.FNE:
488: return !collator.comparesEqual(v1, v2);
489: case Token.FGT:
490: return collator.compare(v1, v2) > 0;
491: case Token.FLT:
492: return collator.compare(v1, v2) < 0;
493: case Token.FGE:
494: return collator.compare(v1, v2) >= 0;
495: case Token.FLE:
496: return collator.compare(v1, v2) <= 0;
497: default:
498: throw new UnsupportedOperationException(
499: "Unknown operator " + op);
500: }
501: } catch (ClassCastException err) {
502: DynamicError e2 = new DynamicError("Cannot compare "
503: + v1.getItemType(null) + " to "
504: + v2.getItemType(null));
505: e2.setErrorCode("XPTY0004");
506: e2.setIsTypeError(true);
507: throw e2;
508: }
509: }
510:
511: /**
512: * Evaluate the expression in a given context
513: * @param context the given context for evaluation
514: * @return a BooleanValue representing the result of the numeric comparison of the two operands,
515: * or null representing the empty sequence
516: */
517:
518: public Item evaluateItem(XPathContext context)
519: throws XPathException {
520: try {
521: AtomicValue v1 = (AtomicValue) operand0
522: .evaluateItem(context);
523: if (v1 == null)
524: return null;
525: if (v1 instanceof UntypedAtomicValue) {
526: v1 = v1.convert(Type.STRING, context);
527: }
528: AtomicValue v2 = (AtomicValue) operand1
529: .evaluateItem(context);
530: if (v2 == null)
531: return null;
532: if (v2 instanceof UntypedAtomicValue) {
533: v2 = v2.convert(Type.STRING, context);
534: }
535: return BooleanValue
536: .get(compare(v1, operator, v2, comparer));
537: } catch (DynamicError e) {
538: // re-throw the exception with location information added
539: if (e.getXPathContext() == null) {
540: e.setXPathContext(context);
541: }
542: if (e.getLocator() == null) {
543: e.setLocator(this );
544: }
545: throw e;
546: }
547: }
548:
549: /**
550: * Determine the data type of the expression
551: * @return Type.BOOLEAN
552: * @param th
553: */
554:
555: public ItemType getItemType(TypeHierarchy th) {
556: return Type.BOOLEAN_TYPE;
557: }
558:
559: }
560:
561: //
562: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
563: // you may not use this file except in compliance with the License. You may obtain a copy of the
564: // License at http://www.mozilla.org/MPL/
565: //
566: // Software distributed under the License is distributed on an "AS IS" basis,
567: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
568: // See the License for the specific language governing rights and limitations under the License.
569: //
570: // The Original Code is: all this file.
571: //
572: // The Initial Developer of the Original Code is Michael H. Kay.
573: //
574: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
575: //
576: // Contributor(s): none.
577: //
|