You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by jo...@apache.org on 2016/08/09 16:30:30 UTC

[1/6] cayenne git commit: Soem fixes for evaluating Expressions with nulls in memory

Repository: cayenne
Updated Branches:
  refs/heads/ics11 [created] 6b73bad78


Soem fixes for evaluating Expressions with nulls in memory

More fixes for evaluating Expression with nulls in memory

null does not short circuit an AND expression
select null and false; -- returns false
select null and true; -- returns null

Add unit tests for in memory evaluation of expression involving nulls, bug fix


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/b30e0fa2
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/b30e0fa2
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/b30e0fa2

Branch: refs/heads/ics11
Commit: b30e0fa21620cda5014adb70f690849965b9b49b
Parents: b93a51c
Author: David Feshbach <df...@icsusa.com>
Authored: Mon Feb 24 11:51:30 2014 -0600
Committer: John Huss <jo...@apache.org>
Committed: Wed Jun 24 11:40:12 2015 -0500

----------------------------------------------------------------------
 .../java/org/apache/cayenne/exp/Expression.java | 1177 +++++++++---------
 .../org/apache/cayenne/exp/parser/ASTAnd.java   |  175 +--
 .../org/apache/cayenne/exp/parser/ASTOr.java    |  145 ++-
 .../apache/cayenne/exp/parser/Evaluator.java    |    2 +-
 .../parser/ExpressionEvaluateInMemoryIT.java    |  682 ++++++++++
 5 files changed, 1448 insertions(+), 733 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/b30e0fa2/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java
index 105bcdd..0c8812d 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java
@@ -46,221 +46,221 @@ 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;
-
-	/**
+    /**
+     * 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;
+     */
+    public static final int BITWISE_LEFT_SHIFT = 43;
 
-	/**
+    /**
 	 * @since 4.0
-	 */
-	public static final int BITWISE_RIGHT_SHIFT = 44;
+     */
+    public static final int BITWISE_RIGHT_SHIFT = 44;
 
-	protected int type;
+    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
+    /**
+     * 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) {
+    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";
-		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;
-	}
-
-	/**
-	 * 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;
-	}
-
-	/**
+    }
+
+    /**
+     * 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";
+        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;
+    }
+
+    /**
+     * 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
@@ -322,380 +322,387 @@ public abstract class Expression implements Serializable, XMLSerializable {
 	}
 
 	/**
-	 * A shortcut for <code>expWithParams(params, true)</code>.
+     * 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.
+    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.
-	 * 
+                    }
+
+    /**
+     * 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".
-	 * 
+     */
+    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".
-	 * 
+     */
+    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.
-	 */
+     */
+    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) {
+        try {
+            return ConversionUtil.toBoolean(evaluate(o));
+        } catch (ExpressionException e) {
+        	if (e.getCause() instanceof UnsupportedOperationException) {
+            return false;
+        }
+            throw e;
+        }
+    }
+
+    /**
+     * 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) {
+    public <T> List<T> filterObjects(Collection<T> objects) {
+        if (objects == null || objects.size() == 0) {
 			return Collections.emptyList();
-		}
-
-		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) {
-				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
+        }
+
+        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) {
+                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);
+     */
+    @Deprecated
+    public abstract void encodeAsString(PrintWriter pw);
 
-	/**
-	 * Appends own content as a String to the provided Appendable.
-	 * 
+    /**
+     * 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
+     * @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.
-	 * 
+     */
+    @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
 	 */
@@ -716,23 +723,23 @@ public abstract class Expression implements Serializable, XMLSerializable {
 	 * EJBQL.
 	 * 
 	 * @since 4.0
-	 * @throws IOException
-	 */
+     * @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();
-	}
-
-	/**
+    @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
@@ -742,16 +749,16 @@ public abstract class Expression implements Serializable, XMLSerializable {
 	 * Expression as EJBQL.
 	 * 
 	 * @since 3.1
-	 */
+     */
 	public String toEJBQL(List<Object> parameterAccumulator, String rootId) {
-		StringBuilder out = new StringBuilder();
-		try {
+        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();
-	}
+        } 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

http://git-wip-us.apache.org/repos/asf/cayenne/blob/b30e0fa2/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAnd.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAnd.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAnd.java
index 33fdcb1..1c782ec 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAnd.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAnd.java
@@ -23,6 +23,7 @@ import java.util.Collection;
 import java.util.Iterator;
 
 import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionException;
 import org.apache.cayenne.exp.ValueInjector;
 import org.apache.cayenne.util.ConversionUtil;
 
