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 */
022package org.granite.gravity.selector;
023
024import java.util.HashSet;
025import java.util.List;
026import java.util.regex.Pattern;
027
028import javax.jms.JMSException;
029
030/**
031 * A filter performing a comparison of two objects
032 *
033 * @version $Revision: 1.2 $
034 */
035public 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}