001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.activemq.filter; 018 019import java.util.HashSet; 020import java.util.List; 021import java.util.Set; 022import java.util.regex.Pattern; 023 024import javax.jms.JMSException; 025 026/** 027 * A filter performing a comparison of two objects 028 * 029 * 030 */ 031public abstract class ComparisonExpression extends BinaryExpression implements BooleanExpression { 032 033 public static final ThreadLocal<Boolean> CONVERT_STRING_EXPRESSIONS = new ThreadLocal<Boolean>(); 034 035 boolean convertStringExpressions = false; 036 private static final Set<Character> REGEXP_CONTROL_CHARS = new HashSet<Character>(); 037 038 /** 039 * @param left 040 * @param right 041 */ 042 public ComparisonExpression(Expression left, Expression right) { 043 super(left, right); 044 convertStringExpressions = CONVERT_STRING_EXPRESSIONS.get()!=null; 045 } 046 047 public static BooleanExpression createBetween(Expression value, Expression left, Expression right) { 048 return LogicExpression.createAND(createGreaterThanEqual(value, left), createLessThanEqual(value, right)); 049 } 050 051 public static BooleanExpression createNotBetween(Expression value, Expression left, Expression right) { 052 return LogicExpression.createOR(createLessThan(value, left), createGreaterThan(value, right)); 053 } 054 055 static { 056 REGEXP_CONTROL_CHARS.add(Character.valueOf('.')); 057 REGEXP_CONTROL_CHARS.add(Character.valueOf('\\')); 058 REGEXP_CONTROL_CHARS.add(Character.valueOf('[')); 059 REGEXP_CONTROL_CHARS.add(Character.valueOf(']')); 060 REGEXP_CONTROL_CHARS.add(Character.valueOf('^')); 061 REGEXP_CONTROL_CHARS.add(Character.valueOf('$')); 062 REGEXP_CONTROL_CHARS.add(Character.valueOf('?')); 063 REGEXP_CONTROL_CHARS.add(Character.valueOf('*')); 064 REGEXP_CONTROL_CHARS.add(Character.valueOf('+')); 065 REGEXP_CONTROL_CHARS.add(Character.valueOf('{')); 066 REGEXP_CONTROL_CHARS.add(Character.valueOf('}')); 067 REGEXP_CONTROL_CHARS.add(Character.valueOf('|')); 068 REGEXP_CONTROL_CHARS.add(Character.valueOf('(')); 069 REGEXP_CONTROL_CHARS.add(Character.valueOf(')')); 070 REGEXP_CONTROL_CHARS.add(Character.valueOf(':')); 071 REGEXP_CONTROL_CHARS.add(Character.valueOf('&')); 072 REGEXP_CONTROL_CHARS.add(Character.valueOf('<')); 073 REGEXP_CONTROL_CHARS.add(Character.valueOf('>')); 074 REGEXP_CONTROL_CHARS.add(Character.valueOf('=')); 075 REGEXP_CONTROL_CHARS.add(Character.valueOf('!')); 076 } 077 078 static class LikeExpression extends UnaryExpression implements BooleanExpression { 079 080 Pattern likePattern; 081 082 /** 083 */ 084 public LikeExpression(Expression right, String like, int escape) { 085 super(right); 086 087 StringBuffer regexp = new StringBuffer(like.length() * 2); 088 regexp.append("\\A"); // The beginning of the input 089 for (int i = 0; i < like.length(); i++) { 090 char c = like.charAt(i); 091 if (escape == (0xFFFF & c)) { 092 i++; 093 if (i >= like.length()) { 094 // nothing left to escape... 095 break; 096 } 097 098 char t = like.charAt(i); 099 regexp.append("\\x"); 100 regexp.append(Integer.toHexString(0xFFFF & t)); 101 } else if (c == '%') { 102 regexp.append(".*?"); // Do a non-greedy match 103 } else if (c == '_') { 104 regexp.append("."); // match one 105 } else if (REGEXP_CONTROL_CHARS.contains(new Character(c))) { 106 regexp.append("\\x"); 107 regexp.append(Integer.toHexString(0xFFFF & c)); 108 } else { 109 regexp.append(c); 110 } 111 } 112 regexp.append("\\z"); // The end of the input 113 114 likePattern = Pattern.compile(regexp.toString(), Pattern.DOTALL); 115 } 116 117 /** 118 * @see org.apache.activemq.filter.UnaryExpression#getExpressionSymbol() 119 */ 120 public String getExpressionSymbol() { 121 return "LIKE"; 122 } 123 124 /** 125 * @see org.apache.activemq.filter.Expression#evaluate(MessageEvaluationContext) 126 */ 127 public Object evaluate(MessageEvaluationContext message) throws JMSException { 128 129 Object rv = this.getRight().evaluate(message); 130 131 if (rv == null) { 132 return null; 133 } 134 135 if (!(rv instanceof String)) { 136 return Boolean.FALSE; 137 // throw new RuntimeException("LIKE can only operate on String 138 // identifiers. LIKE attemped on: '" + rv.getClass()); 139 } 140 141 return likePattern.matcher((String)rv).matches() ? Boolean.TRUE : Boolean.FALSE; 142 } 143 144 public boolean matches(MessageEvaluationContext message) throws JMSException { 145 Object object = evaluate(message); 146 return object != null && object == Boolean.TRUE; 147 } 148 } 149 150 public static BooleanExpression createLike(Expression left, String right, String escape) { 151 if (escape != null && escape.length() != 1) { 152 throw new RuntimeException("The ESCAPE string litteral is invalid. It can only be one character. Litteral used: " + escape); 153 } 154 int c = -1; 155 if (escape != null) { 156 c = 0xFFFF & escape.charAt(0); 157 } 158 159 return new LikeExpression(left, right, c); 160 } 161 162 public static BooleanExpression createNotLike(Expression left, String right, String escape) { 163 return UnaryExpression.createNOT(createLike(left, right, escape)); 164 } 165 166 @SuppressWarnings({ "rawtypes", "unchecked" }) 167 public static BooleanExpression createInFilter(Expression left, List elements) { 168 169 if (!(left instanceof PropertyExpression)) { 170 throw new RuntimeException("Expected a property for In expression, got: " + left); 171 } 172 return UnaryExpression.createInExpression((PropertyExpression)left, elements, false); 173 174 } 175 176 @SuppressWarnings({ "rawtypes", "unchecked" }) 177 public static BooleanExpression createNotInFilter(Expression left, List elements) { 178 179 if (!(left instanceof PropertyExpression)) { 180 throw new RuntimeException("Expected a property for In expression, got: " + left); 181 } 182 return UnaryExpression.createInExpression((PropertyExpression)left, elements, true); 183 184 } 185 186 public static BooleanExpression createIsNull(Expression left) { 187 return doCreateEqual(left, ConstantExpression.NULL); 188 } 189 190 public static BooleanExpression createIsNotNull(Expression left) { 191 return UnaryExpression.createNOT(doCreateEqual(left, ConstantExpression.NULL)); 192 } 193 194 public static BooleanExpression createNotEqual(Expression left, Expression right) { 195 return UnaryExpression.createNOT(createEqual(left, right)); 196 } 197 198 public static BooleanExpression createEqual(Expression left, Expression right) { 199 checkEqualOperand(left); 200 checkEqualOperand(right); 201 checkEqualOperandCompatability(left, right); 202 return doCreateEqual(left, right); 203 } 204 205 @SuppressWarnings({ "rawtypes" }) 206 private static BooleanExpression doCreateEqual(Expression left, Expression right) { 207 return new ComparisonExpression(left, right) { 208 209 public Object evaluate(MessageEvaluationContext message) throws JMSException { 210 Object lv = left.evaluate(message); 211 Object rv = right.evaluate(message); 212 213 // If one of the values is null 214 if (lv == null ^ rv == null) { 215 if (lv == null) { 216 return null; 217 } 218 return Boolean.FALSE; 219 } 220 if (lv == rv || lv.equals(rv)) { 221 return Boolean.TRUE; 222 } 223 if (lv instanceof Comparable && rv instanceof Comparable) { 224 return compare((Comparable)lv, (Comparable)rv); 225 } 226 return Boolean.FALSE; 227 } 228 229 protected boolean asBoolean(int answer) { 230 return answer == 0; 231 } 232 233 public String getExpressionSymbol() { 234 return "="; 235 } 236 }; 237 } 238 239 public static BooleanExpression createGreaterThan(final Expression left, final Expression right) { 240 checkLessThanOperand(left); 241 checkLessThanOperand(right); 242 return new ComparisonExpression(left, right) { 243 protected boolean asBoolean(int answer) { 244 return answer > 0; 245 } 246 247 public String getExpressionSymbol() { 248 return ">"; 249 } 250 }; 251 } 252 253 public static BooleanExpression createGreaterThanEqual(final Expression left, final Expression right) { 254 checkLessThanOperand(left); 255 checkLessThanOperand(right); 256 return new ComparisonExpression(left, right) { 257 protected boolean asBoolean(int answer) { 258 return answer >= 0; 259 } 260 261 public String getExpressionSymbol() { 262 return ">="; 263 } 264 }; 265 } 266 267 public static BooleanExpression createLessThan(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 public static BooleanExpression createLessThanEqual(final Expression left, final Expression right) { 284 checkLessThanOperand(left); 285 checkLessThanOperand(right); 286 return new ComparisonExpression(left, right) { 287 288 protected boolean asBoolean(int answer) { 289 return answer <= 0; 290 } 291 292 public String getExpressionSymbol() { 293 return "<="; 294 } 295 }; 296 } 297 298 /** 299 * Only Numeric expressions can be used in >, >=, < or <= expressions.s 300 * 301 * @param expr 302 */ 303 public static void checkLessThanOperand(Expression expr) { 304 if (expr instanceof ConstantExpression) { 305 Object value = ((ConstantExpression)expr).getValue(); 306 if (value instanceof Number) { 307 return; 308 } 309 310 // Else it's boolean or a String.. 311 throw new RuntimeException("Value '" + expr + "' cannot be compared."); 312 } 313 if (expr instanceof BooleanExpression) { 314 throw new RuntimeException("Value '" + expr + "' cannot be compared."); 315 } 316 } 317 318 /** 319 * Validates that the expression can be used in == or <> expression. Cannot 320 * not be NULL TRUE or FALSE litterals. 321 * 322 * @param expr 323 */ 324 public static void checkEqualOperand(Expression expr) { 325 if (expr instanceof ConstantExpression) { 326 Object value = ((ConstantExpression)expr).getValue(); 327 if (value == null) { 328 throw new RuntimeException("'" + expr + "' cannot be compared."); 329 } 330 } 331 } 332 333 /** 334 * @param left 335 * @param right 336 */ 337 private static void checkEqualOperandCompatability(Expression left, Expression right) { 338 if (left instanceof ConstantExpression && right instanceof ConstantExpression) { 339 if (left instanceof BooleanExpression && !(right instanceof BooleanExpression)) { 340 throw new RuntimeException("'" + left + "' cannot be compared with '" + right + "'"); 341 } 342 } 343 } 344 345 @SuppressWarnings({ "rawtypes", "unchecked" }) 346 public Object evaluate(MessageEvaluationContext message) throws JMSException { 347 Comparable<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({ "rawtypes", "unchecked" }) 359 protected Boolean compare(Comparable lv, Comparable rv) { 360 Class<? extends Comparable> lc = lv.getClass(); 361 Class<? extends Comparable> 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 try { 366 if (lc == Boolean.class) { 367 if (convertStringExpressions && rc == String.class) { 368 lv = Boolean.valueOf((String)lv).booleanValue(); 369 } else { 370 return Boolean.FALSE; 371 } 372 } else if (lc == Byte.class) { 373 if (rc == Short.class) { 374 lv = Short.valueOf(((Number)lv).shortValue()); 375 } else if (rc == Integer.class) { 376 lv = Integer.valueOf(((Number)lv).intValue()); 377 } else if (rc == Long.class) { 378 lv = Long.valueOf(((Number)lv).longValue()); 379 } else if (rc == Float.class) { 380 lv = new Float(((Number)lv).floatValue()); 381 } else if (rc == Double.class) { 382 lv = new Double(((Number)lv).doubleValue()); 383 } else if (convertStringExpressions && rc == String.class) { 384 rv = Byte.valueOf((String)rv); 385 } else { 386 return Boolean.FALSE; 387 } 388 } else if (lc == Short.class) { 389 if (rc == Integer.class) { 390 lv = Integer.valueOf(((Number)lv).intValue()); 391 } else if (rc == Long.class) { 392 lv = Long.valueOf(((Number)lv).longValue()); 393 } else if (rc == Float.class) { 394 lv = new Float(((Number)lv).floatValue()); 395 } else if (rc == Double.class) { 396 lv = new Double(((Number)lv).doubleValue()); 397 } else if (convertStringExpressions && rc == String.class) { 398 rv = Short.valueOf((String)rv); 399 } else { 400 return Boolean.FALSE; 401 } 402 } else if (lc == Integer.class) { 403 if (rc == Long.class) { 404 lv = Long.valueOf(((Number)lv).longValue()); 405 } else if (rc == Float.class) { 406 lv = new Float(((Number)lv).floatValue()); 407 } else if (rc == Double.class) { 408 lv = new Double(((Number)lv).doubleValue()); 409 } else if (convertStringExpressions && rc == String.class) { 410 rv = Integer.valueOf((String)rv); 411 } else { 412 return Boolean.FALSE; 413 } 414 } else if (lc == Long.class) { 415 if (rc == Integer.class) { 416 rv = Long.valueOf(((Number)rv).longValue()); 417 } else if (rc == Float.class) { 418 lv = new Float(((Number)lv).floatValue()); 419 } else if (rc == Double.class) { 420 lv = new Double(((Number)lv).doubleValue()); 421 } else if (convertStringExpressions && rc == String.class) { 422 rv = Long.valueOf((String)rv); 423 } else { 424 return Boolean.FALSE; 425 } 426 } else if (lc == Float.class) { 427 if (rc == Integer.class) { 428 rv = new Float(((Number)rv).floatValue()); 429 } else if (rc == Long.class) { 430 rv = new Float(((Number)rv).floatValue()); 431 } else if (rc == Double.class) { 432 lv = new Double(((Number)lv).doubleValue()); 433 } else if (convertStringExpressions && rc == String.class) { 434 rv = Float.valueOf((String)rv); 435 } else { 436 return Boolean.FALSE; 437 } 438 } else if (lc == Double.class) { 439 if (rc == Integer.class) { 440 rv = new Double(((Number)rv).doubleValue()); 441 } else if (rc == Long.class) { 442 rv = new Double(((Number)rv).doubleValue()); 443 } else if (rc == Float.class) { 444 rv = new Float(((Number)rv).doubleValue()); 445 } else if (convertStringExpressions && rc == String.class) { 446 rv = Double.valueOf((String)rv); 447 } else { 448 return Boolean.FALSE; 449 } 450 } else if (convertStringExpressions && lc == String.class) { 451 if (rc == Boolean.class) { 452 lv = Boolean.valueOf((String)lv); 453 } else if (rc == Byte.class) { 454 lv = Byte.valueOf((String)lv); 455 } else if (rc == Short.class) { 456 lv = Short.valueOf((String)lv); 457 } else if (rc == Integer.class) { 458 lv = Integer.valueOf((String)lv); 459 } else if (rc == Long.class) { 460 lv = Long.valueOf((String)lv); 461 } else if (rc == Float.class) { 462 lv = Float.valueOf((String)lv); 463 } else if (rc == Double.class) { 464 lv = Double.valueOf((String)lv); 465 } else { 466 return Boolean.FALSE; 467 } 468 } else { 469 return Boolean.FALSE; 470 } 471 } catch(NumberFormatException e) { 472 return Boolean.FALSE; 473 } 474 } 475 return asBoolean(lv.compareTo(rv)) ? Boolean.TRUE : Boolean.FALSE; 476 } 477 478 protected abstract boolean asBoolean(int answer); 479 480 public boolean matches(MessageEvaluationContext message) throws JMSException { 481 Object object = evaluate(message); 482 return object != null && object == Boolean.TRUE; 483 } 484 485}