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     */
018    package org.granite.gravity.selector;
019    
020    import java.util.HashSet;
021    import java.util.List;
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     * @version $Revision: 1.2 $
030     */
031    public 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    }