View Javadoc

1   /*
2   Copyright (C) 2004 Grid Systems, S.A.
3   
4   This program is free software; you can redistribute it and/or modify
5   it under the terms of the GNU General Public License, version 2, as
6   published by the Free Software Foundation.
7   
8   This program is distributed in the hope that it will be useful,
9   but WITHOUT ANY WARRANTY; without even the implied warranty of
10  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  GNU General Public License for more details.
12  
13  You should have received a copy of the GNU General Public License
14  along with this program; if not, write to the Free Software
15  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16  */
17  package com.gridsystems.beanfilter;
18  
19  import java.util.ArrayList;
20  import java.util.BitSet;
21  import java.util.HashMap;
22  import java.util.HashSet;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Set;
26  import java.util.regex.PatternSyntaxException;
27  
28  /**
29   * Comparison operation.
30   *
31   * @author Rodrigo Ruiz
32   * @author Xmas
33   * @version 1.0
34   */
35  public class CompareNode extends EvalNode implements FilterParserConstants {
36  
37    /**
38     * The set of comparator operations.
39     */
40    private static final BitSet COMPARATORS = new BitSet();
41  
42    /**
43     * Classes of Primitive Numbers.
44     */
45    private static final Set<Class<?>> PRIMITIVE_NUMBER_CLASSES = new HashSet<Class<?>>();
46  
47    /**
48     * Allowed Operations.
49     */
50    private static final Map<Class<?>, Operation[]> LEFTRIGHTOPERANDS;
51  
52    static {
53      COMPARATORS.set(EQ);
54      COMPARATORS.set(NE);
55      COMPARATORS.set(LT);
56      COMPARATORS.set(LE);
57      COMPARATORS.set(GT);
58      COMPARATORS.set(GE);
59      COMPARATORS.set(MATCHES);
60      COMPARATORS.set(LIKE);
61  
62      LEFTRIGHTOPERANDS = new HashMap<Class<?>, Operation[]>();
63  
64      LEFTRIGHTOPERANDS.put(HashSet.class, new Operation[]{
65        new Operation(HashSet.class, EQ, NE, CONTAINS, IN)
66      });
67      LEFTRIGHTOPERANDS.put(String.class, new Operation[] {
68        new Operation(String.class, EQ, NE, GT, LT, GE, LE, MATCHES, LIKE),
69        new Operation(List.class, IN)
70      });
71      LEFTRIGHTOPERANDS.put(HashSet.class, new Operation[]{
72        new Operation(HashSet.class, EQ, NE, CONTAINS, IN)
73      });
74      LEFTRIGHTOPERANDS.put(Number.class, new Operation[] {
75        new Operation(Number.class, EQ, NE, GT, LT, GE, LE),
76        new Operation(List.class, IN)
77      });
78      LEFTRIGHTOPERANDS.put(Boolean.class, new Operation[] {
79        new Operation(Boolean.class, EQ, NE),
80        new Operation(boolean.class, EQ, NE),
81        new Operation(List.class, IN)
82      });
83      LEFTRIGHTOPERANDS.put(boolean.class, new Operation[] {
84        new Operation(Boolean.class, EQ, NE),
85        new Operation(boolean.class, EQ, NE),
86        new Operation(List.class, IN)
87      });
88  
89      PRIMITIVE_NUMBER_CLASSES.add(int.class);
90      PRIMITIVE_NUMBER_CLASSES.add(long.class);
91      PRIMITIVE_NUMBER_CLASSES.add(float.class);
92      PRIMITIVE_NUMBER_CLASSES.add(double.class);
93      PRIMITIVE_NUMBER_CLASSES.add(short.class);
94      PRIMITIVE_NUMBER_CLASSES.add(byte.class);
95    }
96  
97    /**
98     * Internal class used to encapsulate allowed Operations.
99     * @author Xmas
100    *
101    */
102   private static class Operation {
103 
104     /**
105      * Right class.
106      */
107     final Class<?> right;
108 
109     /**
110      * Allowed operations.
111      */
112     final List<Integer> operations;
113 
114     /**
115      * Constructor.
116      *
117      * @param right Right class.
118      * @param operators List of valid operands
119      */
120     public Operation(Class<?> right, int ... operators) {
121       this.right = right;
122       operations = new ArrayList<Integer>();
123       for (int i = 0; i < operators.length; i++) {
124         operations.add(operators[i]);
125       }
126     }
127 
128     /**
129      * Check class and operand if is valid.
130      * @param right Right class.
131      * @param operation Operation to check.
132      * @return If is Allowed.
133      */
134     public boolean isAllowed(Class<?> right, int operation) {
135       return this.right.equals(right) && operations.contains(operation);
136     }
137 
138   }
139 
140   /**
141    * Left operand.
142    */
143   private EvalValue lval;
144 
145   /**
146    * Right operand.
147    */
148   private EvalValue rval;
149 
150   /**
151    * Operation.
152    */
153   private int op;
154 
155   /**
156    * Creates a new instance.
157    *
158    * @param lval  Left operand
159    * @param op    Operation
160    * @param rval  Right operand
161    * @param parentClass Parent Class
162    * @throws EvalException If an error occurs
163    */
164   public CompareNode(EvalValue lval, int op, EvalValue rval, Class<?> parentClass)
165     throws EvalException {
166     super(lval.getLinePos(), lval.getCharPos());
167     if (!COMPARATORS.get(op)) {
168       String operation;
169       try {
170         operation = tokenImage[op];
171       }  catch (IndexOutOfBoundsException e) {
172         operation = "?";
173       }
174       Object[] eparams = { lval.getLinePos(), lval.getCharPos(), operation };
175       throw new EvalException("FTR006", eparams);
176     }
177     this.lval = lval;
178     this.rval = rval;
179     this.op = op;
180 
181     checkSyntax(parentClass);
182   }
183 
184   /**
185    *
186    * @param parentClass Parent Class
187    * @throws EvalException If an error occurs
188    */
189   private void checkSyntax(Class<?> parentClass) throws EvalException {
190     Class<?> lClass = lval.getClassValue(parentClass);
191     Class<?> rClass = rval.getClassValue(parentClass);
192 
193     if (isNumber(lClass)) {
194       lClass = Number.class;
195     }
196     if (isNumber(rClass)) {
197       rClass = Number.class;
198     }
199 
200     Operation[] operations = LEFTRIGHTOPERANDS.get(lClass);
201 
202     if (operations != null) {
203       for (int i = 0; i < operations.length; i++) {
204         Operation oper = operations[i];
205         if (oper.isAllowed(rClass, this.op)) {
206           return;
207         }
208       }
209     } else {
210       switch (op) {
211         case EQ:
212         case NE:
213           if ((lClass != null) && (rClass != null) && !lClass.equals(rClass)) {
214             // FTR008=The element {0} with type {1} at position [{2}, {3}], has a diff
215             // type that the item {4} with type {5} at position [{6}, {7}]
216             Object[] params = {
217                 lval.toString(), lClass, lval.getLinePos(), lval.getCharPos(),
218                 rval.toString(), rClass, rval.getLinePos(), rval.getCharPos()
219             };
220             throw new EvalException("FTR008", params);
221           }
222           break;
223 
224         default:
225           // FTR006=Invalid operand {2} at position [{0}, {1}]
226           String operation = "?";
227           try {
228             operation = tokenImage[op];
229           }  catch (IndexOutOfBoundsException e) {
230             // ignore this exception
231           }
232           Object[] params = { lval.getLinePos(), lval.getCharPos(), operation };
233           throw new EvalException("FTR006", params);
234       }
235     }
236   }
237 
238   /**
239    * Class is a number instance.
240    * @param number Class to check
241    * @return true, if  class is a number instance
242    */
243   private static boolean isNumber(Class<?> number) {
244     if (number == null) {
245       return false;
246     }
247     if (Number.class.isAssignableFrom(number)) {
248       return true;
249     }
250     return PRIMITIVE_NUMBER_CLASSES.contains(number);
251   }
252 
253   /**
254    * {@inheritDoc}
255    */
256   @Override public boolean eval(Object src) throws EvalException {
257     Object lobj = lval.getValue(src);
258     Object robj = rval.getValue(src);
259 
260     if (lobj instanceof HashSet || robj instanceof HashSet) {
261       CollectionNode tempNode = new CollectionNode(lval, op, rval);
262       return tempNode.eval(src);
263     }
264 
265     return evaluateOperation(this.lval, this.rval, lobj, this.op, robj);
266   }
267 
268   /**
269    * @param lval   Left EvalValue.
270    * @param rval   Right EvalValue.
271    * @param lobj  The left operand
272    * @param op    Operation
273    * @param robj  The right operand
274    * @return If evaluation is passed.
275    * @throws EvalException If error
276    */
277   public static boolean evaluateOperation(EvalValue lval, EvalValue rval,
278     Object lobj, int op, Object robj) throws EvalException {
279     switch (op) {
280       case EQ:
281         return eq(lobj, robj);
282       case NE:
283         return !eq(lobj, robj);
284       case LE:
285         return comp(lval, rval, lobj, robj, true, true);
286       case LT:
287         return comp(lval, rval, lobj, robj, true, false);
288       case GT:
289         return comp(lval, rval, lobj, robj, false, false);
290       case GE:
291         return comp(lval, rval, lobj, robj, false, true);
292       case MATCHES:
293         return match(lval, rval, lobj, robj);
294       case LIKE:
295         return like(lval, rval, lobj, robj);
296       default:
297         return false;
298     }
299   }
300 
301 
302   /**
303    * Gets the left value.
304    *
305    * @return  The left value
306    */
307   public EvalValue getLeftValue() {
308     return lval;
309   }
310 
311   /**
312    * Gets the right value.
313    *
314    * @return  The right value
315    */
316   public EvalValue getRightValue() {
317     return rval;
318   }
319 
320   /**
321    * Gets the operation of this node.
322    *
323    * @return  This node operation
324    */
325   public int getOp() {
326     return op;
327   }
328 
329   /**
330    * Performs an "equals" operation.
331    *
332    * @param o1  The left operand
333    * @param o2  The right operand
334    * @return  <code>true</code> if both operands are equal
335    */
336   private static boolean eq(Object o1, Object o2) {
337     if (o1 == null) {
338       if (o2 == null) {
339         return true;
340       }
341       return false;
342     } else {
343       if (o2 == null) {
344         return false;
345       } else {
346         if (o1 instanceof Number && o2 instanceof Number) {
347           return (isInt(o1) && isInt(o2))
348                  ? ((Number)o1).longValue() == ((Number)o2).longValue()
349                  : ((Number)o1).doubleValue() == ((Number)o2).doubleValue();
350         } else {
351           return o1.equals(o2);
352         }
353       }
354     }
355   }
356 
357   /**
358    * Compares two operands. The result depends on the values of the sign and value flags.
359    *
360    * @param lval   Left EvalValue.
361    * @param rval   Right EvalValue.
362    * @param o1     The left operand
363    * @param o2     The right operand
364    * @param less   If true, it performs a "less than" operation; otherwise, it performs
365    *               a "greater than" operation.
366    * @param eq     If true, it performs a "less/greater than or equals" operation.
367    *               If false, it will perform a "strictly less/greater than" operation
368    * @return  true if the comparison is OK
369    * @throws EvalException FTR006: The operands are not comparable
370    */
371   @SuppressWarnings("unchecked")
372   private static boolean comp(EvalValue lval, EvalValue rval, Object o1, Object o2,
373     boolean less, boolean eq) throws EvalException {
374     // Sanity check.
375     if (o1 == null || o2 == null) {
376       return false;
377     }
378 
379     int sign = (less) ? -1 : 1;
380     int value = (eq) ? 0 : 1;
381 
382     if (o1 instanceof Number && o2 instanceof Number) {
383       Number n1 = (Number)o1;
384       Number n2 = (Number)o2;
385       if (isInt(n1) && isInt(n2)) {
386         long diff = n1.longValue() - n2.longValue();
387         return (sign * diff) >= value;
388       } else {
389         double dif = n1.doubleValue() - n2.doubleValue();
390         long diff = dif == 0.0 ? 0 : (dif > 0.0 ? sign : -sign);
391         return diff >= value;
392       }
393     } else if (o1 instanceof Comparable && o1.getClass().equals(o2.getClass())) {
394       int diff = ((Comparable)o1).compareTo(o2);
395       return (sign * diff) >= value;
396     }
397     throw new EvalException("FTR006", lval.getLinePos(), lval.getCharPos(), "");
398   }
399 
400   /**
401    * Performs a matching operation between two string operands.
402    *
403    * @param lval   Left EvalValue.
404    * @param rval   Right EvalValue.
405    * @param o1  The left operand
406    * @param o2  The right operand
407    * @return  true if o1 matches o2
408    * @throws EvalException FTR007:  Pattern syntax exception
409    */
410   private static boolean match(EvalValue lval, EvalValue rval, Object o1, Object o2)
411     throws EvalException {
412     // Sanity check.
413     if (o1 == null || o2 == null) {
414       return false;
415     }
416     String regex = o2.toString();
417     try {
418       return o1.toString().matches(regex);
419     } catch (PatternSyntaxException e) {
420       throw new EvalException("FTR007", rval.getLinePos(), rval.getCharPos(), regex);
421     }
422   }
423 
424   /**
425    * Performs a 'like' operation.
426    *
427    * @param lval   Left EvalValue.
428    * @param rval   Right EvalValue.
429    * @param o1  The left operand
430    * @param o2  The right operand
431    * @return    true if o1 'is like' o2
432    * @throws EvalException If an error occurs
433    */
434   private static boolean like(EvalValue lval, EvalValue rval, Object o1, Object o2)
435     throws EvalException {
436     // Sanity check.
437     if (o1 == null || o2 == null) {
438       return false;
439     }
440     StringBuffer sb = new StringBuffer();
441     String src = o2.toString();
442     for (int i = 0; i < src.length(); i++) {
443       char c = src.charAt(i);
444       switch (c) {
445         case '.':
446         case '\\':
447           sb.append('\\');
448           break;
449         case '?':
450         case '*':
451           sb.append('.');
452         default:
453           break;
454       }
455       sb.append(c);
456     }
457     return match(lval, rval, o1, sb);
458   }
459 
460   /**
461    * Gets if obj is an integer type (Integer, Long, Short or Byte).
462    *
463    * @param obj  The object
464    * @return <code>true</code> if it is an integer type
465    */
466   private static boolean isInt(Object obj) {
467     if (obj == null) {
468       return false;
469     }
470     return isNumber(obj.getClass());
471   }
472 
473   /**
474    * {@inheritDoc}
475    */
476   @Override public String toString() {
477     String oper = tokenImage[op];
478     if (oper.startsWith("\"")) {
479       oper = oper.substring(1, oper.length() - 1);
480     }
481     return lval + " " + oper + " " + rval;
482   }
483 }