ComparisonExpression.java
001 /*
002  *
003  * Licensed to the Apache Software Foundation (ASF) under one
004  * or more contributor license agreements.  See the NOTICE file
005  * distributed with this work for additional information
006  * regarding copyright ownership.  The ASF licenses this file
007  * to you under the Apache License, Version 2.0 (the
008  * "License"); you may not use this file except in compliance
009  * with the License.  You may obtain a copy of the License at
010  *
011  *   http://www.apache.org/licenses/LICENSE-2.0
012  *
013  * Unless required by applicable law or agreed to in writing,
014  * software distributed under the License is distributed on an
015  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
016  * KIND, either express or implied.  See the License for the
017  * specific language governing permissions and limitations
018  * under the License.
019  *
020  */
021 package org.apache.qpid.server.filter;
022 //
023 // Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html>
024 //
025 
026 import java.util.HashSet;
027 import java.util.List;
028 import java.util.regex.Pattern;
029 
030 import org.apache.qpid.server.queue.Filterable;
031 
032 /**
033  * A filter performing a comparison of two objects
034  */
035 public abstract class ComparisonExpression<E extends Exception> extends BinaryExpression<E> implements BooleanExpression<E>
036 {
037 
038     public static<E extends Exception> BooleanExpression<E> createBetween(Expression<E> value, Expression left, Expression<E> right)
039     {
040         return LogicExpression.createAND(createGreaterThanEqual(value, left), createLessThanEqual(value, right));
041     }
042 
043     public static<E extends Exception> BooleanExpression<E> createNotBetween(Expression<E> value, Expression<E> left, Expression<E> right)
044     {
045         return LogicExpression.createOR(createLessThan(value, left), createGreaterThan(value, right));
046     }
047 
048     private static final HashSet REGEXP_CONTROL_CHARS = new HashSet();
049 
050     static
051     {
052         REGEXP_CONTROL_CHARS.add(new Character('.'));
053         REGEXP_CONTROL_CHARS.add(new Character('\\'));
054         REGEXP_CONTROL_CHARS.add(new Character('['));
055         REGEXP_CONTROL_CHARS.add(new Character(']'));
056         REGEXP_CONTROL_CHARS.add(new Character('^'));
057         REGEXP_CONTROL_CHARS.add(new Character('$'));
058         REGEXP_CONTROL_CHARS.add(new Character('?'));
059         REGEXP_CONTROL_CHARS.add(new Character('*'));
060         REGEXP_CONTROL_CHARS.add(new Character('+'));
061         REGEXP_CONTROL_CHARS.add(new Character('{'));
062         REGEXP_CONTROL_CHARS.add(new Character('}'));
063         REGEXP_CONTROL_CHARS.add(new Character('|'));
064         REGEXP_CONTROL_CHARS.add(new Character('('));
065         REGEXP_CONTROL_CHARS.add(new Character(')'));
066         REGEXP_CONTROL_CHARS.add(new Character(':'));
067         REGEXP_CONTROL_CHARS.add(new Character('&'));
068         REGEXP_CONTROL_CHARS.add(new Character('<'));
069         REGEXP_CONTROL_CHARS.add(new Character('>'));
070         REGEXP_CONTROL_CHARS.add(new Character('='));
071         REGEXP_CONTROL_CHARS.add(new Character('!'));
072     }
073 
074     static class LikeExpression<E extends Exception> extends UnaryExpression<E> implements BooleanExpression<E>
075     {
076 
077         Pattern likePattern;
078 
079         /**
080          @param right
081          */
082         public LikeExpression(Expression<E> right, String like, int escape)
083         {
084             super(right);
085 
086             StringBuffer regexp = new StringBuffer(like.length() 2);
087             regexp.append("\\A")// The beginning of the input
088             for (int i = 0; i < like.length(); i++)
089             {
090                 char c = like.charAt(i);
091                 if (escape == (0xFFFF & c))
092                 {
093                     i++;
094                     if (i >= like.length())
095                     {
096                         // nothing left to escape...
097                         break;
098                     }
099 
100                     char t = like.charAt(i);
101                     regexp.append("\\x");
102                     regexp.append(Integer.toHexString(0xFFFF & t));
103                 }
104                 else if (c == '%')
105                 {
106                     regexp.append(".*?")// Do a non-greedy match
107                 }
108                 else if (c == '_')
109                 {
110                     regexp.append(".")// match one
111                 }
112                 else if (REGEXP_CONTROL_CHARS.contains(new Character(c)))
113                 {
114                     regexp.append("\\x");
115                     regexp.append(Integer.toHexString(0xFFFF & c));
116                 }
117                 else
118                 {
119                     regexp.append(c);
120                 }
121             }
122 
123             regexp.append("\\z")// The end of the input
124 
125             likePattern = Pattern.compile(regexp.toString(), Pattern.DOTALL);
126         }
127 
128         /**
129          *  org.apache.activemq.filter.UnaryExpression#getExpressionSymbol()
130          */
131         public String getExpressionSymbol()
132         {
133             return "LIKE";
134         }
135 
136         /**
137          *  org.apache.activemq.filter.Expression#evaluate(MessageEvaluationContext)
138          */
139         public Object evaluate(Filterable<E> messagethrows E
140         {
141 
142             Object rv = this.getRight().evaluate(message);
143 
144             if (rv == null)
145             {
146                 return null;
147             }
148 
149             if (!(rv instanceof String))
150             {
151                 return
152                     Boolean.FALSE;
153                     // throw new RuntimeException("LIKE can only operate on String identifiers.  LIKE attemped on: '" + rv.getClass());
154             }
155 
156             return likePattern.matcher((Stringrv).matches() ? Boolean.TRUE : Boolean.FALSE;
157         }
158 
159         public boolean matches(Filterable<E> messagethrows E
160         {
161             Object object = evaluate(message);
162 
163             return (object != null&& (object == Boolean.TRUE);
164         }
165     }
166 
167     public static BooleanExpression createLike(Expression left, String right, String escape)
168     {
169         if ((escape != null&& (escape.length() != 1))
170         {
171             throw new RuntimeException(
172                 "The ESCAPE string litteral is invalid.  It can only be one character.  Litteral used: " + escape);
173         }
174 
175         int c = -1;
176         if (escape != null)
177         {
178             c = 0xFFFF & escape.charAt(0);
179         }
180 
181         return new LikeExpression(left, right, c);
182     }
183 
184     public static BooleanExpression createNotLike(Expression left, String right, String escape)
185     {
186         return UnaryExpression.createNOT(createLike(left, right, escape));
187     }
188 
189     public static BooleanExpression createInFilter(Expression left, List elements)
190     {
191 
192         if (!(left instanceof PropertyExpression))
193         {
194             throw new RuntimeException("Expected a property for In expression, got: " + left);
195         }
196 
197         return UnaryExpression.createInExpression((PropertyExpressionleft, elements, false);
198 
199     }
200 
201     public static BooleanExpression createNotInFilter(Expression left, List elements)
202     {
203 
204         if (!(left instanceof PropertyExpression))
205         {
206             throw new RuntimeException("Expected a property for In expression, got: " + left);
207         }
208 
209         return UnaryExpression.createInExpression((PropertyExpressionleft, elements, true);
210 
211     }
212 
213     public static BooleanExpression createIsNull(Expression left)
214     {
215         return doCreateEqual(left, ConstantExpression.NULL);
216     }
217 
218     public static BooleanExpression createIsNotNull(Expression left)
219     {
220         return UnaryExpression.createNOT(doCreateEqual(left, ConstantExpression.NULL));
221     }
222 
223     public static BooleanExpression createNotEqual(Expression left, Expression right)
224     {
225         return UnaryExpression.createNOT(createEqual(left, right));
226     }
227 
228     public static BooleanExpression createEqual(Expression left, Expression right)
229     {
230         checkEqualOperand(left);
231         checkEqualOperand(right);
232         checkEqualOperandCompatability(left, right);
233 
234         return doCreateEqual(left, right);
235     }
236 
237     private static<E extends Exception> BooleanExpression<E> doCreateEqual(Expression<E> left, Expression<E> right)
238     {
239         return new EqualExpression(left, right);
240     }
241 
242     public static BooleanExpression createGreaterThan(final Expression left, final Expression right)
243     {
244         checkLessThanOperand(left);
245         checkLessThanOperand(right);
246 
247         return new ComparisonExpression(left, right)
248             {
249                 protected boolean asBoolean(int answer)
250                 {
251                     return answer > 0;
252                 }
253 
254                 public String getExpressionSymbol()
255                 {
256                     return ">";
257                 }
258             };
259     }
260 
261     public static BooleanExpression createGreaterThanEqual(final Expression left, final Expression right)
262     {
263         checkLessThanOperand(left);
264         checkLessThanOperand(right);
265 
266         return new ComparisonExpression(left, right)
267             {
268                 protected boolean asBoolean(int answer)
269                 {
270                     return answer >= 0;
271                 }
272 
273                 public String getExpressionSymbol()
274                 {
275                     return ">=";
276                 }
277             };
278     }
279 
280     public static BooleanExpression createLessThan(final Expression left, final Expression right)
281     {
282         checkLessThanOperand(left);
283         checkLessThanOperand(right);
284 
285         return new ComparisonExpression(left, right)
286             {
287 
288                 protected boolean asBoolean(int answer)
289                 {
290                     return answer < 0;
291                 }
292 
293                 public String getExpressionSymbol()
294                 {
295                     return "<";
296                 }
297 
298             };
299     }
300 
301     public static BooleanExpression createLessThanEqual(final Expression left, final Expression right)
302     {
303         checkLessThanOperand(left);
304         checkLessThanOperand(right);
305 
306         return new ComparisonExpression(left, right)
307             {
308 
309                 protected boolean asBoolean(int answer)
310                 {
311                     return answer <= 0;
312                 }
313 
314                 public String getExpressionSymbol()
315                 {
316                     return "<=";
317                 }
318             };
319     }
320 
321     /**
322      * Only Numeric expressions can be used in >, >=, < or <= expressions.s
323      *
324      @param expr
325      */
326     public static void checkLessThanOperand(Expression expr)
327     {
328         if (expr instanceof ConstantExpression)
329         {
330             Object value = ((ConstantExpressionexpr).getValue();
331             if (value instanceof Number)
332             {
333                 return;
334             }
335 
336             // Else it's boolean or a String..
337             throw new RuntimeException("Value '" + expr + "' cannot be compared.");
338         }
339 
340         if (expr instanceof BooleanExpression)
341         {
342             throw new RuntimeException("Value '" + expr + "' cannot be compared.");
343         }
344     }
345 
346     /**
347      * Validates that the expression can be used in == or <> expression.
348      * Cannot not be NULL TRUE or FALSE litterals.
349      *
350      @param expr
351      */
352     public static void checkEqualOperand(Expression expr)
353     {
354         if (expr instanceof ConstantExpression)
355         {
356             Object value = ((ConstantExpressionexpr).getValue();
357             if (value == null)
358             {
359                 throw new RuntimeException("'" + expr + "' cannot be compared.");
360             }
361         }
362     }
363 
364     /**
365      *
366      @param left
367      @param right
368      */
369     private static void checkEqualOperandCompatability(Expression left, Expression right)
370     {
371         if ((left instanceof ConstantExpression&& (right instanceof ConstantExpression))
372         {
373             if ((left instanceof BooleanExpression&& !(right instanceof BooleanExpression))
374             {
375                 throw new RuntimeException("'" + left + "' cannot be compared with '" + right + "'");
376             }
377         }
378     }
379 
380     /**
381      @param left
382      @param right
383      */
384     public ComparisonExpression(Expression left, Expression right)
385     {
386         super(left, right);
387     }
388 
389     public Object evaluate(Filterable<E> messagethrows E
390     {
391         Comparable lv = (Comparableleft.evaluate(message);
392         if (lv == null)
393         {
394             return null;
395         }
396 
397         Comparable rv = (Comparableright.evaluate(message);
398         if (rv == null)
399         {
400             return null;
401         }
402 
403         return compare(lv, rv);
404     }
405 
406     protected Boolean compare(Comparable lv, Comparable rv)
407     {
408         Class lc = lv.getClass();
409         Class rc = rv.getClass();
410         // If the the objects are not of the same type,
411         // try to convert up to allow the comparison.
412         if (lc != rc)
413         {
414             if (lc == Byte.class)
415             {
416                 if (rc == Short.class)
417                 {
418                     lv = new Short(((Numberlv).shortValue());
419                 }
420                 else if (rc == Integer.class)
421                 {
422                     lv = new Integer(((Numberlv).intValue());
423                 }
424                 else if (rc == Long.class)
425                 {
426                     lv = new Long(((Numberlv).longValue());
427                 }
428                 else if (rc == Float.class)
429                 {
430                     lv = new Float(((Numberlv).floatValue());
431                 }
432                 else if (rc == Double.class)
433                 {
434                     lv = new Double(((Numberlv).doubleValue());
435                 }
436                 else
437                 {
438                     return Boolean.FALSE;
439                 }
440             }
441             else if (lc == Short.class)
442             {
443                 if (rc == Integer.class)
444                 {
445                     lv = new Integer(((Numberlv).intValue());
446                 }
447                 else if (rc == Long.class)
448                 {
449                     lv = new Long(((Numberlv).longValue());
450                 }
451                 else if (rc == Float.class)
452                 {
453                     lv = new Float(((Numberlv).floatValue());
454                 }
455                 else if (rc == Double.class)
456                 {
457                     lv = new Double(((Numberlv).doubleValue());
458                 }
459                 else
460                 {
461                     return Boolean.FALSE;
462                 }
463             }
464             else if (lc == Integer.class)
465             {
466                 if (rc == Long.class)
467                 {
468                     lv = new Long(((Numberlv).longValue());
469                 }
470                 else if (rc == Float.class)
471                 {
472                     lv = new Float(((Numberlv).floatValue());
473                 }
474                 else if (rc == Double.class)
475                 {
476                     lv = new Double(((Numberlv).doubleValue());
477                 }
478                 else
479                 {
480                     return Boolean.FALSE;
481                 }
482             }
483             else if (lc == Long.class)
484             {
485                 if (rc == Integer.class)
486                 {
487                     rv = new Long(((Numberrv).longValue());
488                 }
489                 else if (rc == Float.class)
490                 {
491                     lv = new Float(((Numberlv).floatValue());
492                 }
493                 else if (rc == Double.class)
494                 {
495                     lv = new Double(((Numberlv).doubleValue());
496                 }
497                 else
498                 {
499                     return Boolean.FALSE;
500                 }
501             }
502             else if (lc == Float.class)
503             {
504                 if (rc == Integer.class)
505                 {
506                     rv = new Float(((Numberrv).floatValue());
507                 }
508                 else if (rc == Long.class)
509                 {
510                     rv = new Float(((Numberrv).floatValue());
511                 }
512                 else if (rc == Double.class)
513                 {
514                     lv = new Double(((Numberlv).doubleValue());
515                 }
516                 else
517                 {
518                     return Boolean.FALSE;
519                 }
520             }
521             else if (lc == Double.class)
522             {
523                 if (rc == Integer.class)
524                 {
525                     rv = new Double(((Numberrv).doubleValue());
526                 }
527                 else if (rc == Long.class)
528                 {
529                     rv = new Double(((Numberrv).doubleValue());
530                 }
531                 else if (rc == Float.class)
532                 {
533                     rv = new Float(((Numberrv).doubleValue());
534                 }
535                 else
536                 {
537                     return Boolean.FALSE;
538                 }
539             }
540             else
541             {
542                 return Boolean.FALSE;
543             }
544         }
545 
546         return asBoolean(lv.compareTo(rv)) ? Boolean.TRUE : Boolean.FALSE;
547     }
548 
549     protected abstract boolean asBoolean(int answer);
550 
551     public boolean matches(Filterable<E> messagethrows E
552     {
553         Object object = evaluate(message);
554 
555         return (object != null&& (object == Boolean.TRUE);
556     }
557 
558     private static class EqualExpression<E extends Exception> extends ComparisonExpression<E>
559     {
560         public EqualExpression(final Expression<E> left, final Expression<E> right)
561         {
562             super(left, right);
563         }
564 
565         public Object evaluate(Filterable<E> messagethrows E
566         {
567             Object lv = left.evaluate(message);
568             Object rv = right.evaluate(message);
569 
570             // Iff one of the values is null
571             if ((lv == null(rv == null))
572             {
573                 return Boolean.FALSE;
574             }
575 
576             if ((lv == rv|| lv.equals(rv))
577             {
578                 return Boolean.TRUE;
579             }
580 
581             if ((lv instanceof Comparable&& (rv instanceof Comparable))
582             {
583                 return compare((Comparablelv, (Comparablerv);
584             }
585 
586             return Boolean.FALSE;
587         }
588 
589         protected boolean asBoolean(int answer)
590         {
591             return answer == 0;
592         }
593 
594         public String getExpressionSymbol()
595         {
596             return "=";
597         }
598     }
599 }