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 }