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