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     */
017    package org.apache.activemq.filter;
018    
019    import java.util.HashSet;
020    import java.util.List;
021    import java.util.Set;
022    import java.util.regex.Pattern;
023    
024    import javax.jms.JMSException;
025    
026    /**
027     * A filter performing a comparison of two objects
028     * 
029     * 
030     */
031    public 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    }