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