001    /** 
002     * 
003     * Copyright 2004 Protique Ltd
004     * Copyright 2004 Hiram Chirino
005     * 
006     * Licensed under the Apache License, Version 2.0 (the "License"); 
007     * you may not use this file except in compliance with the License. 
008     * 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     **/
019    package org.activemq.filter;
020    
021    import java.util.HashSet;
022    import java.util.List;
023    import java.util.regex.Pattern;
024    
025    import javax.jms.JMSException;
026    import javax.jms.Message;
027    
028    /**
029     * A filter performing a comparison of two objects
030     * 
031     * @version $Revision: 1.1.1.1 $
032     */
033    public abstract class ComparisonExpression extends BinaryExpression implements BooleanExpression {
034    
035        public static BooleanExpression createBetween(Expression value, Expression left, Expression right) {
036            return LogicExpression.createAND(createGreaterThanEqual(value, left), createLessThanEqual(value, right));
037        }
038    
039        public static BooleanExpression createNotBetween(Expression value, Expression left, Expression right) {
040            return LogicExpression.createOR(createLessThan(value, left), createGreaterThan(value, right));
041        }
042    
043        static final private HashSet REGEXP_CONTROL_CHARS = new HashSet();
044    
045        static {
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            REGEXP_CONTROL_CHARS.add(new Character('='));
065            REGEXP_CONTROL_CHARS.add(new Character('!'));
066        }
067    
068        static class LikeExpression extends UnaryExpression implements BooleanExpression {
069    
070            Pattern likePattern;
071    
072            /**
073             * @param left
074             */
075            public LikeExpression(Expression right, String like, int escape) {
076                super(right);
077    
078                StringBuffer regexp = new StringBuffer(like.length() * 2);
079                regexp.append("\\A"); // The beginning of the input
080                for (int i = 0; i < like.length(); i++) {
081                    char c = like.charAt(i);
082                    if (escape == (0xFFFF & c)) {
083                        i++;
084                        if (i >= like.length()) {
085                            // nothing left to escape...
086                            break;
087                        }
088    
089                        char t = like.charAt(i);
090                        regexp.append("\\x");
091                        regexp.append(Integer.toHexString(0xFFFF & t));
092                    }
093                    else if (c == '%') {
094                        regexp.append(".*?"); // Do a non-greedy match 
095                    }
096                    else if (c == '_') {
097                        regexp.append("."); // match one 
098                    }
099                    else if (REGEXP_CONTROL_CHARS.contains(new Character(c))) {
100                        regexp.append("\\x");
101                        regexp.append(Integer.toHexString(0xFFFF & c));
102                    }
103                    else {
104                        regexp.append(c);
105                    }
106                }
107                regexp.append("\\z"); // The end of the input
108    
109                System.out.println("regexp: " + like + ": " + regexp);
110                likePattern = Pattern.compile(regexp.toString(), Pattern.DOTALL);
111            }
112    
113            /**
114             * @see org.activemq.filter.UnaryExpression#getExpressionSymbol()
115             */
116            public String getExpressionSymbol() {
117                return "LIKE";
118            }
119    
120            /**
121             * @see org.activemq.filter.Expression#evaluate(javax.jms.Message)
122             */
123            public Object evaluate(Message message) throws JMSException {
124    
125                Object rv = this.getRight().evaluate(message);
126    
127                if (rv == null) {
128                    return null;
129                }
130    
131                if (!(rv instanceof String)) {
132                    return Boolean.FALSE;
133                    //throw new RuntimeException("LIKE can only operate on String identifiers.  LIKE attemped on: '" + rv.getClass());
134                }
135    
136                return likePattern.matcher((String) rv).matches() ? Boolean.TRUE : Boolean.FALSE;
137            }
138    
139        }
140    
141        public static BooleanExpression createLike(Expression left, String right, String escape) {
142            if (escape != null && escape.length() != 1) {
143                throw new RuntimeException("The ESCAPE string litteral is invalid.  It can only be one character.  Litteral used: " + escape);
144            }
145            int c = -1;
146            if (escape != null) {
147                c = 0xFFFF & escape.charAt(0);
148            }
149    
150            return new LikeExpression(left, right, c);
151        }
152    
153        public static BooleanExpression createNotLike(Expression left, String right, String escape) {
154            return UnaryExpression.createNOT(createLike(left, right, escape));
155        }    
156    
157        public static BooleanExpression createInFilter(Expression left, List elements) {
158            
159            if( !(left instanceof PropertyExpression) )
160                    throw new RuntimeException("Expected a property for In expression, got: "+left);        
161            return UnaryExpression.createInExpression((PropertyExpression)left, elements, false);
162            
163        }
164    
165        public static BooleanExpression createNotInFilter(Expression left, List elements) {
166            
167            if( !(left instanceof PropertyExpression) )
168                    throw new RuntimeException("Expected a property for In expression, got: "+left);        
169            return UnaryExpression.createInExpression((PropertyExpression)left, elements, true);
170    
171        }
172    
173        public static BooleanExpression createIsNull(Expression left) {
174            return doCreateEqual(left, ConstantExpression.NULL);
175        }
176    
177        public static BooleanExpression createIsNotNull(Expression left) {
178            return UnaryExpression.createNOT(doCreateEqual(left, ConstantExpression.NULL));
179        }
180    
181        public static BooleanExpression createNotEqual(Expression left, Expression right) {
182            return UnaryExpression.createNOT(createEqual(left, right));
183        }
184    
185        public static BooleanExpression createEqual(Expression left, Expression right) {
186            checkEqualOperand(left);
187            checkEqualOperand(right);
188            checkEqualOperandCompatability(left, right);
189            return doCreateEqual(left, right);
190        }
191        
192            private static BooleanExpression doCreateEqual(Expression left, Expression right) {
193            return new ComparisonExpression(left, right) {
194    
195                public Object evaluate(Message message) throws JMSException {
196                    Object obj1 = left.evaluate(message);
197                    Object obj2 = right.evaluate(message);
198                    
199                    // Iff one of the values is null
200                    if (obj1 == null ^ obj2 == null) {
201                        return Boolean.FALSE;
202                    }
203                    if (obj1 == obj2 || obj1.equals(obj2)) {
204                        return Boolean.TRUE;
205                    }
206                    Comparable lv = obj1 instanceof Comparable ? (Comparable) obj1 : null;
207                    Comparable rv = obj2 instanceof Comparable ? (Comparable) obj2 : null;
208                    if( lv==null || rv==null )
209                        return Boolean.FALSE;
210                    return compare(lv, rv);
211                }
212    
213                protected boolean asBoolean(int answer) {
214                    return answer == 0;
215                }
216    
217                public String getExpressionSymbol() {
218                    return "=";
219                }
220            };
221        }
222    
223        public static BooleanExpression createGreaterThan(final Expression left, final Expression right) {
224            checkLessThanOperand(left);
225            checkLessThanOperand(right);
226            return new ComparisonExpression(left, right) {
227                protected boolean asBoolean(int answer) {
228                    return answer > 0;
229                }
230    
231                public String getExpressionSymbol() {
232                    return ">";
233                }
234            };
235        }
236    
237        public static BooleanExpression createGreaterThanEqual(final Expression left, final Expression right) {
238            checkLessThanOperand(left);
239            checkLessThanOperand(right);
240            return new ComparisonExpression(left, right) {
241                protected boolean asBoolean(int answer) {
242                    return answer >= 0;
243                }
244    
245                public String getExpressionSymbol() {
246                    return ">=";
247                }
248            };
249        }
250    
251        public static BooleanExpression createLessThan(final Expression left, final Expression right) {
252            checkLessThanOperand(left);
253            checkLessThanOperand(right);
254            return new ComparisonExpression(left, right) {
255    
256                protected boolean asBoolean(int answer) {
257                    return answer < 0;
258                }
259    
260                public String getExpressionSymbol() {
261                    return "<";
262                }
263    
264            };
265        }
266    
267            public static BooleanExpression createLessThanEqual(final Expression left, final Expression right) {
268            checkLessThanOperand(left);
269            checkLessThanOperand(right);
270            return new ComparisonExpression(left, right) {
271    
272                protected boolean asBoolean(int answer) {
273                    return answer <= 0;
274                }
275    
276                public String getExpressionSymbol() {
277                    return "<=";
278                }
279            };
280        }
281    
282            /**
283         * Only Numeric expressions can be used in >, >=, < or <= expressions.s 
284         * 
285             * @param expr
286             */
287            public static void checkLessThanOperand(Expression expr ) {
288                    if( expr instanceof ConstantExpression ) {
289                            Object value = ((ConstantExpression)expr).getValue();
290                            if( value instanceof Number )
291                                    return;
292                            
293                            // Else it's boolean or a String..  
294                            throw new RuntimeException("Value '"+expr+"' cannot be compared.");
295                    }
296                    if( expr instanceof BooleanExpression ) {
297                            throw new RuntimeException("Value '"+expr+"' cannot be compared.");
298                    }               
299            }
300    
301            /**
302         * Validates that the expression can be used in == or <> expression.  
303         * Cannot not be NULL TRUE or FALSE litterals.
304         * 
305             * @param expr
306             */
307            public static void checkEqualOperand(Expression expr ) {
308                    if( expr instanceof ConstantExpression ) {
309                            Object value = ((ConstantExpression)expr).getValue();
310                            if( value == null )
311                                    throw new RuntimeException("'"+expr+"' cannot be compared.");
312                    }
313            }
314    
315            /**
316             * 
317             * @param left
318             * @param right
319             */
320            private static void checkEqualOperandCompatability(Expression left, Expression right) {
321                    if( left instanceof ConstantExpression && right instanceof ConstantExpression ) {
322                            if( left instanceof BooleanExpression && !(right instanceof BooleanExpression) )
323                                    throw new RuntimeException("'"+left+"' cannot be compared with '"+right+"'");
324                    }
325            }
326    
327            
328            
329        /**
330         * @param left
331         * @param right
332         */
333        public ComparisonExpression(Expression left, Expression right) {
334            super(left, right);
335        }
336    
337        public Object evaluate(Message message) throws JMSException {
338            Comparable lv = (Comparable) left.evaluate(message);
339            if (lv == null) {
340                return null;
341            }
342            Comparable rv = (Comparable) right.evaluate(message);
343            if (rv == null) {
344                return null;
345            }
346            return compare(lv, rv);
347        }
348    
349        protected Boolean compare(Comparable lv, Comparable rv) {
350            Class lc = lv.getClass();
351            Class rc = rv.getClass();
352            // If the the objects are not of the same type,
353            // try to convert up to allow the comparison.
354            if (lc != rc) {
355                if (lc == Integer.class) {
356                    if (rc == Long.class) {
357                        lv = new Long(((Number) lv).longValue());
358                    }
359                    else if (rc == Float.class) {
360                        lv = new Float(((Number) lv).floatValue());
361                    }
362                    else if (rc == Double.class) {
363                        lv = new Double(((Number) lv).doubleValue());
364                    }
365                    else {
366                        return Boolean.FALSE;
367                    }
368                }
369                else if (lc == Long.class) {
370                    if (rc == Integer.class) {
371                        rv = new Long(((Number) rv).longValue());
372                    }
373                    else if (rc == Float.class) {
374                        lv = new Float(((Number) lv).floatValue());
375                    }
376                    else if (rc == Double.class) {
377                        lv = new Double(((Number) lv).doubleValue());
378                    }
379                    else {
380                        return Boolean.FALSE;
381                    }
382                }
383                else if (lc == Float.class) {
384                    if (rc == Integer.class) {
385                        rv = new Float(((Number) rv).floatValue());
386                    }
387                    else if (rc == Long.class) {
388                        rv = new Float(((Number) rv).floatValue());
389                    }
390                    else if (rc == Double.class) {
391                        lv = new Double(((Number) lv).doubleValue());
392                    }
393                    else {
394                        return Boolean.FALSE;
395                    }
396                } 
397                else if (lc == Double.class) {
398                    if (rc == Integer.class) {
399                        rv = new Double(((Number) rv).doubleValue());
400                    }
401                    else if (rc == Long.class) {
402                        rv = new Double(((Number) rv).doubleValue());
403                    }
404                    else if (rc == Float.class) {
405                            rv = new Float(((Number) rv).doubleValue());
406                    }
407                    else {
408                        return Boolean.FALSE;
409                    }
410                } 
411                else 
412                    return Boolean.FALSE;
413            }
414            return asBoolean(lv.compareTo(rv)) ? Boolean.TRUE : Boolean.FALSE;
415        }
416    
417        protected abstract boolean asBoolean(int answer);
418    }