/*****************************************************************
* 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 static org.apache.cayenne.exp.ExpressionFactory.exp;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.exp.parser.ASTScalar;
import org.apache.cayenne.util.ConversionUtil;
import org.apache.cayenne.util.HashCodeBuilder;
import org.apache.cayenne.util.Util;
import org.apache.cayenne.util.XMLEncoder;
import org.apache.cayenne.util.XMLSerializable;
import org.apache.commons.collections.Transformer;
/**
* Superclass of Cayenne expressions that defines basic API for expressions use.
*/
public abstract class Expression implements Serializable, XMLSerializable {
private static final long serialVersionUID = 5268695167038124596L;
/**
* A value that a Transformer might return to indicate that a node has to be
* pruned from the expression during the transformation.
*
* @since 1.2
*/
public final static Object PRUNED_NODE = new Object();
public static final int AND = 0;
public static final int OR = 1;
public static final int NOT = 2;
public static final int EQUAL_TO = 3;
public static final int NOT_EQUAL_TO = 4;
public static final int LESS_THAN = 5;
public static final int GREATER_THAN = 6;
public static final int LESS_THAN_EQUAL_TO = 7;
public static final int GREATER_THAN_EQUAL_TO = 8;
public static final int BETWEEN = 9;
public static final int IN = 10;
public static final int LIKE = 11;
public static final int LIKE_IGNORE_CASE = 12;
public static final int ADD = 16;
public static final int SUBTRACT = 17;
public static final int MULTIPLY = 18;
public static final int DIVIDE = 19;
public static final int NEGATIVE = 20;
public static final int TRUE = 21;
public static final int FALSE = 22;
/**
* Expression describes a path relative to an ObjEntity. OBJ_PATH expression
* is resolved relative to some root ObjEntity. Path expression components
* are separated by "." (dot). Path can point to either one of these:
* <ul>
* <li><i>An attribute of root ObjEntity.</i> For entity Gallery OBJ_PATH
* expression "galleryName" will point to ObjAttribute "galleryName"
* <li><i>Another ObjEntity related to root ObjEntity via a chain of
* relationships.</i> For entity Gallery OBJ_PATH expression
* "paintingArray.toArtist" will point to ObjEntity "Artist"
* <li><i>ObjAttribute of another ObjEntity related to root ObjEntity via a
* chain of relationships.</i> For entity Gallery OBJ_PATH expression
* "paintingArray.toArtist.artistName" will point to ObjAttribute
* "artistName"
* </ul>
*/
public static final int OBJ_PATH = 26;
/**
* Expression describes a path relative to a DbEntity. DB_PATH expression is
* resolved relative to some root DbEntity. Path expression components are
* separated by "." (dot). Path can point to either one of these:
* <ul>
* <li><i>An attribute of root DbEntity.</i> For entity GALLERY, DB_PATH
* expression "GALLERY_NAME" will point to a DbAttribute "GALLERY_NAME".</li>
* <li><i>Another DbEntity related to root DbEntity via a chain of
* relationships.</i> For entity GALLERY DB_PATH expression
* "paintingArray.toArtist" will point to DbEntity "ARTIST".</li>
* <li><i>DbAttribute of another ObjEntity related to root DbEntity via a
* chain of relationships.</i> For entity GALLERY DB_PATH expression
* "paintingArray.toArtist.ARTIST_NAME" will point to DbAttribute
* "ARTIST_NAME".</li>
* </ul>
*/
public static final int DB_PATH = 27;
/**
* Interpreted as a comma-separated list of literals.
*/
public static final int LIST = 28;
public static final int NOT_BETWEEN = 35;
public static final int NOT_IN = 36;
public static final int NOT_LIKE = 37;
public static final int NOT_LIKE_IGNORE_CASE = 38;
/**
* @since 3.1
*/
public static final int BITWISE_NOT = 39;
/**
* @since 3.1
*/
public static final int BITWISE_AND = 40;
/**
* @since 3.1
*/
public static final int BITWISE_OR = 41;
/**
* @since 3.1
*/
public static final int BITWISE_XOR = 42;
/**
* @since 4.0
*/
public static final int BITWISE_LEFT_SHIFT = 43;
/**
* @since 4.0
*/
public static final int BITWISE_RIGHT_SHIFT = 44;
/**
* @since 4.0
*/
public static final int FUNCTION_CALL = 45;
/**
* @since 4.0
*/
public static final int ASTERISK = 46;
/**
* @since 4.0
*/
public static final int FULL_OBJECT = 47;
protected int type;
/**
* Parses string, converting it to Expression. If string does not represent
* a semantically correct expression, an ExpressionException is thrown.
*
* @since 1.1
* @deprecated since 4.0 use
* {@link ExpressionFactory#exp(String, Object...)}
*/
@Deprecated
public static Expression fromString(String expressionString) {
return exp(expressionString);
}
/**
* Returns a map of path aliases for this expression. It returns a non-empty
* map only if this is a path expression and the aliases are known at the
* expression creation time. Otherwise an empty map is returned.
*
* @since 3.0
*/
public abstract Map<String, String> getPathAliases();
/**
* Returns String label for this expression. Used for debugging.
*/
public String expName() {
switch (type) {
case AND:
return "AND";
case OR:
return "OR";
case NOT:
return "NOT";
case EQUAL_TO:
return "=";
case NOT_EQUAL_TO:
return "<>";
case LESS_THAN:
return "<";
case LESS_THAN_EQUAL_TO:
return "<=";
case GREATER_THAN:
return ">";
case GREATER_THAN_EQUAL_TO:
return ">=";
case BETWEEN:
return "BETWEEN";
case IN:
return "IN";
case LIKE:
return "LIKE";
case LIKE_IGNORE_CASE:
return "LIKE_IGNORE_CASE";
case OBJ_PATH:
return "OBJ_PATH";
case DB_PATH:
return "DB_PATH";
case LIST:
return "LIST";
case NOT_BETWEEN:
return "NOT BETWEEN";
case NOT_IN:
return "NOT IN";
case NOT_LIKE:
return "NOT LIKE";
case NOT_LIKE_IGNORE_CASE:
return "NOT LIKE IGNORE CASE";
case FUNCTION_CALL:
return "FUNCTION_CALL";
default:
return "other";
}
}
@Override
public boolean equals(Object object) {
if (!(object instanceof Expression)) {
return false;
}
Expression e = (Expression) object;
if (e.getType() != getType() || e.getOperandCount() != getOperandCount()) {
return false;
}
// compare operands
int len = e.getOperandCount();
for (int i = 0; i < len; i++) {
if (!Util.nullSafeEquals(e.getOperand(i), getOperand(i))) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
HashCodeBuilder builder = new HashCodeBuilder().append(getType());
int opCount = getOperandCount();
for(int i=0; i<opCount; i++) {
builder.append(getOperand(i));
}
return builder.toHashCode();
}
/**
* Returns a type of expression. Most common types are defined as public
* static fields of this interface.
*/
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
/**
* Creates and returns a new Expression instance based on this expression,
* but with parameters substituted with provided values. This is a
* positional style of binding. If a given parameter name is used more than
* once, only the first occurrence is treated as "position", subsequent
* occurrences are bound with the same value as the first one. If expression
* parameters count is different from the array parameter count, an
* exception will be thrown.
* <p>
* positional style would not allow subexpression pruning.
*
* @since 4.0
*/
public Expression paramsArray(Object... parameters) {
Expression clone = deepCopy();
clone.inPlaceParamsArray(parameters);
return clone;
}
/**
* @since 4.0
*/
void inPlaceParamsArray(Object... parameters) {
InPlaceParamReplacer replacer = new InPlaceParamReplacer(parameters == null ? new Object[0] : parameters);
traverse(replacer);
replacer.onFinish();
}
/**
* Creates and returns a new Expression instance based on this expression,
* but with named parameters substituted with provided values. Any
* subexpressions containing parameters not matching the "name" argument
* will be pruned.
* <p>
* Note that if you want matching against nulls to be preserved, you must
* place NULL values for the corresponding keys in the map.
*
* @since 4.0
*/
public Expression params(Map<String, ?> parameters) {
return transform(new NamedParamTransformer(parameters, true));
}
/**
* Creates and returns a new Expression instance based on this expression,
* but with named parameters substituted with provided values.If any
* subexpressions containing parameters not matching the "name" argument are
* found, the behavior depends on "pruneMissing" argument. If it is false an
* Exception will be thrown, otherwise subexpressions with missing
* parameters will be pruned from the resulting expression.
* <p>
* Note that if you want matching against nulls to be preserved, you must
* place NULL values for the corresponding keys in the map.
*
* @since 4.0
*/
public Expression params(Map<String, ?> parameters, boolean pruneMissing) {
return transform(new NamedParamTransformer(parameters, pruneMissing));
}
/**
* A shortcut for <code>expWithParams(params, true)</code>.
*
* @deprecated since 4.0 use {@link #params(Map)}
*/
@Deprecated
public Expression expWithParameters(Map<String, ?> parameters) {
return expWithParameters(parameters, true);
}
/**
* Creates and returns a new Expression instance using this expression as a
* prototype. All ExpressionParam operands are substituted with the values
* in the <code>params</code> map.
* <p>
* <i>Null values in the <code>params</code> map should be explicitly
* created in the map for the corresponding key. </i>
* </p>
*
* @param parameters
* a map of parameters, with each key being a string name of an
* expression parameter, and value being the value that should be
* used in the final expression.
* @param pruneMissing
* If <code>true</code>, subexpressions that rely on missing
* parameters will be pruned from the resulting tree. If
* <code>false</code> , any missing values will generate an
* exception.
* @return Expression resulting from the substitution of parameters with
* real values, or null if the whole expression was pruned, due to
* the missing parameters.
*
* @deprecated since 4.0 use {@link #params(Map, boolean)} instead.
*/
@Deprecated
public Expression expWithParameters(Map<String, ?> parameters, boolean pruneMissing) {
return params(parameters, pruneMissing);
}
/**
* Creates a new expression that joins this object with another expression,
* using specified join type. It is very useful for incrementally building
* chained expressions, like long AND or OR statements.
*/
public Expression joinExp(int type, Expression exp) {
return joinExp(type, exp, new Expression[0]);
}
/**
* Creates a new expression that joins this object with other expressions,
* using specified join type. It is very useful for incrementally building
* chained expressions, like long AND or OR statements.
*
* @since 4.0
*/
public Expression joinExp(int type, Expression exp, Expression... expressions) {
Expression join = ExpressionFactory.expressionOfType(type);
join.setOperand(0, this);
join.setOperand(1, exp);
for (int i = 0; i < expressions.length; i++) {
Expression expressionInArray = expressions[i];
join.setOperand(2 + i, expressionInArray);
}
join.flattenTree();
return join;
}
/**
* Chains this expression with another expression using "and".
*/
public Expression andExp(Expression exp) {
return joinExp(Expression.AND, exp);
}
/**
* Chains this expression with other expressions using "and".
*
* @since 4.0
*/
public Expression andExp(Expression exp, Expression... expressions) {
return joinExp(Expression.AND, exp, expressions);
}
/**
* Chains this expression with another expression using "or".
*/
public Expression orExp(Expression exp) {
return joinExp(Expression.OR, exp);
}
/**
* Chains this expression with other expressions using "or".
*
* @since 4.0
*/
public Expression orExp(Expression exp, Expression... expressions) {
return joinExp(Expression.OR, exp, expressions);
}
/**
* Returns a logical NOT of current expression.
*
* @since 1.0.6
*/
public abstract Expression notExp();
/**
* Returns a count of operands of this expression. In real life there are
* unary (count == 1), binary (count == 2) and ternary (count == 3)
* expressions.
*/
public abstract int getOperandCount();
/**
* Returns a value of operand at <code>index</code>. Operand indexing starts
* at 0.
*/
public abstract Object getOperand(int index);
/**
* Sets a value of operand at <code>index</code>. Operand indexing starts at
* 0.
*/
public abstract void setOperand(int index, Object value);
/**
* Calculates expression value with object as a context for path
* expressions.
*
* @since 1.1
*/
public abstract Object evaluate(Object o);
/**
* Calculates expression boolean value with object as a context for path
* expressions.
*
* @since 1.1
*/
public boolean match(Object o) {
return ConversionUtil.toBoolean(evaluate(o));
}
/**
* Returns the first object in the list that matches the expression.
*
* @since 3.1
*/
public <T> T first(List<T> objects) {
for (T o : objects) {
if (match(o)) {
return o;
}
}
return null;
}
/**
* Returns a list of objects that match the expression.
*/
@SuppressWarnings("unchecked")
public <T> List<T> filterObjects(Collection<T> objects) {
if (objects == null || objects.size() == 0) {
return new LinkedList<>(); // returning Collections.emptyList() could cause random client exceptions if they try to mutate the resulting list
}
return (List<T>) filter(objects, new LinkedList<T>());
}
/**
* Adds objects matching this expression from the source collection to the
* target collection.
*
* @since 1.1
*/
public <T> Collection<?> filter(Collection<T> source, Collection<T> target) {
for (T o : source) {
if (match(o)) {
target.add(o);
}
}
return target;
}
/**
* Clones this expression.
*
* @since 1.1
*/
public Expression deepCopy() {
return transform(null);
}
/**
* Creates a copy of this expression node, without copying children.
*
* @since 1.1
*/
public abstract Expression shallowCopy();
/**
* Returns true if this node should be pruned from expression tree in the
* event a child is removed.
*
* @since 1.1
*/
protected abstract boolean pruneNodeForPrunedChild(Object prunedChild);
/**
* Restructures expression to make sure that there are no children of the
* same type as this expression.
*
* @since 1.1
*/
protected abstract void flattenTree();
/**
* Traverses itself and child expressions, notifying visitor via callback
* methods as it goes. This is an Expression-specific implementation of the
* "Visitor" design pattern.
*
* @since 1.1
*/
public void traverse(TraversalHandler visitor) {
if (visitor == null) {
throw new NullPointerException("Null Visitor.");
}
traverse(null, visitor);
}
/**
* Traverses itself and child expressions, notifying visitor via callback
* methods as it goes.
*
* @since 1.1
*/
protected void traverse(Expression parentExp, TraversalHandler visitor) {
visitor.startNode(this, parentExp);
// recursively traverse each child
int count = getOperandCount();
for (int i = 0; i < count; i++) {
Object child = getOperand(i);
if (child instanceof Expression && !(child instanceof ASTScalar)) {
Expression childExp = (Expression) child;
childExp.traverse(this, visitor);
} else {
visitor.objectNode(child, this);
}
visitor.finishedChild(this, i, i < count - 1);
}
visitor.endNode(this, parentExp);
}
/**
* Creates a transformed copy of this expression, applying transformation
* provided by Transformer to all its nodes. Null transformer will result in
* an identical deep copy of this expression.
* <p>
* To force a node and its children to be pruned from the copy, Transformer
* should return Expression.PRUNED_NODE. Otherwise an expectation is that if
* a node is an Expression it must be transformed to null or another
* Expression. Any other object type would result in a ExpressionException.
*
* @since 1.1
*/
public Expression transform(Transformer transformer) {
Object transformed = transformExpression(transformer);
if (transformed == PRUNED_NODE || transformed == null) {
return null;
} else if (transformed instanceof Expression) {
return (Expression) transformed;
}
throw new ExpressionException("Invalid transformed expression: " + transformed);
}
/**
* A recursive method called from "transform" to do the actual
* transformation.
*
* @return null, Expression.PRUNED_NODE or transformed expression.
* @since 1.2
*/
protected Object transformExpression(Transformer transformer) {
Expression copy = shallowCopy();
int count = getOperandCount();
for (int i = 0, j = 0; i < count; i++) {
Object operand = getOperand(i);
Object transformedChild;
if (operand instanceof Expression) {
transformedChild = ((Expression) operand).transformExpression(transformer);
} else if (transformer != null) {
transformedChild = transformer.transform(operand);
} else {
transformedChild = operand;
}
// prune null children only if there is a transformer and it
// indicated so
boolean prune = transformer != null && transformedChild == PRUNED_NODE;
if (!prune) {
copy.setOperand(j, transformedChild);
j++;
}
if (prune && pruneNodeForPrunedChild(operand)) {
// bail out early...
return PRUNED_NODE;
}
}
// all the children are processed, only now transform this copy
return (transformer != null) ? (Expression) transformer.transform(copy) : copy;
}
/**
* Encodes itself, wrapping the string into XML CDATA section.
*
* @since 1.1
*/
public void encodeAsXML(XMLEncoder encoder) {
encoder.print("<![CDATA[");
try {
appendAsString(encoder.getPrintWriter());
} catch (IOException e) {
throw new CayenneRuntimeException("Unexpected IO exception appending to PrintWriter", e);
}
encoder.print("]]>");
}
/**
* Stores a String representation of Expression using a provided
* PrintWriter.
*
* @since 1.1
* @deprecated since 4.0 use {@link #appendAsString(Appendable)}.
*/
@Deprecated
public abstract void encodeAsString(PrintWriter pw);
/**
* Appends own content as a String to the provided Appendable.
*
* @since 4.0
* @throws IOException
*/
public abstract void appendAsString(Appendable out) throws IOException;
/**
* Stores a String representation of Expression as EJBQL using a provided
* PrintWriter. DB path expressions produce non-standard EJBQL path
* expressions.
*
* @since 3.0
* @deprecated since 4.0 use {@link #appendAsEJBQL(Appendable, String)}
*/
@Deprecated
public abstract void encodeAsEJBQL(PrintWriter pw, String rootId);
/**
* Stores a String representation of Expression as EJBQL using a provided
* Appendable. DB path expressions produce non-standard EJBQL path
* expressions.
*
* @since 4.0
* @throws IOException
*/
public void appendAsEJBQL(Appendable out, String rootId) throws IOException {
appendAsEJBQL(null, out, rootId);
}
/**
* Stores a String representation of Expression as EJBQL using a provided
* PrintWriter. DB path expressions produce non-standard EJBQL path
* expressions. If the parameterAccumulator is supplied then as the EJBQL is
* output, it may load parameters into this list. In this case, the EJBQL
* output will contain reference to positional parameters. If no
* parameterAccumulator is supplied and a scalar type is encountered for
* which there is no EJBQL literal representation (such as dates) then this
* method will throw a runtime exception to indicate that it was not
* possible to generate a string-only representation of the Expression in
* EJBQL.
*
* @since 4.0
* @throws IOException
*/
public abstract void appendAsEJBQL(List<Object> parameterAccumulator, Appendable out, String rootId)
throws IOException;
@Override
public String toString() {
StringBuilder out = new StringBuilder();
try {
appendAsString(out);
} catch (IOException e) {
throw new CayenneRuntimeException("Unexpected IO exception appending to StringBuilder", e);
}
return out.toString();
}
/**
* Produces an EJBQL string that represents this expression. If the
* parameterAccumulator is supplied then, where appropriate, parameters to
* the EJBQL may be written into the parameterAccumulator. If this method
* encounters a scalar type which is not able to be represented as an EJBQL
* literal then this method will throw a runtime exception to indicate that
* it was not possible to generate a string-only representation of the
* Expression as EJBQL.
*
* @since 3.1
*/
public String toEJBQL(List<Object> parameterAccumulator, String rootId) {
StringBuilder out = new StringBuilder();
try {
appendAsEJBQL(parameterAccumulator, out, rootId);
} catch (IOException e) {
throw new CayenneRuntimeException("Unexpected IO exception appending to StringBuilder", e);
}
return out.toString();
}
/**
* Produces an EJBQL string that represents this expression. If this method
* encounters a scalar type which is not able to be represented as an EJBQL
* literal then this method will throw a runtime exception.
*
* @since 3.0
*/
public String toEJBQL(String rootId) {
return toEJBQL(null, rootId);
}
final class NamedParamTransformer implements Transformer {
private Map<String, ?> parameters;
private boolean pruneMissing;
NamedParamTransformer(Map<String, ?> parameters, boolean pruneMissing) {
this.parameters = parameters;
this.pruneMissing = pruneMissing;
}
@Override
public Object transform(Object object) {
if (!(object instanceof ExpressionParameter)) {
// normally Object[] is an ASTList child
if (object instanceof Object[]) {
Object[] source = (Object[]) object;
int len = source.length;
Object[] target = new Object[len];
for (int i = 0; i < len; i++) {
target[i] = transform(source[i]);
}
return target;
}
return object;
}
String name = ((ExpressionParameter) object).getName();
if (!parameters.containsKey(name)) {
if (pruneMissing) {
return PRUNED_NODE;
} else {
throw new ExpressionException("Missing required parameter: $" + name);
}
} else {
Object value = parameters.get(name);
// wrap lists (for now); also support null parameters
// TODO: andrus 8/14/2007 - shouldn't we also wrap non-null
// object
// values in ASTScalars?
return (value != null) ? ExpressionFactory.wrapPathOperand(value) : new ASTScalar(null);
}
}
}
final class InPlaceParamReplacer extends TraversalHelper {
private Object[] parameters;
private int i;
private Map<String, Object> seen;
InPlaceParamReplacer(Object[] parameters) {
this.parameters = parameters;
}
void onFinish() {
if (i < parameters.length) {
throw new ExpressionException("Too many parameters to bind expression. Expected: " + i + ", actual: "
+ parameters.length);
}
}
@Override
public void finishedChild(Expression node, int childIndex, boolean hasMoreChildren) {
Object child = node.getOperand(childIndex);
if (child instanceof ExpressionParameter) {
node.setOperand(childIndex, nextValue(((ExpressionParameter) child).getName()));
}
// normally Object[] is an ASTList child
else if (child instanceof Object[]) {
Object[] array = (Object[]) child;
for (int i = 0; i < array.length; i++) {
if (array[i] instanceof ExpressionParameter) {
array[i] = nextValue(((ExpressionParameter) array[i]).getName());
}
}
}
}
private Object nextValue(String name) {
if (seen == null) {
seen = new HashMap<>();
}
Object p;
if (seen.containsKey(name)) {
p = seen.get(name);
} else {
if (i >= parameters.length) {
throw new ExpressionException("Too few parameters to bind expression: " + parameters.length);
}
p = parameters[i++];
seen.put(name, p);
}
// wrap lists (for now); also support null parameters
// TODO: andrus 8/14/2007 - shouldn't we also wrap non-null
// object values in ASTScalars?
return (p != null) ? ExpressionFactory.wrapPathOperand(p) : new ASTScalar(null);
}
}
}