KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > objectstyle > cayenne > exp > ASTNode


1 /* ====================================================================
2  *
3  * The ObjectStyle Group Software License, version 1.1
4  * ObjectStyle Group - http://objectstyle.org/
5  *
6  * Copyright (c) 2002-2005, Andrei (Andrus) Adamchik and individual authors
7  * of the software. All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  *
13  * 1. Redistributions of source code must retain the above copyright
14  * notice, this list of conditions and the following disclaimer.
15  *
16  * 2. Redistributions in binary form must reproduce the above copyright
17  * notice, this list of conditions and the following disclaimer in
18  * the documentation and/or other materials provided with the
19  * distribution.
20  *
21  * 3. The end-user documentation included with the redistribution, if any,
22  * must include the following acknowlegement:
23  * "This product includes software developed by independent contributors
24  * and hosted on ObjectStyle Group web site (http://objectstyle.org/)."
25  * Alternately, this acknowlegement may appear in the software itself,
26  * if and wherever such third-party acknowlegements normally appear.
27  *
28  * 4. The names "ObjectStyle Group" and "Cayenne" must not be used to endorse
29  * or promote products derived from this software without prior written
30  * permission. For written permission, email
31  * "andrus at objectstyle dot org".
32  *
33  * 5. Products derived from this software may not be called "ObjectStyle"
34  * or "Cayenne", nor may "ObjectStyle" or "Cayenne" appear in their
35  * names without prior written permission.
36  *
37  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40  * DISCLAIMED. IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
41  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
43  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
44  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
46  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
47  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
48  * SUCH DAMAGE.
49  * ====================================================================
50  *
51  * This software consists of voluntary contributions made by many
52  * individuals and hosted on ObjectStyle Group web site. For more
53  * information on the ObjectStyle Group, please see
54  * <http://objectstyle.org/>.
55  */

56 package org.objectstyle.cayenne.exp;
57
58 import java.util.Collection JavaDoc;
59
60 import org.apache.commons.beanutils.PropertyUtils;
61 import org.apache.log4j.Logger;
62 import org.apache.oro.text.perl.Perl5Util;
63 import org.objectstyle.cayenne.DataObject;
64 import org.objectstyle.cayenne.map.Entity;
65 import org.objectstyle.cayenne.util.Util;
66
67 /**
68  * A node of the Abstract Syntax Tree (AST) for a compiled Cayenne expression.
69  * ASTNode is implemented as a linked list holding a reference to the next node
70  * in the chain. This way, a chain of AST nodes can be evaluated by calling
71  * a method on the starting node. ASTNodes also has abstract API for various
72  * optimizations based on conditional execution.
73  *
74  * <p>Also serves as a factory for specialized nodes handling various operations.</p>
75  *
76  * @since 1.0.6
77  * @author Andrei Adamchik
78  */

