001/** 002 * GRANITE DATA SERVICES 003 * Copyright (C) 2006-2013 GRANITE DATA SERVICES S.A.S. 004 * 005 * This file is part of the Granite Data Services Platform. 006 * 007 * Granite Data Services is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * Granite Data Services is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 015 * General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this library; if not, write to the Free Software 019 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 020 * USA, or see <http://www.gnu.org/licenses/>. 021 */ 022package org.granite.gravity.selector; 023 024import java.util.HashSet; 025import java.util.List; 026import java.util.regex.Pattern; 027 028import javax.jms.JMSException; 029 030/** 031 * A filter performing a comparison of two objects 032 * 033 * @version $Revision: 1.2 $ 034 */ 035public abstract class ComparisonExpression extends BinaryExpression implements BooleanExpression { 036 037 public static BooleanExpression createBetween(Expression value, Expression left, Expression right) { 038 return LogicExpression.createAND(createGreaterThanEqual(value, left), createLessThanEqual(value, right)); 039 } 040 041 public static BooleanExpression createNotBetween(Expression value, Expression left, Expression right) { 042 return LogicExpression.createOR(createLessThan(value, left), createGreaterThan(value, right)); 043 } 044 045 static final private HashSet<Character> REGEXP_CONTROL_CHARS = new HashSet<Character>(); 046 047 static { 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 REGEXP_CONTROL_CHARS.add(new Character('<')); 065 REGEXP_CONTROL_CHARS.add(new Character('>')); 066 REGEXP_CONTROL_CHARS.add(new Character('=')); 067 REGEXP_CONTROL_CHARS.add(new Character('!')); 068 } 069 070 static class LikeExpression extends UnaryExpression implements BooleanExpression { 071 072 Pattern likePattern; 073 074 /** 075 * @param left 076 */ 077 public LikeExpression(Expression right, String like, int escape) { 078 super(right); 079 080 StringBuffer regexp = new StringBuffer(like.length() * 2); 081 regexp.append("\\A"); // The beginning of the input 082 for (int i = 0; i < like.length(); i++) { 083 char c = like.charAt(i); 084 if (escape == (0xFFFF & c)) { 085 i++; 086 if (i >= like.length()) { 087 // nothing left to escape... 088 break; 089 } 090 091 char t = like.charAt(i); 092 regexp.append("\\x"); 093 regexp.append(Integer.toHexString(0xFFFF & t)); 094 } 095 else if (c == '%') { 096 regexp.append(".*?"); // Do a non-greedy match 097 } 098 else if (c == '_') { 099 regexp.append("."); // match one 100 } 101 else if (REGEXP_CONTROL_CHARS.contains(new Character(c))) { 102 regexp.append("\\x"); 103 regexp.append(Integer.toHexString(0xFFFF & c)); 104 } 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 @Override 118 public String getExpressionSymbol() { 119 return "LIKE"; 120 } 121 122 /** 123 * @see org.apache.activemq.filter.Expression#evaluate(MessageEvaluationContext) 124 */ 125 public Object evaluate(MessageEvaluationContext message) throws JMSException { 126 127 Object rv = this.getRight().evaluate(message); 128 129 if (rv == null) { 130 return null; 131 } 132 133 if (!(rv instanceof String)) { 134 return Boolean.FALSE; 135 //throw new RuntimeException("LIKE can only operate on String 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 return UnaryExpression.createInExpression((PropertyExpression)left, elements, false); 168 169 } 170 171 public static BooleanExpression createNotInFilter(Expression left, List<?> elements) { 172 173 if( !(left instanceof PropertyExpression) ) 174 throw new RuntimeException("Expected a property for In expression, got: "+left); 175 return UnaryExpression.createInExpression((PropertyExpression)left, elements, true); 176 177 } 178 179 public static BooleanExpression createIsNull(Expression left) { 180 return doCreateEqual(left, ConstantExpression.NULL); 181 } 182 183 public static BooleanExpression createIsNotNull(Expression left) { 184 return UnaryExpression.createNOT(doCreateEqual(left, ConstantExpression.NULL)); 185 } 186 187 public static BooleanExpression createNotEqual(Expression left, Expression right) { 188 return UnaryExpression.createNOT(createEqual(left, right)); 189 } 190 191 public static BooleanExpression createEqual(Expression left, Expression right) { 192 checkEqualOperand(left); 193 checkEqualOperand(right); 194 checkEqualOperandCompatability(left, right); 195 return doCreateEqual(left, right); 196 } 197 198 private static BooleanExpression doCreateEqual(Expression left, Expression right) { 199 return new ComparisonExpression(left, right) { 200 @Override 201 public Object evaluate(MessageEvaluationContext message) throws JMSException { 202 Object lv = left.evaluate(message); 203 Object rv = right.evaluate(message); 204 205 // Iff one of the values is null 206 if (lv == null ^ rv == null) { 207 return Boolean.FALSE; 208 } 209 if (lv == rv || (lv != null && lv.equals(rv))) { 210 return Boolean.TRUE; 211 } 212 if( lv instanceof Comparable<?> && rv instanceof Comparable<?> ) { 213 return compare((Comparable<?>)lv, (Comparable<?>)rv); 214 } 215 return Boolean.FALSE; 216 } 217 218 @Override 219 protected boolean asBoolean(int answer) { 220 return answer == 0; 221 } 222 223 @Override 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 @Override 235 protected boolean asBoolean(int answer) { 236 return answer > 0; 237 } 238 239 @Override 240 public String getExpressionSymbol() { 241 return ">"; 242 } 243 }; 244 } 245 246 public static BooleanExpression createGreaterThanEqual(final Expression left, final Expression right) { 247 checkLessThanOperand(left); 248 checkLessThanOperand(right); 249 return new ComparisonExpression(left, right) { 250 @Override 251 protected boolean asBoolean(int answer) { 252 return answer >= 0; 253 } 254 255 @Override 256 public String getExpressionSymbol() { 257 return ">="; 258 } 259 }; 260 } 261 262 public static BooleanExpression createLessThan(final Expression left, final Expression right) { 263 checkLessThanOperand(left); 264 checkLessThanOperand(right); 265 return new ComparisonExpression(left, right) { 266 @Override 267 protected boolean asBoolean(int answer) { 268 return answer < 0; 269 } 270 271 @Override 272 public String getExpressionSymbol() { 273 return "<"; 274 } 275 276 }; 277 } 278 279 public static BooleanExpression createLessThanEqual(final Expression left, final Expression right) { 280 checkLessThanOperand(left); 281 checkLessThanOperand(right); 282 return new ComparisonExpression(left, right) { 283 @Override 284 protected boolean asBoolean(int answer) { 285 return answer <= 0; 286 } 287 288 @Override 289 public String getExpressionSymbol() { 290 return "<="; 291 } 292 }; 293 } 294 295 /** 296 * Only Numeric expressions can be used in >, >=, < or <= expressions.s 297 * 298 * @param expr 299 */ 300 public static void checkLessThanOperand(Expression expr ) { 301 if( expr instanceof ConstantExpression ) { 302 Object value = ((ConstantExpression)expr).getValue(); 303 if( value instanceof Number ) 304 return; 305 306 // Else it's boolean or a String.. 307 throw new RuntimeException("Value '"+expr+"' cannot be compared."); 308 } 309 if( expr instanceof BooleanExpression ) { 310 throw new RuntimeException("Value '"+expr+"' cannot be compared."); 311 } 312 } 313 314 /** 315 * Validates that the expression can be used in == or <> expression. 316 * Cannot not be NULL TRUE or FALSE litterals. 317 * 318 * @param expr 319 */ 320 public static void checkEqualOperand(Expression expr ) { 321 if( expr instanceof ConstantExpression ) { 322 Object value = ((ConstantExpression)expr).getValue(); 323 if( value == null ) 324 throw new RuntimeException("'"+expr+"' cannot be compared."); 325 } 326 } 327 328 /** 329 * 330 * @param left 331 * @param right 332 */ 333 private static void checkEqualOperandCompatability(Expression left, Expression right) { 334 if( left instanceof ConstantExpression && right instanceof ConstantExpression ) { 335 if( left instanceof BooleanExpression && !(right instanceof BooleanExpression) ) 336 throw new RuntimeException("'"+left+"' cannot be compared with '"+right+"'"); 337 } 338 } 339 340 341 342 /** 343 * @param left 344 * @param right 345 */ 346 public ComparisonExpression(Expression left, Expression right) { 347 super(left, right); 348 } 349 350 public Object evaluate(MessageEvaluationContext message) throws JMSException { 351 Comparable<?> lv = (Comparable<?>)left.evaluate(message); 352 if (lv == null) { 353 return null; 354 } 355 Comparable<?> rv = (Comparable<?>)right.evaluate(message); 356 if (rv == null) { 357 return null; 358 } 359 return compare(lv, rv); 360 } 361 362 @SuppressWarnings({ "unchecked", "rawtypes", "boxing" }) 363 protected Boolean compare(Comparable lv, Comparable rv) { 364 Class<?> lc = lv.getClass(); 365 Class<?> rc = rv.getClass(); 366 // If the the objects are not of the same type, 367 // try to convert up to allow the comparison. 368 if (lc != rc) { 369 if (lc == Byte.class) { 370 if (rc == Short.class) { 371 return ((Number)lv).shortValue() == ((Short)rv).shortValue(); 372 } 373 else if (rc == Integer.class) { 374 return ((Number)lv).intValue() == ((Integer)rv).intValue(); 375 } 376 else if (rc == Long.class) { 377 return ((Number)lv).longValue() == ((Long)rv).longValue(); 378 } 379 else if (rc == Float.class) { 380 return ((Number)lv).floatValue() == ((Float)rv).floatValue(); 381 } 382 else if (rc == Double.class) { 383 return ((Double)lv).doubleValue() == ((Double)rv).doubleValue(); 384 } 385 else { 386 return Boolean.FALSE; 387 } 388 } else if (lc == Short.class) { 389 if (rc == Integer.class) { 390 lv = new Integer(((Number) lv).intValue()); 391 } 392 else if (rc == Long.class) { 393 lv = new Long(((Number) lv).longValue()); 394 } 395 else if (rc == Float.class) { 396 lv = new Float(((Number) lv).floatValue()); 397 } 398 else if (rc == Double.class) { 399 lv = new Double(((Number) lv).doubleValue()); 400 } 401 else { 402 return Boolean.FALSE; 403 } 404 } else if (lc == Integer.class) { 405 if (rc == Long.class) { 406 lv = new Long(((Number)lv).longValue()); 407 } 408 else if (rc == Float.class) { 409 lv = new Float(((Number)lv).floatValue()); 410 } 411 else if (rc == Double.class) { 412 lv = new Double(((Number)lv).doubleValue()); 413 } 414 else { 415 return Boolean.FALSE; 416 } 417 } 418 else if (lc == Long.class) { 419 if (rc == Integer.class) { 420 rv = new Long(((Number)rv).longValue()); 421 } 422 else if (rc == Float.class) { 423 lv = new Float(((Number)lv).floatValue()); 424 } 425 else if (rc == Double.class) { 426 lv = new Double(((Number)lv).doubleValue()); 427 } 428 else { 429 return Boolean.FALSE; 430 } 431 } 432 else if (lc == Float.class) { 433 if (rc == Integer.class) { 434 rv = new Float(((Number)rv).floatValue()); 435 } 436 else if (rc == Long.class) { 437 rv = new Float(((Number)rv).floatValue()); 438 } 439 else if (rc == Double.class) { 440 lv = new Double(((Number)lv).doubleValue()); 441 } 442 else { 443 return Boolean.FALSE; 444 } 445 } 446 else if (lc == Double.class) { 447 if (rc == Integer.class) { 448 rv = new Double(((Number)rv).doubleValue()); 449 } 450 else if (rc == Long.class) { 451 rv = new Double(((Number)rv).doubleValue()); 452 } 453 else if (rc == Float.class) { 454 rv = new Float(((Number)rv).doubleValue()); 455 } 456 else { 457 return Boolean.FALSE; 458 } 459 } 460 else 461 return Boolean.FALSE; 462 } 463 464 return asBoolean(lv.compareTo(rv)) ? Boolean.TRUE : Boolean.FALSE; 465 } 466 467 protected abstract boolean asBoolean(int answer); 468 469 public boolean matches(MessageEvaluationContext message) throws JMSException { 470 Object object = evaluate(message); 471 return object != null && object == Boolean.TRUE; 472 } 473 474}