/***************************************************************** * 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.cayenne.exp; import org.apache.cayenne.Persistent; import org.apache.cayenne.exp.parser.ASTAdd; import org.apache.cayenne.exp.parser.ASTAnd; import org.apache.cayenne.exp.parser.ASTBetween; import org.apache.cayenne.exp.parser.ASTBitwiseAnd; import org.apache.cayenne.exp.parser.ASTBitwiseLeftShift; import org.apache.cayenne.exp.parser.ASTBitwiseNot; import org.apache.cayenne.exp.parser.ASTBitwiseOr; import org.apache.cayenne.exp.parser.ASTBitwiseRightShift; import org.apache.cayenne.exp.parser.ASTBitwiseXor; import org.apache.cayenne.exp.parser.ASTDbPath; import org.apache.cayenne.exp.parser.ASTDivide; import org.apache.cayenne.exp.parser.ASTEqual; import org.apache.cayenne.exp.parser.ASTFalse; import org.apache.cayenne.exp.parser.ASTFullObject; import org.apache.cayenne.exp.parser.ASTGreater; import org.apache.cayenne.exp.parser.ASTGreaterOrEqual; import org.apache.cayenne.exp.parser.ASTIn; import org.apache.cayenne.exp.parser.ASTLess; import org.apache.cayenne.exp.parser.ASTLessOrEqual; import org.apache.cayenne.exp.parser.ASTLike; import org.apache.cayenne.exp.parser.ASTLikeIgnoreCase; import org.apache.cayenne.exp.parser.ASTList; import org.apache.cayenne.exp.parser.ASTMultiply; import org.apache.cayenne.exp.parser.ASTNegate; import org.apache.cayenne.exp.parser.ASTNot; import org.apache.cayenne.exp.parser.ASTNotBetween; import org.apache.cayenne.exp.parser.ASTNotEqual; import org.apache.cayenne.exp.parser.ASTNotIn; import org.apache.cayenne.exp.parser.ASTNotLike; import org.apache.cayenne.exp.parser.ASTNotLikeIgnoreCase; import org.apache.cayenne.exp.parser.ASTObjPath; import org.apache.cayenne.exp.parser.ASTOr; import org.apache.cayenne.exp.parser.ASTPath; import org.apache.cayenne.exp.parser.ASTScalar; import org.apache.cayenne.exp.parser.ASTSubtract; import org.apache.cayenne.exp.parser.ASTTrue; import org.apache.cayenne.exp.parser.ExpressionParser; import org.apache.cayenne.exp.parser.ExpressionParserTokenManager; import org.apache.cayenne.exp.parser.JavaCharStream; import org.apache.cayenne.exp.parser.SimpleNode; import org.apache.cayenne.map.Entity; import java.io.Reader; import java.io.StringReader; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; /** * Helper class to build expressions. */ public class ExpressionFactory { /** * A "split" character, "|", that is understood by some of the * ExpressionFactory methods that require splitting joins in the middle of * the path. * * @since 3.0 */ public static final char SPLIT_SEPARATOR = '|'; private static Class<?>[] typeLookup; private static volatile int autoAliasId; private static final int PARSE_BUFFER_MAX_SIZE = 4096; static { // make sure all types are small integers, then we can use // them as indexes in lookup array int[] allTypes = new int[] { Expression.AND, Expression.OR, Expression.NOT, Expression.EQUAL_TO, Expression.NOT_EQUAL_TO, Expression.LESS_THAN, Expression.GREATER_THAN, Expression.LESS_THAN_EQUAL_TO, Expression.GREATER_THAN_EQUAL_TO, Expression.BETWEEN, Expression.IN, Expression.LIKE, Expression.LIKE_IGNORE_CASE, Expression.ADD, Expression.SUBTRACT, Expression.MULTIPLY, Expression.DIVIDE, Expression.NEGATIVE, Expression.OBJ_PATH, Expression.DB_PATH, Expression.LIST, Expression.NOT_BETWEEN, Expression.NOT_IN, Expression.NOT_LIKE, Expression.NOT_LIKE_IGNORE_CASE, Expression.TRUE, Expression.FALSE, Expression.BITWISE_NOT, Expression.BITWISE_AND, Expression.BITWISE_OR, Expression.BITWISE_XOR, Expression.BITWISE_LEFT_SHIFT, Expression.BITWISE_RIGHT_SHIFT }; int max = 0; int min = 0; int allLen = allTypes.length; for (int i = 0; i < allLen; i++) { if (allTypes[i] > max) max = allTypes[i]; else if (allTypes[i] < min) min = allTypes[i]; } // sanity check.... if (max > 500) throw new RuntimeException("Types values are too big: " + max); if (min < 0) throw new RuntimeException("Types values are too small: " + min); // now we know that if types are used as indexes, // they will fit in array "max + 1" long (though gaps are possible) typeLookup = new Class[max + 1]; typeLookup[Expression.AND] = ASTAnd.class; typeLookup[Expression.OR] = ASTOr.class; typeLookup[Expression.BETWEEN] = ASTBetween.class; typeLookup[Expression.NOT_BETWEEN] = ASTNotBetween.class; // binary types typeLookup[Expression.EQUAL_TO] = ASTEqual.class; typeLookup[Expression.NOT_EQUAL_TO] = ASTNotEqual.class; typeLookup[Expression.LESS_THAN] = ASTLess.class; typeLookup[Expression.GREATER_THAN] = ASTGreater.class; typeLookup[Expression.LESS_THAN_EQUAL_TO] = ASTLessOrEqual.class; typeLookup[Expression.GREATER_THAN_EQUAL_TO] = ASTGreaterOrEqual.class; typeLookup[Expression.IN] = ASTIn.class; typeLookup[Expression.NOT_IN] = ASTNotIn.class; typeLookup[Expression.LIKE] = ASTLike.class; typeLookup[Expression.LIKE_IGNORE_CASE] = ASTLikeIgnoreCase.class; typeLookup[Expression.NOT_LIKE] = ASTNotLike.class; typeLookup[Expression.NOT_LIKE_IGNORE_CASE] = ASTNotLikeIgnoreCase.class; typeLookup[Expression.ADD] = ASTAdd.class; typeLookup[Expression.SUBTRACT] = ASTSubtract.class; typeLookup[Expression.MULTIPLY] = ASTMultiply.class; typeLookup[Expression.DIVIDE] = ASTDivide.class; typeLookup[Expression.NOT] = ASTNot.class; typeLookup[Expression.NEGATIVE] = ASTNegate.class; typeLookup[Expression.OBJ_PATH] = ASTObjPath.class; typeLookup[Expression.DB_PATH] = ASTDbPath.class; typeLookup[Expression.LIST] = ASTList.class; typeLookup[Expression.TRUE] = ASTTrue.class; typeLookup[Expression.FALSE] = ASTFalse.class; typeLookup[Expression.BITWISE_NOT] = ASTBitwiseNot.class; typeLookup[Expression.BITWISE_OR] = ASTBitwiseOr.class; typeLookup[Expression.BITWISE_AND] = ASTBitwiseAnd.class; typeLookup[Expression.BITWISE_XOR] = ASTBitwiseXor.class; typeLookup[Expression.BITWISE_LEFT_SHIFT] = ASTBitwiseLeftShift.class; typeLookup[Expression.BITWISE_RIGHT_SHIFT] = ASTBitwiseRightShift.class; } /** * Creates a new expression for the type requested. If type is unknown, * ExpressionException is thrown. */ public static Expression expressionOfType(int type) { if (type < 0 || type >= typeLookup.length) { throw new ExpressionException("Bad expression type: " + type); } if (typeLookup[type] == null) { throw new ExpressionException("Bad expression type: " + type); } // expected this if (SimpleNode.class.isAssignableFrom(typeLookup[type])) { try { return (Expression) typeLookup[type].newInstance(); } catch (Exception ex) { throw new ExpressionException("Error creating expression", ex); } } throw new ExpressionException("Bad expression type: " + type); } /** * Applies a few default rules for adding operands to expressions. In * particular wraps all lists into LIST expressions. Applied only in path * expressions. */ protected static Object wrapPathOperand(Object op) { if (op instanceof Collection<?>) { return new ASTList((Collection<?>) op); } else if (op instanceof Object[]) { return new ASTList((Object[]) op); } else { return op; } } /** * Creates an expression that matches any of the key-values pairs in * <code>map</code>. * <p> * For each pair <code>pairType</code> operator is used to build a binary * expression. Key is considered to be a DB_PATH expression. OR is used to * join pair binary expressions. */ public static Expression matchAnyDbExp(Map<String, ?> map, int pairType) { List<Expression> pairs = new ArrayList<>(map.size()); for (Map.Entry<String, ?> entry : map.entrySet()) { Expression exp = expressionOfType(pairType); exp.setOperand(0, new ASTDbPath(entry.getKey())); exp.setOperand(1, wrapPathOperand(entry.getValue())); pairs.add(exp); } return joinExp(Expression.OR, pairs); } /** * Creates an expression that matches all key-values pairs in * <code>map</code>. * <p> * For each pair <code>pairType</code> operator is used to build a binary * expression. Key is considered to be a DB_PATH expression. AND is used to * join pair binary expressions. */ public static Expression matchAllDbExp(Map<String, ?> map, int pairType) { List<Expression> pairs = new ArrayList<>(map.size()); for (Map.Entry<String, ?> entry : map.entrySet()) { Expression exp = expressionOfType(pairType); exp.setOperand(0, new ASTDbPath(entry.getKey())); exp.setOperand(1, wrapPathOperand(entry.getValue())); pairs.add(exp); } return joinExp(Expression.AND, pairs); } /** * Creates an expression that matches any of the key-values pairs in the * <code>map</code>. * <p> * For each pair <code>pairType</code> operator is used to build a binary * expression. Key is considered to be a OBJ_PATH expression. OR is used to * join pair binary expressions. */ public static Expression matchAnyExp(Map<String, ?> map, int pairType) { List<Expression> pairs = new ArrayList<>(map.size()); for (Map.Entry<String, ?> entry : map.entrySet()) { Expression exp = expressionOfType(pairType); exp.setOperand(0, new ASTObjPath(entry.getKey())); exp.setOperand(1, wrapPathOperand(entry.getValue())); pairs.add(exp); } return joinExp(Expression.OR, pairs); } /** * Creates an expression to match a collection of values against a single * path expression. <h3>Splits</h3> * <p> * Note that "path" argument here can use a split character (a pipe symbol - * '|') instead of dot to indicate that relationship following a path should * be split into a separate set of joins. There can only be one split at * most. Split must always precede a relationship. E.g. * "|exhibits.paintings", "exhibits|paintings", etc. * * @param path * @param values * @since 3.0 */ public static Expression matchAllExp(String path, Collection<?> values) { if (values == null) { throw new NullPointerException("Null values collection"); } if (values.size() == 0) { return new ASTTrue(); } return matchAllExp(path, values.toArray()); } /** * @since 3.0 */ public static Expression matchAllExp(String path, Object... values) { if (values == null) { throw new NullPointerException("Null values collection"); } if (values.length == 0) { return new ASTTrue(); } int split = path.indexOf(SPLIT_SEPARATOR); List<Expression> matches = new ArrayList<>(values.length); if (split >= 0 && split < path.length() - 1) { int splitEnd = path.indexOf(Entity.PATH_SEPARATOR, split + 1); String beforeSplit = split > 0 ? path.substring(0, split) + "." : ""; String afterSplit = splitEnd > 0 ? "." + path.substring(splitEnd + 1) : ""; String aliasBase = "split" + autoAliasId++ + "_"; String splitChunk = splitEnd > 0 ? path.substring(split + 1, splitEnd) : path.substring(split + 1); // fix the path - replace split with dot if it's in the middle, or // strip it if // it's in the beginning path = split == 0 ? path.substring(1) : path.replace(SPLIT_SEPARATOR, '.'); int i = 0; for (Object value : values) { String alias = aliasBase + i; String aliasedPath = beforeSplit + alias + afterSplit; i++; ASTPath pathExp = new ASTObjPath(aliasedPath); pathExp.setPathAliases(Collections.singletonMap(alias, splitChunk)); matches.add(new ASTEqual(pathExp, value)); } } else { for (Object value : values) { matches.add(new ASTEqual(new ASTObjPath(path), value)); } } return joinExp(Expression.AND, matches); } /** * Creates an expression that matches all key-values pairs in * <code>map</code>. * <p> * For each pair <code>pairType</code> operator is used to build a binary * expression. Key is considered to be a OBJ_PATH expression. AND is used to * join pair binary expressions. */ public static Expression matchAllExp(Map<String, ?> map, int pairType) { List<Expression> pairs = new ArrayList<>(map.size()); for (Map.Entry<String, ?> entry : map.entrySet()) { Expression exp = expressionOfType(pairType); exp.setOperand(0, new ASTObjPath(entry.getKey())); exp.setOperand(1, wrapPathOperand(entry.getValue())); pairs.add(exp); } return joinExp(Expression.AND, pairs); } /** * A convenience method to create an DB_PATH "equal to" expression. */ public static Expression matchDbExp(String pathSpec, Object value) { return new ASTEqual(new ASTDbPath(pathSpec), value); } /** * A convenience method to create an DB_PATH "not equal to" expression. */ public static Expression noMatchDbExp(String pathSpec, Object value) { return new ASTNotEqual(new ASTDbPath(pathSpec), value); } /** * A convenience method to create an OBJ_PATH "equal to" expression. */ public static Expression matchExp(String pathSpec, Object value) { return matchExp(new ASTObjPath(pathSpec), value); } /** * @since 4.0 * @see ExpressionFactory#matchExp(String, Object) */ static Expression matchExp(Expression exp, Object value) { if(!(exp instanceof SimpleNode)) { throw new IllegalArgumentException("exp should be instance of SimpleNode"); } return new ASTEqual((SimpleNode)exp, value); } /** * A convenience method to create an OBJ_PATH "not equal to" expression. */ public static Expression noMatchExp(String pathSpec, Object value) { return noMatchExp(new ASTObjPath(pathSpec), value); } /** * @since 4.0 * @see ExpressionFactory#noMatchExp(String, Object) */ static Expression noMatchExp(Expression exp, Object value) { if(!(exp instanceof SimpleNode)) { throw new IllegalArgumentException("exp should be instance of SimpleNode"); } return new ASTNotEqual((SimpleNode)exp, value); } /** * A convenience method to create an OBJ_PATH "less than" expression. */ public static Expression lessExp(String pathSpec, Object value) { return lessExp(new ASTObjPath(pathSpec), value); } /** * @since 4.0 * @see ExpressionFactory#lessExp(String, Object) */ static Expression lessExp(Expression exp, Object value) { if(!(exp instanceof SimpleNode)) { throw new IllegalArgumentException("exp should be instance of SimpleNode"); } return new ASTLess((SimpleNode)exp, value); } /** * A convenience method to create an DB_PATH "less than" expression. * * @since 3.0 */ public static Expression lessDbExp(String pathSpec, Object value) { return new ASTLess(new ASTDbPath(pathSpec), value); } /** * A convenience method to create an OBJ_PATH "less than or equal to" * expression. */ public static Expression lessOrEqualExp(String pathSpec, Object value) { return lessOrEqualExp(new ASTObjPath(pathSpec), value); } /** * @since 4.0 * @see ExpressionFactory#lessOrEqualExp(String, Object) */ static Expression lessOrEqualExp(Expression exp, Object value) { if(!(exp instanceof SimpleNode)) { throw new IllegalArgumentException("exp should be instance of SimpleNode"); } return new ASTLessOrEqual((SimpleNode)exp, value); } /** * A convenience method to create an DB_PATH "less than or equal to" * expression. * * @since 3.0 */ public static Expression lessOrEqualDbExp(String pathSpec, Object value) { return new ASTLessOrEqual(new ASTDbPath(pathSpec), value); } /** * A convenience method to create an OBJ_PATH "greater than" expression. */ public static Expression greaterExp(String pathSpec, Object value) { return greaterExp(new ASTObjPath(pathSpec), value); } /** * @since 4.0 * @see ExpressionFactory#greaterExp(String, Object) */ static Expression greaterExp(Expression exp, Object value) { if(!(exp instanceof SimpleNode)) { throw new IllegalArgumentException("exp should be instance of SimpleNode"); } return new ASTGreater((SimpleNode)exp, value); } /** * A convenience method to create an DB_PATH "greater than" expression. * * @since 3.0 */ public static Expression greaterDbExp(String pathSpec, Object value) { return new ASTGreater(new ASTDbPath(pathSpec), value); } /** * A convenience method to create an OBJ_PATH "greater than or equal to" * expression. */ public static Expression greaterOrEqualExp(String pathSpec, Object value) { return greaterOrEqualExp(new ASTObjPath(pathSpec), value); } /** * @since 4.0 * @see ExpressionFactory#greaterOrEqualExp(String, Object) */ static Expression greaterOrEqualExp(Expression exp, Object value) { if(!(exp instanceof SimpleNode)) { throw new IllegalArgumentException("exp should be instance of SimpleNode"); } return new ASTGreaterOrEqual((SimpleNode)exp, value); } /** * A convenience method to create an DB_PATH "greater than or equal to" * expression. * * @since 3.0 */ public static Expression greaterOrEqualDbExp(String pathSpec, Object value) { return new ASTGreaterOrEqual(new ASTDbPath(pathSpec), value); } /** * A convenience shortcut for building IN expression. Return ASTFalse for * empty collection. */ public static Expression inExp(String pathSpec, Object... values) { return inExp(new ASTObjPath(pathSpec), values); } /** * @since 4.0 * @see ExpressionFactory#inExp(String, Object[]) */ static Expression inExp(Expression exp, Object... values) { if (values.length == 0) { return new ASTFalse(); } if(!(exp instanceof SimpleNode)) { throw new IllegalArgumentException("exp should be instance of SimpleNode"); } return new ASTIn((SimpleNode)exp, new ASTList(values)); } /** * A convenience shortcut for building IN DB expression. Return ASTFalse for * empty collection. */ public static Expression inDbExp(String pathSpec, Object... values) { if (values.length == 0) { return new ASTFalse(); } return new ASTIn(new ASTDbPath(pathSpec), new ASTList(values)); } /** * A convenience shortcut for building IN expression. Return ASTFalse for * empty collection. */ public static Expression inExp(String pathSpec, Collection<?> values) { return inExp(new ASTObjPath(pathSpec), values); } /** * @since 4.0 * @see ExpressionFactory#inExp(String, Collection) */ static Expression inExp(Expression exp, Collection<?> values) { if (values.isEmpty()) { return new ASTFalse(); } if(!(exp instanceof SimpleNode)) { throw new IllegalArgumentException("exp should be instance of SimpleNode"); } return new ASTIn((SimpleNode)exp, new ASTList(values)); } /** * A convenience shortcut for building IN DB expression. Return ASTFalse for * empty collection. */ public static Expression inDbExp(String pathSpec, Collection<?> values) { if (values.isEmpty()) { return new ASTFalse(); } return new ASTIn(new ASTDbPath(pathSpec), new ASTList(values)); } /** * A convenience shortcut for building NOT_IN expression. Return ASTTrue for * empty collection. */ public static Expression notInExp(String pathSpec, Collection<?> values) { return notInExp(new ASTObjPath(pathSpec), values); } /** * @since 4.0 * @see ExpressionFactory#notInExp(String, Collection) */ static Expression notInExp(Expression exp, Collection<?> values) { if (values.isEmpty()) { return new ASTFalse(); } if(!(exp instanceof SimpleNode)) { throw new IllegalArgumentException("exp should be instance of SimpleNode"); } return new ASTNotIn((SimpleNode)exp, new ASTList(values)); } /** * A convenience shortcut for building NOT_IN expression. Return ASTTrue for * empty collection. * * @since 3.0 */ public static Expression notInDbExp(String pathSpec, Collection<?> values) { if (values.isEmpty()) { return new ASTTrue(); } return new ASTNotIn(new ASTDbPath(pathSpec), new ASTList(values)); } /** * A convenience shortcut for building NOT_IN expression. Return ASTTrue for * empty collection. * * @since 1.0.6 */ public static Expression notInExp(String pathSpec, Object... values) { return notInExp(new ASTObjPath(pathSpec), values); } /** * @since 4.0 * @see ExpressionFactory#notInExp(String, Object[]) */ static Expression notInExp(Expression exp, Object... values) { if (values.length == 0) { return new ASTTrue(); } if(!(exp instanceof SimpleNode)) { throw new IllegalArgumentException("exp should be instance of SimpleNode"); } return new ASTNotIn((SimpleNode)exp, new ASTList(values)); } /** * A convenience shortcut for building NOT_IN expression. Return ASTTrue for * empty collection. * * @since 3.0 */ public static Expression notInDbExp(String pathSpec, Object... values) { if (values.length == 0) { return new ASTTrue(); } return new ASTNotIn(new ASTDbPath(pathSpec), new ASTList(values)); } /** * A convenience shortcut for building BETWEEN expressions. */ public static Expression betweenExp(String pathSpec, Object value1, Object value2) { return betweenExp(new ASTObjPath(pathSpec), value1, value2); } /** * @since 4.0 * @see ExpressionFactory#betweenExp(String, Object, Object) */ static Expression betweenExp(Expression exp, Object value1, Object value2) { if(!(exp instanceof SimpleNode)) { throw new IllegalArgumentException("exp should be instance of SimpleNode"); } return new ASTBetween((SimpleNode)exp, value1, value2); } /** * A convenience shortcut for building BETWEEN expressions. * * @since 3.0 */ public static Expression betweenDbExp(String pathSpec, Object value1, Object value2) { return new ASTBetween(new ASTDbPath(pathSpec), value1, value2); } /** * A convenience shortcut for building NOT_BETWEEN expressions. */ public static Expression notBetweenExp(String pathSpec, Object value1, Object value2) { return notBetweenExp(new ASTObjPath(pathSpec), value1, value2); } /** * @since 4.0 * @see ExpressionFactory#notBetweenExp(String, Object, Object) */ static Expression notBetweenExp(Expression exp, Object value1, Object value2) { if(!(exp instanceof SimpleNode)) { throw new IllegalArgumentException("exp should be instance of SimpleNode"); } return new ASTNotBetween((SimpleNode)exp, value1, value2); } /** * A convenience shortcut for building NOT_BETWEEN expressions. * * @since 3.0 */ public static Expression notBetweenDbExp(String pathSpec, Object value1, Object value2) { return new ASTNotBetween(new ASTDbPath(pathSpec), value1, value2); } /** * A convenience shortcut for building LIKE expression. */ public static Expression likeExp(String pathSpec, Object value) { return likeExpInternal(pathSpec, value, (char) 0); } /** * @since 4.0 * @see ExpressionFactory#likeExp(String, Object) */ static Expression likeExp(Expression exp, Object value) { return likeExpInternal(exp, value, (char) 0); } /** * <p> * A convenience shortcut for building LIKE expression. * </p> * <p> * The escape character allows for escaping meta-characters in the LIKE * clause. Note that the escape character cannot be '?'. To specify no * escape character, supply 0 as the escape character. * </p> * * @since 3.0.1 */ public static Expression likeExp(String pathSpec, Object value, char escapeChar) { return likeExpInternal(pathSpec, value, escapeChar); } /** * @since 4.0 * @see ExpressionFactory#likeExp(String, Object) */ static Expression likeExp(Expression exp, Object value, char escapeChar) { return likeExpInternal(exp, value, escapeChar); } static ASTLike likeExpInternal(String pathSpec, Object value, char escapeChar) { return likeExpInternal(new ASTObjPath(pathSpec), value, escapeChar); } static ASTLike likeExpInternal(Expression expression, Object value, char escapeChar) { if(!(expression instanceof SimpleNode)) { throw new IllegalArgumentException("exp should be instance of SimpleNode"); } return new ASTLike((SimpleNode) expression, value, escapeChar); } /** * A convenience shortcut for building LIKE DB_PATH expression. * * @since 3.0 */ public static Expression likeDbExp(String pathSpec, Object value) { return new ASTLike(new ASTDbPath(pathSpec), value); } /** * <p> * A convenience shortcut for building LIKE DB_PATH expression. * </p> * <p> * The escape character allows for escaping meta-characters in the LIKE * clause. Note that the escape character cannot be '?'. To specify no * escape character, supply 0 as the escape character. * </p> * * @since 3.0.1 */ public static Expression likeDbExp(String pathSpec, Object value, char escapeChar) { return new ASTLike(new ASTDbPath(pathSpec), value, escapeChar); } /** * A convenience shortcut for building NOT_LIKE expression. */ public static Expression notLikeExp(String pathSpec, Object value) { return notLikeExp(new ASTObjPath(pathSpec), value); } /** * @since 4.0 * @see ExpressionFactory#notLikeExp(String, Object) */ static Expression notLikeExp(Expression exp, Object value) { if(!(exp instanceof SimpleNode)) { throw new IllegalArgumentException("exp should be instance of SimpleNode"); } return new ASTNotLike((SimpleNode)exp, value); } /** * <p> * A convenience shortcut for building NOT_LIKE expression. * </p> * <p> * The escape character allows for escaping meta-characters in the LIKE * clause. Note that the escape character cannot be '?'. To specify no * escape character, supply 0 as the escape character. * </p> * * @since 3.0.1 */ public static Expression notLikeExp(String pathSpec, Object value, char escapeChar) { return notLikeExp(new ASTObjPath(pathSpec), value, escapeChar); } /** * @since 4.0 * @see ExpressionFactory#notLikeExp(String, Object) */ static Expression notLikeExp(Expression exp, Object value, char escapeChar) { if(!(exp instanceof SimpleNode)) { throw new IllegalArgumentException("exp should be instance of SimpleNode"); } return new ASTNotLike((SimpleNode)exp, value, escapeChar); } /** * A convenience shortcut for building NOT_LIKE expression. * * @since 3.0 */ public static Expression notLikeDbExp(String pathSpec, Object value) { return new ASTNotLike(new ASTDbPath(pathSpec), value); } /** * <p> * A convenience shortcut for building NOT_LIKE expression. * </p> * <p> * The escape character allows for escaping meta-characters in the LIKE * clause. Note that the escape character cannot be '?'. To specify no * escape character, supply 0 as the escape character. * </p> * * @since 3.0.1 */ public static Expression notLikeDbExp(String pathSpec, Object value, char escapeChar) { return new ASTNotLike(new ASTDbPath(pathSpec), value, escapeChar); } /** * A convenience shortcut for building LIKE_IGNORE_CASE expression. */ public static Expression likeIgnoreCaseExp(String pathSpec, Object value) { return likeIgnoreCaseExpInternal(pathSpec, value, (char) 0); } /** * @since 4.0 * @see ExpressionFactory#likeIgnoreCaseExp(String, Object) */ static Expression likeIgnoreCaseExp(Expression exp, Object value) { return likeIgnoreCaseExp(exp, value, (char) 0); } /** * <p> * A convenience shortcut for building LIKE_IGNORE_CASE expression. * </p> * <p> * The escape character allows for escaping meta-characters in the LIKE * clause. Note that the escape character cannot be '?'. To specify no * escape character, supply 0 as the escape character. * </p> * * @since 3.0.1 */ public static Expression likeIgnoreCaseExp(String pathSpec, Object value, char escapeChar) { return likeIgnoreCaseExpInternal(pathSpec, value, escapeChar); } static ASTLikeIgnoreCase likeIgnoreCaseExpInternal(String pathSpec, Object value, char escapeChar) { return likeIgnoreCaseExp(new ASTObjPath(pathSpec), value, escapeChar); } static ASTLikeIgnoreCase likeIgnoreCaseExp(Expression exp, Object value, char escapeChar) { if(!(exp instanceof SimpleNode)) { throw new IllegalArgumentException("exp should be instance of SimpleNode"); } return new ASTLikeIgnoreCase((SimpleNode) exp, value, escapeChar); } /** * A convenience shortcut for building LIKE_IGNORE_CASE expression. * * @since 3.0 */ public static Expression likeIgnoreCaseDbExp(String pathSpec, Object value) { return new ASTLikeIgnoreCase(new ASTDbPath(pathSpec), value); } /** * <p> * A convenience shortcut for building LIKE_IGNORE_CASE expression. * </p> * <p> * The escape character allows for escaping meta-characters in the LIKE * clause. Note that the escape character cannot be '?'. To specify no * escape character, supply 0 as the escape character. * </p> * * @since 3.0.1 */ public static Expression likeIgnoreCaseDbExp(String pathSpec, Object value, char escapeChar) { return new ASTLikeIgnoreCase(new ASTDbPath(pathSpec), value, escapeChar); } /** * A convenience shortcut for building NOT_LIKE_IGNORE_CASE expression. */ public static Expression notLikeIgnoreCaseExp(String pathSpec, Object value) { return notLikeIgnoreCaseExp(new ASTObjPath(pathSpec), value); } /** * @since 4.0 * @see ExpressionFactory#notLikeIgnoreCaseExp(String, Object) */ static Expression notLikeIgnoreCaseExp(Expression exp, Object value) { if(!(exp instanceof SimpleNode)) { throw new IllegalArgumentException("exp should be instance of SimpleNode"); } return new ASTNotLikeIgnoreCase((SimpleNode)exp, value); } /** * <p> * A convenience shortcut for building NOT_LIKE_IGNORE_CASE expression. * </p> * <p> * The escape character allows for escaping meta-characters in the LIKE * clause. Note that the escape character cannot be '?'. To specify no * escape character, supply 0 as the escape character. * </p> * * @since 3.0.1 */ public static Expression notLikeIgnoreCaseExp(String pathSpec, Object value, char escapeChar) { return notLikeIgnoreCaseExp(new ASTObjPath(pathSpec), value, escapeChar); } /** * @since 4.0 * @see ExpressionFactory#notLikeIgnoreCaseExp(String, Object, char) */ static Expression notLikeIgnoreCaseExp(Expression exp, Object value, char escapeChar) { if(!(exp instanceof SimpleNode)) { throw new IllegalArgumentException("exp should be instance of SimpleNode"); } return new ASTNotLikeIgnoreCase((SimpleNode)exp, value, escapeChar); } /** * A convenience shortcut for building NOT_LIKE_IGNORE_CASE expression. * * @since 3.0 */ public static Expression notLikeIgnoreCaseDbExp(String pathSpec, Object value) { return new ASTNotLikeIgnoreCase(new ASTDbPath(pathSpec), value); } /** * <p> * A convenience shortcut for building NOT_LIKE_IGNORE_CASE expression. * </p> * <p> * The escape character allows for escaping meta-characters in the LIKE * clause. Note that the escape character cannot be '?'. To specify no * escape character, supply 0 as the escape character. * </p> * * @since 3.0.1 */ public static Expression notLikeIgnoreCaseDbExp(String pathSpec, Object value, char escapeChar) { return new ASTNotLikeIgnoreCase(new ASTDbPath(pathSpec), value, escapeChar); } /** * @return An expression for a database "LIKE" query with the value * converted to a pattern matching anywhere in the String. * @since 4.0 */ public static Expression containsExp(String pathSpec, String value) { ASTLike like = likeExpInternal(pathSpec, value, (char) 0); LikeExpressionHelper.toContains(like); return like; } /** * @since 4.0 * @see ExpressionFactory#containsExp(String, String) */ static Expression containsExp(Expression exp, String value) { ASTLike like = likeExpInternal(exp, value, (char) 0); LikeExpressionHelper.toContains(like); return like; } /** * @return An expression for a database "LIKE" query with the value * converted to a pattern matching the beginning of the String. * @since 4.0 */ public static Expression startsWithExp(String pathSpec, String value) { ASTLike like = likeExpInternal(pathSpec, value, (char) 0); LikeExpressionHelper.toStartsWith(like); return like; } /** * @since 4.0 * @see ExpressionFactory#startsWithExp(String, String) */ static Expression startsWithExp(Expression exp, String value) { ASTLike like = likeExpInternal(exp, value, (char) 0); LikeExpressionHelper.toStartsWith(like); return like; } /** * @return An expression for a database "LIKE" query with the value * converted to a pattern matching the beginning of the String. * @since 4.0 */ public static Expression endsWithExp(String pathSpec, String value) { ASTLike like = likeExpInternal(pathSpec, value, (char) 0); LikeExpressionHelper.toEndsWith(like); return like; } /** * @since 4.0 * @see ExpressionFactory#endsWithExp(String, String) */ static Expression endsWithExp(Expression exp, String value) { ASTLike like = likeExpInternal(exp, value, (char) 0); LikeExpressionHelper.toEndsWith(like); return like; } /** * Same as {@link #containsExp(String, String)} only using case-insensitive * comparison. * * @since 4.0 */ public static Expression containsIgnoreCaseExp(String pathSpec, String value) { ASTLikeIgnoreCase like = likeIgnoreCaseExpInternal(pathSpec, value, (char) 0); LikeExpressionHelper.toContains(like); return like; } /** * @since 4.0 * @see ExpressionFactory#containsIgnoreCaseExp(String, String) */ static Expression containsIgnoreCaseExp(Expression exp, String value) { ASTLikeIgnoreCase like = likeIgnoreCaseExp(exp, value, (char) 0); LikeExpressionHelper.toContains(like); return like; } /** * Same as {@link #startsWithExp(String, String)} only using * case-insensitive comparison. * * @since 4.0 */ public static Expression startsWithIgnoreCaseExp(String pathSpec, String value) { ASTLikeIgnoreCase like = likeIgnoreCaseExpInternal(pathSpec, value, (char) 0); LikeExpressionHelper.toStartsWith(like); return like; } /** * @since 4.0 * @see ExpressionFactory#startsWithIgnoreCaseExp(String, String) */ static Expression startsWithIgnoreCaseExp(Expression exp, String value) { ASTLikeIgnoreCase like = likeIgnoreCaseExp(exp, value, (char) 0); LikeExpressionHelper.toStartsWith(like); return like; } /** * Same as {@link #endsWithExp(String, String)} only using case-insensitive * comparison. * * @since 4.0 */ public static Expression endsWithIgnoreCaseExp(String pathSpec, String value) { ASTLikeIgnoreCase like = likeIgnoreCaseExpInternal(pathSpec, value, (char) 0); LikeExpressionHelper.toEndsWith(like); return like; } /** * @since 4.0 * @see ExpressionFactory#endsWithIgnoreCaseExp(String, String) */ static Expression endsWithIgnoreCaseExp(Expression exp, String value) { ASTLikeIgnoreCase like = likeIgnoreCaseExp(exp, value, (char) 0); LikeExpressionHelper.toEndsWith(like); return like; } /** * @param pathSpec a String "obj:" path. * @since 4.0 * @return a new "obj:" path expression for the specified String path. */ public static Expression pathExp(String pathSpec) { return new ASTObjPath(pathSpec); } /** * @param pathSpec a String db: path. * @since 4.0 * @return a new "db:" path expression for the specified String path. */ public static Expression dbPathExp(String pathSpec) { return new ASTDbPath(pathSpec); } /** * A convenience shortcut for boolean true expression. * * @since 3.0 */ public static Expression expTrue() { return new ASTTrue(); } /** * A convenience shortcut for boolean false expression. * * @since 3.0 */ public static Expression expFalse() { return new ASTFalse(); } /** * Joins all expressions, making a single expression. <code>type</code> is * used as an expression type for expressions joining each one of the items * on the list. <code>type</code> must be binary expression type. * <p> * For example, if type is Expression.AND, resulting expression would match * all expressions in the list. If type is Expression.OR, resulting * expression would match any of the expressions. * </p> */ public static Expression joinExp(int type, Collection<Expression> expressions) { int len = expressions.size(); if (len == 0) { return null; } return join(type, expressions.toArray(new Expression[len])); } private static Expression join(int type, Expression... expressions) { int len = expressions != null ? expressions.length : 0; if (len == 0) { return null; } Expression currentExp = expressions[0]; if (len == 1) { return currentExp; } Expression exp = expressionOfType(type); for (int i = 0; i < len; i++) { exp.setOperand(i, expressions[i]); } return exp; } /** * Creates an expression that matches the primary key of object in * <code>ObjectId</code>'s <code>IdSnapshot</code> for the argument * <code>object</code>. */ public static Expression matchExp(Persistent object) { return matchAllDbExp(object.getObjectId().getIdSnapshot(), Expression.EQUAL_TO); } /** * Creates an expression that matches any of the objects contained in the * list <code>objects</code> */ public static Expression matchAnyExp(List<? extends Persistent> objects) { if (objects == null || objects.size() == 0) { return expFalse(); } return matchAnyExp(objects.toArray(new Persistent[objects.size()])); } /** * Creates an expression that matches any of the objects contained in the * <code>objects</code> array */ public static Expression matchAnyExp(Persistent... objects) { if (objects == null || objects.length == 0) { return expFalse(); } List<Expression> pairs = new ArrayList<>(objects.length); for (Persistent object : objects) { pairs.add(matchExp(object)); } return joinExp(Expression.OR, pairs); } public static Expression fullObjectExp() { return new ASTFullObject(); } public static Expression fullObjectExp(Expression exp) { return new ASTFullObject(exp); } /** * @since 4.0 */ public static Expression and(Collection<Expression> expressions) { return joinExp(Expression.AND, expressions); } /** * @since 4.0 */ public static Expression and(Expression... expressions) { return join(Expression.AND, expressions); } /** * @since 4.0 */ public static Expression or(Collection<Expression> expressions) { return joinExp(Expression.OR, expressions); } /** * @since 4.0 */ public static Expression or(Expression... expressions) { return join(Expression.OR, expressions); } /** * Parses string, converting it to Expression and optionally binding * positional parameters. If a string does not represent a semantically * correct expression, an ExpressionException is thrown. * <p> * Binding of parameters by name (as opposed to binding by position) can be * achieved by chaining this call with {@link Expression#params(Map)}. * * @since 4.0 */ public static Expression exp(String expressionString, Object... parameters) { Expression e = fromString(expressionString); if (parameters != null && parameters.length > 0) { // apply parameters in-place... it is wasteful to clone the // expression that hasn't been exposed to the callers e.inPlaceParamsArray(parameters); } return e; } /** * Wrap value into ASTScalar * @since 4.0 */ static Expression wrapScalarValue(Object value) { return new ASTScalar(value); } /** * Parses string, converting it to Expression. If string does not represent * a semantically correct expression, an ExpressionException is thrown. * * @since 4.0 */ private static Expression fromString(String expressionString) { if (expressionString == null) { throw new NullPointerException("Null expression string."); } // optimizing parser buffers per CAY-1667... // adding 1 extra char to the buffer size above the String length, as // otherwise resizing still occurs at the end of the stream int bufferSize = expressionString.length() > PARSE_BUFFER_MAX_SIZE ? PARSE_BUFFER_MAX_SIZE : expressionString.length() + 1; Reader reader = new StringReader(expressionString); JavaCharStream stream = new JavaCharStream(reader, 1, 1, bufferSize); ExpressionParserTokenManager tm = new ExpressionParserTokenManager(stream); ExpressionParser parser = new ExpressionParser(tm); try { return parser.expression(); } catch (Throwable th) { String message = th.getMessage(); throw new ExpressionException("%s", th, message != null ? message : ""); } } }