79 abstract class ASTNode {
80     private static Logger logObj = Logger.getLogger(ASTNode.class);
81
82     // used by all regex processing nodes
83
// note that according to the docs it is synchronized
84
private static final Perl5Util regexUtil = new Perl5Util();
85
86     protected ASTNode nextNode;
87
88     static ASTNode buildObjectNode(Object JavaDoc object, ASTNode parent) {
89         ASTNode node = new PushNode(object);
90         return parent != null ? parent.wrapChildNode(node) : node;
91     }
92
93     static ASTNode buildExpressionNode(Expression expression, ASTNode parent) {
94         ASTNode node;
95
96         switch (expression.getType()) {
97             case Expression.OBJ_PATH :
98             case Expression.DB_PATH :
99                 node = new PropertyNode(expression);
100                 break;
101             case Expression.EQUAL_TO :
102                 node = new EqualsNode();
103                 break;
104             case Expression.OR :
105                 node = new OrNode();
106                 break;
107             case Expression.AND :
108                 node = new AndNode();
109                 break;
110             case Expression.NOT :
111                 node = new NotNode();
112                 break;
113             case Expression.LIST :
114                 node = new ListNode(expression.getOperand(0));
115                 break;
116             case Expression.NOT_EQUAL_TO :
117                 node = new NotEqualsNode();
118                 break;
119             case Expression.LESS_THAN :
120                 node = new LessThanNode();
121                 break;
122             case Expression.LESS_THAN_EQUAL_TO :
123                 node = new LessThanEqualsToNode();
124                 break;
125             case Expression.GREATER_THAN :
126                 node = new GreaterThanNode();
127                 break;
128             case Expression.GREATER_THAN_EQUAL_TO :
129                 node = new GreaterThanEqualsToNode();
130                 break;
131             case Expression.BETWEEN :
132                 node = new BetweenNode(false);
133                 break;
134             case Expression.NOT_BETWEEN :
135                 node = new BetweenNode(true);
136                 break;
137             case Expression.IN :
138                 node = new InNode(false);
139                 break;
140             case Expression.NOT_IN :
141                 node = new InNode(true);
142                 break;
143             case Expression.LIKE :
144                 node = new LikeNode((String JavaDoc) expression.getOperand(1), false, false);
145                 break;
146             case Expression.NOT_LIKE :
147                 node = new LikeNode((String JavaDoc) expression.getOperand(1), true, false);
148                 break;
149             case Expression.LIKE_IGNORE_CASE :
150                 node = new LikeNode((String JavaDoc) expression.getOperand(1), false, true);
151                 break;
152             case Expression.NOT_LIKE_IGNORE_CASE :
153                 node = new LikeNode((String JavaDoc) expression.getOperand(1), true, true);
154                 break;
155             default :
156                 throw new ExpressionException(
157                     "Unsupported expression type: "
158                         + expression.getType()
159                         + " ("
160                         + expression.expName()
161                         + ")");
162         }
163
164         return parent != null ? parent.wrapChildNode(node) : node;
165     }
166
167     private static boolean contains(Object JavaDoc[] objects, Object JavaDoc object) {
168         int size = objects.length;
169
170         for (int i = 0; i < size; i++) {
171             if (Util.nullSafeEquals(objects[i], object)) {
172                 return true;
173             }
174         }
175
176         return false;
177     }
178
179     /**
180      * Optionally can wrap a child node with a reference wrapper.
181      * Default implementation simply returns childNode unchanged.
182      */

183     ASTNode wrapChildNode(ASTNode childNode) {
184         return childNode;
185     }
186
187     ASTNode getNextNode() {
188         return nextNode;
189     }
190
191     void setNextNode(ASTNode nextNode) {
192         this.nextNode = nextNode;
193     }
194
195     abstract void appendString(StringBuffer JavaDoc buffer);
196
197     /**
198      * Evaluates a chain of ASTNodes, starting from this node, in the context
199      * of a JavaBean object that will provide property values for the path nodes.
200      * The result is converted to boolean.
201      */

202     boolean evaluateBooleanASTChain(Object JavaDoc bean) throws ExpressionException {
203         return ASTStack.booleanFromObject(evaluateASTChain(bean));
204     }
205
206     /**
207      * Evaluates a chain of ASTNodes, starting from this node, in the context
208      * of a JavaBean object that will provide property values for the path nodes.
209      */

210     Object JavaDoc evaluateASTChain(Object JavaDoc bean) throws ExpressionException {
211         ASTNode currentNode = this;
212         ASTStack stack = new ASTStack();
213
214         // wrap in try/catch to provide unified exception processing
215
try {
216             while ((currentNode = currentNode.evaluateWithObject(stack, bean)) != null) {
217                 // empty loop, all the action happens in condition part
218
}
219
220             return stack.pop();
221         }
222         catch (Throwable JavaDoc th) {
223             if (th instanceof ExpressionException) {
224                 throw (ExpressionException) th;
225             }
226             else {
227                 throw new ExpressionException(
228                     "Error evaluating expression.",
229                     this.toString(),
230                     Util.unwindException(th));
231             }
232         }
233     }
234
235     /**
236      * Evaluates expression using stack for the current values and
237      * an object parameter as a context for evaluation. Returns the next
238      * ASTNode to evaluate or null if evaluation is complete.
239      */

240     abstract ASTNode evaluateWithObject(ASTStack stack, Object JavaDoc bean);
241
242     public String JavaDoc toString() {
243         // appends to a buffer producing a reverse polish notation (RPN)
244
StringBuffer JavaDoc buffer = new StringBuffer JavaDoc();
245
246         for (ASTNode node = this; node != null; node = node.getNextNode()) {
247             node.appendString(buffer);
248         }
249
250         return buffer.toString();
251     }
252
253     // ***************** concrete subclasses
254

255     final static class PushNode extends ASTNode {
256         Object JavaDoc value;
257
258         PushNode(Object JavaDoc value) {
259             this.value = value;
260         }
261
262         ASTNode evaluateWithObject(ASTStack stack, Object JavaDoc bean) {
263             stack.push(value);
264             return nextNode;
265         }
266
267         void appendString(StringBuffer JavaDoc buffer) {
268             buffer.append("%@");
269         }
270     }
271
272     final static class ListNode extends ASTNode {
273         Object JavaDoc[] value;
274
275         ListNode(Object JavaDoc object) {
276             // TODO: maybe it makes sense to sort the list
277
// now, to speed up lookups later?
278
if (object instanceof Collection JavaDoc) {
279                 value = ((Collection JavaDoc) object).toArray();
280             }
281             else if (object instanceof Object JavaDoc[]) {
282                 value = (Object JavaDoc[]) object;
283             }
284             else {
285                 // object is really not a collection... I guess it would make
286
// sense to wrap it in a list
287
value = new Object JavaDoc[] { object };
288             }
289         }
290
291         ASTNode evaluateWithObject(ASTStack stack, Object JavaDoc bean) {
292             stack.push(value);
293             return nextNode;
294         }
295
296         void appendString(StringBuffer JavaDoc buffer) {
297             buffer.append("(%@)");
298         }
299     }
300
301     final static class PropertyNode extends ASTNode {
302         Expression pathExp;
303         String JavaDoc propertyPath;
304
305         PropertyNode(Expression pathExp) {
306             this.pathExp = pathExp;
307             this.propertyPath = (String JavaDoc) pathExp.getOperand(0);
308         }
309
310         ASTNode evaluateWithObject(ASTStack stack, Object JavaDoc bean) {
311             // push property value on stack
312
try {
313                 // for DataObjects it should be faster to read property via
314
// dataObject methods instead of reflection
315
// for entities the whole meaning is different - we should return
316
// an iterator over attributes/relationships...
317
stack.push(
318                     (bean instanceof DataObject)
319                         ? ((DataObject) bean).readNestedProperty(propertyPath)
320                         : (bean instanceof Entity)
321                         ? ((Entity) bean).resolvePathComponents(pathExp)
322                         : PropertyUtils.getProperty(bean, propertyPath));
323             }
324             catch (Exception JavaDoc ex) {
325                 String JavaDoc beanClass = (bean != null) ? bean.getClass().getName() : "<null>";
326                 String JavaDoc msg =
327                     "Error reading property '" + beanClass + "." + propertyPath + "'.";
328                 logObj.warn(msg, ex);
329                 throw new ExpressionException(msg, ex);
330             }
331             return nextNode;
332         }
333
334         void appendString(StringBuffer JavaDoc buffer) {
335             buffer.append("'").append(propertyPath).append("'");
336         }
337     }
338
339     final static class EqualsNode extends ASTNode {
340         ASTNode evaluateWithObject(ASTStack stack, Object JavaDoc bean) {
341             // expects at least two values on the stack
342
stack.push(Util.nullSafeEquals(stack.pop(), stack.pop()));
343             return nextNode;
344         }
345
346         void appendString(StringBuffer JavaDoc buffer) {
347             buffer.append(" = ");
348         }
349     }
350
351     final static class AndNode extends ASTNode {
352         ASTNode evaluateWithObject(ASTStack stack, Object JavaDoc bean) {
353             // expects two booleans on the stack
354
stack.push(stack.popBoolean() && stack.popBoolean());
355             return nextNode;
356         }
357
358         ASTNode wrapChildNode(ASTNode childNode) {
359             return new AndOperandWrapper(childNode, this);
360         }
361
362         void appendString(StringBuffer JavaDoc buffer) {
363             buffer.append(" and ");
364         }
365     }
366
367     final static class OrNode extends ASTNode {
368         ASTNode evaluateWithObject(ASTStack stack, Object JavaDoc bean) {
369             // expects two booleans on the stack
370
stack.push(stack.popBoolean() || stack.popBoolean());
371             return nextNode;
372         }
373
374         ASTNode wrapChildNode(ASTNode childNode) {
375             return new OrOperandWrapper(childNode, this);
376         }
377
378         void appendString(StringBuffer JavaDoc buffer) {
379             buffer.append(" or ");
380         }
381     }
382
383     final static class NotNode extends ASTNode {
384         ASTNode evaluateWithObject(ASTStack stack, Object JavaDoc bean) {
385             // expects one boolean on the stack
386
stack.push(!stack.popBoolean());
387             return nextNode;
388         }
389
390         void appendString(StringBuffer JavaDoc buffer) {
391             buffer.append(" not ");
392         }
393     }
394
395     final static class NotEqualsNode extends ASTNode {
396         ASTNode evaluateWithObject(ASTStack stack, Object JavaDoc bean) {
397             // expects at least two values on the stack
398
stack.push(!Util.nullSafeEquals(stack.pop(), stack.pop()));
399             return nextNode;
400         }
401
402         void appendString(StringBuffer JavaDoc buffer) {
403             buffer.append(" != ");
404         }
405     }
406
407     final static class LessThanNode extends ASTNode {
408         ASTNode evaluateWithObject(ASTStack stack, Object JavaDoc bean) {
409             // expects at least two values on the stack
410
boolean result = false;
411
412             Object JavaDoc c1 = stack.pop();
413             Comparable JavaDoc c2 = stack.popComparable();
414
415             // can't compare nulls...be consistent with SQL
416
if (c1 != null && c2 != null) {
417                 // values are popped in reverse order of insertion,
418
// so compare the one popped last...
419
result = c2.compareTo(c1) < 0;
420             }
421
422             stack.push(result);
423             return nextNode;
424         }
425
426         void appendString(StringBuffer JavaDoc buffer) {
427             buffer.append(" < ");
428         }
429     }
430
431     final static class LessThanEqualsToNode extends ASTNode {
432         ASTNode evaluateWithObject(ASTStack stack, Object JavaDoc bean) {
433             // expects at least two values on the stack
434
boolean result = false;
435
436             Object JavaDoc c1 = stack.pop();
437             Comparable JavaDoc c2 = stack.popComparable();
438
439             // can't compare nulls...be consistent with SQL
440
if (c1 != null && c2 != null) {
441                 // values are popped in reverse order of insertion,
442
// so compare the one popped last...
443
result = c2.compareTo(c1) <= 0;
444             }
445
446             stack.push(result);
447             return nextNode;
448         }
449
450         void appendString(StringBuffer JavaDoc buffer) {
451             buffer.append(" <= ");
452         }
453     }
454
455     final static class GreaterThanNode extends ASTNode {
456         ASTNode evaluateWithObject(ASTStack stack, Object JavaDoc bean) {
457             // expects at least two values on the stack
458
boolean result = false;
459
460             Object JavaDoc c1 = stack.pop();
461             Comparable JavaDoc c2 = stack.popComparable();
462
463             // can't compare nulls...be consistent with SQL
464
if (c1 != null && c2 != null) {
465                 // values are popped in reverse order of insertion,
466
// so compare the one popped last...
467
result = c2.compareTo(c1) > 0;
468             }
469
470             stack.push(result);
471             return nextNode;
472         }
473
474         void appendString(StringBuffer JavaDoc buffer) {
475             buffer.append(" > ");
476         }
477     }
478
479     final static class GreaterThanEqualsToNode extends ASTNode {
480         ASTNode evaluateWithObject(ASTStack stack, Object JavaDoc bean) {
481             // expects at least two values on the stack
482
boolean result = false;
483
484             Object JavaDoc c1 = stack.pop();
485             Comparable JavaDoc c2 = stack.popComparable();
486
487             // can't compare nulls...be consistent with SQL
488
if (c1 != null && c2 != null) {
489                 // values are popped in reverse order of insertion,
490
// so compare the one popped last...
491
result = c2.compareTo(c1) >= 0;
492             }
493
494             stack.push(result);
495             return nextNode;
496         }
497
498         void appendString(StringBuffer JavaDoc buffer) {
499             buffer.append(" >= ");
500         }
501     }
502
503     final static class BetweenNode extends ASTNode {
504         boolean negate;
505
506         BetweenNode(boolean negate) {
507             this.negate = negate;
508         }
509
510         ASTNode evaluateWithObject(ASTStack stack, Object JavaDoc bean) {
511             boolean result = false;
512
513             // pop in reverse order - c3 must be BETWEEN c2 and c1
514

515             Comparable JavaDoc c1 = stack.popComparable();
516             Comparable JavaDoc c2 = stack.popComparable();
517             Object JavaDoc c3 = stack.pop();
518
519             // can't compare nulls...be consistent with SQL
520
if (c1 != null && c2 != null && c3 != null) {
521                 // values are popped in reverse order of insertion,
522
// so compare the one popped last...
523
result = c2.compareTo(c3) <= 0 && c1.compareTo(c3) >= 0;
524             }
525
526             stack.push((negate) ? !result : result);
527             return nextNode;
528         }
529
530         void appendString(StringBuffer JavaDoc buffer) {
531             if (negate) {
532                 buffer.append(" NOT");
533             }
534             buffer.append(" BETWEEN ");
535         }
536     }
537
538     final static class InNode extends ASTNode {
539         boolean negate;
540
541         InNode(boolean negate) {
542             this.negate = negate;
543         }
544
545         ASTNode evaluateWithObject(ASTStack stack, Object JavaDoc bean) {
546
547             // pop in reverse order - o2 must be IN o1 list
548
Object JavaDoc[] o1 = (Object JavaDoc[]) stack.pop();
549             Object JavaDoc o2 = stack.pop();
550
551             boolean result = contains(o1, o2);
552             stack.push((negate) ? !result : result);
553             return nextNode;
554         }
555
556         void appendString(StringBuffer JavaDoc buffer) {
557             if (negate) {
558                 buffer.append(" NOT");
559             }
560             buffer.append(" IN ");
561         }
562     }
563
564     final static class LikeNode extends ASTNode {
565         String JavaDoc regex;
566         boolean negate;
567
568         LikeNode(String JavaDoc pattern, boolean negate, boolean ignoreCase) {
569             this.regex = Util.sqlPatternToRegex(pattern, ignoreCase);
570             this.negate = negate;
571         }
572
573         ASTNode evaluateWithObject(ASTStack stack, Object JavaDoc bean) {
574             // LIKE AST uses a single operand, since regex is precompiled
575
Object JavaDoc o = stack.pop();
576             String JavaDoc string = (o != null) ? o.toString() : null;
577
578             boolean match = regexUtil.match(regex, string);
579             stack.push((negate) ? !match : match);
580             return nextNode;
581         }
582
583         void appendString(StringBuffer JavaDoc buffer) {
584             if (negate) {
585                 buffer.append(" NOT");
586             }
587
588             buffer.append(" LIKE ").append(regex);
589         }
590     }
591
592     // ***************** wrappers and other helpers
593

594     /**
595      * A wrapper for an ASTNode that allows to skip further peer
596      * nodes evaluation on a certain outcome. Useful for optimizing
597      * AND, OR operations, etc.
598      */

599     abstract static class ConditionalJumpNode extends ASTNode {
600         ASTNode wrappedNode;
601         ASTNode altNode;
602
603         ConditionalJumpNode(ASTNode wrappedNode, ASTNode altNode) {
604             this.wrappedNode = wrappedNode;
605             this.altNode = altNode;
606         }
607
608         ASTNode evaluateWithObject(ASTStack stack, Object JavaDoc bean) {
609             ASTNode next = wrappedNode.evaluateWithObject(stack, bean);
610
611             // pick on stack state and decide whether to continue
612
// with the normal flow, or jump to alternative node
613
return jumpPastAltNode(stack)
614                 ? ((altNode != null) ? altNode.getNextNode() : null)
615                 : next;
616         }
617
618         ASTNode getNextNode() {
619             return (wrappedNode != null) ? wrappedNode.getNextNode() : null;
620         }
621
622         void setNextNode(ASTNode nextNode) {
623             if (wrappedNode != null) {
624                 wrappedNode.setNextNode(nextNode);
625             }
626         }
627
628         void appendString(StringBuffer JavaDoc buffer) {
629             buffer.append("(");
630             if (wrappedNode != null) {
631                 wrappedNode.appendString(buffer);
632             }
633             else {
634                 buffer.append("?");
635             }
636             buffer.append(")");
637         }
638
639         abstract boolean jumpPastAltNode(ASTStack stack);
640     }
641
642     final static class AndOperandWrapper extends ConditionalJumpNode {
643         AndOperandWrapper(ASTNode wrappedNode, ASTNode altNode) {
644             super(wrappedNode, altNode);
645         }
646
647         boolean jumpPastAltNode(ASTStack stack) {
648             return !stack.peekBoolean();
649         }
650     }
651
652     final static class OrOperandWrapper extends ConditionalJumpNode {
653         OrOperandWrapper(ASTNode wrappedNode, ASTNode altNode) {
654             super(wrappedNode, altNode);
655         }
656
657         boolean jumpPastAltNode(ASTStack stack) {
658             return stack.peekBoolean();
659         }
660     }
661 }
662
Popular Tags