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    public static BooleanExpression createInFilter(Expression left, List elements) {
164
165        if (!(left instanceof PropertyExpression)) {
166            throw new RuntimeException("Expected a property for In expression, got: " + left);
167        }
168        return UnaryExpression.createInExpression((PropertyExpression)left, elements, false);
169
170    }
171
172    public static BooleanExpression createNotInFilter(Expression left, List elements) {
173
174        if (!(left instanceof PropertyExpression)) {
175            throw new RuntimeException("Expected a property for In expression, got: " + left);
176        }
177        return UnaryExpression.createInExpression((PropertyExpression)left, elements, true);
178
179    }
180
181    public static BooleanExpression createIsNull(Expression left) {
182        return doCreateEqual(left, ConstantExpression.NULL);
183    }
184
185    public static BooleanExpression createIsNotNull(Expression left) {
186        return UnaryExpression.createNOT(doCreateEqual(left, ConstantExpression.NULL));
187    }
188
189    public static BooleanExpression createNotEqual(Expression left, Expression right) {
190        return UnaryExpression.createNOT(createEqual(left, right));
191    }
192
193    public static BooleanExpression createEqual(Expression left, Expression right) {
194        checkEqualOperand(left);
195        checkEqualOperand(right);
196        checkEqualOperandCompatability(left, right);
197        return doCreateEqual(left, right);
198    }
199
200    private static BooleanExpression doCreateEqual(Expression left, Expression right) {
201        return new ComparisonExpression(left, right) {
202
203            public Object evaluate(MessageEvaluationContext message) throws JMSException {
204                Object lv = left.evaluate(message);
205                Object rv = right.evaluate(message);
206
207                // Iff one of the values is null
208                if (lv == null ^ rv == null) {
209                    return Boolean.FALSE;
210                }
211                if (lv == rv || lv.equals(rv)) {
212                    return Boolean.TRUE;
213                }
214                if (lv instanceof Comparable && rv instanceof Comparable) {
215                    return compare((Comparable)lv, (Comparable)rv);
216                }
217                return Boolean.FALSE;
218            }
219
220            protected boolean asBoolean(int answer) {
221                return answer == 0;
222            }
223
224            public String getExpressionSymbol() {
225                return "=";
226            }
227        };
228    }
229
230    public static BooleanExpression createGreaterThan(final Expression left, final Expression right) {
231        checkLessThanOperand(left);
232        checkLessThanOperand(right);
233        return new ComparisonExpression(left, right) {
234            protected boolean asBoolean(int answer) {
235                return answer > 0;
236            }
237
238            public String getExpressionSymbol() {
239                return ">";
240            }
241        };
242    }
243
244    public static BooleanExpression createGreaterThanEqual(final Expression left, final Expression right) {
245        checkLessThanOperand(left);
246        checkLessThanOperand(right);
247        return new ComparisonExpression(left, right) {
248            protected boolean asBoolean(int answer) {
249                return answer >= 0;
250            }
251
252            public String getExpressionSymbol() {
253                return ">=";
254            }
255        };
256    }
257
258    public static BooleanExpression createLessThan(final Expression left, final Expression right) {
259        checkLessThanOperand(left);
260        checkLessThanOperand(right);
261        return new ComparisonExpression(left, right) {
262
263            protected boolean asBoolean(int answer) {
264                return answer < 0;
265            }
266
267            public String getExpressionSymbol() {
268                return "<";
269            }
270
271        };
272    }
273
274    public static BooleanExpression createLessThanEqual(final Expression left, final Expression right) {
275        checkLessThanOperand(left);
276        checkLessThanOperand(right);
277        return new ComparisonExpression(left, right) {
278
279            protected boolean asBoolean(int answer) {
280                return answer <= 0;
281            }
282
283            public String getExpressionSymbol() {
284                return "<=";
285            }
286        };
287    }
288
289    /**
290     * Only Numeric expressions can be used in >, >=, < or <= expressions.s
291     * 
292     * @param expr
293     */
294    public static void checkLessThanOperand(Expression expr) {
295        if (expr instanceof ConstantExpression) {
296            Object value = ((ConstantExpression)expr).getValue();
297            if (value instanceof Number) {
298                return;
299            }
300
301            // Else it's boolean or a String..
302            throw new RuntimeException("Value '" + expr + "' cannot be compared.");
303        }
304        if (expr instanceof BooleanExpression) {
305            throw new RuntimeException("Value '" + expr + "' cannot be compared.");
306        }
307    }
308
309    /**
310     * Validates that the expression can be used in == or <> expression. Cannot
311     * not be NULL TRUE or FALSE litterals.
312     * 
313     * @param expr
314     */
315    public static void checkEqualOperand(Expression expr) {
316        if (expr instanceof ConstantExpression) {
317            Object value = ((ConstantExpression)expr).getValue();
318            if (value == null) {
319                throw new RuntimeException("'" + expr + "' cannot be compared.");
320            }
321        }
322    }
323
324    /**
325     * @param left
326     * @param right
327     */
328    private static void checkEqualOperandCompatability(Expression left, Expression right) {
329        if (left instanceof ConstantExpression && right instanceof ConstantExpression) {
330            if (left instanceof BooleanExpression && !(right instanceof BooleanExpression)) {
331                throw new RuntimeException("'" + left + "' cannot be compared with '" + right + "'");
332            }
333        }
334    }
335
336    public Object evaluate(MessageEvaluationContext message) throws JMSException {
337        Comparable<Comparable> lv = (Comparable)left.evaluate(message);
338        if (lv == null) {
339            return null;
340        }
341        Comparable rv = (Comparable)right.evaluate(message);
342        if (rv == null) {
343            return null;
344        }
345        return compare(lv, rv);
346    }
347
348    protected Boolean compare(Comparable lv, Comparable rv) {
349        Class<? extends Comparable> lc = lv.getClass();
350        Class<? extends Comparable> rc = rv.getClass();
351        // If the the objects are not of the same type,
352        // try to convert up to allow the comparison.
353        if (lc != rc) {
354            if (lc == Byte.class) {
355                if (rc == Short.class) {
356                    lv = Short.valueOf(((Number)lv).shortValue());
357                } else if (rc == Integer.class) {
358                    lv = Integer.valueOf(((Number)lv).intValue());
359                } else if (rc == Long.class) {
360                    lv = Long.valueOf(((Number)lv).longValue());
361                } else if (rc == Float.class) {
362                    lv = new Float(((Number)lv).floatValue());
363                } else if (rc == Double.class) {
364                    lv = new Double(((Number)lv).doubleValue());
365                } else {
366                    return Boolean.FALSE;
367                }
368            } else if (lc == Short.class) {
369                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 {
378                    return Boolean.FALSE;
379                }
380            } else if (lc == Integer.class) {
381                if (rc == Long.class) {
382                    lv = Long.valueOf(((Number)lv).longValue());
383                } else if (rc == Float.class) {
384                    lv = new Float(((Number)lv).floatValue());
385                } else if (rc == Double.class) {
386                    lv = new Double(((Number)lv).doubleValue());
387                } else {
388                    return Boolean.FALSE;
389                }
390            } else if (lc == Long.class) {
391                if (rc == Integer.class) {
392                    rv = Long.valueOf(((Number)rv).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 {
398                    return Boolean.FALSE;
399                }
400            } else if (lc == Float.class) {
401                if (rc == Integer.class) {
402                    rv = new Float(((Number)rv).floatValue());
403                } else if (rc == Long.class) {
404                    rv = new Float(((Number)rv).floatValue());
405                } else if (rc == Double.class) {
406                    lv = new Double(((Number)lv).doubleValue());
407                } else {
408                    return Boolean.FALSE;
409                }
410            } else if (lc == Double.class) {
411                if (rc == Integer.class) {
412                    rv = new Double(((Number)rv).doubleValue());
413                } else if (rc == Long.class) {
414                    rv = new Double(((Number)rv).doubleValue());
415                } else if (rc == Float.class) {
416                    rv = new Float(((Number)rv).doubleValue());
417                } else {
418                    return Boolean.FALSE;
419                }
420            } else {
421                return Boolean.FALSE;
422            }
423        }
424        return asBoolean(lv.compareTo(rv)) ? Boolean.TRUE : Boolean.FALSE;
425    }
426
427    protected abstract boolean asBoolean(int answer);
428
429    public boolean matches(MessageEvaluationContext message) throws JMSException {
430        Object object = evaluate(message);
431        return object != null && object == Boolean.TRUE;
432    }
433
434}