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