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) && shouldEscapeNext(like, i, c)) { 092 i++; 093 char t = like.charAt(i); 094 regexp.append("\\x"); 095 regexp.append(Integer.toHexString(0xFFFF & t)); 096 } else { 097 append(regexp, c); 098 } 099 } 100 regexp.append("\\z"); // The end of the input 101 102 likePattern = Pattern.compile(regexp.toString(), Pattern.DOTALL); 103 } 104 105 private boolean shouldEscapeNext(String selector, int i, char escape) { 106 int next = i+1; 107 if (next < selector.length()) { 108 final char c = selector.charAt(next); 109 return (c == '_' || c == '%' || c == escape); 110 } 111 return false; 112 } 113 114 private void append(StringBuffer regexp, char c) { 115 if (c == '%') { 116 regexp.append(".*?"); // Do a non-greedy match 117 } else if (c == '_') { 118 regexp.append("."); // match one 119 } else if (REGEXP_CONTROL_CHARS.contains(new Character(c))) { 120 regexp.append("\\x"); 121 regexp.append(Integer.toHexString(0xFFFF & c)); 122 } else { 123 regexp.append(c); 124 } 125 } 126 127 /** 128 * @see org.apache.activemq.filter.UnaryExpression#getExpressionSymbol() 129 */ 130 public String getExpressionSymbol() { 131 return "LIKE"; 132 } 133 134 /** 135 * @see org.apache.activemq.filter.Expression#evaluate(MessageEvaluationContext) 136 */ 137 public Object evaluate(MessageEvaluationContext message) throws JMSException { 138 139 Object rv = this.getRight().evaluate(message); 140 141 if (rv == null) { 142 return null; 143 } 144 145 if (!(rv instanceof String)) { 146 return Boolean.FALSE; 147 // throw new RuntimeException("LIKE can only operate on String 148 // identifiers. LIKE attemped on: '" + rv.getClass()); 149 } 150 151 return likePattern.matcher((String)rv).matches() ? Boolean.TRUE : Boolean.FALSE; 152 } 153 154 public boolean matches(MessageEvaluationContext message) throws JMSException { 155 Object object = evaluate(message); 156 return object != null && object == Boolean.TRUE; 157 } 158 } 159 160 public static BooleanExpression createLike(Expression left, String right, String escape) { 161 if (escape != null && escape.length() != 1) { 162 throw new RuntimeException("The ESCAPE string litteral is invalid. It can only be one character. Litteral used: " + escape); 163 } 164 int c = -1; 165 if (escape != null) { 166 c = 0xFFFF & escape.charAt(0); 167 } 168 169 return new LikeExpression(left, right, c); 170 } 171 172 public static BooleanExpression createNotLike(Expression left, String right, String escape) { 173 return UnaryExpression.createNOT(createLike(left, right, escape)); 174 } 175 176 @SuppressWarnings({ "rawtypes", "unchecked" }) 177 public static BooleanExpression createInFilter(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, false); 183 184 } 185 186 @SuppressWarnings({ "rawtypes", "unchecked" }) 187 public static BooleanExpression createNotInFilter(Expression left, List elements) { 188 189 if (!(left instanceof PropertyExpression)) { 190 throw new RuntimeException("Expected a property for In expression, got: " + left); 191 } 192 return UnaryExpression.createInExpression((PropertyExpression)left, elements, true); 193 194 } 195 196 public static BooleanExpression createIsNull(Expression left) { 197 return doCreateEqual(left, ConstantExpression.NULL); 198 } 199 200 public static BooleanExpression createIsNotNull(Expression left) { 201 return UnaryExpression.createNOT(doCreateEqual(left, ConstantExpression.NULL)); 202 } 203 204 public static BooleanExpression createNotEqual(Expression left, Expression right) { 205 return UnaryExpression.createNOT(createEqual(left, right)); 206 } 207 208 public static BooleanExpression createEqual(Expression left, Expression right) { 209 checkEqualOperand(left); 210 checkEqualOperand(right); 211 checkEqualOperandCompatability(left, right); 212 return doCreateEqual(left, right); 213 } 214 215 @SuppressWarnings({ "rawtypes" }) 216 private static BooleanExpression doCreateEqual(Expression left, Expression right) { 217 return new ComparisonExpression(left, right) { 218 219 public Object evaluate(MessageEvaluationContext message) throws JMSException { 220 Object lv = left.evaluate(message); 221 Object rv = right.evaluate(message); 222 223 // If one of the values is null 224 if (lv == null ^ rv == null) { 225 if (lv == null) { 226 return null; 227 } 228 return Boolean.FALSE; 229 } 230 if (lv == rv || lv.equals(rv)) { 231 return Boolean.TRUE; 232 } 233 if (lv instanceof Comparable && rv instanceof Comparable) { 234 return compare((Comparable)lv, (Comparable)rv); 235 } 236 return Boolean.FALSE; 237 } 238 239 protected boolean asBoolean(int answer) { 240 return answer == 0; 241 } 242 243 public String getExpressionSymbol() { 244 return "="; 245 } 246 }; 247 } 248 249 public static BooleanExpression createGreaterThan(final Expression left, final Expression right) { 250 checkLessThanOperand(left); 251 checkLessThanOperand(right); 252 return new ComparisonExpression(left, right) { 253 protected boolean asBoolean(int answer) { 254 return answer > 0; 255 } 256 257 public String getExpressionSymbol() { 258 return ">"; 259 } 260 }; 261 } 262 263 public static BooleanExpression createGreaterThanEqual(final Expression left, final Expression right) { 264 checkLessThanOperand(left); 265 checkLessThanOperand(right); 266 return new ComparisonExpression(left, right) { 267 protected boolean asBoolean(int answer) { 268 return answer >= 0; 269 } 270 271 public String getExpressionSymbol() { 272 return ">="; 273 } 274 }; 275 } 276 277 public static BooleanExpression createLessThan(final Expression left, final Expression right) { 278 checkLessThanOperand(left); 279 checkLessThanOperand(right); 280 return new ComparisonExpression(left, right) { 281 282 protected boolean asBoolean(int answer) { 283 return answer < 0; 284 } 285 286 public String getExpressionSymbol() { 287 return "<"; 288 } 289 290 }; 291 } 292 293 public static BooleanExpression createLessThanEqual(final Expression left, final Expression right) { 294 checkLessThanOperand(left); 295 checkLessThanOperand(right); 296 return new ComparisonExpression(left, right) { 297 298 protected boolean asBoolean(int answer) { 299 return answer <= 0; 300 } 301 302 public String getExpressionSymbol() { 303 return "<="; 304 } 305 }; 306 } 307 308 /** 309 * Only Numeric expressions can be used in >, >=, < or <= expressions.s 310 * 311 * @param expr 312 */ 313 public static void checkLessThanOperand(Expression expr) { 314 if (expr instanceof ConstantExpression) { 315 Object value = ((ConstantExpression)expr).getValue(); 316 if (value instanceof Number) { 317 return; 318 } 319 320 // Else it's boolean or a String.. 321 throw new RuntimeException("Value '" + expr + "' cannot be compared."); 322 } 323 if (expr instanceof BooleanExpression) { 324 throw new RuntimeException("Value '" + expr + "' cannot be compared."); 325 } 326 } 327 328 /** 329 * Validates that the expression can be used in == or <> expression. Cannot 330 * not be NULL TRUE or FALSE litterals. 331 * 332 * @param expr 333 */ 334 public static void checkEqualOperand(Expression expr) { 335 if (expr instanceof ConstantExpression) { 336 Object value = ((ConstantExpression)expr).getValue(); 337 if (value == null) { 338 throw new RuntimeException("'" + expr + "' cannot be compared."); 339 } 340 } 341 } 342 343 /** 344 * @param left 345 * @param right 346 */ 347 private static void checkEqualOperandCompatability(Expression left, Expression right) { 348 if (left instanceof ConstantExpression && right instanceof ConstantExpression) { 349 if (left instanceof BooleanExpression && !(right instanceof BooleanExpression)) { 350 throw new RuntimeException("'" + left + "' cannot be compared with '" + right + "'"); 351 } 352 } 353 } 354 355 @SuppressWarnings({ "rawtypes", "unchecked" }) 356 public Object evaluate(MessageEvaluationContext message) throws JMSException { 357 Comparable<Comparable> lv = (Comparable)left.evaluate(message); 358 if (lv == null) { 359 return null; 360 } 361 Comparable rv = (Comparable)right.evaluate(message); 362 if (rv == null) { 363 return null; 364 } 365 return compare(lv, rv); 366 } 367 368 @SuppressWarnings({ "rawtypes", "unchecked" }) 369 protected Boolean compare(Comparable lv, Comparable rv) { 370 Class<? extends Comparable> lc = lv.getClass(); 371 Class<? extends Comparable> rc = rv.getClass(); 372 // If the the objects are not of the same type, 373 // try to convert up to allow the comparison. 374 if (lc != rc) { 375 try { 376 if (lc == Boolean.class) { 377 if (convertStringExpressions && rc == String.class) { 378 lv = Boolean.valueOf((String)lv).booleanValue(); 379 } else { 380 return Boolean.FALSE; 381 } 382 } else if (lc == Byte.class) { 383 if (rc == Short.class) { 384 lv = Short.valueOf(((Number)lv).shortValue()); 385 } else if (rc == Integer.class) { 386 lv = Integer.valueOf(((Number)lv).intValue()); 387 } else if (rc == Long.class) { 388 lv = Long.valueOf(((Number)lv).longValue()); 389 } else if (rc == Float.class) { 390 lv = new Float(((Number)lv).floatValue()); 391 } else if (rc == Double.class) { 392 lv = new Double(((Number)lv).doubleValue()); 393 } else if (convertStringExpressions && rc == String.class) { 394 rv = Byte.valueOf((String)rv); 395 } else { 396 return Boolean.FALSE; 397 } 398 } else if (lc == Short.class) { 399 if (rc == Integer.class) { 400 lv = Integer.valueOf(((Number)lv).intValue()); 401 } else if (rc == Long.class) { 402 lv = Long.valueOf(((Number)lv).longValue()); 403 } else if (rc == Float.class) { 404 lv = new Float(((Number)lv).floatValue()); 405 } else if (rc == Double.class) { 406 lv = new Double(((Number)lv).doubleValue()); 407 } else if (convertStringExpressions && rc == String.class) { 408 rv = Short.valueOf((String)rv); 409 } else { 410 return Boolean.FALSE; 411 } 412 } else if (lc == Integer.class) { 413 if (rc == Long.class) { 414 lv = Long.valueOf(((Number)lv).longValue()); 415 } else if (rc == Float.class) { 416 lv = new Float(((Number)lv).floatValue()); 417 } else if (rc == Double.class) { 418 lv = new Double(((Number)lv).doubleValue()); 419 } else if (convertStringExpressions && rc == String.class) { 420 rv = Integer.valueOf((String)rv); 421 } else { 422 return Boolean.FALSE; 423 } 424 } else if (lc == Long.class) { 425 if (rc == Integer.class) { 426 rv = Long.valueOf(((Number)rv).longValue()); 427 } else if (rc == Float.class) { 428 lv = new Float(((Number)lv).floatValue()); 429 } else if (rc == Double.class) { 430 lv = new Double(((Number)lv).doubleValue()); 431 } else if (convertStringExpressions && rc == String.class) { 432 rv = Long.valueOf((String)rv); 433 } else { 434 return Boolean.FALSE; 435 } 436 } else if (lc == Float.class) { 437 if (rc == Integer.class) { 438 rv = new Float(((Number)rv).floatValue()); 439 } else if (rc == Long.class) { 440 rv = new Float(((Number)rv).floatValue()); 441 } else if (rc == Double.class) { 442 lv = new Double(((Number)lv).doubleValue()); 443 } else if (convertStringExpressions && rc == String.class) { 444 rv = Float.valueOf((String)rv); 445 } else { 446 return Boolean.FALSE; 447 } 448 } else if (lc == Double.class) { 449 if (rc == Integer.class) { 450 rv = new Double(((Number)rv).doubleValue()); 451 } else if (rc == Long.class) { 452 rv = new Double(((Number)rv).doubleValue()); 453 } else if (rc == Float.class) { 454 rv = new Float(((Number)rv).doubleValue()); 455 } else if (convertStringExpressions && rc == String.class) { 456 rv = Double.valueOf((String)rv); 457 } else { 458 return Boolean.FALSE; 459 } 460 } else if (convertStringExpressions && lc == String.class) { 461 if (rc == Boolean.class) { 462 lv = Boolean.valueOf((String)lv); 463 } else if (rc == Byte.class) { 464 lv = Byte.valueOf((String)lv); 465 } else if (rc == Short.class) { 466 lv = Short.valueOf((String)lv); 467 } else if (rc == Integer.class) { 468 lv = Integer.valueOf((String)lv); 469 } else if (rc == Long.class) { 470 lv = Long.valueOf((String)lv); 471 } else if (rc == Float.class) { 472 lv = Float.valueOf((String)lv); 473 } else if (rc == Double.class) { 474 lv = Double.valueOf((String)lv); 475 } else { 476 return Boolean.FALSE; 477 } 478 } else { 479 return Boolean.FALSE; 480 } 481 } catch(NumberFormatException e) { 482 return Boolean.FALSE; 483 } 484 } 485 return asBoolean(lv.compareTo(rv)) ? Boolean.TRUE : Boolean.FALSE; 486 } 487 488 protected abstract boolean asBoolean(int answer); 489 490 public boolean matches(MessageEvaluationContext message) throws JMSException { 491 Object object = evaluate(message); 492 return object != null && object == Boolean.TRUE; 493 } 494 495}