001/**
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one or more
004 * contributor license agreements.  See the NOTICE file distributed with
005 * this work for additional information regarding copyright ownership.
006 * The ASF licenses this file to You under the Apache License, Version 2.0
007 * (the "License"); you may not use this file except in compliance with
008 * the License.  You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.granite.gravity.selector;
019
020import java.util.HashSet;
021import java.util.List;
022import java.util.regex.Pattern;
023
024import javax.jms.JMSException;
025
026/**
027 * A filter performing a comparison of two objects
028 *
029 * @version $Revision: 1.2 $
030 */
031public abstract class ComparisonExpression extends BinaryExpression implements BooleanExpression {
032
033    public static BooleanExpression createBetween(Expression value, Expression left, Expression right) {
034        return LogicExpression.createAND(createGreaterThanEqual(value, left), createLessThanEqual(value, right));
035    }
036
037    public static BooleanExpression createNotBetween(Expression value, Expression left, Expression right) {
038        return LogicExpression.createOR(createLessThan(value, left), createGreaterThan(value, right));
039    }
040
041    static final private HashSet<Character> REGEXP_CONTROL_CHARS = new HashSet<Character>();
042
043    static {
044        REGEXP_CONTROL_CHARS.add(new Character('.'));
045        REGEXP_CONTROL_CHARS.add(new Character('\\'));
046        REGEXP_CONTROL_CHARS.add(new Character('['));
047        REGEXP_CONTROL_CHARS.add(new Character(']'));
048        REGEXP_CONTROL_CHARS.add(new Character('^'));
049        REGEXP_CONTROL_CHARS.add(new Character('$'));
050        REGEXP_CONTROL_CHARS.add(new Character('?'));
051        REGEXP_CONTROL_CHARS.add(new Character('*'));
052        REGEXP_CONTROL_CHARS.add(new Character('+'));
053        REGEXP_CONTROL_CHARS.add(new Character('{'));
054        REGEXP_CONTROL_CHARS.add(new Character('}'));
055        REGEXP_CONTROL_CHARS.add(new Character('|'));
056        REGEXP_CONTROL_CHARS.add(new Character('('));
057        REGEXP_CONTROL_CHARS.add(new Character(')'));
058        REGEXP_CONTROL_CHARS.add(new Character(':'));
059        REGEXP_CONTROL_CHARS.add(new Character('&'));
060        REGEXP_CONTROL_CHARS.add(new Character('<'));
061        REGEXP_CONTROL_CHARS.add(new Character('>'));
062        REGEXP_CONTROL_CHARS.add(new Character('='));
063        REGEXP_CONTROL_CHARS.add(new Character('!'));
064    }
065
066    static class LikeExpression extends UnaryExpression implements BooleanExpression {
067
068        Pattern likePattern;
069
070        /**
071         * @param left
072         */
073        public LikeExpression(Expression right, String like, int escape) {
074            super(right);
075
076            StringBuffer regexp = new StringBuffer(like.length() * 2);
077            regexp.append("\\A"); // The beginning of the input
078            for (int i = 0; i < like.length(); i++) {
079                char c = like.charAt(i);
080                if (escape == (0xFFFF & c)) {
081                    i++;
082                    if (i >= like.length()) {
083                        // nothing left to escape...
084                        break;
085                    }
086
087                    char t = like.charAt(i);
088                    regexp.append("\\x");
089                    regexp.append(Integer.toHexString(0xFFFF & t));
090                }
091                else if (c == '%') {
092                    regexp.append(".*?"); // Do a non-greedy match
093                }
094                else if (c == '_') {
095                    regexp.append("."); // match one
096                }
097                else if (REGEXP_CONTROL_CHARS.contains(new Character(c))) {
098                    regexp.append("\\x");
099                    regexp.append(Integer.toHexString(0xFFFF & c));
100                }
101                else {
102                    regexp.append(c);
103                }
104            }
105            regexp.append("\\z"); // The end of the input
106
107            likePattern = Pattern.compile(regexp.toString(), Pattern.DOTALL);
108        }
109
110        /**
111         * @see org.apache.activemq.filter.UnaryExpression#getExpressionSymbol()
112         */
113        @Override
114        public String getExpressionSymbol() {
115            return "LIKE";
116        }
117
118        /**
119         * @see org.apache.activemq.filter.Expression#evaluate(MessageEvaluationContext)
120         */
121        public Object evaluate(MessageEvaluationContext message) throws JMSException {
122
123            Object rv = this.getRight().evaluate(message);
124
125            if (rv == null) {
126                return null;
127            }
128
129            if (!(rv instanceof String)) {
130                return Boolean.FALSE;
131                //throw new RuntimeException("LIKE can only operate on String identifiers.  LIKE attemped on: '" + rv.getClass());
132            }
133
134            return likePattern.matcher((String) rv).matches() ? Boolean.TRUE : Boolean.FALSE;
135        }
136
137        public boolean matches(MessageEvaluationContext message) throws JMSException {
138            Object object = evaluate(message);
139            return object!=null && object==Boolean.TRUE;
140        }
141    }
142
143    public static BooleanExpression createLike(Expression left, String right, String escape) {
144        if (escape != null && escape.length() != 1) {
145            throw new RuntimeException("The ESCAPE string litteral is invalid.  It can only be one character.  Litteral used: " + escape);
146        }
147        int c = -1;
148        if (escape != null) {
149            c = 0xFFFF & escape.charAt(0);
150        }
151
152        return new LikeExpression(left, right, c);
153    }
154
155    public static BooleanExpression createNotLike(Expression left, String right, String escape) {
156        return UnaryExpression.createNOT(createLike(left, right, escape));
157    }
158
159    public static BooleanExpression createInFilter(Expression left, List<?> elements) {
160
161        if( !(left instanceof PropertyExpression) )
162            throw new RuntimeException("Expected a property for In expression, got: "+left);
163        return UnaryExpression.createInExpression((PropertyExpression)left, elements, false);
164
165    }
166
167    public static BooleanExpression createNotInFilter(Expression left, List<?> elements) {
168
169        if( !(left instanceof PropertyExpression) )
170            throw new RuntimeException("Expected a property for In expression, got: "+left);
171        return UnaryExpression.createInExpression((PropertyExpression)left, elements, true);
172
173    }
174
175    public static BooleanExpression createIsNull(Expression left) {
176        return doCreateEqual(left, ConstantExpression.NULL);
177    }
178
179    public static BooleanExpression createIsNotNull(Expression left) {
180        return UnaryExpression.createNOT(doCreateEqual(left, ConstantExpression.NULL));
181    }
182
183    public static BooleanExpression createNotEqual(Expression left, Expression right) {
184        return UnaryExpression.createNOT(createEqual(left, right));
185    }
186
187    public static BooleanExpression createEqual(Expression left, Expression right) {
188        checkEqualOperand(left);
189        checkEqualOperand(right);
190        checkEqualOperandCompatability(left, right);
191        return doCreateEqual(left, right);
192    }
193
194    private static BooleanExpression doCreateEqual(Expression left, Expression right) {
195        return new ComparisonExpression(left, right) {
196            @Override
197            public Object evaluate(MessageEvaluationContext message) throws JMSException {
198                Object lv = left.evaluate(message);
199                Object rv = right.evaluate(message);
200
201                // Iff one of the values is null
202                if (lv == null ^ rv == null) {
203                    return Boolean.FALSE;
204                }
205                if (lv == rv || (lv != null && lv.equals(rv))) {
206                    return Boolean.TRUE;
207                }
208                if( lv instanceof Comparable<?> && rv instanceof Comparable<?> ) {
209                    return compare((Comparable<?>)lv, (Comparable<?>)rv);
210                }
211                return Boolean.FALSE;
212            }
213
214            @Override
215            protected boolean asBoolean(int answer) {
216                return answer == 0;
217            }
218
219            @Override
220            public String getExpressionSymbol() {
221                return "=";
222            }
223        };
224    }
225
226    public static BooleanExpression createGreaterThan(final Expression left, final Expression right) {
227        checkLessThanOperand(left);
228        checkLessThanOperand(right);
229        return new ComparisonExpression(left, right) {
230            @Override
231            protected boolean asBoolean(int answer) {
232                return answer > 0;
233            }
234
235            @Override
236            public String getExpressionSymbol() {
237                return ">";
238            }
239        };
240    }
241
242    public static BooleanExpression createGreaterThanEqual(final Expression left, final Expression right) {
243        checkLessThanOperand(left);
244        checkLessThanOperand(right);
245        return new ComparisonExpression(left, right) {
246            @Override
247            protected boolean asBoolean(int answer) {
248                return answer >= 0;
249            }
250
251            @Override
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            @Override
263            protected boolean asBoolean(int answer) {
264                return answer < 0;
265            }
266
267            @Override
268            public String getExpressionSymbol() {
269                return "<";
270            }
271
272        };
273    }
274
275    public static BooleanExpression createLessThanEqual(final Expression left, final Expression right) {
276        checkLessThanOperand(left);
277        checkLessThanOperand(right);
278        return new ComparisonExpression(left, right) {
279            @Override
280            protected boolean asBoolean(int answer) {
281                return answer <= 0;
282            }
283
284            @Override
285            public String getExpressionSymbol() {
286                return "<=";
287            }
288        };
289    }
290
291    /**
292     * Only Numeric expressions can be used in >, >=, < or <= expressions.s
293     *
294     * @param expr
295     */
296    public static void checkLessThanOperand(Expression expr ) {
297        if( expr instanceof ConstantExpression ) {
298            Object value = ((ConstantExpression)expr).getValue();
299            if( value instanceof Number )
300                return;
301
302            // Else it's boolean or a String..
303            throw new RuntimeException("Value '"+expr+"' cannot be compared.");
304        }
305        if( expr instanceof BooleanExpression ) {
306            throw new RuntimeException("Value '"+expr+"' cannot be compared.");
307        }
308    }
309
310    /**
311     * Validates that the expression can be used in == or <> expression.
312     * Cannot not be NULL TRUE or FALSE litterals.
313     *
314     * @param expr
315     */
316    public static void checkEqualOperand(Expression expr ) {
317        if( expr instanceof ConstantExpression ) {
318            Object value = ((ConstantExpression)expr).getValue();
319            if( value == null )
320                throw new RuntimeException("'"+expr+"' cannot be compared.");
321        }
322    }
323
324    /**
325     *
326     * @param left
327     * @param right
328     */
329    private static void checkEqualOperandCompatability(Expression left, Expression right) {
330        if( left instanceof ConstantExpression && right instanceof ConstantExpression ) {
331            if( left instanceof BooleanExpression && !(right instanceof BooleanExpression) )
332                throw new RuntimeException("'"+left+"' cannot be compared with '"+right+"'");
333        }
334    }
335
336
337
338    /**
339     * @param left
340     * @param right
341     */
342    public ComparisonExpression(Expression left, Expression right) {
343        super(left, right);
344    }
345
346    public Object evaluate(MessageEvaluationContext message) throws JMSException {
347        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({ "unchecked", "rawtypes", "boxing" })
359    protected Boolean compare(Comparable lv, Comparable rv) {
360        Class<?> lc = lv.getClass();
361        Class<?> 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            if (lc == Byte.class) {
366                if (rc == Short.class) {
367                    return ((Number)lv).shortValue() == ((Short)rv).shortValue();
368                }
369                else if (rc == Integer.class) {
370                    return ((Number)lv).intValue() == ((Integer)rv).intValue();
371                }
372                else if (rc == Long.class) {
373                    return ((Number)lv).longValue() == ((Long)rv).longValue();
374                }
375                else if (rc == Float.class) {
376                    return ((Number)lv).floatValue() == ((Float)rv).floatValue();
377                }
378                else if (rc == Double.class) {
379                    return ((Double)lv).doubleValue() == ((Double)rv).doubleValue();
380                }
381                else {
382                    return Boolean.FALSE;
383                }
384             } else if (lc == Short.class) {
385                if (rc == Integer.class) {
386                    lv = new Integer(((Number) lv).intValue());
387                }
388                else if (rc == Long.class) {
389                    lv = new Long(((Number) lv).longValue());
390                }
391                else if (rc == Float.class) {
392                    lv = new Float(((Number) lv).floatValue());
393                }
394                else if (rc == Double.class) {
395                    lv = new Double(((Number) lv).doubleValue());
396                }
397                else {
398                    return Boolean.FALSE;
399                }
400            } else if (lc == Integer.class) {
401                if (rc == Long.class) {
402                    lv = new Long(((Number)lv).longValue());
403                }
404                else if (rc == Float.class) {
405                    lv = new Float(((Number)lv).floatValue());
406                }
407                else if (rc == Double.class) {
408                    lv = new Double(((Number)lv).doubleValue());
409                }
410                else {
411                    return Boolean.FALSE;
412                }
413            }
414            else if (lc == Long.class) {
415                if (rc == Integer.class) {
416                    rv = new Long(((Number)rv).longValue());
417                }
418                else if (rc == Float.class) {
419                    lv = new Float(((Number)lv).floatValue());
420                }
421                else if (rc == Double.class) {
422                    lv = new Double(((Number)lv).doubleValue());
423                }
424                else {
425                    return Boolean.FALSE;
426                }
427            }
428            else if (lc == Float.class) {
429                if (rc == Integer.class) {
430                    rv = new Float(((Number)rv).floatValue());
431                }
432                else if (rc == Long.class) {
433                    rv = new Float(((Number)rv).floatValue());
434                }
435                else if (rc == Double.class) {
436                    lv = new Double(((Number)lv).doubleValue());
437                }
438                else {
439                    return Boolean.FALSE;
440                }
441            }
442            else if (lc == Double.class) {
443                if (rc == Integer.class) {
444                    rv = new Double(((Number)rv).doubleValue());
445                }
446                else if (rc == Long.class) {
447                    rv = new Double(((Number)rv).doubleValue());
448                }
449                else if (rc == Float.class) {
450                    rv = new Float(((Number)rv).doubleValue());
451                }
452                else {
453                    return Boolean.FALSE;
454                }
455            }
456            else
457                return Boolean.FALSE;
458        }
459
460        return asBoolean(lv.compareTo(rv)) ? Boolean.TRUE : Boolean.FALSE;
461    }
462
463    protected abstract boolean asBoolean(int answer);
464
465    public boolean matches(MessageEvaluationContext message) throws JMSException {
466        Object object = evaluate(message);
467        return object != null && object == Boolean.TRUE;
468    }
469
470}