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(new Character(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 ComparisonExpression(left, right) {
218
219            public Object evaluate(MessageEvaluationContext message) throws JMSException {
220                Object lv = left.evaluate(message);
221                Object rv = right.evaluate(message);
222
223                // If one of the values is null
224                if (lv == null ^ rv == null) {
225                    if (lv == null) {
226                        return null;
227                    }
228                    return Boolean.FALSE;
229                }
230                if (lv == rv || lv.equals(rv)) {
231                    return Boolean.TRUE;
232                }
233                if (lv instanceof Comparable && rv instanceof Comparable) {
234                    return compare((Comparable)lv, (Comparable)rv);
235                }
236                return Boolean.FALSE;
237            }
238
239            protected boolean asBoolean(int answer) {
240                return answer == 0;
241            }
242
243            public String getExpressionSymbol() {
244                return "=";
245            }
246        };
247    }
248
249    public static BooleanExpression createGreaterThan(final Expression left, final Expression right) {
250        checkLessThanOperand(left);
251        checkLessThanOperand(right);
252        return new ComparisonExpression(left, right) {
253            protected boolean asBoolean(int answer) {
254                return answer > 0;
255            }
256
257            public String getExpressionSymbol() {
258                return ">";
259            }
260        };
261    }
262
263    public static BooleanExpression createGreaterThanEqual(final Expression left, final Expression right) {
264        checkLessThanOperand(left);
265        checkLessThanOperand(right);
266        return new ComparisonExpression(left, right) {
267            protected boolean asBoolean(int answer) {
268                return answer >= 0;
269            }
270
271            public String getExpressionSymbol() {
272                return ">=";
273            }
274        };
275    }
276
277    public static BooleanExpression createLessThan(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    public static BooleanExpression createLessThanEqual(final Expression left, final Expression right) {
294        checkLessThanOperand(left);
295        checkLessThanOperand(right);
296        return new ComparisonExpression(left, right) {
297
298            protected boolean asBoolean(int answer) {
299                return answer <= 0;
300            }
301
302            public String getExpressionSymbol() {
303                return "<=";
304            }
305        };
306    }
307
308    /**
309     * Only Numeric expressions can be used in >, >=, < or <= expressions.s
310     *
311     * @param expr
312     */
313    public static void checkLessThanOperand(Expression expr) {
314        if (expr instanceof ConstantExpression) {
315            Object value = ((ConstantExpression)expr).getValue();
316            if (value instanceof Number) {
317                return;
318            }
319
320            // Else it's boolean or a String..
321            throw new RuntimeException("Value '" + expr + "' cannot be compared.");
322        }
323        if (expr instanceof BooleanExpression) {
324            throw new RuntimeException("Value '" + expr + "' cannot be compared.");
325        }
326    }
327
328    /**
329     * Validates that the expression can be used in == or <> expression. Cannot
330     * not be NULL TRUE or FALSE litterals.
331     *
332     * @param expr
333     */
334    public static void checkEqualOperand(Expression expr) {
335        if (expr instanceof ConstantExpression) {
336            Object value = ((ConstantExpression)expr).getValue();
337            if (value == null) {
338                throw new RuntimeException("'" + expr + "' cannot be compared.");
339            }
340        }
341    }
342
343    /**
344     * @param left
345     * @param right
346     */
347    private static void checkEqualOperandCompatability(Expression left, Expression right) {
348        if (left instanceof ConstantExpression && right instanceof ConstantExpression) {
349            if (left instanceof BooleanExpression && !(right instanceof BooleanExpression)) {
350                throw new RuntimeException("'" + left + "' cannot be compared with '" + right + "'");
351            }
352        }
353    }
354
355    @SuppressWarnings({ "rawtypes", "unchecked" })
356    public Object evaluate(MessageEvaluationContext message) throws JMSException {
357        Comparable<Comparable> lv = (Comparable)left.evaluate(message);
358        if (lv == null) {
359            return null;
360        }
361        Comparable rv = (Comparable)right.evaluate(message);
362        if (rv == null) {
363            return null;
364        }
365        return compare(lv, rv);
366    }
367
368    @SuppressWarnings({ "rawtypes", "unchecked" })
369    protected Boolean compare(Comparable lv, Comparable rv) {
370        Class<? extends Comparable> lc = lv.getClass();
371        Class<? extends Comparable> rc = rv.getClass();
372        // If the the objects are not of the same type,
373        // try to convert up to allow the comparison.
374        if (lc != rc) {
375            try {
376                if (lc == Boolean.class) {
377                    if (convertStringExpressions && rc == String.class) {
378                        lv = Boolean.valueOf((String)lv).booleanValue();
379                    } else {
380                        return Boolean.FALSE;
381                    }
382                } else if (lc == Byte.class) {
383                    if (rc == Short.class) {
384                        lv = Short.valueOf(((Number)lv).shortValue());
385                    } else if (rc == Integer.class) {
386                        lv = Integer.valueOf(((Number)lv).intValue());
387                    } else if (rc == Long.class) {
388                        lv = Long.valueOf(((Number)lv).longValue());
389                    } else if (rc == Float.class) {
390                        lv = new Float(((Number)lv).floatValue());
391                    } else if (rc == Double.class) {
392                        lv = new Double(((Number)lv).doubleValue());
393                    } else if (convertStringExpressions && rc == String.class) {
394                        rv = Byte.valueOf((String)rv);
395                    } else {
396                        return Boolean.FALSE;
397                    }
398                } else if (lc == Short.class) {
399                    if (rc == Integer.class) {
400                        lv = Integer.valueOf(((Number)lv).intValue());
401                    } else if (rc == Long.class) {
402                        lv = Long.valueOf(((Number)lv).longValue());
403                    } else if (rc == Float.class) {
404                        lv = new Float(((Number)lv).floatValue());
405                    } else if (rc == Double.class) {
406                        lv = new Double(((Number)lv).doubleValue());
407                    } else if (convertStringExpressions && rc == String.class) {
408                        rv = Short.valueOf((String)rv);
409                    } else {
410                        return Boolean.FALSE;
411                    }
412                } else if (lc == Integer.class) {
413                    if (rc == Long.class) {
414                        lv = Long.valueOf(((Number)lv).longValue());
415                    } else if (rc == Float.class) {
416                        lv = new Float(((Number)lv).floatValue());
417                    } else if (rc == Double.class) {
418                        lv = new Double(((Number)lv).doubleValue());
419                    } else if (convertStringExpressions && rc == String.class) {
420                        rv = Integer.valueOf((String)rv);
421                    } else {
422                        return Boolean.FALSE;
423                    }
424                } else if (lc == Long.class) {
425                    if (rc == Integer.class) {
426                        rv = Long.valueOf(((Number)rv).longValue());
427                    } else if (rc == Float.class) {
428                        lv = new Float(((Number)lv).floatValue());
429                    } else if (rc == Double.class) {
430                        lv = new Double(((Number)lv).doubleValue());
431                    } else if (convertStringExpressions && rc == String.class) {
432                        rv = Long.valueOf((String)rv);
433                    } else {
434                        return Boolean.FALSE;
435                    }
436                } else if (lc == Float.class) {
437                    if (rc == Integer.class) {
438                        rv = new Float(((Number)rv).floatValue());
439                    } else if (rc == Long.class) {
440                        rv = new Float(((Number)rv).floatValue());
441                    } else if (rc == Double.class) {
442                        lv = new Double(((Number)lv).doubleValue());
443                    } else if (convertStringExpressions && rc == String.class) {
444                        rv = Float.valueOf((String)rv);
445                    } else {
446                        return Boolean.FALSE;
447                    }
448                } else if (lc == Double.class) {
449                    if (rc == Integer.class) {
450                        rv = new Double(((Number)rv).doubleValue());
451                    } else if (rc == Long.class) {
452                        rv = new Double(((Number)rv).doubleValue());
453                    } else if (rc == Float.class) {
454                        rv = new Float(((Number)rv).doubleValue());
455                    } else if (convertStringExpressions && rc == String.class) {
456                        rv = Double.valueOf((String)rv);
457                    } else {
458                        return Boolean.FALSE;
459                    }
460                } else if (convertStringExpressions && lc == String.class) {
461                    if (rc == Boolean.class) {
462                        lv = Boolean.valueOf((String)lv);
463                    } else if (rc == Byte.class) {
464                        lv = Byte.valueOf((String)lv);
465                    } else if (rc == Short.class) {
466                        lv = Short.valueOf((String)lv);
467                    } else if (rc == Integer.class) {
468                        lv = Integer.valueOf((String)lv);
469                    } else if (rc == Long.class) {
470                        lv = Long.valueOf((String)lv);
471                    } else if (rc == Float.class) {
472                        lv = Float.valueOf((String)lv);
473                    } else if (rc == Double.class) {
474                        lv = Double.valueOf((String)lv);
475                    } else {
476                        return Boolean.FALSE;
477                    }
478                } else {
479                    return Boolean.FALSE;
480                }
481            } catch(NumberFormatException e) {
482                return Boolean.FALSE;
483            }
484        }
485        return asBoolean(lv.compareTo(rv)) ? Boolean.TRUE : Boolean.FALSE;
486    }
487
488    protected abstract boolean asBoolean(int answer);
489
490    public boolean matches(MessageEvaluationContext message) throws JMSException {
491        Object object = evaluate(message);
492        return object != null && object == Boolean.TRUE;
493    }
494
495}