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 */ 017 package org.apache.activemq.filter; 018 019 import java.util.HashSet; 020 import java.util.List; 021 import java.util.Set; 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 * 030 */ 031 public 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 public static BooleanExpression createInFilter(Expression left, List elements) { 164 165 if (!(left instanceof PropertyExpression)) { 166 throw new RuntimeException("Expected a property for In expression, got: " + left); 167 } 168 return UnaryExpression.createInExpression((PropertyExpression)left, elements, false); 169 170 } 171 172 public static BooleanExpression createNotInFilter(Expression left, List elements) { 173 174 if (!(left instanceof PropertyExpression)) { 175 throw new RuntimeException("Expected a property for In expression, got: " + left); 176 } 177 return UnaryExpression.createInExpression((PropertyExpression)left, elements, true); 178 179 } 180 181 public static BooleanExpression createIsNull(Expression left) { 182 return doCreateEqual(left, ConstantExpression.NULL); 183 } 184 185 public static BooleanExpression createIsNotNull(Expression left) { 186 return UnaryExpression.createNOT(doCreateEqual(left, ConstantExpression.NULL)); 187 } 188 189 public static BooleanExpression createNotEqual(Expression left, Expression right) { 190 return UnaryExpression.createNOT(createEqual(left, right)); 191 } 192 193 public static BooleanExpression createEqual(Expression left, Expression right) { 194 checkEqualOperand(left); 195 checkEqualOperand(right); 196 checkEqualOperandCompatability(left, right); 197 return doCreateEqual(left, right); 198 } 199 200 private static BooleanExpression doCreateEqual(Expression left, Expression right) { 201 return new ComparisonExpression(left, right) { 202 203 public Object evaluate(MessageEvaluationContext message) throws JMSException { 204 Object lv = left.evaluate(message); 205 Object rv = right.evaluate(message); 206 207 // Iff one of the values is null 208 if (lv == null ^ rv == null) { 209 return Boolean.FALSE; 210 } 211 if (lv == rv || lv.equals(rv)) { 212 return Boolean.TRUE; 213 } 214 if (lv instanceof Comparable && rv instanceof Comparable) { 215 return compare((Comparable)lv, (Comparable)rv); 216 } 217 return Boolean.FALSE; 218 } 219 220 protected boolean asBoolean(int answer) { 221 return answer == 0; 222 } 223 224 public String getExpressionSymbol() { 225 return "="; 226 } 227 }; 228 } 229 230 public static BooleanExpression createGreaterThan(final Expression left, final Expression right) { 231 checkLessThanOperand(left); 232 checkLessThanOperand(right); 233 return new ComparisonExpression(left, right) { 234 protected boolean asBoolean(int answer) { 235 return answer > 0; 236 } 237 238 public String getExpressionSymbol() { 239 return ">"; 240 } 241 }; 242 } 243 244 public static BooleanExpression createGreaterThanEqual(final Expression left, final Expression right) { 245 checkLessThanOperand(left); 246 checkLessThanOperand(right); 247 return new ComparisonExpression(left, right) { 248 protected boolean asBoolean(int answer) { 249 return answer >= 0; 250 } 251 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 263 protected boolean asBoolean(int answer) { 264 return answer < 0; 265 } 266 267 public String getExpressionSymbol() { 268 return "<"; 269 } 270 271 }; 272 } 273 274 public static BooleanExpression createLessThanEqual(final Expression left, final Expression right) { 275 checkLessThanOperand(left); 276 checkLessThanOperand(right); 277 return new ComparisonExpression(left, right) { 278 279 protected boolean asBoolean(int answer) { 280 return answer <= 0; 281 } 282 283 public String getExpressionSymbol() { 284 return "<="; 285 } 286 }; 287 } 288 289 /** 290 * Only Numeric expressions can be used in >, >=, < or <= expressions.s 291 * 292 * @param expr 293 */ 294 public static void checkLessThanOperand(Expression expr) { 295 if (expr instanceof ConstantExpression) { 296 Object value = ((ConstantExpression)expr).getValue(); 297 if (value instanceof Number) { 298 return; 299 } 300 301 // Else it's boolean or a String.. 302 throw new RuntimeException("Value '" + expr + "' cannot be compared."); 303 } 304 if (expr instanceof BooleanExpression) { 305 throw new RuntimeException("Value '" + expr + "' cannot be compared."); 306 } 307 } 308 309 /** 310 * Validates that the expression can be used in == or <> expression. Cannot 311 * not be NULL TRUE or FALSE litterals. 312 * 313 * @param expr 314 */ 315 public static void checkEqualOperand(Expression expr) { 316 if (expr instanceof ConstantExpression) { 317 Object value = ((ConstantExpression)expr).getValue(); 318 if (value == null) { 319 throw new RuntimeException("'" + expr + "' cannot be compared."); 320 } 321 } 322 } 323 324 /** 325 * @param left 326 * @param right 327 */ 328 private static void checkEqualOperandCompatability(Expression left, Expression right) { 329 if (left instanceof ConstantExpression && right instanceof ConstantExpression) { 330 if (left instanceof BooleanExpression && !(right instanceof BooleanExpression)) { 331 throw new RuntimeException("'" + left + "' cannot be compared with '" + right + "'"); 332 } 333 } 334 } 335 336 public Object evaluate(MessageEvaluationContext message) throws JMSException { 337 Comparable<Comparable> lv = (Comparable)left.evaluate(message); 338 if (lv == null) { 339 return null; 340 } 341 Comparable rv = (Comparable)right.evaluate(message); 342 if (rv == null) { 343 return null; 344 } 345 return compare(lv, rv); 346 } 347 348 protected Boolean compare(Comparable lv, Comparable rv) { 349 Class<? extends Comparable> lc = lv.getClass(); 350 Class<? extends Comparable> rc = rv.getClass(); 351 // If the the objects are not of the same type, 352 // try to convert up to allow the comparison. 353 if (lc != rc) { 354 if (lc == Byte.class) { 355 if (rc == Short.class) { 356 lv = Short.valueOf(((Number)lv).shortValue()); 357 } else if (rc == Integer.class) { 358 lv = Integer.valueOf(((Number)lv).intValue()); 359 } else if (rc == Long.class) { 360 lv = Long.valueOf(((Number)lv).longValue()); 361 } else if (rc == Float.class) { 362 lv = new Float(((Number)lv).floatValue()); 363 } else if (rc == Double.class) { 364 lv = new Double(((Number)lv).doubleValue()); 365 } else { 366 return Boolean.FALSE; 367 } 368 } else if (lc == Short.class) { 369 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 { 378 return Boolean.FALSE; 379 } 380 } else if (lc == Integer.class) { 381 if (rc == Long.class) { 382 lv = Long.valueOf(((Number)lv).longValue()); 383 } else if (rc == Float.class) { 384 lv = new Float(((Number)lv).floatValue()); 385 } else if (rc == Double.class) { 386 lv = new Double(((Number)lv).doubleValue()); 387 } else { 388 return Boolean.FALSE; 389 } 390 } else if (lc == Long.class) { 391 if (rc == Integer.class) { 392 rv = Long.valueOf(((Number)rv).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 { 398 return Boolean.FALSE; 399 } 400 } else if (lc == Float.class) { 401 if (rc == Integer.class) { 402 rv = new Float(((Number)rv).floatValue()); 403 } else if (rc == Long.class) { 404 rv = new Float(((Number)rv).floatValue()); 405 } else if (rc == Double.class) { 406 lv = new Double(((Number)lv).doubleValue()); 407 } else { 408 return Boolean.FALSE; 409 } 410 } else if (lc == Double.class) { 411 if (rc == Integer.class) { 412 rv = new Double(((Number)rv).doubleValue()); 413 } else if (rc == Long.class) { 414 rv = new Double(((Number)rv).doubleValue()); 415 } else if (rc == Float.class) { 416 rv = new Float(((Number)rv).doubleValue()); 417 } else { 418 return Boolean.FALSE; 419 } 420 } else { 421 return Boolean.FALSE; 422 } 423 } 424 return asBoolean(lv.compareTo(rv)) ? Boolean.TRUE : Boolean.FALSE; 425 } 426 427 protected abstract boolean asBoolean(int answer); 428 429 public boolean matches(MessageEvaluationContext message) throws JMSException { 430 Object object = evaluate(message); 431 return object != null && object == Boolean.TRUE; 432 } 433 434 }