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