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