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