@@ -35,86 +36,98 @@ public class ASTAnd extends AggregateConditionNode implements ValueInjector {
 
 	private static final long serialVersionUID = -5936206826390819160L;
 
-	/**
-	 * Constructor used by expression parser. Do not invoke directly.
-	 */
-	ASTAnd(int id) {
-		super(id);
-	}
-
-	public ASTAnd() {
-		super(ExpressionParserTreeConstants.JJTAND);
-	}
-
-	public ASTAnd(Object[] nodes) {
-		super(ExpressionParserTreeConstants.JJTAND);
-		int len = nodes.length;
-		for (int i = 0; i < len; i++) {
-			jjtAddChild((Node) nodes[i], i);
-		}
-
-		connectChildren();
-	}
-
-	public ASTAnd(Collection<? extends Node> nodes) {
-		super(ExpressionParserTreeConstants.JJTAND);
-		int len = nodes.size();
-		Iterator<? extends Node> it = nodes.iterator();
-		for (int i = 0; i < len; i++) {
-			jjtAddChild(it.next(), i);
-		}
-
-		connectChildren();
-	}
-
-	@Override
-	protected Object evaluateNode(Object o) throws Exception {
-		int len = jjtGetNumChildren();
-		if (len == 0) {
-			return Boolean.FALSE;
-		}
-
-		for (int i = 0; i < len; i++) {
-			if (!ConversionUtil.toBoolean(evaluateChild(i, o))) {
-				return Boolean.FALSE;
-			}
-		}
-
-		return Boolean.TRUE;
-	}
-
-	/**
-	 * Creates a copy of this expression node, without copying children.
-	 */
-	@Override
-	public Expression shallowCopy() {
-		return new ASTAnd(id);
-	}
-
-	@Override
-	public int getType() {
-		return Expression.AND;
-	}
-
-	@Override
-	public void jjtClose() {
-		super.jjtClose();
-		flattenTree();
-	}
-
-	@Override
-	protected String getExpressionOperator(int index) {
-		return "and";
-	}
-
-	public void injectValue(Object o) {
+    /**
+     * Constructor used by expression parser. Do not invoke directly.
+     */
+    ASTAnd(int id) {
+        super(id);
+    }
+
+    public ASTAnd() {
+        super(ExpressionParserTreeConstants.JJTAND);
+    }
+
+    public ASTAnd(Object[] nodes) {
+        super(ExpressionParserTreeConstants.JJTAND);
+        int len = nodes.length;
+        for (int i = 0; i < len; i++) {
+            jjtAddChild((Node) nodes[i], i);
+        }
+        
+        connectChildren();
+    }
+
+    public ASTAnd(Collection<? extends Node> nodes) {
+        super(ExpressionParserTreeConstants.JJTAND);
+        int len = nodes.size();
+        Iterator<? extends Node> it = nodes.iterator();
+        for (int i = 0; i < len; i++) {
+            jjtAddChild(it.next(), i);
+        }
+        
+        connectChildren();
+    }
+
+    @Override
+    protected Object evaluateNode(Object o) throws Exception {
+        int len = jjtGetNumChildren();
+        if (len == 0) {
+            return Boolean.FALSE;
+        }
+
+        ExpressionException ex = null;
+        for (int i = 0; i < len; i++) {
+            try {
+            if (!ConversionUtil.toBoolean(evaluateChild(i, o))) {
+                return Boolean.FALSE;
+            }
+            } catch (ExpressionException e) {
+                if (e.getCause() instanceof UnsupportedOperationException) {
+                    ex = e;
+                } else {
+                    throw e;
+                }
+        }
+        }
+
+        if (ex != null) {
+            throw ex;
+        }
+        return Boolean.TRUE;
+    }
+
+    /**
+     * Creates a copy of this expression node, without copying children.
+     */
+    @Override
+    public Expression shallowCopy() {
+        return new ASTAnd(id);
+    }
+
+    @Override
+    public int getType() {
+        return Expression.AND;
+    }
+
+    @Override
+    public void jjtClose() {
+        super.jjtClose();
+        flattenTree();
+    }
+
+    @Override
+    protected String getExpressionOperator(int index) {
+        return "and";
+    }
+
+    public void injectValue(Object o) {
 		// iterate through all operands, inject all possible
-		int len = jjtGetNumChildren();
-		for (int i = 0; i < len; i++) {
-			Node node = jjtGetChild(i);
-			if (node instanceof ValueInjector) {
-				((ValueInjector) node).injectValue(o);
-			}
-		}
-	}
+        int len = jjtGetNumChildren();
+        for (int i = 0; i < len; i++) {
+            Node node = jjtGetChild(i);
+            if (node instanceof ValueInjector) {
+                ((ValueInjector) node).injectValue(o);
+            }
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/b30e0fa2/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTOr.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTOr.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTOr.java
index 1f6ef24..f7ff414 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTOr.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTOr.java
@@ -23,6 +23,7 @@ import java.util.Collection;
 import java.util.Iterator;
 
 import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionException;
 import org.apache.cayenne.util.ConversionUtil;
 
 /**
@@ -34,70 +35,82 @@ public class ASTOr extends AggregateConditionNode {
 
 	private static final long serialVersionUID = 780157841581581297L;
 
-	ASTOr(int id) {
-		super(id);
-	}
-
-	public ASTOr() {
-		super(ExpressionParserTreeConstants.JJTOR);
-	}
-
-	public ASTOr(Object[] nodes) {
-		super(ExpressionParserTreeConstants.JJTOR);
-		int len = nodes.length;
-		for (int i = 0; i < len; i++) {
-			jjtAddChild((Node) nodes[i], i);
-		}
-		connectChildren();
-	}
-
-	public ASTOr(Collection<? extends Node> nodes) {
-		super(ExpressionParserTreeConstants.JJTOR);
-		int len = nodes.size();
-		Iterator<? extends Node> it = nodes.iterator();
-		for (int i = 0; i < len; i++) {
-			jjtAddChild(it.next(), i);
-		}
-		connectChildren();
-	}
-
-	@Override
-	protected Object evaluateNode(Object o) throws Exception {
-		int len = jjtGetNumChildren();
-		if (len == 0) {
-			return Boolean.FALSE;
-		}
-
-		for (int i = 0; i < len; i++) {
-			if (ConversionUtil.toBoolean(evaluateChild(i, o))) {
-				return Boolean.TRUE;
-			}
-		}
-
-		return Boolean.FALSE;
-	}
-
-	/**
-	 * Creates a copy of this expression node, without copying children.
-	 */
-	@Override
-	public Expression shallowCopy() {
-		return new ASTOr(id);
-	}
-
-	@Override
-	protected String getExpressionOperator(int index) {
-		return "or";
-	}
-
-	@Override
-	public int getType() {
-		return Expression.OR;
-	}
-
-	@Override
-	public void jjtClose() {
-		super.jjtClose();
-		flattenTree();
-	}
+    ASTOr(int id) {
+        super(id);
+    }
+
+    public ASTOr() {
+        super(ExpressionParserTreeConstants.JJTOR);
+    }
+
+    public ASTOr(Object[] nodes) {
+        super(ExpressionParserTreeConstants.JJTOR);
+        int len = nodes.length;
+        for (int i = 0; i < len; i++) {
+            jjtAddChild((Node) nodes[i], i);
+        }
+        connectChildren();
+    }
+
+    public ASTOr(Collection<? extends Node> nodes) {
+        super(ExpressionParserTreeConstants.JJTOR);
+        int len = nodes.size();
+        Iterator<? extends Node> it = nodes.iterator();
+        for (int i = 0; i < len; i++) {
+            jjtAddChild(it.next(), i);
+        }
+        connectChildren();
+    }
+
+    @Override
+    protected Object evaluateNode(Object o) throws Exception {
+        int len = jjtGetNumChildren();
+        if (len == 0) {
+            return Boolean.FALSE;
+        }
+
+        ExpressionException ex = null;
+        for (int i = 0; i < len; i++) {
+            try {
+            if (ConversionUtil.toBoolean(evaluateChild(i, o))) {
+                return Boolean.TRUE;
+            }
+            } catch (ExpressionException e) {
+                if (e.getCause() instanceof UnsupportedOperationException) {
+                    ex = e;
+                } else {
+                    throw e;
+                }
+        }
+        }
+
+        if (ex != null) {
+            throw ex;
+        }
+        return Boolean.FALSE;
+    }
+
+    /**
+     * Creates a copy of this expression node, without copying children.
+     */
+    @Override
+    public Expression shallowCopy() {
+        return new ASTOr(id);
+    }
+
+    @Override
+    protected String getExpressionOperator(int index) {
+        return "or";
+    }
+
+    @Override
+    public int getType() {
+        return Expression.OR;
+    }
+
+    @Override
+    public void jjtClose() {
+        super.jjtClose();
+        flattenTree();
+    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/b30e0fa2/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/Evaluator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/Evaluator.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/Evaluator.java
index df6785b..7d37e80 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/Evaluator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/Evaluator.java
@@ -66,7 +66,7 @@ abstract class Evaluator {
         Integer compare(Object lhs, Object rhs) {
 
             if (rhs == null) {
-                return 1;
+                throw new UnsupportedOperationException("Unsupported");
             } else {
                 return delegate.compare(lhs, rhs);
             }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/b30e0fa2/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ExpressionEvaluateInMemoryIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ExpressionEvaluateInMemoryIT.java b/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ExpressionEvaluateInMemoryIT.java
new file mode 100644
index 0000000..e4a20e3
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ExpressionEvaluateInMemoryIT.java
@@ -0,0 +1,682 @@
+/*****************************************************************
+ *   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.parser;
+
+import org.apache.cayenne.Cayenne;
+import org.apache.cayenne.access.DataContext;
+import org.apache.cayenne.configuration.server.ServerRuntime;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.test.jdbc.DBHelper;
+import org.apache.cayenne.test.jdbc.TableHelper;
+import org.apache.cayenne.testdo.testmap.Artist;
+import org.apache.cayenne.testdo.testmap.Painting;
+import org.apache.cayenne.unit.di.server.CayenneProjects;
+import org.apache.cayenne.unit.di.server.ServerCase;
+import org.apache.cayenne.unit.di.server.UseServerRuntime;
+import org.apache.cayenne.unit.util.TstBean;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.math.BigDecimal;
+import java.sql.Types;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
+public class ExpressionEvaluateInMemoryIT extends ServerCase {
+
+    @Inject
+    private ServerRuntime runtime;
+
+    @Inject
+    private DataContext context;
+
+    @Inject
+    protected DBHelper dbHelper;
+
+    protected TableHelper tArtist;
+    protected TableHelper tPainting;
+
+    @Before
+    public void setUp() throws Exception {
+        tArtist = new TableHelper(dbHelper, "ARTIST");
+        tArtist.setColumns("ARTIST_ID", "ARTIST_NAME");
+
+        tPainting = new TableHelper(dbHelper, "PAINTING");
+        tPainting.setColumns("PAINTING_ID", "ARTIST_ID", "PAINTING_TITLE", "ESTIMATED_PRICE").setColumnTypes(
+                Types.INTEGER, Types.BIGINT, Types.VARCHAR, Types.DECIMAL);
+    }
+
+    protected void createTwoArtistsThreePaintings() throws Exception {
+
+        tArtist.insert(1, "artist1");
+        tArtist.insert(2, "artist2");
+        tPainting.insert(1, 1, "P1", 3000);
+        tPainting.insert(2, 2, "P2", 3000);
+        tPainting.insert(3, null, "P3", 3000);
+    }
+
+    @Test
+    public void testEvaluateOBJ_PATH_DataObject() throws Exception {
+        ASTObjPath node = new ASTObjPath("artistName");
+
+        Artist a1 = new Artist();
+        a1.setArtistName("abc");
+        assertEquals("abc", node.evaluate(a1));
+
+        Artist a2 = new Artist();
+        a2.setArtistName("123");
+        assertEquals("123", node.evaluate(a2));
+    }
+
+    @Test
+    public void testEvaluateOBJ_PATH_JavaBean() throws Exception {
+        ASTObjPath node = new ASTObjPath("property2");
+
+        TstBean b1 = new TstBean();
+        b1.setProperty2(1);
+        assertEquals(new Integer(1), node.evaluate(b1));
+
+        TstBean b2 = new TstBean();
+        b2.setProperty2(-3);
+        assertEquals(new Integer(-3), node.evaluate(b2));
+    }
+
+    @Test
+    public void testEvaluateOBJ_PATH_ObjEntity() throws Exception {
+        ASTObjPath node = new ASTObjPath("paintingArray.paintingTitle");
+
+        ObjEntity ae = runtime.getDataDomain().getEntityResolver().getObjEntity(Artist.class);
+
+        Object target = node.evaluate(ae);
+        assertTrue(target instanceof ObjAttribute);
+    }
+
+    @Test
+    public void testEvaluateDB_PATH_DbEntity() throws Exception {
+        Expression e = Expression.fromString("db:paintingArray.PAINTING_TITLE");
+
+        ObjEntity ae = runtime.getDataDomain().getEntityResolver().getObjEntity(Artist.class);
+        DbEntity ade = ae.getDbEntity();
+
+        Object objTarget = e.evaluate(ae);
+        assertTrue(objTarget instanceof DbAttribute);
+
+        Object dbTarget = e.evaluate(ade);
+        assertTrue(dbTarget instanceof DbAttribute);
+    }
+
+    @Test
+    public void testEvaluateEQUAL_TOBigDecimal() throws Exception {
+        BigDecimal bd1 = new BigDecimal("2.0");
+        BigDecimal bd2 = new BigDecimal("2.0");
+        BigDecimal bd3 = new BigDecimal("2.00");
+        BigDecimal bd4 = new BigDecimal("2.01");
+
+        Expression equalTo = new ASTEqual(new ASTObjPath(Painting.ESTIMATED_PRICE_PROPERTY), bd1);
+
+        Painting p = new Painting();
+        p.setEstimatedPrice(bd2);
+        assertTrue(equalTo.match(p));
+
+        // BigDecimals must compare regardless of the number of trailing zeros
+        // (see CAY-280)
+        p.setEstimatedPrice(bd3);
+        assertTrue(equalTo.match(p));
+
+        p.setEstimatedPrice(bd4);
+        assertFalse(equalTo.match(p));
+    }
+
+    @Test
+    public void testEvaluateEQUAL_TO() throws Exception {
+        Expression equalTo = new ASTEqual(new ASTObjPath("artistName"), "abc");
+        Expression notEqualTo = new ASTNotEqual(new ASTObjPath("artistName"), "abc");
+
+        Artist match = new Artist();
+        match.setArtistName("abc");
+        assertTrue(equalTo.match(match));
+        assertFalse(notEqualTo.match(match));
+
+        Artist noMatch = new Artist();
+        noMatch.setArtistName("123");
+        assertFalse("Failed: " + equalTo, equalTo.match(noMatch));
+        assertTrue("Failed: " + notEqualTo, notEqualTo.match(noMatch));
+    }
+
+    @Test
+    public void testEvaluateEQUAL_TO_Null() throws Exception {
+        Expression equalToNull = new ASTEqual(new ASTObjPath("artistName"), null);
+        Expression equalToNotNull = new ASTEqual(new ASTObjPath("artistName"), "abc");
+
+        Artist match = new Artist();
+        assertTrue(equalToNull.match(match));
+        assertFalse(equalToNotNull.match(match));
+
+        Artist noMatch = new Artist();
+        noMatch.setArtistName("abc");
+        assertFalse(equalToNull.match(noMatch));
+    }
+
+    @Test
+    public void testEvaluateNOT_EQUAL_TONull() throws Exception {
+        Expression notEqualToNull = new ASTNotEqual(new ASTObjPath("artistName"), null);
+        Expression notEqualToNotNull = new ASTNotEqual(new ASTObjPath("artistName"), "abc");
+
+        Artist match = new Artist();
+        assertFalse(notEqualToNull.match(match));
+        assertTrue(notEqualToNotNull.match(match));
+
+        Artist noMatch = new Artist();
+        noMatch.setArtistName("123");
+        assertTrue("Failed: " + notEqualToNull, notEqualToNull.match(noMatch));
+    }
+
+    @Test
+    public void testEvaluateEQUAL_TODataObject() throws Exception {
+        Artist a1 = (Artist) context.newObject("Artist");
+        Artist a2 = (Artist) context.newObject("Artist");
+        Painting p1 = (Painting) context.newObject("Painting");
+        Painting p2 = (Painting) context.newObject("Painting");
+        Painting p3 = (Painting) context.newObject("Painting");
+        
+        a1.setArtistName("a1");
+        a2.setArtistName("a2");
+        p1.setPaintingTitle("p1");
+        p2.setPaintingTitle("p2");
+        p3.setPaintingTitle("p3");
+        
+        context.commitChanges();
+        
+        p1.setToArtist(a1);
+        p2.setToArtist(a2);
+
+        Expression e = new ASTEqual(new ASTObjPath("toArtist"), a1);
+
+        assertTrue(e.match(p1));
+        assertFalse(e.match(p2));
+        assertFalse(e.match(p3));
+    }
+
+    @Test
+    public void testEvaluateEQUAL_TO_Temp_ObjectId() throws Exception {
+        Artist a1 = (Artist) context.newObject("Artist");
+        Artist a2 = (Artist) context.newObject("Artist");
+        Painting p1 = (Painting) context.newObject("Painting");
+        Painting p2 = (Painting) context.newObject("Painting");
+        Painting p3 = (Painting) context.newObject("Painting");
+
+        p1.setToArtist(a1);
+        p2.setToArtist(a2);
+
+        Expression e = new ASTEqual(new ASTObjPath("toArtist"), a1.getObjectId());
+
+        assertTrue(e.match(p1));
+        assertFalse(e.match(p2));
+        assertFalse(e.match(p3));
+    }
+
+    @Test
+    public void testEvaluateEQUAL_TO_Id() throws Exception {
+
+        createTwoArtistsThreePaintings();
+
+        Artist a1 = Cayenne.objectForPK(context, Artist.class, 1);
+        Painting p1 = Cayenne.objectForPK(context, Painting.class, 1);
+        Painting p2 = Cayenne.objectForPK(context, Painting.class, 2);
+        Painting p3 = Cayenne.objectForPK(context, Painting.class, 3);
+
+        Expression e = new ASTEqual(new ASTObjPath("toArtist"), Cayenne.intPKForObject(a1));
+
+        assertTrue(e.match(p1));
+        assertFalse(e.match(p2));
+        assertFalse(e.match(p3));
+    }
+    
+    public void testEvaluateEQUAL_TO_DbPath() throws Exception {
+        createTwoArtistsThreePaintings();
+
+        Artist a1 = Cayenne.objectForPK(context, Artist.class, 1);
+        Painting p1 = Cayenne.objectForPK(context, Painting.class, 1);
+        Painting p2 = Cayenne.objectForPK(context, Painting.class, 2);
+        Painting p3 = Cayenne.objectForPK(context, Painting.class, 3);
+
+        Expression e = new ASTEqual(new ASTDbPath("toArtist.ARTIST_NAME"), a1.getArtistName());
+        assertTrue(e.match(p1));
+        assertFalse(e.match(p2));
+        assertFalse(e.match(p3));
+    }
+
+    @Test
+    public void testEvaluateAND() throws Exception {
+        Expression e1 = new ASTEqual(new ASTObjPath("artistName"), "abc");
+        Expression e2 = new ASTEqual(new ASTObjPath("artistName"), "abc");
+
+        ASTAnd e = new ASTAnd(new Object[] { e1, e2 });
+
+        Artist match = new Artist();
+        match.setArtistName("abc");
+        assertTrue(e.match(match));
+
+        Artist noMatch = new Artist();
+        noMatch.setArtistName("123");
+        assertFalse(e.match(noMatch));
+    }
+
+    @Test
+    public void testEvaluateOR() throws Exception {
+        Expression e1 = new ASTEqual(new ASTObjPath("artistName"), "abc");
+        Expression e2 = new ASTEqual(new ASTObjPath("artistName"), "xyz");
+
+        ASTOr e = new ASTOr(new Object[] { e1, e2 });
+
+        Artist match1 = new Artist();
+        match1.setArtistName("abc");
+        assertTrue("Failed: " + e, e.match(match1));
+
+        Artist match2 = new Artist();
+        match2.setArtistName("xyz");
+        assertTrue("Failed: " + e, e.match(match2));
+
+        Artist noMatch = new Artist();
+        noMatch.setArtistName("123");
+        assertFalse("Failed: " + e, e.match(noMatch));
+    }
+
+    @Test
+    public void testEvaluateNOT() throws Exception {
+        ASTNot e = new ASTNot(new ASTEqual(new ASTObjPath("artistName"), "abc"));
+
+        Artist noMatch = new Artist();
+        noMatch.setArtistName("abc");
+        assertFalse(e.match(noMatch));
+
+        Artist match = new Artist();
+        match.setArtistName("123");
+        assertTrue("Failed: " + e, e.match(match));
+    }
+
+    @Test
+    public void testEvaluateLESS_THAN() throws Exception {
+        Expression e = new ASTLess(new ASTObjPath("estimatedPrice"), new BigDecimal(10000d));
+
+        Painting noMatch = new Painting();
+        noMatch.setEstimatedPrice(new BigDecimal(10001));
+        assertFalse("Failed: " + e, e.match(noMatch));
+
+        Painting noMatch1 = new Painting();
+        noMatch1.setEstimatedPrice(new BigDecimal(10000));
+        assertFalse("Failed: " + e, e.match(noMatch1));
+
+        Painting match = new Painting();
+        match.setEstimatedPrice(new BigDecimal(9999));
+        assertTrue("Failed: " + e, e.match(match));
+    }
+
+    @Test
+    public void testEvaluateLESS_THAN_Null() throws Exception {
+        Expression ltNull = new ASTLess(new ASTObjPath("estimatedPrice"), null);
+        Expression ltNotNull = new ASTLess(new ASTObjPath("estimatedPrice"), new BigDecimal(10000d));
+
+        Painting noMatch = new Painting();
+        assertFalse(ltNull.match(noMatch));
+        assertFalse(ltNotNull.match(noMatch));
+    }
+
+    @Test
+    public void testEvaluateLESS_THAN_EQUAL_TO() throws Exception {
+        Expression e = new ASTLessOrEqual(new ASTObjPath("estimatedPrice"), new BigDecimal(10000d));
+
+        Painting noMatch = new Painting();
+        noMatch.setEstimatedPrice(new BigDecimal(10001));
+        assertFalse(e.match(noMatch));
+
+        Painting match1 = new Painting();
+        match1.setEstimatedPrice(new BigDecimal(10000));
+        assertTrue(e.match(match1));
+
+        Painting match = new Painting();
+        match.setEstimatedPrice(new BigDecimal(9999));
+        assertTrue("Failed: " + e, e.match(match));
+    }
+
+    @Test
+    public void testEvaluateLESS_THAN_EQUAL_TO_Null() throws Exception {
+        Expression ltNull = new ASTLessOrEqual(new ASTObjPath("estimatedPrice"), null);
+        Expression ltNotNull = new ASTLessOrEqual(new ASTObjPath("estimatedPrice"), new BigDecimal(10000d));
+
+        Painting noMatch = new Painting();
+        assertFalse(ltNull.match(noMatch));
+        assertFalse(ltNotNull.match(noMatch));
+    }
+
+    @Test
+    public void testEvaluateGREATER_THAN() throws Exception {
+        Expression e = new ASTGreater(new ASTObjPath("estimatedPrice"), new BigDecimal(10000d));
+
+        Painting noMatch = new Painting();
+        noMatch.setEstimatedPrice(new BigDecimal(9999));
+        assertFalse(e.match(noMatch));
+
+        Painting noMatch1 = new Painting();
+        noMatch1.setEstimatedPrice(new BigDecimal(10000));
+        assertFalse(e.match(noMatch1));
+
+        Painting match = new Painting();
+        match.setEstimatedPrice(new BigDecimal(10001));
+        assertTrue("Failed: " + e, e.match(match));
+    }
+
+    @Test
+    public void testEvaluateGREATER_THAN_Null() throws Exception {
+        Expression gtNull = new ASTGreater(new ASTObjPath("estimatedPrice"), null);
+        Expression gtNotNull = new ASTGreater(new ASTObjPath("estimatedPrice"), new BigDecimal(10000d));
+
+        Painting noMatch = new Painting();
+        assertFalse(gtNull.match(noMatch));
+        assertFalse(gtNotNull.match(noMatch));
+    }
+
+    @Test
+    public void testEvaluateGREATER_THAN_EQUAL_TO() throws Exception {
+        Expression e = new ASTGreaterOrEqual(new ASTObjPath("estimatedPrice"), new BigDecimal(10000d));
+
+        Painting noMatch = new Painting();
+        noMatch.setEstimatedPrice(new BigDecimal(9999));
+        assertFalse(e.match(noMatch));
+
+        Painting match1 = new Painting();
+        match1.setEstimatedPrice(new BigDecimal(10000));
+        assertTrue(e.match(match1));
+
+        Painting match = new Painting();
+        match.setEstimatedPrice(new BigDecimal(10001));
+        assertTrue("Failed: " + e, e.match(match));
+    }
+
+    @Test
+    public void testEvaluateGREATER_THAN_EQUAL_TO_Null() throws Exception {
+        Expression gtNull = new ASTGreaterOrEqual(new ASTObjPath("estimatedPrice"), null);
+        Expression gtNotNull = new ASTGreaterOrEqual(new ASTObjPath("estimatedPrice"), new BigDecimal(10000d));
+
+        Painting noMatch = new Painting();
+        assertFalse(gtNull.match(noMatch));
+        assertFalse(gtNotNull.match(noMatch));
+    }
+
+    @Test
+    public void testEvaluateBETWEEN() throws Exception {
+        // evaluate both BETWEEN and NOT_BETWEEN
+        Expression between = new ASTBetween(new ASTObjPath("estimatedPrice"), new BigDecimal(10d), new BigDecimal(20d));
+        Expression notBetween = new ASTNotBetween(new ASTObjPath("estimatedPrice"), new BigDecimal(10d),
+                new BigDecimal(20d));
+
+        Painting noMatch = new Painting();
+        noMatch.setEstimatedPrice(new BigDecimal(21));
+        assertFalse(between.match(noMatch));
+        assertTrue(notBetween.match(noMatch));
+
+        Painting match1 = new Painting();
+        match1.setEstimatedPrice(new BigDecimal(20));
+        assertTrue(between.match(match1));
+        assertFalse(notBetween.match(match1));
+
+        Painting match2 = new Painting();
+        match2.setEstimatedPrice(new BigDecimal(10));
+        assertTrue("Failed: " + between, between.match(match2));
+        assertFalse("Failed: " + notBetween, notBetween.match(match2));
+
+        Painting match3 = new Painting();
+        match3.setEstimatedPrice(new BigDecimal(11));
+        assertTrue("Failed: " + between, between.match(match3));
+        assertFalse("Failed: " + notBetween, notBetween.match(match3));
+    }
+
+    @Test
+    public void testEvaluateBETWEEN_Null() throws Exception {
+        Expression btNull = new ASTBetween(new ASTObjPath("estimatedPrice"), new BigDecimal(10d), new BigDecimal(20d));
+        Expression btNotNull = new ASTNotBetween(new ASTObjPath("estimatedPrice"), new BigDecimal(10d),
+                new BigDecimal(20d));
+
+        Painting noMatch = new Painting();
+        assertFalse(btNull.match(noMatch));
+        assertFalse(btNotNull.match(noMatch));
+    }
+
+    @Test
+    public void testEvaluateIN() throws Exception {
+        Expression in = new ASTIn(new ASTObjPath("estimatedPrice"), new ASTList(new Object[] { new BigDecimal("10"),
+                new BigDecimal("20") }));
+
+        Expression notIn = new ASTNotIn(new ASTObjPath("estimatedPrice"), new ASTList(new Object[] {
+                new BigDecimal("10"), new BigDecimal("20") }));
+
+        Painting noMatch1 = new Painting();
+        noMatch1.setEstimatedPrice(new BigDecimal("21"));
+        assertFalse(in.match(noMatch1));
+        assertTrue(notIn.match(noMatch1));
+
+        Painting noMatch2 = new Painting();
+        noMatch2.setEstimatedPrice(new BigDecimal("11"));
+        assertFalse("Failed: " + in, in.match(noMatch2));
+        assertTrue("Failed: " + notIn, notIn.match(noMatch2));
+
+        Painting match1 = new Painting();
+        match1.setEstimatedPrice(new BigDecimal("20"));
+        assertTrue(in.match(match1));
+        assertFalse(notIn.match(match1));
+
+        Painting match2 = new Painting();
+        match2.setEstimatedPrice(new BigDecimal("10"));
+        assertTrue("Failed: " + in, in.match(match2));
+        assertFalse("Failed: " + notIn, notIn.match(match2));
+    }
+
+    @Test
+    public void testEvaluateIN_Null() throws Exception {
+        Expression in = new ASTIn(new ASTObjPath("estimatedPrice"), new ASTList(new Object[] {
+                new BigDecimal("10"), new BigDecimal("20") }));
+        Expression notIn = new ASTNotIn(new ASTObjPath("estimatedPrice"), new ASTList(new Object[] {
+                new BigDecimal("10"), new BigDecimal("20") }));
+
+        Painting noMatch = new Painting();
+        assertFalse(in.match(noMatch));
+        assertFalse(notIn.match(noMatch));
+    }
+
+    @Test
+    public void testEvaluateLIKE1() throws Exception {
+        Expression like = new ASTLike(new ASTObjPath("artistName"), "abc%d");
+        Expression notLike = new ASTNotLike(new ASTObjPath("artistName"), "abc%d");
+
+        Artist noMatch = new Artist();
+        noMatch.setArtistName("dabc");
+        assertFalse(like.match(noMatch));
+        assertTrue(notLike.match(noMatch));
+
+        Artist match1 = new Artist();
+        match1.setArtistName("abc123d");
+        assertTrue("Failed: " + like, like.match(match1));
+        assertFalse("Failed: " + notLike, notLike.match(match1));
+
+        Artist match2 = new Artist();
+        match2.setArtistName("abcd");
+        assertTrue("Failed: " + like, like.match(match2));
+        assertFalse("Failed: " + notLike, notLike.match(match2));
+    }
+
+    @Test
+    public void testEvaluateLIKE2() throws Exception {
+        Expression like = new ASTLike(new ASTObjPath("artistName"), "abc?d");
+        Expression notLike = new ASTNotLike(new ASTObjPath("artistName"), "abc?d");
+
+        Artist noMatch1 = new Artist();
+        noMatch1.setArtistName("dabc");
+        assertFalse(like.match(noMatch1));
+        assertTrue(notLike.match(noMatch1));
+
+        Artist noMatch2 = new Artist();
+        noMatch2.setArtistName("abc123d");
+        assertFalse("Failed: " + like, like.match(noMatch2));
+        assertTrue("Failed: " + notLike, notLike.match(noMatch2));
+
+        Artist match = new Artist();
+        match.setArtistName("abcXd");
+        assertTrue("Failed: " + like, like.match(match));
+        assertFalse("Failed: " + notLike, notLike.match(match));
+    }
+
+    @Test
+    public void testEvaluateLIKE3() throws Exception {
+        // test special chars
+        Expression like = new ASTLike(new ASTObjPath("artistName"), "/./");
+
+        Artist noMatch1 = new Artist();
+        noMatch1.setArtistName("/a/");
+        assertFalse(like.match(noMatch1));
+
+        Artist match = new Artist();
+        match.setArtistName("/./");
+        assertTrue("Failed: " + like, like.match(match));
+    }
+
+    @Test
+    public void testEvaluateLIKE_IGNORE_CASE() throws Exception {
+        Expression like = new ASTLikeIgnoreCase(new ASTObjPath("artistName"), "aBcD");
+        Expression notLike = new ASTNotLikeIgnoreCase(new ASTObjPath("artistName"), "aBcD");
+
+        Artist noMatch1 = new Artist();
+        noMatch1.setArtistName("dabc");
+        assertFalse(like.match(noMatch1));
+        assertTrue(notLike.match(noMatch1));
+
+        Artist match1 = new Artist();
+        match1.setArtistName("abcd");
+        assertTrue("Failed: " + like, like.match(match1));
+        assertFalse("Failed: " + notLike, notLike.match(match1));
+
+        Artist match2 = new Artist();
+        match2.setArtistName("ABcD");
+        assertTrue("Failed: " + like, like.match(match2));
+        assertFalse("Failed: " + notLike, notLike.match(match2));
+    }
+
+    @Test
+    public void testEvaluateADD() throws Exception {
+        Expression add = new ASTAdd(new Object[] { new Integer(1), new Double(5.5) });
+        assertEquals(6.5, ((Number) add.evaluate(null)).doubleValue(), 0.0001);
+    }
+
+    @Test
+    public void testEvaluateSubtract() throws Exception {
+        Expression subtract = new ASTSubtract(new Object[] { new Integer(1), new Double(0.1), new Double(0.2) });
+        assertEquals(0.7, ((Number) subtract.evaluate(null)).doubleValue(), 0.0001);
+    }
+
+    @Test
+    public void testEvaluateMultiply() throws Exception {
+        Expression multiply = new ASTMultiply(new Object[] { new Integer(2), new Double(3.5) });
+        assertEquals(7, ((Number) multiply.evaluate(null)).doubleValue(), 0.0001);
+    }
+
+    @Test
+    public void testEvaluateDivide() throws Exception {
+        Expression divide = new ASTDivide(new Object[] { new BigDecimal("7.0"), new BigDecimal("2.0") });
+        assertEquals(3.5, ((Number) divide.evaluate(null)).doubleValue(), 0.0001);
+    }
+
+    @Test
+    public void testEvaluateNegate() throws Exception {
+        assertEquals(-3, ((Number) new ASTNegate(new Integer(3)).evaluate(null)).intValue());
+        assertEquals(5, ((Number) new ASTNegate(new Integer(-5)).evaluate(null)).intValue());
+    }
+
+    @Test
+    public void testEvaluateTrue() throws Exception {
+        assertEquals(Boolean.TRUE, new ASTTrue().evaluate(null));
+    }
+
+    @Test
+    public void testEvaluateFalse() throws Exception {
+        assertEquals(Boolean.FALSE, new ASTFalse().evaluate(null));
+    }
+
+    public void testEvaluateNullCompare() throws Exception {
+    	assertFalse(new ASTGreater(new ASTObjPath("artistName"), "A").match(new Artist()));
+    }
+
+    public void testEvaluateCompareNull() throws Exception {
+    	Artist artist = new Artist();
+    	Artist a1 = artist;
+		a1.setArtistName("Name");
+		Expression expression = new ASTGreater(new ASTObjPath("artistName"), null);
+		assertFalse(expression.match(a1));
+		assertFalse(expression.notExp().match(a1));
+    }
+
+    public void testEvaluateEqualsNull() throws Exception {
+    	Artist a1 = new Artist();
+    	Expression isNull = Artist.ARTIST_NAME.isNull();
+		assertTrue(isNull.match(a1));
+		assertFalse(isNull.notExp().match(a1));
+    }
+
+    public void testEvaluateEqualsNullColumn() throws Exception {
+    	Expression equals = Expression.fromString("artistName = someOtherProperty");
+		assertFalse(equals.match(new Artist()));
+    }
+
+    public void testEvaluatNotEqualsNullColumn() throws Exception {
+    	Expression notEquals = Expression.fromString("artistName <> someOtherProperty");
+    	assertFalse(notEquals.match(new Artist()));
+    }
+
+    public void testNullAnd() {
+        Expression nullExp = Expression.fromString("null > 0");
+
+        ASTAnd nullAndTrue = new ASTAnd(new Object[] {nullExp, new ASTTrue()});
+        assertFalse(nullAndTrue.match(null));
+        assertFalse(nullAndTrue.notExp().match(null));
+
+        ASTAnd nullAndFalse = new ASTAnd(new Object[] {nullExp, new ASTFalse()});
+        assertFalse(nullAndFalse.match(null));
+        assertTrue(nullAndFalse.notExp().match(null));
+    }
+
+    public void testNullOr() {
+        Expression nullExp = Expression.fromString("null > 0");
+
+        ASTOr nullOrTrue = new ASTOr(new Object[] {nullExp, new ASTTrue()});
+        assertTrue(nullOrTrue.match(null));
+        assertFalse(nullOrTrue.notExp().match(null));
+
+        ASTOr nullOrFalse = new ASTOr(new Object[] {nullExp, new ASTFalse()});
+        assertFalse(nullOrFalse.match(null));
+        assertFalse(nullOrFalse.notExp().match(null));
+    }
+}


[2/6] cayenne git commit: Fix flattened attributes

Posted by jo...@apache.org.
Fix flattened attributes


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/b93a51c6
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/b93a51c6
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/b93a51c6

Branch: refs/heads/ics11
Commit: b93a51c61ee491b9d9e188dadc0d6606f264a28b
Parents: 34bdd84
Author: John Huss <jo...@apache.org>
Authored: Fri Jan 31 17:00:47 2014 -0600
Committer: John Huss <jo...@apache.org>
Committed: Wed Jun 24 11:40:12 2015 -0500

----------------------------------------------------------------------
 .../cayenne/access/DataNodeSyncQualifierDescriptor.java      | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/b93a51c6/cayenne-server/src/main/java/org/apache/cayenne/access/DataNodeSyncQualifierDescriptor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/DataNodeSyncQualifierDescriptor.java b/cayenne-server/src/main/java/org/apache/cayenne/access/DataNodeSyncQualifierDescriptor.java
index 4d037ac..9f117da 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/DataNodeSyncQualifierDescriptor.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/DataNodeSyncQualifierDescriptor.java
@@ -109,7 +109,7 @@ class DataNodeSyncQualifierDescriptor {
 
                             public Object transform(Object input) {
                                 ObjectId id = (ObjectId) ((ObjectDiff) input).getNodeId();
-                                return id.getIdSnapshot().get(dbAttrPair.getSourceName());
+                                return id.getIdSnapshot().get(dbAttrPair.getTargetName());
                             }
                         });
                     }
@@ -117,16 +117,16 @@ class DataNodeSyncQualifierDescriptor {
             }
         }
 
-        if (usingOptimisticLocking) {
+        if (descriptor.isMaster() && usingOptimisticLocking) {
 
             for (final ObjAttribute attribute : descriptor.getEntity().getAttributes()) {
 
-                if (attribute.isUsedForLocking()) {
+                if (attribute.isUsedForLocking() && !attribute.isFlattened()) {
                     // only care about first step in a flattened attribute
                     DbAttribute dbAttribute = (DbAttribute) attribute
                             .getDbPathIterator()
                             .next();
-
+                    
                     if (!attributes.contains(dbAttribute)) {
                         attributes.add(dbAttribute);
 


[6/6] cayenne git commit: Fix unnecessary fetching of relationships when optimistic locking is enabled for an entity, but not for all its relationships

Posted by jo...@apache.org.
Fix unnecessary fetching of relationships when optimistic locking is enabled for an entity, but not for all its relationships


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/6b73bad7
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/6b73bad7
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/6b73bad7

Branch: refs/heads/ics11
Commit: 6b73bad7888f213ad609dab78b81236e61c8adec
Parents: 4d9a66c
Author: John Huss <jo...@apache.org>
Authored: Mon Jun 13 11:00:33 2016 -0500
Committer: John Huss <jo...@apache.org>
Committed: Mon Jun 13 11:00:33 2016 -0500

----------------------------------------------------------------------
 .../src/main/java/org/apache/cayenne/access/ObjectDiff.java   | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/6b73bad7/cayenne-server/src/main/java/org/apache/cayenne/access/ObjectDiff.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/ObjectDiff.java b/cayenne-server/src/main/java/org/apache/cayenne/access/ObjectDiff.java
index f85d482..f54c905 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/ObjectDiff.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/ObjectDiff.java
@@ -88,7 +88,7 @@ class ObjectDiff extends NodeDiff {
         if (state == PersistenceState.COMMITTED || state == PersistenceState.DELETED
                 || state == PersistenceState.MODIFIED) {
 
-            ObjEntity entity = entityResolver.getObjEntity(entityName);
+            final ObjEntity entity = entityResolver.getObjEntity(entityName);
             final boolean lock = entity.getLockType() == ObjEntity.LOCK_TYPE_OPTIMISTIC;
 
             this.snapshot = new HashMap<String, Object>();
@@ -109,9 +109,10 @@ class ObjectDiff extends NodeDiff {
 
                 @Override
                 public boolean visitToOne(ToOneProperty property) {
-
+                    boolean isUsedForLocking = entity.getRelationship(property.getName()).isUsedForLocking();
+                    
                     // eagerly resolve optimistically locked relationships
-                    Object target = lock ? property.readProperty(object) : property.readPropertyDirectly(object);
+                    Object target = isUsedForLocking ? property.readProperty(object) : property.readPropertyDirectly(object);
 
                     if (target instanceof Persistent) {
                         target = ((Persistent) target).getObjectId();


[4/6] cayenne git commit: CAY-2064 Issue with BeanAccessor for classes with complex inheritance

Posted by jo...@apache.org.
CAY-2064 Issue with BeanAccessor for classes with complex inheritance


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/b0619b49
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/b0619b49
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/b0619b49

Branch: refs/heads/ics11
Commit: b0619b49db594557110585d28c1e56c81a57ab48
Parents: 7e0f801
Author: David Feshbach <dj...@gmail.com>
Authored: Thu Feb 25 16:26:24 2016 -0600
Committer: David Feshbach <dj...@gmail.com>
Committed: Fri Feb 26 11:25:29 2016 -0600

----------------------------------------------------------------------
 .../apache/cayenne/reflect/BeanAccessor.java    | 67 ++++++++++++++------
 .../cayenne/reflect/BeanAccessorTest.java       | 16 +++++
 .../apache/cayenne/reflect/TstHasRelated.java   | 24 +++++++
 .../cayenne/reflect/TstJavaBeanChild.java       | 24 +++++++
 4 files changed, 113 insertions(+), 18 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/b0619b49/cayenne-server/src/main/java/org/apache/cayenne/reflect/BeanAccessor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/reflect/BeanAccessor.java b/cayenne-server/src/main/java/org/apache/cayenne/reflect/BeanAccessor.java
index bb5db57..ec30df0 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/reflect/BeanAccessor.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/reflect/BeanAccessor.java
@@ -53,30 +53,61 @@ public class BeanAccessor implements Accessor {
 		this.nullValue = PropertyUtils.defaultNullValueForType(propertyType);
 
 		String capitalized = Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);
-
-		try {
-			this.readMethod = objectClass.getMethod("get" + capitalized);
-		} catch (NoSuchMethodException e) {
-
-			// try boolean
-			try {
-				Method readMethod = objectClass.getMethod("is" + capitalized);
-				this.readMethod = (readMethod.getReturnType().equals(Boolean.TYPE)) ? readMethod : null;
-			} catch (NoSuchMethodException e1) {
-				// not readable...
+		String isGetterName = "is" + capitalized;
+		String getGetterName = "get" + capitalized;
+		String setterName = "set" + capitalized;
+
+		Method[] publicMethods = objectClass.getMethods();
+
+		Method getter = null;
+		for (Method method : publicMethods) {
+			Class<?> returnType = method.getReturnType();
+			// following Java Bean naming conventions, "is" methods are preferred over "get" methods
+			if (method.getName().equals(isGetterName) && returnType.equals(Boolean.TYPE) && method.getParameterTypes().length == 0) {
+				getter = method;
+				break;
+			}
+			// Find the method with the most specific return type.
+			// This is the same behavior as Class.getMethod(String, Class...) except that
+			// Class.getMethod prefers synthetic methods generated for interfaces
+			// over methods with more specific return types in a super class.
+			if (method.getName().equals(getGetterName) && method.getParameterTypes().length == 0) {
+				if (returnType.isPrimitive()) {
+					getter = returnType.equals(Void.TYPE) ? null : method;
+					if (returnType.equals(Boolean.TYPE)) {
+						// keep looking for the "is" method
+						continue;
+					} else {
+						// nothing more specific than a primitive, so stop here
+						break;
+					}
+				}
+				if (getter == null || getter.getReturnType().isAssignableFrom(returnType)) {
+					getter = method;
+				}
 			}
 		}
 
-		if (readMethod == null) {
-			throw new IllegalArgumentException("Property '" + propertyName + "' is not readbale");
+		if (getter == null) {
+			throw new IllegalArgumentException("Property '" + propertyName + "' is not readable");
 		}
 
+		this.readMethod = getter;
+
 		// TODO: compare 'propertyType' arg with readMethod.getReturnType()
 
-		try {
-			this.writeMethod = objectClass.getMethod("set" + capitalized, readMethod.getReturnType());
-		} catch (NoSuchMethodException e) {
-			// read-only is supported...
+		for (Method method : publicMethods) {
+			if (!method.getName().equals(setterName) || !method.getReturnType().equals(Void.TYPE)) {
+				continue;
+			}
+			Class<?>[] parameterTypes = method.getParameterTypes();
+			if (parameterTypes.length != 1) {
+				continue;
+			}
+			if (getter.getReturnType().isAssignableFrom(parameterTypes[0])) {
+				this.writeMethod = method;
+				break;
+			}
 		}
 	}
 
@@ -118,7 +149,7 @@ public class BeanAccessor implements Accessor {
 		try {
 			writeMethod.invoke(object, newValue);
 		} catch (Throwable th) {
-			throw new PropertyException("Error reading property: " + propertyName, this, object, th);
+			throw new PropertyException("Error writing property: " + propertyName, this, object, th);
 		}
 	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/b0619b49/cayenne-server/src/test/java/org/apache/cayenne/reflect/BeanAccessorTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/reflect/BeanAccessorTest.java b/cayenne-server/src/test/java/org/apache/cayenne/reflect/BeanAccessorTest.java
index 9f62fb5..9129aaf 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/reflect/BeanAccessorTest.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/reflect/BeanAccessorTest.java
@@ -81,4 +81,20 @@ public class BeanAccessorTest {
         assertEquals("Incorrectly set null default", 0, o1.getIntField());
     }
 
+    @Test
+    public void testInheritedCovariantProperty() {
+
+    	BeanAccessor accessor = new BeanAccessor(
+    			TstJavaBeanChild.class,
+    			"related",
+    			null);
+
+    	TstJavaBeanChild o1 = new TstJavaBeanChild();
+
+    	assertNull(o1.getRelated());
+    	accessor.setValue(o1, o1);
+    	assertSame(o1, o1.getRelated());
+    	assertSame(o1, accessor.getValue(o1));
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/b0619b49/cayenne-server/src/test/java/org/apache/cayenne/reflect/TstHasRelated.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/reflect/TstHasRelated.java b/cayenne-server/src/test/java/org/apache/cayenne/reflect/TstHasRelated.java
new file mode 100644
index 0000000..8ce9302
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/reflect/TstHasRelated.java
@@ -0,0 +1,24 @@
+/*****************************************************************
+ *   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.reflect;
+
+public interface TstHasRelated {
+	Object getRelated();
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/b0619b49/cayenne-server/src/test/java/org/apache/cayenne/reflect/TstJavaBeanChild.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/reflect/TstJavaBeanChild.java b/cayenne-server/src/test/java/org/apache/cayenne/reflect/TstJavaBeanChild.java
new file mode 100644
index 0000000..4a0060e
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/reflect/TstJavaBeanChild.java
@@ -0,0 +1,24 @@
+/*****************************************************************
+ *   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.reflect;
+
+public class TstJavaBeanChild extends TstJavaBean implements TstHasRelated {
+
+}


[5/6] cayenne git commit: Expression.filterObjects needs to always return a new mutable collection to avoid blindsiding clients

Posted by jo...@apache.org.
Expression.filterObjects needs to always return a new mutable collection to avoid blindsiding clients


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/4d9a66c6
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/4d9a66c6
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/4d9a66c6

Branch: refs/heads/ics11
Commit: 4d9a66c632bd9b320513f8e284d5ca3040121239
Parents: b0619b4
Author: John Huss <jo...@apache.org>
Authored: Tue Sep 15 11:37:37 2015 -0500
Committer: John Huss <jo...@apache.org>
Committed: Fri Mar 11 11:32:36 2016 -0600

----------------------------------------------------------------------
 .../java/org/apache/cayenne/exp/Expression.java | 1104 +++++++++---------
 .../org/apache/cayenne/CayenneDataObjectIT.java |   19 +-
 2 files changed, 568 insertions(+), 555 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/4d9a66c6/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java
index 0c8812d..562b0f7 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java
@@ -46,221 +46,221 @@ 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;
-
-    /**
+	/**
+	 * 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;
+	 */
+	public static final int BITWISE_LEFT_SHIFT = 43;
 
-    /**
+	/**
 	 * @since 4.0
-     */
-    public static final int BITWISE_RIGHT_SHIFT = 44;
+	 */
+	public static final int BITWISE_RIGHT_SHIFT = 44;
 
-    protected int type;
+	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
+	/**
+	 * 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) {
+	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";
-        default:
-            return "other";
-        }
-    }
+	/**
+	 * 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();
 
-    @Override
-    public boolean equals(Object object) {
-        if (!(object instanceof Expression)) {
-            return false;
-        }
+	/**
+	 * 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";
+		default:
+			return "other";
+		}
+	}
 
-        Expression e = (Expression) object;
+	@Override
+	public boolean equals(Object object) {
+		if (!(object instanceof Expression)) {
+			return false;
+		}
 
-        if (e.getType() != getType() || e.getOperandCount() != getOperandCount()) {
-            return false;
-        }
+		Expression e = (Expression) object;
 
-        // compare operands
-        int len = e.getOperandCount();
-        for (int i = 0; i < len; i++) {
-            if (!Util.nullSafeEquals(e.getOperand(i), getOperand(i))) {
-                return false;
-            }
-        }
+		if (e.getType() != getType() || e.getOperandCount() != getOperandCount()) {
+			return false;
+		}
 
-        return true;
-    }
+		// compare operands
+		int len = e.getOperandCount();
+		for (int i = 0; i < len; i++) {
+			if (!Util.nullSafeEquals(e.getOperand(i), getOperand(i))) {
+				return false;
+			}
+		}
 
-    /**
-     * Returns a type of expression. Most common types are defined as public
-     * static fields of this interface.
-     */
-    public int getType() {
-        return type;
-    }
+		return true;
+	}
 
-    public void setType(int type) {
-        this.type = type;
-    }
+	/**
+	 * 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
@@ -322,387 +322,387 @@ public abstract class Expression implements Serializable, XMLSerializable {
 	}
 
 	/**
-     * A shortcut for <code>expWithParams(params, true)</code>.
+	 * 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);
-    }
+	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.
+	/**
+	 * 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 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.
-     * 
+	/**
+	 * 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;
-    }
+	 */
+	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 another expression using "and".
+	 */
+	public Expression andExp(Expression exp) {
+		return joinExp(Expression.AND, exp);
+	}
 
-    /**
-     * Chains this expression with other expressions using "and".
-     * 
+	/**
+	 * Chains this expression with other expressions using "and".
+	 * 
 	 * @since 4.0
-     */
-    public Expression andExp(Expression exp, Expression... expressions) {
-        return joinExp(Expression.AND, exp, expressions);
-    }
+	 */
+	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 another expression using "or".
+	 */
+	public Expression orExp(Expression exp) {
+		return joinExp(Expression.OR, exp);
+	}
 
-    /**
-     * Chains this expression with other expressions using "or".
-     * 
+	/**
+	 * Chains this expression with other expressions using "or".
+	 * 
 	 * @since 4.0
-     */
-    public Expression orExp(Expression exp, Expression... expressions) {
-        return joinExp(Expression.OR, exp, expressions);
-    }
+	 */
+	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 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) {
+	/**
+	 * 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) {
         try {
-            return ConversionUtil.toBoolean(evaluate(o));
+		return ConversionUtil.toBoolean(evaluate(o));
         } catch (ExpressionException e) {
         	if (e.getCause() instanceof UnsupportedOperationException) {
             return false;
-        }
+	}
             throw e;
         }
     }
 
-    /**
-     * 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;
-            }
-        }
+	/**
+	 * 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;
-    }
+		return null;
+	}
 
-    /**
-     * Returns a list of objects that match the expression.
-     */
+	/**
+	 * 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 Collections.emptyList();
-        }
+	public <T> List<T> filterObjects(Collection<T> objects) {
+		if (objects == null || objects.size() == 0) {
+			return new LinkedList<T>(); // returning Collections.emptyList() could cause random client exceptions if they try to mutate the resulting list
+		}
 
-        return (List<T>) filter(objects, new LinkedList<T>());
-    }
+		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);
-            }
-        }
+	/**
+	 * 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;
-    }
+		return target;
+	}
 
-    /**
-     * Clones this expression.
-     * 
-     * @since 1.1
-     */
-    public Expression deepCopy() {
-        return transform(null);
-    }
+	/**
+	 * 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.");
-        }
+	/**
+	 * Creates a copy of this expression node, without copying children.
+	 * 
+	 * @since 1.1
+	 */
+	public abstract Expression shallowCopy();
 
-        traverse(null, visitor);
-    }
+	/**
+	 * 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);
 
-    /**
-     * 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) {
-                Expression childExp = (Expression) child;
-                childExp.traverse(this, visitor);
-            } else {
-                visitor.objectNode(child, this);
-            }
-
-            visitor.finishedChild(this, i, i < count - 1);
-        }
+	/**
+	 * Restructures expression to make sure that there are no children of the
+	 * same type as this expression.
+	 * 
+	 * @since 1.1
+	 */
+	protected abstract void flattenTree();
 
-        visitor.endNode(this, parentExp);
-    }
+	/**
+	 * 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.");
+		}
 
-    /**
-     * 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;
-        }
+		traverse(null, visitor);
+	}
 
-        throw new ExpressionException("Invalid transformed expression: " + transformed);
-    }
+	/**
+	 * Traverses itself and child expressions, notifying visitor via callback
+	 * methods as it goes.
+	 * 
+	 * @since 1.1
+	 */
+	protected void traverse(Expression parentExp, TraversalHandler visitor) {
 
-    /**
-     * 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;
-            }
-        }
+		visitor.startNode(this, parentExp);
 
-        // all the children are processed, only now transform this copy
-        return (transformer != null) ? (Expression) transformer.transform(copy) : copy;
-    }
+		// recursively traverse each child
+		int count = getOperandCount();
+		for (int i = 0; i < count; i++) {
+			Object child = getOperand(i);
 
-    /**
-     * 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("]]>");
-    }
+			if (child instanceof Expression) {
+				Expression childExp = (Expression) child;
+				childExp.traverse(this, visitor);
+			} else {
+				visitor.objectNode(child, this);
+			}
+
+			visitor.finishedChild(this, i, i < count - 1);
+		}
+
+		visitor.endNode(this, parentExp);
+	}
 
-    /**
-     * Stores a String representation of Expression using a provided
-     * PrintWriter.
-     * 
-     * @since 1.1
+	/**
+	 * 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);
+	 */
+	@Deprecated
+	public abstract void encodeAsString(PrintWriter pw);
 
-    /**
-     * Appends own content as a String to the provided Appendable.
-     * 
+	/**
+	 * 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
+	 * @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.
-     * 
+	 */
+	@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
 	 */
@@ -723,23 +723,23 @@ public abstract class Expression implements Serializable, XMLSerializable {
 	 * EJBQL.
 	 * 
 	 * @since 4.0
-     * @throws IOException
-     */
+	 * @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();
-    }
+	@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
@@ -749,16 +749,16 @@ public abstract class Expression implements Serializable, XMLSerializable {
 	 * Expression as EJBQL.
 	 * 
 	 * @since 3.1
-     */
+	 */
 	public String toEJBQL(List<Object> parameterAccumulator, String rootId) {
-        StringBuilder out = new StringBuilder();
-        try {
+		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();
-    }
+		} 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

http://git-wip-us.apache.org/repos/asf/cayenne/blob/4d9a66c6/cayenne-server/src/test/java/org/apache/cayenne/CayenneDataObjectIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/CayenneDataObjectIT.java b/cayenne-server/src/test/java/org/apache/cayenne/CayenneDataObjectIT.java
index 8efccd7..01c39b5 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/CayenneDataObjectIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/CayenneDataObjectIT.java
@@ -19,9 +19,7 @@
 
 package org.apache.cayenne;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
+import static org.junit.Assert.*;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -163,4 +161,19 @@ public class CayenneDataObjectIT extends ServerCase {
 		List<Painting> rezult = exp.filterObjects(paintingList);
 		assertEquals(a1, rezult.get(0).getToArtist());
 	}
+	
+	@Test
+	public void testFilterObjectsResultIsMutable() {
+
+		List<Artist> artistList = new ArrayList<Artist>();
+		Artist a = context.newObject(Artist.class);
+		a.setArtistName("Pablo");
+
+		Expression exp = ExpressionFactory.matchExp("artistName", "Mismatch");
+
+		List<Artist> result = exp.filterObjects(artistList);
+		assertTrue(result.isEmpty());
+		result.add(a); // list should be mutable
+		assertTrue(!result.isEmpty());
+	}
 }


[3/6] cayenne git commit: Allow specifying multiple comma-separated DataDomain locations for CayenneFilter (web.xml)

Posted by jo...@apache.org.
Allow specifying multiple comma-separated DataDomain locations for CayenneFilter (web.xml)


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/7e0f8011
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/7e0f8011
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/7e0f8011

Branch: refs/heads/ics11
Commit: 7e0f8011e63abc869abc65b297b9c146c96a8e61
Parents: b30e0fa
Author: John Huss <jo...@apache.org>
Authored: Mon Oct 6 16:43:45 2014 -0500
Committer: John Huss <jo...@apache.org>
Committed: Wed Jun 24 11:42:25 2015 -0500

----------------------------------------------------------------------
 .../org/apache/cayenne/configuration/web/CayenneFilter.java    | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/7e0f8011/cayenne-server/src/main/java/org/apache/cayenne/configuration/web/CayenneFilter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/configuration/web/CayenneFilter.java b/cayenne-server/src/main/java/org/apache/cayenne/configuration/web/CayenneFilter.java
index 12226a1..d218f79 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/configuration/web/CayenneFilter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/configuration/web/CayenneFilter.java
@@ -70,11 +70,15 @@ public class CayenneFilter implements Filter {
         WebConfiguration configAdapter = new WebConfiguration(config);
 
         String configurationLocation = configAdapter.getConfigurationLocation();
+        String[] configurationLocations = null;
+        if (configurationLocation != null) {
+        	configurationLocations = configurationLocation.split(",\\s*");
+        }
         Collection<Module> modules = configAdapter.createModules(new WebModule());
         modules.addAll(getAdditionalModules());
         
         ServerRuntime runtime = new ServerRuntime(
-                configurationLocation,
+                configurationLocations,
                 modules.toArray(new Module[modules.size()]));
 
         WebUtil.setCayenneRuntime(config.getServletContext(), runtime);