cayenne-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From johnth...@apache.org
Subject [5/6] cayenne git commit: Expression.filterObjects needs to always return a new mutable collection to avoid blindsiding clients
Date Tue, 09 Aug 2016 16:30:34 GMT
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 <johnthuss@apache.org>
Authored: Tue Sep 15 11:37:37 2015 -0500
Committer: John Huss <johnthuss@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());
+	}
 }


Mime
View raw message