/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.activemq.artemis.selector.filter; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.regex.Pattern; /** * A filter performing a comparison of two objects * * @version $Revision: 1.2 $ */ public abstract class ComparisonExpression extends BinaryExpression implements BooleanExpression { public static final ThreadLocal<Boolean> CONVERT_STRING_EXPRESSIONS = new ThreadLocal<>(); boolean convertStringExpressions = false; private static final Set<Character> REGEXP_CONTROL_CHARS = new HashSet<>(); /** * @param left * @param right */ public ComparisonExpression(Expression left, Expression right) { super(left, right); convertStringExpressions = CONVERT_STRING_EXPRESSIONS.get() != null; } public static BooleanExpression createBetween(Expression value, Expression left, Expression right) { return LogicExpression.createAND(createGreaterThanEqual(value, left), createLessThanEqual(value, right)); } public static BooleanExpression createNotBetween(Expression value, Expression left, Expression right) { return LogicExpression.createOR(createLessThan(value, left), createGreaterThan(value, right)); } static { REGEXP_CONTROL_CHARS.add(Character.valueOf('.')); REGEXP_CONTROL_CHARS.add(Character.valueOf('\\')); REGEXP_CONTROL_CHARS.add(Character.valueOf('[')); REGEXP_CONTROL_CHARS.add(Character.valueOf(']')); REGEXP_CONTROL_CHARS.add(Character.valueOf('^')); REGEXP_CONTROL_CHARS.add(Character.valueOf('$')); REGEXP_CONTROL_CHARS.add(Character.valueOf('?')); REGEXP_CONTROL_CHARS.add(Character.valueOf('*')); REGEXP_CONTROL_CHARS.add(Character.valueOf('+')); REGEXP_CONTROL_CHARS.add(Character.valueOf('{')); REGEXP_CONTROL_CHARS.add(Character.valueOf('}')); REGEXP_CONTROL_CHARS.add(Character.valueOf('|')); REGEXP_CONTROL_CHARS.add(Character.valueOf('(')); REGEXP_CONTROL_CHARS.add(Character.valueOf(')')); REGEXP_CONTROL_CHARS.add(Character.valueOf(':')); REGEXP_CONTROL_CHARS.add(Character.valueOf('&')); REGEXP_CONTROL_CHARS.add(Character.valueOf('<')); REGEXP_CONTROL_CHARS.add(Character.valueOf('>')); REGEXP_CONTROL_CHARS.add(Character.valueOf('=')); REGEXP_CONTROL_CHARS.add(Character.valueOf('!')); } static class LikeExpression extends UnaryExpression implements BooleanExpression { Pattern likePattern; /** */ LikeExpression(Expression right, String like, int escape) { super(right); StringBuffer regexp = new StringBuffer(like.length() * 2); regexp.append("\\A"); // The beginning of the input for (int i = 0; i < like.length(); i++) { char c = like.charAt(i); if (escape == (0xFFFF & c) && shouldEscapeNext(like, i, c)) { i++; char t = like.charAt(i); regexp.append("\\x"); regexp.append(Integer.toHexString(0xFFFF & t)); } else { append(regexp, c); } } regexp.append("\\z"); // The end of the input likePattern = Pattern.compile(regexp.toString(), Pattern.DOTALL); } private boolean shouldEscapeNext(String selector, int i, char escape) { int next = i + 1; if (next < selector.length()) { final char c = selector.charAt(next); return (c == '_' || c == '%' || c == escape); } return false; } private void append(StringBuffer regexp, char c) { if (c == '%') { regexp.append(".*?"); // Do a non-greedy match } else if (c == '_') { regexp.append("."); // match one } else if (REGEXP_CONTROL_CHARS.contains(new Character(c))) { regexp.append("\\x"); regexp.append(Integer.toHexString(0xFFFF & c)); } else { regexp.append(c); } } /** * @see org.apache.activemq.filter.UnaryExpression#getExpressionSymbol() */ @Override public String getExpressionSymbol() { return "LIKE"; } /** * @see org.apache.activemq.filter.Expression#evaluate(Filterable) */ @Override public Object evaluate(Filterable message) throws FilterException { Object rv = this.getRight().evaluate(message); if (rv == null) { return null; } if (!(rv instanceof String)) { return Boolean.FALSE; // throw new RuntimeException("LIKE can only operate on String // identifiers. LIKE attempted on: '" + rv.getClass()); } return likePattern.matcher((String) rv).matches() ? Boolean.TRUE : Boolean.FALSE; } @Override public boolean matches(Filterable message) throws FilterException { Object object = evaluate(message); return object == Boolean.TRUE; } } public static BooleanExpression createLike(Expression left, String right, String escape) { if (escape != null && escape.length() != 1) { throw new RuntimeException("The ESCAPE string literal is invalid. It can only be one character. Literal used: " + escape); } int c = -1; if (escape != null) { c = 0xFFFF & escape.charAt(0); } return new LikeExpression(left, right, c); } public static BooleanExpression createNotLike(Expression left, String right, String escape) { return UnaryExpression.createNOT(createLike(left, right, escape)); } public static BooleanExpression createInFilter(Expression left, List<Object> elements) { if (!(left instanceof PropertyExpression)) { throw new RuntimeException("Expected a property for In expression, got: " + left); } return UnaryExpression.createInExpression((PropertyExpression) left, elements, false); } public static BooleanExpression createNotInFilter(Expression left, List<Object> elements) { if (!(left instanceof PropertyExpression)) { throw new RuntimeException("Expected a property for In expression, got: " + left); } return UnaryExpression.createInExpression((PropertyExpression) left, elements, true); } public static BooleanExpression createIsNull(Expression left) { return doCreateEqual(left, ConstantExpression.NULL); } public static BooleanExpression createIsNotNull(Expression left) { return UnaryExpression.createNOT(doCreateEqual(left, ConstantExpression.NULL)); } public static BooleanExpression createNotEqual(Expression left, Expression right) { return UnaryExpression.createNOT(createEqual(left, right)); } public static BooleanExpression createEqual(Expression left, Expression right) { checkEqualOperand(left); checkEqualOperand(right); checkEqualOperandCompatibility(left, right); return doCreateEqual(left, right); } private static BooleanExpression doCreateEqual(Expression left, Expression right) { return new ComparisonExpression(left, right) { @Override public Object evaluate(Filterable message) throws FilterException { Object lv = left.evaluate(message); Object rv = right.evaluate(message); // Iff one of the values is null if (lv == null ^ rv == null) { return Boolean.FALSE; } if (lv == rv || lv.equals(rv)) { return Boolean.TRUE; } if (lv instanceof Comparable && rv instanceof Comparable) { return compare((Comparable) lv, (Comparable) rv); } return Boolean.FALSE; } @Override protected boolean asBoolean(int answer) { return answer == 0; } @Override public String getExpressionSymbol() { return "="; } }; } public static BooleanExpression createGreaterThan(final Expression left, final Expression right) { checkLessThanOperand(left); checkLessThanOperand(right); return new ComparisonExpression(left, right) { @Override protected boolean asBoolean(int answer) { return answer > 0; } @Override public String getExpressionSymbol() { return ">"; } }; } public static BooleanExpression createGreaterThanEqual(final Expression left, final Expression right) { checkLessThanOperand(left); checkLessThanOperand(right); return new ComparisonExpression(left, right) { @Override protected boolean asBoolean(int answer) { return answer >= 0; } @Override public String getExpressionSymbol() { return ">="; } }; } public static BooleanExpression createLessThan(final Expression left, final Expression right) { checkLessThanOperand(left); checkLessThanOperand(right); return new ComparisonExpression(left, right) { @Override protected boolean asBoolean(int answer) { return answer < 0; } @Override public String getExpressionSymbol() { return "<"; } }; } public static BooleanExpression createLessThanEqual(final Expression left, final Expression right) { checkLessThanOperand(left); checkLessThanOperand(right); return new ComparisonExpression(left, right) { @Override protected boolean asBoolean(int answer) { return answer <= 0; } @Override public String getExpressionSymbol() { return "<="; } }; } /** * Only Numeric expressions can be used in {@code >}, {@code >=}, {@code <} or {@code <=} expressions. * * @param expr */ public static void checkLessThanOperand(Expression expr) { if (expr instanceof ConstantExpression) { Object value = ((ConstantExpression) expr).getValue(); if (value instanceof Number) { return; } // Else it's boolean or a String.. throw new RuntimeException("Value '" + expr + "' cannot be compared."); } if (expr instanceof BooleanExpression) { throw new RuntimeException("Value '" + expr + "' cannot be compared."); } } /** * Validates that the expression can be used in {@code ==} or {@code <>} expression. Cannot * not be NULL TRUE or FALSE literals. * * @param expr */ public static void checkEqualOperand(Expression expr) { if (expr instanceof ConstantExpression) { Object value = ((ConstantExpression) expr).getValue(); if (value == null) { throw new RuntimeException("'" + expr + "' cannot be compared."); } } } /** * @param left * @param right */ private static void checkEqualOperandCompatibility(Expression left, Expression right) { if (left instanceof ConstantExpression && right instanceof ConstantExpression) { if (left instanceof BooleanExpression && !(right instanceof BooleanExpression)) { throw new RuntimeException("'" + left + "' cannot be compared with '" + right + "'"); } } } @Override public Object evaluate(Filterable message) throws FilterException { Comparable<Comparable> lv = (Comparable) left.evaluate(message); if (lv == null) { return null; } Comparable rv = (Comparable) right.evaluate(message); if (rv == null) { return null; } return compare(lv, rv); } protected Boolean compare(Comparable lv, Comparable rv) { Class<? extends Comparable> lc = lv.getClass(); Class<? extends Comparable> rc = rv.getClass(); // If the the objects are not of the same type, // try to convert up to allow the comparison. if (lc != rc) { try { if (lc == Boolean.class) { if (convertStringExpressions && rc == String.class) { rv = Boolean.valueOf((String) rv); } else { return Boolean.FALSE; } } else if (lc == Byte.class) { if (rc == Short.class) { lv = ((Number) lv).shortValue(); } else if (rc == Integer.class) { lv = ((Number) lv).intValue(); } else if (rc == Long.class) { lv = ((Number) lv).longValue(); } else if (rc == Float.class) { lv = ((Number) lv).floatValue(); } else if (rc == Double.class) { lv = ((Number) lv).doubleValue(); } else if (convertStringExpressions && rc == String.class) { rv = Byte.valueOf((String) rv); } else { return Boolean.FALSE; } } else if (lc == Short.class) { if (rc == Integer.class) { lv = ((Number) lv).intValue(); } else if (rc == Long.class) { lv = ((Number) lv).longValue(); } else if (rc == Float.class) { lv = ((Number) lv).floatValue(); } else if (rc == Double.class) { lv = ((Number) lv).doubleValue(); } else if (convertStringExpressions && rc == String.class) { rv = Short.valueOf((String) rv); } else { return Boolean.FALSE; } } else if (lc == Integer.class) { if (rc == Long.class) { lv = ((Number) lv).longValue(); } else if (rc == Float.class) { lv = ((Number) lv).floatValue(); } else if (rc == Double.class) { lv = ((Number) lv).doubleValue(); } else if (convertStringExpressions && rc == String.class) { rv = Integer.valueOf((String) rv); } else { return Boolean.FALSE; } } else if (lc == Long.class) { if (rc == Integer.class) { rv = ((Number) rv).longValue(); } else if (rc == Float.class) { lv = ((Number) lv).floatValue(); } else if (rc == Double.class) { lv = ((Number) lv).doubleValue(); } else if (convertStringExpressions && rc == String.class) { rv = Long.valueOf((String) rv); } else { return Boolean.FALSE; } } else if (lc == Float.class) { if (rc == Integer.class || rc == Long.class) { rv = ((Number) rv).floatValue(); } else if (rc == Double.class) { lv = ((Number) lv).doubleValue(); } else if (convertStringExpressions && rc == String.class) { rv = Float.valueOf((String) rv); } else { return Boolean.FALSE; } } else if (lc == Double.class) { if (rc == Integer.class || rc == Long.class || rc == Float.class) { rv = ((Number) rv).doubleValue(); } else if (convertStringExpressions && rc == String.class) { rv = Double.valueOf((String) rv); } else { return Boolean.FALSE; } } else if (convertStringExpressions && lc == String.class) { if (rc == Boolean.class) { lv = Boolean.valueOf((String) lv); } else if (rc == Byte.class) { lv = Byte.valueOf((String) lv); } else if (rc == Short.class) { lv = Short.valueOf((String) lv); } else if (rc == Integer.class) { lv = Integer.valueOf((String) lv); } else if (rc == Long.class) { lv = Long.valueOf((String) lv); } else if (rc == Float.class) { lv = Float.valueOf((String) lv); } else if (rc == Double.class) { lv = Double.valueOf((String) lv); } else { return Boolean.FALSE; } } else { return Boolean.FALSE; } } catch (NumberFormatException e) { return Boolean.FALSE; } } return asBoolean(lv.compareTo(rv)) ? Boolean.TRUE : Boolean.FALSE; } protected abstract boolean asBoolean(int answer); @Override public boolean matches(Filterable message) throws FilterException { Object object = evaluate(message); return object == Boolean.TRUE; } }