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    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}