001    /**
002     *   GRANITE DATA SERVICES
003     *   Copyright (C) 2006-2013 GRANITE DATA SERVICES S.A.S.
004     *
005     *   This file is part of the Granite Data Services Platform.
006     *
007     *   Granite Data Services is free software; you can redistribute it and/or
008     *   modify it under the terms of the GNU Lesser General Public
009     *   License as published by the Free Software Foundation; either
010     *   version 2.1 of the License, or (at your option) any later version.
011     *
012     *   Granite Data Services is distributed in the hope that it will be useful,
013     *   but WITHOUT ANY WARRANTY; without even the implied warranty of
014     *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
015     *   General Public License for more details.
016     *
017     *   You should have received a copy of the GNU Lesser General Public
018     *   License along with this library; if not, write to the Free Software
019     *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
020     *   USA, or see <http://www.gnu.org/licenses/>.
021     */
022    package org.granite.gravity.selector;
023    
024    import java.util.HashSet;
025    import java.util.List;
026    import java.util.regex.Pattern;
027    
028    import javax.jms.JMSException;
029    
030    /**
031     * A filter performing a comparison of two objects
032     *
033     * @version $Revision: 1.2 $
034     */
035    public abstract class ComparisonExpression extends BinaryExpression implements BooleanExpression {
036    
037        public static BooleanExpression createBetween(Expression value, Expression left, Expression right) {
038            return LogicExpression.createAND(createGreaterThanEqual(value, left), createLessThanEqual(value, right));
039        }
040    
041        public static BooleanExpression createNotBetween(Expression value, Expression left, Expression right) {
042            return LogicExpression.createOR(createLessThan(value, left), createGreaterThan(value, right));
043        }
044    
045        static final private HashSet<Character> REGEXP_CONTROL_CHARS = new HashSet<Character>();
046    
047        static {
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            REGEXP_CONTROL_CHARS.add(new Character('<'));
065            REGEXP_CONTROL_CHARS.add(new Character('>'));
066            REGEXP_CONTROL_CHARS.add(new Character('='));
067            REGEXP_CONTROL_CHARS.add(new Character('!'));
068        }
069    
070        static class LikeExpression extends UnaryExpression implements BooleanExpression {
071    
072            Pattern likePattern;
073    
074            /**
075             * @param left
076             */
077            public LikeExpression(Expression right, String like, int escape) {
078                super(right);
079    
080                StringBuffer regexp = new StringBuffer(like.length() * 2);
081                regexp.append("\\A"); // The beginning of the input
082                for (int i = 0; i < like.length(); i++) {
083                    char c = like.charAt(i);
084                    if (escape == (0xFFFF & c)) {
085                        i++;
086                        if (i >= like.length()) {
087                            // nothing left to escape...
088                            break;
089                        }
090    
091                        char t = like.charAt(i);
092                        regexp.append("\\x");
093                        regexp.append(Integer.toHexString(0xFFFF & t));
094                    }
095                    else if (c == '%') {
096                        regexp.append(".*?"); // Do a non-greedy match
097                    }
098                    else if (c == '_') {
099                        regexp.append("."); // match one
100                    }
101                    else if (REGEXP_CONTROL_CHARS.contains(new Character(c))) {
102                        regexp.append("\\x");
103                        regexp.append(Integer.toHexString(0xFFFF & c));
104                    }
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            @Override
118            public String getExpressionSymbol() {
119                return "LIKE";
120            }
121    
122            /**
123             * @see org.apache.activemq.filter.Expression#evaluate(MessageEvaluationContext)
124             */
125            public Object evaluate(MessageEvaluationContext message) throws JMSException {
126    
127                Object rv = this.getRight().evaluate(message);
128    
129                if (rv == null) {
130                    return null;
131                }
132    
133                if (!(rv instanceof String)) {
134                    return Boolean.FALSE;
135                    //throw new RuntimeException("LIKE can only operate on String 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            return UnaryExpression.createInExpression((PropertyExpression)left, elements, false);
168    
169        }
170    
171        public static BooleanExpression createNotInFilter(Expression left, List<?> elements) {
172    
173            if( !(left instanceof PropertyExpression) )
174                throw new RuntimeException("Expected a property for In expression, got: "+left);
175            return UnaryExpression.createInExpression((PropertyExpression)left, elements, true);
176    
177        }
178    
179        public static BooleanExpression createIsNull(Expression left) {
180            return doCreateEqual(left, ConstantExpression.NULL);
181        }
182    
183        public static BooleanExpression createIsNotNull(Expression left) {
184            return UnaryExpression.createNOT(doCreateEqual(left, ConstantExpression.NULL));
185        }
186    
187        public static BooleanExpression createNotEqual(Expression left, Expression right) {
188            return UnaryExpression.createNOT(createEqual(left, right));
189        }
190    
191        public static BooleanExpression createEqual(Expression left, Expression right) {
192            checkEqualOperand(left);
193            checkEqualOperand(right);
194            checkEqualOperandCompatability(left, right);
195            return doCreateEqual(left, right);
196        }
197    
198        private static BooleanExpression doCreateEqual(Expression left, Expression right) {
199            return new ComparisonExpression(left, right) {
200                @Override
201                public Object evaluate(MessageEvaluationContext message) throws JMSException {
202                    Object lv = left.evaluate(message);
203                    Object rv = right.evaluate(message);
204    
205                    // Iff one of the values is null
206                    if (lv == null ^ rv == null) {
207                        return Boolean.FALSE;
208                    }
209                    if (lv == rv || (lv != null && lv.equals(rv))) {
210                        return Boolean.TRUE;
211                    }
212                    if( lv instanceof Comparable<?> && rv instanceof Comparable<?> ) {
213                        return compare((Comparable<?>)lv, (Comparable<?>)rv);
214                    }
215                    return Boolean.FALSE;
216                }
217    
218                @Override
219                protected boolean asBoolean(int answer) {
220                    return answer == 0;
221                }
222    
223                @Override
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                @Override
235                protected boolean asBoolean(int answer) {
236                    return answer > 0;
237                }
238    
239                @Override
240                public String getExpressionSymbol() {
241                    return ">";
242                }
243            };
244        }
245    
246        public static BooleanExpression createGreaterThanEqual(final Expression left, final Expression right) {
247            checkLessThanOperand(left);
248            checkLessThanOperand(right);
249            return new ComparisonExpression(left, right) {
250                @Override
251                protected boolean asBoolean(int answer) {
252                    return answer >= 0;
253                }
254    
255                @Override
256                public String getExpressionSymbol() {
257                    return ">=";
258                }
259            };
260        }
261    
262        public static BooleanExpression createLessThan(final Expression left, final Expression right) {
263            checkLessThanOperand(left);
264            checkLessThanOperand(right);
265            return new ComparisonExpression(left, right) {
266                @Override
267                protected boolean asBoolean(int answer) {
268                    return answer < 0;
269                }
270    
271                @Override
272                public String getExpressionSymbol() {
273                    return "<";
274                }
275    
276            };
277        }
278    
279        public static BooleanExpression createLessThanEqual(final Expression left, final Expression right) {
280            checkLessThanOperand(left);
281            checkLessThanOperand(right);
282            return new ComparisonExpression(left, right) {
283                @Override
284                protected boolean asBoolean(int answer) {
285                    return answer <= 0;
286                }
287    
288                @Override
289                public String getExpressionSymbol() {
290                    return "<=";
291                }
292            };
293        }
294    
295        /**
296         * Only Numeric expressions can be used in >, >=, < or <= expressions.s
297         *
298         * @param expr
299         */
300        public static void checkLessThanOperand(Expression expr ) {
301            if( expr instanceof ConstantExpression ) {
302                Object value = ((ConstantExpression)expr).getValue();
303                if( value instanceof Number )
304                    return;
305    
306                // Else it's boolean or a String..
307                throw new RuntimeException("Value '"+expr+"' cannot be compared.");
308            }
309            if( expr instanceof BooleanExpression ) {
310                throw new RuntimeException("Value '"+expr+"' cannot be compared.");
311            }
312        }
313    
314        /**
315         * Validates that the expression can be used in == or <> expression.
316         * Cannot not be NULL TRUE or FALSE litterals.
317         *
318         * @param expr
319         */
320        public static void checkEqualOperand(Expression expr ) {
321            if( expr instanceof ConstantExpression ) {
322                Object value = ((ConstantExpression)expr).getValue();
323                if( value == null )
324                    throw new RuntimeException("'"+expr+"' cannot be compared.");
325            }
326        }
327    
328        /**
329         *
330         * @param left
331         * @param right
332         */
333        private static void checkEqualOperandCompatability(Expression left, Expression right) {
334            if( left instanceof ConstantExpression && right instanceof ConstantExpression ) {
335                if( left instanceof BooleanExpression && !(right instanceof BooleanExpression) )
336                    throw new RuntimeException("'"+left+"' cannot be compared with '"+right+"'");
337            }
338        }
339    
340    
341    
342        /**
343         * @param left
344         * @param right
345         */
346        public ComparisonExpression(Expression left, Expression right) {
347            super(left, right);
348        }
349    
350        public Object evaluate(MessageEvaluationContext message) throws JMSException {
351            Comparable<?> lv = (Comparable<?>)left.evaluate(message);
352            if (lv == null) {
353                return null;
354            }
355            Comparable<?> rv = (Comparable<?>)right.evaluate(message);
356            if (rv == null) {
357                return null;
358            }
359            return compare(lv, rv);
360        }
361    
362        @SuppressWarnings({ "unchecked", "rawtypes", "boxing" })
363        protected Boolean compare(Comparable lv, Comparable rv) {
364            Class<?> lc = lv.getClass();
365            Class<?> rc = rv.getClass();
366            // If the the objects are not of the same type,
367            // try to convert up to allow the comparison.
368            if (lc != rc) {
369                if (lc == Byte.class) {
370                    if (rc == Short.class) {
371                        return ((Number)lv).shortValue() == ((Short)rv).shortValue();
372                    }
373                    else if (rc == Integer.class) {
374                        return ((Number)lv).intValue() == ((Integer)rv).intValue();
375                    }
376                    else if (rc == Long.class) {
377                        return ((Number)lv).longValue() == ((Long)rv).longValue();
378                    }
379                    else if (rc == Float.class) {
380                        return ((Number)lv).floatValue() == ((Float)rv).floatValue();
381                    }
382                    else if (rc == Double.class) {
383                        return ((Double)lv).doubleValue() == ((Double)rv).doubleValue();
384                    }
385                    else {
386                        return Boolean.FALSE;
387                    }
388                 } else if (lc == Short.class) {
389                    if (rc == Integer.class) {
390                        lv = new Integer(((Number) lv).intValue());
391                    }
392                    else if (rc == Long.class) {
393                        lv = new Long(((Number) lv).longValue());
394                    }
395                    else if (rc == Float.class) {
396                        lv = new Float(((Number) lv).floatValue());
397                    }
398                    else if (rc == Double.class) {
399                        lv = new Double(((Number) lv).doubleValue());
400                    }
401                    else {
402                        return Boolean.FALSE;
403                    }
404                } else if (lc == Integer.class) {
405                    if (rc == Long.class) {
406                        lv = new Long(((Number)lv).longValue());
407                    }
408                    else if (rc == Float.class) {
409                        lv = new Float(((Number)lv).floatValue());
410                    }
411                    else if (rc == Double.class) {
412                        lv = new Double(((Number)lv).doubleValue());
413                    }
414                    else {
415                        return Boolean.FALSE;
416                    }
417                }
418                else if (lc == Long.class) {
419                    if (rc == Integer.class) {
420                        rv = new Long(((Number)rv).longValue());
421                    }
422                    else if (rc == Float.class) {
423                        lv = new Float(((Number)lv).floatValue());
424                    }
425                    else if (rc == Double.class) {
426                        lv = new Double(((Number)lv).doubleValue());
427                    }
428                    else {
429                        return Boolean.FALSE;
430                    }
431                }
432                else if (lc == Float.class) {
433                    if (rc == Integer.class) {
434                        rv = new Float(((Number)rv).floatValue());
435                    }
436                    else if (rc == Long.class) {
437                        rv = new Float(((Number)rv).floatValue());
438                    }
439                    else if (rc == Double.class) {
440                        lv = new Double(((Number)lv).doubleValue());
441                    }
442                    else {
443                        return Boolean.FALSE;
444                    }
445                }
446                else if (lc == Double.class) {
447                    if (rc == Integer.class) {
448                        rv = new Double(((Number)rv).doubleValue());
449                    }
450                    else if (rc == Long.class) {
451                        rv = new Double(((Number)rv).doubleValue());
452                    }
453                    else if (rc == Float.class) {
454                        rv = new Float(((Number)rv).doubleValue());
455                    }
456                    else {
457                        return Boolean.FALSE;
458                    }
459                }
460                else
461                    return Boolean.FALSE;
462            }
463    
464            return asBoolean(lv.compareTo(rv)) ? Boolean.TRUE : Boolean.FALSE;
465        }
466    
467        protected abstract boolean asBoolean(int answer);
468    
469        public boolean matches(MessageEvaluationContext message) throws JMSException {
470            Object object = evaluate(message);
471            return object != null && object == Boolean.TRUE;
472        }
473    
474    }