cayenne-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ntimof...@apache.org
Subject [3/6] cayenne git commit: CAY-2467 New type-aware Property API core API
Date Wed, 26 Dec 2018 12:19:07 GMT
CAY-2467 New type-aware Property API
  core API


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

Branch: refs/heads/master
Commit: 2050c2e1fade582edf37e75bff2b09ffc9de4ead
Parents: bf7dba9
Author: Nikita Timofeev <stariy95@gmail.com>
Authored: Wed Dec 26 14:59:22 2018 +0300
Committer: Nikita Timofeev <stariy95@gmail.com>
Committed: Wed Dec 26 14:59:22 2018 +0300

----------------------------------------------------------------------
 .../cayenne/query/ClientExpressionIT.java       |   2 +-
 .../select/DefaultSelectTranslator.java         |  12 +-
 .../apache/cayenne/exp/ExpressionFactory.java   |  52 +-
 .../java/org/apache/cayenne/exp/Property.java   | 689 ++++---------------
 .../org/apache/cayenne/exp/parser/ASTAdd.java   |   2 +-
 .../apache/cayenne/exp/parser/ASTDivide.java    |   2 +-
 .../org/apache/cayenne/exp/parser/ASTLike.java  |   4 +-
 .../cayenne/exp/parser/ASTLikeIgnoreCase.java   |   4 +-
 .../apache/cayenne/exp/parser/ASTMultiply.java  |   2 +-
 .../apache/cayenne/exp/parser/ASTNotLike.java   |   4 +-
 .../exp/parser/ASTNotLikeIgnoreCase.java        |   4 +-
 .../apache/cayenne/exp/parser/ASTSubtract.java  |   2 +-
 .../cayenne/exp/parser/PatternMatchNode.java    |  38 +-
 .../cayenne/exp/property/BaseProperty.java      | 389 +++++++++++
 .../exp/property/CollectionProperty.java        | 203 ++++++
 .../exp/property/ComparableProperty.java        | 126 ++++
 .../cayenne/exp/property/DateProperty.java      | 140 ++++
 .../cayenne/exp/property/EntityProperty.java    |  71 ++
 .../cayenne/exp/property/ListProperty.java      |  67 ++
 .../cayenne/exp/property/MapProperty.java       | 233 +++++++
 .../cayenne/exp/property/NumericProperty.java   | 186 +++++
 .../apache/cayenne/exp/property/Property.java   |  45 ++
 .../cayenne/exp/property/PropertyFactory.java   | 375 ++++++++++
 .../exp/property/RelationshipProperty.java      | 158 +++++
 .../cayenne/exp/property/SetProperty.java       |  63 ++
 .../cayenne/exp/property/StringProperty.java    | 304 ++++++++
 .../cayenne/exp/property/package-info.java      |  65 ++
 .../org/apache/cayenne/query/ColumnSelect.java  |  60 +-
 .../org/apache/cayenne/query/ObjectSelect.java  |  74 +-
 .../org/apache/cayenne/query/SelectQuery.java   |  15 +-
 .../cayenne/query/SelectQueryMetadata.java      |  10 +-
 .../org/apache/cayenne/exp/ExpressionTest.java  |   2 +-
 .../exp/FunctionExpressionFactoryTest.java      |  74 +-
 .../org/apache/cayenne/exp/PropertyTest.java    |   6 +-
 .../cayenne/exp/property/BasePropertyTest.java  | 268 ++++++++
 .../cayenne/exp/property/DatePropertyTest.java  |  98 +++
 .../exp/property/EntityPropertyTest.java        |  74 ++
 .../cayenne/exp/property/ListPropertyTest.java  | 101 +++
 .../cayenne/exp/property/MapPropertyTest.java   | 158 +++++
 .../exp/property/NumericPropertyTest.java       | 247 +++++++
 .../exp/property/PropertyFactoryTest.java       | 275 ++++++++
 .../cayenne/exp/property/SetPropertyTest.java   |  75 ++
 .../exp/property/StringPropertyTest.java        | 176 +++++
 43 files changed, 4250 insertions(+), 705 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/2050c2e1/cayenne-client/src/test/java/org/apache/cayenne/query/ClientExpressionIT.java
----------------------------------------------------------------------
diff --git a/cayenne-client/src/test/java/org/apache/cayenne/query/ClientExpressionIT.java b/cayenne-client/src/test/java/org/apache/cayenne/query/ClientExpressionIT.java
index 7bc471d..d7a9836 100644
--- a/cayenne-client/src/test/java/org/apache/cayenne/query/ClientExpressionIT.java
+++ b/cayenne-client/src/test/java/org/apache/cayenne/query/ClientExpressionIT.java
@@ -80,7 +80,7 @@ public class ClientExpressionIT extends ClientCase {
         
         context.commitChanges();
         
-        Expression scalar = ExpressionFactory.matchExp(null, t1);
+        Expression scalar = ExpressionFactory.matchExp((String)null, t1);
         Expression list = ExpressionFactory.matchAllExp("|", Arrays.asList(t1, t2));
         
         assertEquals(t1.getObjectId(), scalar.getOperand(1));

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2050c2e1/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslator.java
index 4a2c591..4f1a10c 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslator.java
@@ -25,9 +25,9 @@ import org.apache.cayenne.access.translator.DbAttributeBinding;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.dba.QuotingStrategy;
 import org.apache.cayenne.dba.TypesMapping;
+import org.apache.cayenne.exp.property.BaseProperty;
 import org.apache.cayenne.exp.Expression;
 import org.apache.cayenne.exp.ExpressionFactory;
-import org.apache.cayenne.exp.Property;
 import org.apache.cayenne.exp.TraversalHelper;
 import org.apache.cayenne.exp.parser.ASTAggregateFunctionCall;
 import org.apache.cayenne.exp.parser.ASTDbPath;
@@ -435,7 +435,7 @@ public class DefaultSelectTranslator extends QueryAssembler implements SelectTra
 		joinListener = () -> joinTableAliasForProperty[0] = getCurrentAlias();
 		setAddBindingListener(bindingListener);
 
-		for(Property<?> property : query.getColumns()) {
+		for(BaseProperty<?> property : query.getColumns()) {
 			joinTableAliasForProperty[0] = null;
 			int expressionType = property.getExpression().getType();
 
@@ -508,7 +508,7 @@ public class DefaultSelectTranslator extends QueryAssembler implements SelectTra
 		return columns;
 	}
 
-	private int getJdbcTypeForProperty(Property<?> property) {
+	private int getJdbcTypeForProperty(BaseProperty<?> property) {
 		int expressionType = property.getExpression().getType();
 		if(expressionType == Expression.OBJ_PATH) {
 			// Scan obj path, stop as soon as DbAttribute found
@@ -542,7 +542,7 @@ public class DefaultSelectTranslator extends QueryAssembler implements SelectTra
 		return TypesMapping.getSqlTypeByJava(property.getType());
 	}
 
-	private boolean isAggregate(Property<?> property) {
+	private boolean isAggregate(BaseProperty<?> property) {
 		final boolean[] isAggregate = new boolean[1];
 		Expression exp = property.getExpression();
 		exp.traverse(new TraversalHelper() {
@@ -894,11 +894,11 @@ public class DefaultSelectTranslator extends QueryAssembler implements SelectTra
 
 	@Override
 	public String getAliasForExpression(Expression exp) {
-		Collection<Property<?>> columns = getSelectQuery().getColumns();
+		Collection<BaseProperty<?>> columns = getSelectQuery().getColumns();
 		if(columns == null) {
 			return null;
 		}
-		for(Property<?> property : columns) {
+		for(BaseProperty<?> property : columns) {
 			if(property.getExpression().equals(exp)) {
 				return property.getAlias();
 			}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2050c2e1/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
index 9be7c1c..92f45cc 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
@@ -384,7 +384,7 @@ public class ExpressionFactory {
 	 * @since 4.0
 	 * @see ExpressionFactory#matchExp(String, Object)
 	 */
-	static Expression matchExp(Expression exp, Object value) {
+	public static Expression matchExp(Expression exp, Object value) {
 		if(!(exp instanceof SimpleNode)) {
 			throw new IllegalArgumentException("exp should be instance of SimpleNode");
 		}
@@ -402,7 +402,7 @@ public class ExpressionFactory {
 	 * @since 4.0
 	 * @see ExpressionFactory#noMatchExp(String, Object)
 	 */
-	static Expression noMatchExp(Expression exp, Object value) {
+	public static Expression noMatchExp(Expression exp, Object value) {
 		if(!(exp instanceof SimpleNode)) {
 			throw new IllegalArgumentException("exp should be instance of SimpleNode");
 		}
@@ -420,7 +420,7 @@ public class ExpressionFactory {
 	 * @since 4.0
 	 * @see ExpressionFactory#lessExp(String, Object)
 	 */
-	static Expression lessExp(Expression exp, Object value) {
+	public static Expression lessExp(Expression exp, Object value) {
 		if(!(exp instanceof SimpleNode)) {
 			throw new IllegalArgumentException("exp should be instance of SimpleNode");
 		}
@@ -448,7 +448,7 @@ public class ExpressionFactory {
 	 * @since 4.0
 	 * @see ExpressionFactory#lessOrEqualExp(String, Object)
 	 */
-	static Expression lessOrEqualExp(Expression exp, Object value) {
+	public static Expression lessOrEqualExp(Expression exp, Object value) {
 		if(!(exp instanceof SimpleNode)) {
 			throw new IllegalArgumentException("exp should be instance of SimpleNode");
 		}
@@ -476,7 +476,7 @@ public class ExpressionFactory {
 	 * @since 4.0
 	 * @see ExpressionFactory#greaterExp(String, Object)
 	 */
-	static Expression greaterExp(Expression exp, Object value) {
+	public static Expression greaterExp(Expression exp, Object value) {
 		if(!(exp instanceof SimpleNode)) {
 			throw new IllegalArgumentException("exp should be instance of SimpleNode");
 		}
@@ -504,7 +504,7 @@ public class ExpressionFactory {
 	 * @since 4.0
 	 * @see ExpressionFactory#greaterOrEqualExp(String, Object)
 	 */
-	static Expression greaterOrEqualExp(Expression exp, Object value) {
+	public static Expression greaterOrEqualExp(Expression exp, Object value) {
 		if(!(exp instanceof SimpleNode)) {
 			throw new IllegalArgumentException("exp should be instance of SimpleNode");
 		}
@@ -533,7 +533,7 @@ public class ExpressionFactory {
 	 * @since 4.0
 	 * @see ExpressionFactory#inExp(String, Object[])
 	 */
-	static Expression inExp(Expression exp, Object... values) {
+	public static Expression inExp(Expression exp, Object... values) {
 		if (values.length == 0) {
 			return new ASTFalse();
 		}
@@ -566,7 +566,7 @@ public class ExpressionFactory {
 	 * @since 4.0
 	 * @see ExpressionFactory#inExp(String, Collection)
 	 */
-	static Expression inExp(Expression exp, Collection<?> values) {
+	public static Expression inExp(Expression exp, Collection<?> values) {
 		if (values.isEmpty()) {
 			return new ASTFalse();
 		}
@@ -599,7 +599,7 @@ public class ExpressionFactory {
 	 * @since 4.0
 	 * @see ExpressionFactory#notInExp(String, Collection)
 	 */
-	static Expression notInExp(Expression exp, Collection<?> values) {
+	public static Expression notInExp(Expression exp, Collection<?> values) {
 		if (values.isEmpty()) {
 			return new ASTTrue();
 		}
@@ -636,7 +636,7 @@ public class ExpressionFactory {
 	 * @since 4.0
 	 * @see ExpressionFactory#notInExp(String, Object[])
 	 */
-	static Expression notInExp(Expression exp, Object... values) {
+	public static Expression notInExp(Expression exp, Object... values) {
 		if (values.length == 0) {
 			return new ASTTrue();
 		}
@@ -670,7 +670,7 @@ public class ExpressionFactory {
 	 * @since 4.0
 	 * @see ExpressionFactory#betweenExp(String, Object, Object)
 	 */
-	static Expression betweenExp(Expression exp, Object value1, Object value2) {
+	public static Expression betweenExp(Expression exp, Object value1, Object value2) {
 		if(!(exp instanceof SimpleNode)) {
 			throw new IllegalArgumentException("exp should be instance of SimpleNode");
 		}
@@ -697,7 +697,7 @@ public class ExpressionFactory {
 	 * @since 4.0
 	 * @see ExpressionFactory#notBetweenExp(String, Object, Object)
 	 */
-	static Expression notBetweenExp(Expression exp, Object value1, Object value2) {
+	public static Expression notBetweenExp(Expression exp, Object value1, Object value2) {
 		if(!(exp instanceof SimpleNode)) {
 			throw new IllegalArgumentException("exp should be instance of SimpleNode");
 		}
@@ -724,7 +724,7 @@ public class ExpressionFactory {
 	 * @since 4.0
 	 * @see ExpressionFactory#likeExp(String, Object)
 	 */
-	static Expression likeExp(Expression exp, Object value) {
+	public static Expression likeExp(Expression exp, Object value) {
 		return likeExpInternal(exp, value, (char) 0);
 	}
 
@@ -748,7 +748,7 @@ public class ExpressionFactory {
 	 * @since 4.0
 	 * @see ExpressionFactory#likeExp(String, Object)
 	 */
-	static Expression likeExp(Expression exp, Object value, char escapeChar) {
+	public static Expression likeExp(Expression exp, Object value, char escapeChar) {
 		return likeExpInternal(exp, value, escapeChar);
 	}
 
@@ -799,7 +799,7 @@ public class ExpressionFactory {
 	 * @since 4.0
 	 * @see ExpressionFactory#notLikeExp(String, Object)
 	 */
-	static Expression notLikeExp(Expression exp, Object value) {
+	public static Expression notLikeExp(Expression exp, Object value) {
 		if(!(exp instanceof SimpleNode)) {
 			throw new IllegalArgumentException("exp should be instance of SimpleNode");
 		}
@@ -826,7 +826,7 @@ public class ExpressionFactory {
 	 * @since 4.0
 	 * @see ExpressionFactory#notLikeExp(String, Object)
 	 */
-	static Expression notLikeExp(Expression exp, Object value, char escapeChar) {
+	public static Expression notLikeExp(Expression exp, Object value, char escapeChar) {
 		if(!(exp instanceof SimpleNode)) {
 			throw new IllegalArgumentException("exp should be instance of SimpleNode");
 		}
@@ -869,7 +869,7 @@ public class ExpressionFactory {
 	 * @since 4.0
 	 * @see ExpressionFactory#likeIgnoreCaseExp(String, Object)
 	 */
-	static Expression likeIgnoreCaseExp(Expression exp, Object value) {
+	public static Expression likeIgnoreCaseExp(Expression exp, Object value) {
 		return likeIgnoreCaseExp(exp, value, (char) 0);
 	}
 
@@ -936,7 +936,7 @@ public class ExpressionFactory {
 	 * @since 4.0
 	 * @see ExpressionFactory#notLikeIgnoreCaseExp(String, Object)
 	 */
-	static Expression notLikeIgnoreCaseExp(Expression exp, Object value) {
+	public static Expression notLikeIgnoreCaseExp(Expression exp, Object value) {
 		if(!(exp instanceof SimpleNode)) {
 			throw new IllegalArgumentException("exp should be instance of SimpleNode");
 		}
@@ -963,7 +963,7 @@ public class ExpressionFactory {
 	 * @since 4.0
 	 * @see ExpressionFactory#notLikeIgnoreCaseExp(String, Object, char)
 	 */
-	static Expression notLikeIgnoreCaseExp(Expression exp, Object value, char escapeChar) {
+	public static Expression notLikeIgnoreCaseExp(Expression exp, Object value, char escapeChar) {
 		if(!(exp instanceof SimpleNode)) {
 			throw new IllegalArgumentException("exp should be instance of SimpleNode");
 		}
@@ -1010,7 +1010,7 @@ public class ExpressionFactory {
 	 * @since 4.0
 	 * @see ExpressionFactory#containsExp(String, String)
 	 */
-	static Expression containsExp(Expression exp, String value) {
+	public static Expression containsExp(Expression exp, String value) {
 		ASTLike like = likeExpInternal(exp, value, (char) 0);
 		LikeExpressionHelper.toContains(like);
 		return like;
@@ -1031,7 +1031,7 @@ public class ExpressionFactory {
 	 * @since 4.0
 	 * @see ExpressionFactory#startsWithExp(String, String)
 	 */
-	static Expression startsWithExp(Expression exp, String value) {
+	public static Expression startsWithExp(Expression exp, String value) {
 		ASTLike like = likeExpInternal(exp, value, (char) 0);
 		LikeExpressionHelper.toStartsWith(like);
 		return like;
@@ -1052,7 +1052,7 @@ public class ExpressionFactory {
 	 * @since 4.0
 	 * @see ExpressionFactory#endsWithExp(String, String)
 	 */
-	static Expression endsWithExp(Expression exp, String value) {
+	public static Expression endsWithExp(Expression exp, String value) {
 		ASTLike like = likeExpInternal(exp, value, (char) 0);
 		LikeExpressionHelper.toEndsWith(like);
 		return like;
@@ -1074,7 +1074,7 @@ public class ExpressionFactory {
 	 * @since 4.0
 	 * @see ExpressionFactory#containsIgnoreCaseExp(String, String)
 	 */
-	static Expression containsIgnoreCaseExp(Expression exp, String value) {
+	public static Expression containsIgnoreCaseExp(Expression exp, String value) {
 		ASTLikeIgnoreCase like = likeIgnoreCaseExp(exp, value, (char) 0);
 		LikeExpressionHelper.toContains(like);
 		return like;
@@ -1096,7 +1096,7 @@ public class ExpressionFactory {
 	 * @since 4.0
 	 * @see ExpressionFactory#startsWithIgnoreCaseExp(String, String)
 	 */
-	static Expression startsWithIgnoreCaseExp(Expression exp, String value) {
+	public static Expression startsWithIgnoreCaseExp(Expression exp, String value) {
 		ASTLikeIgnoreCase like = likeIgnoreCaseExp(exp, value, (char) 0);
 		LikeExpressionHelper.toStartsWith(like);
 		return like;
@@ -1118,7 +1118,7 @@ public class ExpressionFactory {
 	 * @since 4.0
 	 * @see ExpressionFactory#endsWithIgnoreCaseExp(String, String)
 	 */
-	static Expression endsWithIgnoreCaseExp(Expression exp, String value) {
+	public static Expression endsWithIgnoreCaseExp(Expression exp, String value) {
 		ASTLikeIgnoreCase like = likeIgnoreCaseExp(exp, value, (char) 0);
 		LikeExpressionHelper.toEndsWith(like);
 		return like;
@@ -1310,7 +1310,7 @@ public class ExpressionFactory {
 	 * Wrap value into ASTScalar
 	 * @since 4.0
 	 */
-	static Expression wrapScalarValue(Object value) {
+	public static Expression wrapScalarValue(Object value) {
 		return new ASTScalar(value);
 	}
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2050c2e1/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java
index 449934c..a46ec33 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java
@@ -18,19 +18,14 @@
  ****************************************************************/
 package org.apache.cayenne.exp;
 
-import java.util.ArrayList;
 import java.util.Collection;
-import java.util.List;
 import java.util.Map;
 
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.Persistent;
-import org.apache.cayenne.exp.parser.ASTPath;
-import org.apache.cayenne.query.Ordering;
-import org.apache.cayenne.query.Orderings;
-import org.apache.cayenne.query.PrefetchTreeNode;
-import org.apache.cayenne.query.SortOrder;
-import org.apache.cayenne.reflect.PropertyUtils;
+import org.apache.cayenne.exp.property.BaseProperty;
+import org.apache.cayenne.exp.property.ComparableProperty;
+import org.apache.cayenne.exp.property.RelationshipProperty;
 
 /**
  * <p>
@@ -52,9 +47,14 @@ import org.apache.cayenne.reflect.PropertyUtils;
  * @see Property#create(Expression, Class)
  * @see Property#create(String, Expression, Class)
  *
+ * @see org.apache.cayenne.exp.property.PropertyFactory
+ *
  * @since 4.0
+ * @deprecated since 4.2 in favour of type-specific set of properties, see {@link org.apache.cayenne.exp.property.PropertyFactory}
+ * and {@link org.apache.cayenne.exp.property} package.
  */
-public class Property<E> {
+@Deprecated
+public class Property<E> extends BaseProperty<E> implements ComparableProperty<E>, RelationshipProperty<E> {
 
     /**
      * <p>Property that can be used in COUNT(*) queries</p>
@@ -66,25 +66,12 @@ public class Property<E> {
      *         .select(context);
      * }</pre>
      * </p>
+     * @deprecated since 4.2 use {@link org.apache.cayenne.exp.property.PropertyFactory#COUNT}
      */
+    @Deprecated
     public static final Property<Long> COUNT = Property.create(FunctionExpressionFactory.countExp(), Long.class);
 
     /**
-     * Name of the property in the object
-     */
-    private final String name;
-
-    /**
-     * Expression provider for the property
-     */
-    private final ExpressionProvider expressionProvider;
-
-    /**
-     * Explicit type of the property
-     */
-    private final Class<? super E> type;
-
-    /**
      * Constructs a new property with the given name and type.
      *
      * @param name of the property (usually it's obj path)
@@ -92,15 +79,8 @@ public class Property<E> {
      *
      * @see Property#create(String, Class)
      */
-    protected Property(final String name, final Class<? super E> type) {
-        this.name = name;
-        expressionProvider = new ExpressionProvider() {
-            @Override
-            public Expression get() {
-                return ExpressionFactory.pathExp(name);
-            }
-        };
-        this.type = type;
+    protected Property(final String name, final Class<E> type) {
+        super(name, null, type);
     }
 
     /**
@@ -112,177 +92,8 @@ public class Property<E> {
      *
      * @see Property#create(String, Expression, Class)
      */
-    protected Property(final String name, final Expression expression, final Class<? super E> type) {
-        this.name = name;
-        expressionProvider = new ExpressionProvider() {
-            @Override
-            public Expression get() {
-                return expression.deepCopy();
-            }
-        };
-        this.type = type;
-    }
-
-    /**
-     * @return Name of the property in the object.
-     */
-    public String getName() {
-        return name;
-    }
-
-    /**
-     * @return alias for this property
-     */
-    public String getAlias() {
-        if(getName() == null) {
-            return null;
-        }
-
-        // check if default name for Path expression is overridden
-        Expression exp = getExpression();
-        if(exp instanceof ASTPath) {
-            if(((ASTPath) exp).getPath().equals(getName())) {
-                return null;
-            }
-        }
-
-        return getName();
-    }
-
-    /**
-     * This method returns fresh copy of the expression for each call.
-     * @return expression that represents this Property
-     */
-    public Expression getExpression() {
-        return expressionProvider.get();
-    }
-
-    @Override
-    public int hashCode() {
-        int result = name != null ? name.hashCode() : expressionProvider.get().hashCode();
-        if(type != null) {
-            result = 31 * result + type.hashCode();
-        }
-        return result;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-
-        Property<?> property = (Property<?>) o;
-        if (name != null ? !name.equals(property.name) : property.name != null) {
-            return false;
-        }
-        if (name == null && !expressionProvider.get().equals(property.expressionProvider.get())) {
-            return false;
-        }
-        return (type == null ? property.type == null : type.equals(property.type));
-    }
-
-    /**
-     * Constructs a property path by appending the argument to the existing property separated by a dot.
-     *
-     * @return a newly created Property object.
-     */
-    public Property<Object> dot(String property) {
-        return create(getName() + "." + property, null);
-    }
-
-    /**
-     * Constructs a new property path by appending the argument to the existing property separated by a dot.
-     *
-     * @return a newly created Property object.
-     */
-    public <T> Property<T> dot(Property<T> property) {
-        return create(getName() + "." + property.getName(), property.getType());
-    }
-
-    /**
-     * Returns a version of this property that represents an OUTER join. It is
-     * up to caller to ensure that the property corresponds to a relationship,
-     * as "outer" attributes make no sense.
-     */
-    public Property<E> outer() {
-        return isOuter() ? this : create(name + "+", type);
-    }
-
-    private boolean isOuter() {
-        return name.endsWith("+");
-    }
-
-    /**
-     * Converts this property to a path expression.
-     * This method is equivalent of getExpression() which is preferred as more generic.
-     *
-     * @return a newly created expression.
-     * @see Property#getExpression()
-     */
-    public Expression path() {
-        return getExpression();
-    }
-
-    /**
-     * @return An expression representing null.
-     */
-    public Expression isNull() {
-        return ExpressionFactory.matchExp(getExpression(), null);
-    }
-
-    /**
-     * @return An expression representing a non-null value.
-     */
-    public Expression isNotNull() {
-        return ExpressionFactory.matchExp(getExpression(), null).notExp();
-    }
-
-    /**
-     * @return An expression representing equality to TRUE.
-     */
-    public Expression isTrue() {
-        return ExpressionFactory.matchExp(getExpression(), Boolean.TRUE);
-    }
-
-    /**
-     * @return An expression representing equality to FALSE.
-     */
-    public Expression isFalse() {
-        return ExpressionFactory.matchExp(getExpression(), Boolean.FALSE);
-    }
-
-    /**
-     * @return An expression representing equality to a value.
-     */
-    public Expression eq(E value) {
-        return ExpressionFactory.matchExp(getExpression(), value);
-    }
-
-    /**
-     * @return An expression representing equality between two attributes
-     * (columns).
-     */
-    public Expression eq(Property<?> value) {
-        return ExpressionFactory.matchExp(getExpression(), value.getExpression());
-    }
-
-    /**
-     * @return An expression representing inequality to a value.
-     */
-    public Expression ne(E value) {
-        return ExpressionFactory.noMatchExp(getExpression(), value);
-    }
-
-    /**
-     * @return An expression representing inequality between two attributes
-     * (columns).
-     */
-    public Expression ne(Property<?> value) {
-        return ExpressionFactory.noMatchExp(getExpression(), value.getExpression());
+    protected Property(final String name, final Expression expression, final Class<E> type) {
+        super(name, expression, type);
     }
 
     /**
@@ -294,7 +105,7 @@ public class Property<E> {
      * @return An expression for a Database "LIKE" query.
      */
     public Expression like(String pattern) {
-        return ExpressionFactory.likeExp(getExpression(), pattern);
+        return ExpressionFactory.likeExp(path(), pattern);
     }
 
     /**
@@ -305,28 +116,28 @@ public class Property<E> {
      * @return An expression for a Database "LIKE" query.
      */
     public Expression like(String pattern, char escapeChar) {
-        return ExpressionFactory.likeExp(getExpression(), pattern, escapeChar);
+        return ExpressionFactory.likeExp(path(), pattern, escapeChar);
     }
 
     /**
      * @return An expression for a case insensitive "LIKE" query.
      */
     public Expression likeIgnoreCase(String pattern) {
-        return ExpressionFactory.likeIgnoreCaseExp(getExpression(), pattern);
+        return ExpressionFactory.likeIgnoreCaseExp(path(), pattern);
     }
 
     /**
      * @return An expression for a Database "NOT LIKE" query.
      */
     public Expression nlike(String value) {
-        return ExpressionFactory.notLikeExp(getExpression(), value);
+        return ExpressionFactory.notLikeExp(path(), value);
     }
 
     /**
      * @return An expression for a case insensitive "NOT LIKE" query.
      */
     public Expression nlikeIgnoreCase(String value) {
-        return ExpressionFactory.notLikeIgnoreCaseExp(getExpression(), value);
+        return ExpressionFactory.notLikeIgnoreCaseExp(path(), value);
     }
 
     /**
@@ -339,7 +150,7 @@ public class Property<E> {
      * @return a newly created expression.
      */
     public Expression contains(String substring) {
-        return ExpressionFactory.containsExp(getExpression(), substring);
+        return ExpressionFactory.containsExp(path(), substring);
     }
 
     /**
@@ -352,7 +163,7 @@ public class Property<E> {
      * @return a newly created expression.
      */
     public Expression startsWith(String value) {
-        return ExpressionFactory.startsWithExp(getExpression(), value);
+        return ExpressionFactory.startsWithExp(path(), value);
     }
 
     /**
@@ -365,7 +176,7 @@ public class Property<E> {
      * @return a newly created expression.
      */
     public Expression endsWith(String value) {
-        return ExpressionFactory.endsWithExp(getExpression(), value);
+        return ExpressionFactory.endsWithExp(path(), value);
     }
 
     /**
@@ -373,7 +184,7 @@ public class Property<E> {
      * comparison.
      */
     public Expression containsIgnoreCase(String value) {
-        return ExpressionFactory.containsIgnoreCaseExp(getExpression(), value);
+        return ExpressionFactory.containsIgnoreCaseExp(path(), value);
     }
 
     /**
@@ -381,7 +192,7 @@ public class Property<E> {
      * comparison.
      */
     public Expression startsWithIgnoreCase(String value) {
-        return ExpressionFactory.startsWithIgnoreCaseExp(getExpression(), value);
+        return ExpressionFactory.startsWithIgnoreCaseExp(path(), value);
     }
 
     /**
@@ -389,358 +200,49 @@ public class Property<E> {
      * comparison.
      */
     public Expression endsWithIgnoreCase(String value) {
-        return ExpressionFactory.endsWithIgnoreCaseExp(getExpression(), value);
-    }
-
-    /**
-     * @param lower The lower bound.
-     * @param upper The upper bound.
-     * @return An expression checking for objects between a lower and upper
-     * bound inclusive
-     */
-    public Expression between(E lower, E upper) {
-        return ExpressionFactory.betweenExp(getExpression(), lower, upper);
-    }
-
-    /**
-     * @return An expression for finding objects with values in the given set.
-     */
-    public Expression in(E firstValue, E... moreValues) {
-
-        int moreValuesLength = moreValues != null ? moreValues.length : 0;
-
-        Object[] values = new Object[moreValuesLength + 1];
-        values[0] = firstValue;
-
-        if (moreValuesLength > 0) {
-            System.arraycopy(moreValues, 0, values, 1, moreValuesLength);
-        }
-
-        return ExpressionFactory.inExp(getExpression(), values);
-    }
-
-    /**
-     * @return An expression for finding objects with values not in the given
-     * set.
-     */
-    public Expression nin(E firstValue, E... moreValues) {
-
-        int moreValuesLength = moreValues != null ? moreValues.length : 0;
-
-        Object[] values = new Object[moreValuesLength + 1];
-        values[0] = firstValue;
-
-        if (moreValuesLength > 0) {
-            System.arraycopy(moreValues, 0, values, 1, moreValuesLength);
-        }
-
-        return ExpressionFactory.notInExp(getExpression(), values);
-    }
-
-    /**
-     * @return An expression for finding objects with values in the given set.
-     */
-    public Expression in(Collection<E> values) {
-        return ExpressionFactory.inExp(getExpression(), values);
-    }
-
-    /**
-     * @return An expression for finding objects with values not in the given
-     * set.
-     */
-    public Expression nin(Collection<E> values) {
-        return ExpressionFactory.notInExp(getExpression(), values);
-    }
-
-    /**
-     * @return A greater than Expression.
-     */
-    public Expression gt(E value) {
-        return ExpressionFactory.greaterExp(getExpression(), value);
-    }
-
-    /**
-     * @return Represents a greater than relationship between two attributes
-     * (columns).
-     */
-    public Expression gt(Property<?> value) {
-        return ExpressionFactory.greaterExp(getExpression(), value.getExpression());
-    }
-
-    /**
-     * @return A greater than or equal to Expression.
-     */
-    public Expression gte(E value) {
-        return ExpressionFactory.greaterOrEqualExp(getExpression(), value);
-    }
-
-    /**
-     * @return Represents a greater than or equal relationship between two
-     * attributes (columns).
-     */
-    public Expression gte(Property<?> value) {
-        return ExpressionFactory.greaterOrEqualExp(getExpression(), value.getExpression());
-    }
-
-    /**
-     * @return A less than Expression.
-     */
-    public Expression lt(E value) {
-        return ExpressionFactory.lessExp(getExpression(), value);
-    }
-
-    /**
-     * @return Represents a less than relationship between two attributes
-     * (columns).
-     */
-    public Expression lt(Property<?> value) {
-        return ExpressionFactory.lessExp(getExpression(), value.getExpression());
-    }
-
-    /**
-     * @return A less than or equal to Expression.
-     */
-    public Expression lte(E value) {
-        return ExpressionFactory.lessOrEqualExp(getExpression(), value);
-    }
-
-    /**
-     * @return Represents a less than or equal relationship between two
-     * attributes (columns).
-     */
-    public Expression lte(Property<?> value) {
-        return ExpressionFactory.lessOrEqualExp(getExpression(), value.getExpression());
-    }
-
-    /**
-     * @return Ascending sort orderings on this property.
-     */
-    public Ordering asc() {
-        return new Ordering(getExpression(), SortOrder.ASCENDING);
-    }
-
-    /**
-     * @return Ascending sort orderings on this property.
-     */
-	public Orderings ascs() {
-		return new Orderings(asc());
-	}
-
-    /**
-     * @return Ascending case insensitive sort orderings on this property.
-     */
-    public Ordering ascInsensitive() {
-        return new Ordering(getExpression(), SortOrder.ASCENDING_INSENSITIVE);
-    }
-
-    /**
-     * @return Ascending case insensitive sort orderings on this property.
-     */
-	public Orderings ascInsensitives() {
-		return new Orderings(ascInsensitive());
-	}
-
-    /**
-     * @return Descending sort orderings on this property.
-     */
-    public Ordering desc() {
-        return new Ordering(getExpression(), SortOrder.DESCENDING);
-    }
-
-    /**
-     * @return Descending sort orderings on this property.
-     */
-    public Orderings descs() {
-        return new Orderings(desc());
-    }
-
-    /**
-     * @return Descending case insensitive sort orderings on this property.
-     */
-    public Ordering descInsensitive() {
-        return new Ordering(getExpression(), SortOrder.DESCENDING_INSENSITIVE);
-    }
-
-    /**
-     * @return Descending case insensitive sort orderings on this property.
-     */
-    public Orderings descInsensitives() {
-        return new Orderings(descInsensitive());
-    }
-
-    /**
-     * Returns a prefetch tree that follows this property path, potentially
-     * spanning a number of phantom nodes, and having a single leaf with "joint"
-     * prefetch semantics.
-     */
-    public PrefetchTreeNode joint() {
-        return PrefetchTreeNode.withPath(getName(), PrefetchTreeNode.JOINT_PREFETCH_SEMANTICS);
-    }
-
-    /**
-     * Returns a prefetch tree that follows this property path, potentially
-     * spanning a number of phantom nodes, and having a single leaf with
-     * "disjoint" prefetch semantics.
-     */
-    public PrefetchTreeNode disjoint() {
-        return PrefetchTreeNode.withPath(getName(), PrefetchTreeNode.DISJOINT_PREFETCH_SEMANTICS);
-    }
-
-    /**
-     * Returns a prefetch tree that follows this property path, potentially
-     * spanning a number of phantom nodes, and having a single leaf with
-     * "disjoint by id" prefetch semantics.
-     */
-    public PrefetchTreeNode disjointById() {
-        return PrefetchTreeNode.withPath(getName(), PrefetchTreeNode.DISJOINT_BY_ID_PREFETCH_SEMANTICS);
-    }
-
-    /**
-     * Extracts property value from an object using JavaBean-compatible
-     * introspection with one addition - a property can be a dot-separated
-     * property name path.
-     */
-    @SuppressWarnings("unchecked")
-    public E getFrom(Object bean) {
-        return (E) PropertyUtils.getProperty(bean, getName());
-    }
-
-    /**
-     * Extracts property value from a collection of objects using
-     * JavaBean-compatible introspection with one addition - a property can be a
-     * dot-separated property name path.
-     */
-    public List<E> getFromAll(Collection<?> beans) {
-        List<E> result = new ArrayList<>(beans.size());
-        for (Object bean : beans) {
-            result.add(getFrom(bean));
-        }
-        return result;
-    }
-
-    /**
-     * Sets a property value in 'obj' using JavaBean-compatible introspection
-     * with one addition - a property can be a dot-separated property name path.
-     */
-    public void setIn(Object bean, E value) {
-        PropertyUtils.setProperty(bean, getName(), value);
-    }
-
-    /**
-     * Sets a property value in a collection of objects using
-     * JavaBean-compatible introspection with one addition - a property can be a
-     * dot-separated property name path.
-     */
-    public void setInAll(Collection<?> beans, E value) {
-        for (Object bean : beans) {
-            setIn(bean, value);
-        }
-    }
-
-    /**
-     * @see FunctionExpressionFactory#countExp(Expression)
-     */
-    public Property<Long> count() {
-        return create(FunctionExpressionFactory.countExp(getExpression()), Long.class);
-    }
-    
-    /**
-     * @see FunctionExpressionFactory#countDistinctExp(Expression)
-     * @since 4.1
-     */
-    public Property<Long> countDistinct() {
-        return create(FunctionExpressionFactory.countDistinctExp(getExpression()), Long.class);
-    }
-
-    /**
-     * @see FunctionExpressionFactory#maxExp(Expression)
-     */
-    public Property<E> max() {
-        return create(FunctionExpressionFactory.maxExp(getExpression()), getType());
-    }
-
-    /**
-     * @see FunctionExpressionFactory#minExp(Expression)
-     */
-    public Property<E> min() {
-        return create(FunctionExpressionFactory.minExp(getExpression()), getType());
-    }
-
-    /**
-     * @see FunctionExpressionFactory#avgExp(Expression)
-     */
-    public Property<E> avg() {
-        return create(FunctionExpressionFactory.avgExp(getExpression()), getType());
-    }
-
-    /**
-     * @see FunctionExpressionFactory#sumExp(Expression)
-     */
-    public Property<E> sum() {
-        return create(FunctionExpressionFactory.sumExp(getExpression()), getType());
-    }
-
-    /**
-     * @see FunctionExpressionFactory#modExp(Expression, Number)
-     */
-    public Property<E> mod(Number number) {
-        return create(FunctionExpressionFactory.modExp(getExpression(), number), getType());
-    }
-
-    /**
-     * @see FunctionExpressionFactory#absExp(Expression)
-     */
-    public Property<E> abs() {
-        return create(FunctionExpressionFactory.absExp(getExpression()), getType());
-    }
-
-    /**
-     * @see FunctionExpressionFactory#sqrtExp(Expression)
-     */
-    public Property<E> sqrt() {
-        return create(FunctionExpressionFactory.sqrtExp(getExpression()), getType());
+        return ExpressionFactory.endsWithIgnoreCaseExp(path(), value);
     }
 
     /**
      * @see FunctionExpressionFactory#lengthExp(Expression)
      */
     public Property<Integer> length() {
-        return create(FunctionExpressionFactory.lengthExp(getExpression()), Integer.class);
+        return create(FunctionExpressionFactory.lengthExp(path()), Integer.class);
     }
 
     /**
      * @see FunctionExpressionFactory#locateExp(String, Expression)
      */
     public Property<Integer> locate(String string) {
-        return create(FunctionExpressionFactory.locateExp(ExpressionFactory.wrapScalarValue(string), getExpression()), Integer.class);
+        return create(FunctionExpressionFactory.locateExp(ExpressionFactory.wrapScalarValue(string), path()), Integer.class);
     }
 
     /**
      * @see FunctionExpressionFactory#locateExp(Expression, Expression)
      */
     public Property<Integer> locate(Property<? extends String> property) {
-        return create(FunctionExpressionFactory.locateExp(property.getExpression(), getExpression()), Integer.class);
+        return create(FunctionExpressionFactory.locateExp(property.path(), path()), Integer.class);
     }
 
     /**
      * @see FunctionExpressionFactory#trimExp(Expression)
      */
     public Property<String> trim() {
-        return create(FunctionExpressionFactory.trimExp(getExpression()), String.class);
+        return create(FunctionExpressionFactory.trimExp(path()), String.class);
     }
 
     /**
      * @see FunctionExpressionFactory#upperExp(Expression)
      */
     public Property<String> upper() {
-        return create(FunctionExpressionFactory.upperExp(getExpression()), String.class);
+        return create(FunctionExpressionFactory.upperExp(path()), String.class);
     }
 
     /**
      * @see FunctionExpressionFactory#lowerExp(Expression)
      */
     public Property<String> lower() {
-        return create(FunctionExpressionFactory.lowerExp(getExpression()), String.class);
+        return create(FunctionExpressionFactory.lowerExp(path()), String.class);
     }
 
     /**
@@ -762,10 +264,10 @@ public class Property<E> {
     public Property<String> concat(Object... args) {
         Expression[] exp = new Expression[args.length + 1];
         int i = 0;
-        exp[i++] = getExpression();
+        exp[i++] = path();
         for(Object arg : args) {
-            if(arg instanceof Property) {
-                exp[i++] = ((Property) arg).getExpression();
+            if(arg instanceof BaseProperty) {
+                exp[i++] = ((BaseProperty) arg).getExpression();
             } else if(arg instanceof Expression) {
                 exp[i++] = (Expression) arg;
             } else if(arg != null) {
@@ -779,17 +281,51 @@ public class Property<E> {
      * @see FunctionExpressionFactory#substringExp(Expression, int, int)
      */
     public Property<String> substring(int offset, int length) {
-        return create(FunctionExpressionFactory.substringExp(getExpression(), offset, length), String.class);
+        return create(FunctionExpressionFactory.substringExp(path(), offset, length), String.class);
     }
 
+    // TODO: end of StringProperty related methods
+
+    // TODO: start of NumericProperty related methods
+
     /**
-     * Creates alias with different name for this property
+     * @see FunctionExpressionFactory#avgExp(Expression)
      */
-    public Property<E> alias(String alias) {
-        return new Property<>(alias, this.getExpression(), this.getType());
+    public Property<E> avg() {
+        return create(FunctionExpressionFactory.avgExp(path()), getType());
+    }
+
+    /**
+     * @see FunctionExpressionFactory#sumExp(Expression)
+     */
+    public Property<E> sum() {
+        return create(FunctionExpressionFactory.sumExp(path()), getType());
+    }
+
+    /**
+     * @see FunctionExpressionFactory#modExp(Expression, Number)
+     */
+    public Property<E> mod(Number number) {
+        return create(FunctionExpressionFactory.modExp(path(), number), getType());
+    }
+
+    /**
+     * @see FunctionExpressionFactory#absExp(Expression)
+     */
+    public Property<E> abs() {
+        return create(FunctionExpressionFactory.absExp(path()), getType());
     }
 
     /**
+     * @see FunctionExpressionFactory#sqrtExp(Expression)
+     */
+    public Property<E> sqrt() {
+        return create(FunctionExpressionFactory.sqrtExp(path()), getType());
+    }
+
+    // TODO: end of NumericProperty related methods
+
+    /**
      * <p>Create new "flat" property for toMany relationship.</p>
      * <p>
      *     Example:
@@ -800,15 +336,65 @@ public class Property<E> {
      *     }</pre>
      * </p>
      */
-    public <T extends Persistent> Property<T> flat(Class<? super T> tClass) {
+    public <T extends Persistent> Property<T> flat(Class<T> tClass) {
         if(!Collection.class.isAssignableFrom(type) && !Map.class.isAssignableFrom(type)) {
             throw new CayenneRuntimeException("Can use flat() function only on Property mapped on toMany relationship.");
         }
-        return create(ExpressionFactory.fullObjectExp(getExpression()), tClass);
+        return create(ExpressionFactory.fullObjectExp(path()), tClass);
     }
 
-    public Class<? super E> getType() {
-        return type;
+    /**
+     * Creates alias with different name for this property
+     */
+    @Override
+    public Property<E> alias(String alias) {
+        return new Property<>(alias, this.getExpression(), this.getType());
+    }
+
+    @Override
+    public Property<E> outer() {
+        return getName().endsWith("+")
+                ? this
+                : Property.create(getName() + "+", getType());
+    }
+
+    public <T> Property<T> dot(Property<T> property) {
+        return Property.create(getName() + "." + property.getName(), property.getType());
+    }
+
+    /**
+     * Constructs a property path by appending the argument to the existing property separated by a dot.
+     *
+     * @return a newly created Property object.
+     */
+    @Override
+    public Property<Object> dot(String property) {
+        return Property.create(getName() + "." + property, null);
+    }
+
+    // TODO: this method has incompatible return type in BaseProperty
+//    /**
+//     * @see FunctionExpressionFactory#countExp(Expression)
+//     */
+//    @Override
+//    public NumericProperty<Long> count() {
+//        return Property.create(FunctionExpressionFactory.countExp(getExpression()), Long.class);
+//    }
+
+    /**
+     * @see FunctionExpressionFactory#maxExp(Expression)
+     */
+    @Override
+    public Property<E> max() {
+        return Property.create(FunctionExpressionFactory.maxExp(path()), getType());
+    }
+
+    /**
+     * @see FunctionExpressionFactory#minExp(Expression)
+     */
+    @Override
+    public Property<E> min() {
+        return Property.create(FunctionExpressionFactory.minExp(path()), getType());
     }
 
     /**
@@ -816,8 +402,9 @@ public class Property<E> {
      * @see Property#create(Expression, Class)
      * @see Property#create(String, Expression, Class)
      */
+    @SuppressWarnings("unchecked")
     public static <T> Property<T> create(String name, Class<? super T> type) {
-        return new Property<>(name, type);
+        return (Property<T>)new Property<>(name, type);
     }
 
     /**
@@ -825,8 +412,9 @@ public class Property<E> {
      * @see Property#create(String, Class)
      * @see Property#create(String, Expression, Class)
      */
+    @SuppressWarnings("unchecked")
     public static <T> Property<T> create(Expression expression, Class<? super T> type) {
-        return new Property<>(null, expression, type);
+        return (Property<T>)new Property<>(null, expression, type);
     }
 
     /**
@@ -834,8 +422,9 @@ public class Property<E> {
      * @see Property#create(String, Class)
      * @see Property#create(Expression, Class)
      */
+    @SuppressWarnings("unchecked")
     public static <T> Property<T> create(String name, Expression expression, Class<? super T> type) {
-        return new Property<>(name, expression, type);
+        return (Property<T>)new Property<>(name, expression, type);
     }
 
     /**
@@ -855,17 +444,9 @@ public class Property<E> {
      *     </pre>
      * </p>
      */
+    @SuppressWarnings("unchecked")
     public static <T extends Persistent> Property<T> createSelf(Class<? super T> type) {
-        return new Property<>(null, ExpressionFactory.fullObjectExp(), type);
+        return (Property<T>)new Property<>(null, ExpressionFactory.fullObjectExp(), type);
     }
 
-    /**
-     * Since Expression is mutable we need to provide clean Expression for every getter call.
-     * So to keep Property itself immutable we use ExpressionProvider.
-     * @see Property#Property(String, Class)
-     * @see Property#Property(String, Expression, Class)
-     */
-    private interface ExpressionProvider {
-        Expression get();
-    }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2050c2e1/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAdd.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAdd.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAdd.java
index 8554bdc..3c8fb3d 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAdd.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAdd.java
@@ -41,7 +41,7 @@ public class ASTAdd extends EvaluatedMathNode {
 		super(ExpressionParserTreeConstants.JJTADD);
 	}
 
-	public ASTAdd(Object[] nodes) {
+	public ASTAdd(Object... nodes) {
 		this(Arrays.asList(nodes));
 	}
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2050c2e1/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTDivide.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTDivide.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTDivide.java
index b9759e1..906ad70 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTDivide.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTDivide.java
@@ -43,7 +43,7 @@ public class ASTDivide extends EvaluatedMathNode {
 		super(ExpressionParserTreeConstants.JJTDIVIDE);
 	}
 
-	public ASTDivide(Object[] nodes) {
+	public ASTDivide(Object... nodes) {
 		this(Arrays.asList(nodes));
 	}
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2050c2e1/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLike.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLike.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLike.java
index bd45d9b..e30b3bf 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLike.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLike.java
@@ -44,14 +44,14 @@ public class ASTLike extends PatternMatchNode {
 	public ASTLike(SimpleNode path, Object pattern) {
 		super(ExpressionParserTreeConstants.JJTLIKE, false);
 		jjtAddChild(path, 0);
-		jjtAddChild(new ASTScalar(pattern), 1);
+		jjtAddChild(wrap(pattern), 1);
 		connectChildren();
 	}
 
 	public ASTLike(SimpleNode path, Object pattern, char escapeChar) {
 		super(ExpressionParserTreeConstants.JJTLIKE, false, escapeChar);
 		jjtAddChild(path, 0);
-		jjtAddChild(new ASTScalar(pattern), 1);
+		jjtAddChild(wrap(pattern), 1);
 		connectChildren();
 	}
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2050c2e1/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLikeIgnoreCase.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLikeIgnoreCase.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLikeIgnoreCase.java
index 1cdc7f0..19fdd99 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLikeIgnoreCase.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLikeIgnoreCase.java
@@ -45,14 +45,14 @@ public class ASTLikeIgnoreCase extends IgnoreCaseNode {
 	public ASTLikeIgnoreCase(SimpleNode path, Object pattern) {
 		super(ExpressionParserTreeConstants.JJTLIKEIGNORECASE, true);
 		jjtAddChild(path, 0);
-		jjtAddChild(new ASTScalar(pattern), 1);
+		jjtAddChild(wrap(pattern), 1);
 		connectChildren();
 	}
 
 	public ASTLikeIgnoreCase(SimpleNode path, Object pattern, char escapeChar) {
 		super(ExpressionParserTreeConstants.JJTLIKEIGNORECASE, true, escapeChar);
 		jjtAddChild(path, 0);
-		jjtAddChild(new ASTScalar(pattern), 1);
+		jjtAddChild(wrap(pattern), 1);
 		connectChildren();
 	}
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2050c2e1/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTMultiply.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTMultiply.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTMultiply.java
index edc9c70..0bbe04a 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTMultiply.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTMultiply.java
@@ -44,7 +44,7 @@ public class ASTMultiply extends EvaluatedMathNode {
 		super(ExpressionParserTreeConstants.JJTMULTIPLY);
 	}
 
-	public ASTMultiply(Object[] nodes) {
+	public ASTMultiply(Object... nodes) {
 		this(Arrays.asList(nodes));
 	}
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2050c2e1/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotLike.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotLike.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotLike.java
index d01970c..9f326d6 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotLike.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotLike.java
@@ -40,14 +40,14 @@ public class ASTNotLike extends PatternMatchNode {
     public ASTNotLike(SimpleNode path, Object value) {
         super(ExpressionParserTreeConstants.JJTNOTLIKE, false);
         jjtAddChild(path, 0);
-        jjtAddChild(new ASTScalar(value), 1);
+        jjtAddChild(wrap(value), 1);
         connectChildren();
     }
 
     public ASTNotLike(SimpleNode path, Object value, char escapeChar) {
         super(ExpressionParserTreeConstants.JJTNOTLIKE, false, escapeChar);
         jjtAddChild(path, 0);
-        jjtAddChild(new ASTScalar(value), 1);
+        jjtAddChild(wrap(value), 1);
         connectChildren();
     }
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2050c2e1/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotLikeIgnoreCase.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotLikeIgnoreCase.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotLikeIgnoreCase.java
index 7af54eb..b9055ff 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotLikeIgnoreCase.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotLikeIgnoreCase.java
@@ -41,14 +41,14 @@ public class ASTNotLikeIgnoreCase extends IgnoreCaseNode {
 	public ASTNotLikeIgnoreCase(SimpleNode path, Object value) {
 		super(ExpressionParserTreeConstants.JJTNOTLIKEIGNORECASE, true);
 		jjtAddChild(path, 0);
-		jjtAddChild(new ASTScalar(value), 1);
+		jjtAddChild(wrap(value), 1);
 		connectChildren();
 	}
 
 	public ASTNotLikeIgnoreCase(SimpleNode path, Object value, char escapeChar) {
 		super(ExpressionParserTreeConstants.JJTNOTLIKEIGNORECASE, true, escapeChar);
 		jjtAddChild(path, 0);
-		jjtAddChild(new ASTScalar(value), 1);
+		jjtAddChild(wrap(value), 1);
 		connectChildren();
 	}
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2050c2e1/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTSubtract.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTSubtract.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTSubtract.java
index ce68e40..a18c883 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTSubtract.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTSubtract.java
@@ -41,7 +41,7 @@ public class ASTSubtract extends EvaluatedMathNode {
         super(ExpressionParserTreeConstants.JJTSUBTRACT);
     }
 
-    public ASTSubtract(Object[] nodes) {
+    public ASTSubtract(Object... nodes) {
         this(Arrays.asList(nodes));
     }
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2050c2e1/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/PatternMatchNode.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/PatternMatchNode.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/PatternMatchNode.java
index b3ec972..6fb7bff 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/PatternMatchNode.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/PatternMatchNode.java
@@ -50,6 +50,13 @@ public abstract class PatternMatchNode extends ConditionNode {
         setEscapeChar(escapeChar);
     }
 
+    SimpleNode wrap(Object pattern) {
+        if(pattern instanceof SimpleNode) {
+            return (SimpleNode)pattern;
+        }
+        return new ASTScalar(pattern);
+    }
+
     /**
      * <p>This method will return an escape character for the like
      * clause.  The escape character will eventually end up in the
@@ -79,15 +86,13 @@ public abstract class PatternMatchNode extends ConditionNode {
     }
 
     protected boolean matchPattern(String string) {
-        return (string != null) ? getPattern().matcher(string).find() : false;
+        return (string != null) && getPattern().matcher(string).find();
     }
 
     protected Pattern getPattern() {
         // compile pattern on demand
         if (!patternCompiled) {
-
             synchronized (this) {
-
                 if (!patternCompiled) {
                     pattern = null;
 
@@ -97,20 +102,23 @@ public abstract class PatternMatchNode extends ConditionNode {
                     }
 
                     // precompile pattern
-                    ASTScalar patternNode = (ASTScalar) jjtGetChild(1);
-                    if (patternNode == null) {
+                    Node node = jjtGetChild(1);
+                    if(node instanceof ASTScalar) {
+                        ASTScalar patternNode = (ASTScalar) node;
+                        if (patternNode == null) {
+                            patternCompiled = true;
+                            return null;
+                        }
+
+                        String srcPattern = (String) patternNode.getValue();
+                        if (srcPattern == null) {
+                            patternCompiled = true;
+                            return null;
+                        }
+
+                        pattern = Util.sqlPatternToPattern(srcPattern, ignoringCase);
                         patternCompiled = true;
-                        return null;
                     }
-
-                    String srcPattern = (String) patternNode.getValue();
-                    if (srcPattern == null) {
-                        patternCompiled = true;
-                        return null;
-                    }
-
-                    pattern = Util.sqlPatternToPattern(srcPattern, ignoringCase);
-                    patternCompiled = true;
                 }
             }
         }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2050c2e1/cayenne-server/src/main/java/org/apache/cayenne/exp/property/BaseProperty.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/BaseProperty.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/BaseProperty.java
new file mode 100644
index 0000000..1ce3836
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/BaseProperty.java
@@ -0,0 +1,389 @@
+/*****************************************************************
+ *   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.property;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Supplier;
+
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
+import org.apache.cayenne.exp.FunctionExpressionFactory;
+import org.apache.cayenne.exp.parser.ASTPath;
+import org.apache.cayenne.query.Ordering;
+import org.apache.cayenne.query.Orderings;
+import org.apache.cayenne.query.SortOrder;
+import org.apache.cayenne.reflect.PropertyUtils;
+
+/**
+ * Property that represents generic attribute.
+ * <p>
+ * Provides equality checks and sorting API along with some utility methods.
+ *
+ * @see org.apache.cayenne.exp.property
+ * @since 4.2
+ */
+public class BaseProperty<E> implements Property<E> {
+
+    /**
+     * Name of the property in the object
+     */
+    protected final String name;
+
+    /**
+     * Expression provider for the property
+     */
+    protected final Supplier<Expression> expressionSupplier;
+
+    /**
+     * Explicit type of the property
+     */
+    protected final Class<E> type;
+
+    /**
+     * Constructs a new property with the given name and expression
+     *
+     * @param name of the property (will be used as alias for the expression)
+     * @param expression expression for property
+     * @param type of the property
+     *
+     * @see PropertyFactory#createBase(String, Expression, Class)
+     */
+    @SuppressWarnings("unchecked")
+    protected BaseProperty(String name, Expression expression, Class<? super E> type) {
+        this.name = name;
+        if(expression == null) {
+            this.expressionSupplier = () -> ExpressionFactory.pathExp(name);
+        } else {
+            this.expressionSupplier = expression::deepCopy;
+        }
+        this.type = (Class<E>)type;
+    }
+
+    /**
+     * @return Name of the property in the object.
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * @return alias for this property
+     */
+    public String getAlias() {
+        if(getName() == null) {
+            return null;
+        }
+
+        // check if default name for Path expression is overridden
+        Expression exp = getExpression();
+        if(exp instanceof ASTPath) {
+            if(((ASTPath) exp).getPath().equals(getName())) {
+                return null;
+            }
+        }
+
+        return getName();
+    }
+
+    /**
+     * This method returns fresh copy of the expression for each call.
+     * @return expression that represents this Property
+     */
+    public Expression getExpression() {
+        return expressionSupplier.get();
+    }
+
+    @Override
+    public int hashCode() {
+        int result = name != null ? name.hashCode() : expressionSupplier.get().hashCode();
+        if(type != null) {
+            result = 31 * result + type.hashCode();
+        }
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        BaseProperty<?> property = (BaseProperty<?>) o;
+        if (name != null ? !name.equals(property.name) : property.name != null) {
+            return false;
+        }
+        if (name == null && !expressionSupplier.get().equals(property.expressionSupplier.get())) {
+            return false;
+        }
+        return (type == null ? property.type == null : type.equals(property.type));
+    }
+
+    /**
+     * Converts this property to a path expression.
+     * This method is equivalent of getExpression() which is preferred as more generic.
+     *
+     * @return a newly created expression.
+     * @see BaseProperty#getExpression()
+     * @deprecated since 4.2, use {@link #getExpression()} method instead
+     */
+    @Deprecated
+    public Expression path() {
+        return getExpression();
+    }
+
+    /**
+     * @return An expression representing null.
+     */
+    public Expression isNull() {
+        return ExpressionFactory.matchExp(getExpression(), null);
+    }
+
+    /**
+     * @return An expression representing a non-null value.
+     */
+    public Expression isNotNull() {
+        return ExpressionFactory.noMatchExp(getExpression(), null);
+    }
+
+    /**
+     * @return Ascending sort orderings on this property.
+     */
+    public Ordering asc() {
+        return new Ordering(getExpression(), SortOrder.ASCENDING);
+    }
+
+    /**
+     * @return Ascending sort orderings on this property.
+     */
+	public Orderings ascs() {
+		return new Orderings(asc());
+	}
+
+    /**
+     * @return Ascending case insensitive sort orderings on this property.
+     */
+    public Ordering ascInsensitive() {
+        return new Ordering(getExpression(), SortOrder.ASCENDING_INSENSITIVE);
+    }
+
+    /**
+     * @return Ascending case insensitive sort orderings on this property.
+     */
+	public Orderings ascInsensitives() {
+		return new Orderings(ascInsensitive());
+	}
+
+    /**
+     * @return Descending sort orderings on this property.
+     */
+    public Ordering desc() {
+        return new Ordering(getExpression(), SortOrder.DESCENDING);
+    }
+
+    /**
+     * @return Descending sort orderings on this property.
+     */
+    public Orderings descs() {
+        return new Orderings(desc());
+    }
+
+    /**
+     * @return Descending case insensitive sort orderings on this property.
+     */
+    public Ordering descInsensitive() {
+        return new Ordering(getExpression(), SortOrder.DESCENDING_INSENSITIVE);
+    }
+
+    /**
+     * @return Descending case insensitive sort orderings on this property.
+     */
+    public Orderings descInsensitives() {
+        return new Orderings(descInsensitive());
+    }
+
+    /**
+     * Extracts property value from an object using JavaBean-compatible
+     * introspection with one addition - a property can be a dot-separated
+     * property name path.
+     */
+    @SuppressWarnings("unchecked")
+    public E getFrom(Object bean) {
+        return (E) PropertyUtils.getProperty(bean, getName());
+    }
+
+    /**
+     * Extracts property value from a collection of objects using
+     * JavaBean-compatible introspection with one addition - a property can be a
+     * dot-separated property name path.
+     */
+    public List<E> getFromAll(Collection<?> beans) {
+        List<E> result = new ArrayList<>(beans.size());
+        for (Object bean : beans) {
+            result.add(getFrom(bean));
+        }
+        return result;
+    }
+
+    /**
+     * Sets a property value in 'obj' using JavaBean-compatible introspection
+     * with one addition - a property can be a dot-separated property name path.
+     */
+    public void setIn(Object bean, E value) {
+        PropertyUtils.setProperty(bean, getName(), value);
+    }
+
+    /**
+     * Sets a property value in a collection of objects using
+     * JavaBean-compatible introspection with one addition - a property can be a
+     * dot-separated property name path.
+     */
+    public void setInAll(Collection<?> beans, E value) {
+        for (Object bean : beans) {
+            setIn(bean, value);
+        }
+    }
+
+    /**
+     * @see FunctionExpressionFactory#countExp(Expression)
+     */
+    public NumericProperty<Long> count() {
+        return PropertyFactory.createNumeric(FunctionExpressionFactory.countExp(getExpression()), Long.class);
+    }
+
+    /**
+     * @see FunctionExpressionFactory#countDistinctExp(Expression)
+     */
+    public NumericProperty<Long> countDistinct() {
+        return PropertyFactory.createNumeric(FunctionExpressionFactory.countDistinctExp(getExpression()), Long.class);
+    }
+
+    /**
+     * Creates alias with different name for this property
+     */
+    public BaseProperty<E> alias(String alias) {
+        return PropertyFactory.createBase(alias, this.getExpression(), this.getType());
+    }
+
+    /**
+     * @return type of entity attribute described by this property
+     */
+    public Class<E> getType() {
+        return type;
+    }
+
+    /**
+     * @return An expression representing equality to TRUE.
+     */
+    public Expression isTrue() {
+        return ExpressionFactory.matchExp(getExpression(), Boolean.TRUE);
+    }
+
+    /**
+     * @return An expression representing equality to FALSE.
+     */
+    public Expression isFalse() {
+        return ExpressionFactory.matchExp(getExpression(), Boolean.FALSE);
+    }
+
+    /**
+     * @return An expression representing equality to a value.
+     */
+    public Expression eq(E value) {
+        return ExpressionFactory.matchExp(getExpression(), value);
+    }
+
+    /**
+     * @return An expression representing equality between two attributes
+     * (columns).
+     */
+    public Expression eq(BaseProperty<?> value) {
+        return ExpressionFactory.matchExp(getExpression(), value.getExpression());
+    }
+
+    /**
+     * @return An expression representing inequality to a value.
+     */
+    public Expression ne(E value) {
+        return ExpressionFactory.noMatchExp(getExpression(), value);
+    }
+
+    /**
+     * @return An expression representing inequality between two attributes
+     * (columns).
+     */
+    public Expression ne(BaseProperty<?> value) {
+        return ExpressionFactory.noMatchExp(getExpression(), value.getExpression());
+    }
+
+    /**
+     * @return An expression for finding objects with values in the given set.
+     */
+    public Expression in(E firstValue, E... moreValues) {
+
+        int moreValuesLength = moreValues != null ? moreValues.length : 0;
+
+        Object[] values = new Object[moreValuesLength + 1];
+        values[0] = firstValue;
+
+        if (moreValuesLength > 0) {
+            System.arraycopy(moreValues, 0, values, 1, moreValuesLength);
+        }
+
+        return ExpressionFactory.inExp(getExpression(), values);
+    }
+
+    /**
+     * @return An expression for finding objects with values not in the given
+     * set.
+     */
+    public Expression nin(E firstValue, E... moreValues) {
+
+        int moreValuesLength = moreValues != null ? moreValues.length : 0;
+
+        Object[] values = new Object[moreValuesLength + 1];
+        values[0] = firstValue;
+
+        if (moreValuesLength > 0) {
+            System.arraycopy(moreValues, 0, values, 1, moreValuesLength);
+        }
+
+        return ExpressionFactory.notInExp(getExpression(), values);
+    }
+
+    /**
+     * @return An expression for finding objects with values in the given set.
+     */
+    public Expression in(Collection<E> values) {
+        return ExpressionFactory.inExp(getExpression(), values);
+    }
+
+    /**
+     * @return An expression for finding objects with values not in the given
+     * set.
+     */
+    public Expression nin(Collection<E> values) {
+        return ExpressionFactory.notInExp(getExpression(), values);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2050c2e1/cayenne-server/src/main/java/org/apache/cayenne/exp/property/CollectionProperty.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/CollectionProperty.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/CollectionProperty.java
new file mode 100644
index 0000000..9021ea2
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/CollectionProperty.java
@@ -0,0 +1,203 @@
+/*****************************************************************
+ *   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.property;
+
+import java.util.Collection;
+
+import org.apache.cayenne.Persistent;
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
+
+/**
+ * Base class for {@link ListProperty} and {@link SetProperty}
+ * @since 4.2
+ */
+public abstract class CollectionProperty<V extends Persistent, E extends Collection<V>>
+        extends BaseProperty<E> implements RelationshipProperty<E> {
+
+    protected Class<V> entityType;
+
+    /**
+     * Constructs a new property with the given name and expression
+     *
+     * @param name       of the property (will be used as alias for the expression)
+     * @param expression expression for property
+     * @param collectionType type of the collection
+     * @param entityType type of related entity
+     */
+    @SuppressWarnings("unchecked")
+    protected CollectionProperty(String name, Expression expression, Class<? super E> collectionType, Class<V> entityType) {
+        super(name, expression, collectionType);
+        this.entityType = entityType;
+    }
+
+    /**
+     * <p>Create new "flat" property for toMany relationship.</p>
+     * <p>
+     * Example:
+     * <pre>{@code
+     * List<Object[]> result = ObjectSelect
+     *      .columnQuery(Artist.class, Artist.ARTIST_NAME, Artist.PAINTING_ARRAY.flat())
+     *      .select(context);
+     * }</pre>
+     * </p>
+     */
+    public EntityProperty<V> flat() {
+        return PropertyFactory.createEntity(ExpressionFactory.fullObjectExp(getExpression()), getEntityType());
+    }
+
+    /**
+     * @return An expression representing equality to a value.
+     */
+    public Expression contains(V value) {
+        return ExpressionFactory.matchExp(getExpression(), value);
+    }
+
+
+    /**
+     * @return An expression representing inequality to a value.
+     */
+    public Expression notContains(V value) {
+        return ExpressionFactory.noMatchExp(getExpression(), value);
+    }
+
+    // TODO: move all *contains* methods to RelationshipProperty once Property class is removed
+
+    /**
+     * @return An expression for finding objects with values in the given set.
+     */
+    @SafeVarargs
+    public final Expression contains(V firstValue, V... moreValues) {
+
+        int moreValuesLength = moreValues != null ? moreValues.length : 0;
+
+        Object[] values = new Object[moreValuesLength + 1];
+        values[0] = firstValue;
+
+        if (moreValuesLength > 0) {
+            System.arraycopy(moreValues, 0, values, 1, moreValuesLength);
+        }
+
+        return ExpressionFactory.inExp(getExpression(), values);
+    }
+
+    /**
+     * @return An expression for finding objects with values in the given set.
+     */
+    public Expression contains(Collection<V> values) {
+        return ExpressionFactory.inExp(getExpression(), values);
+    }
+
+    /**
+     * @param id object id
+     * @return An expression for finding object with given id.
+     */
+    public Expression containsId(Object id) {
+        return ExpressionFactory.matchExp(getExpression(), id);
+    }
+
+    /**
+     * @return An expression for finding objects with given id set
+     */
+    public Expression containsId(Object firstId, Object... moreId) {
+
+        int moreValuesLength = moreId != null ? moreId.length : 0;
+
+        Object[] values = new Object[moreValuesLength + 1];
+        values[0] = firstId;
+
+        if (moreValuesLength > 0) {
+            System.arraycopy(moreId, 0, values, 1, moreValuesLength);
+        }
+
+        return ExpressionFactory.inExp(getExpression(), values);
+    }
+
+    /**
+     * @return An expression for finding objects with given id set.
+     */
+    public Expression containsId(Collection<Object> ids) {
+        return ExpressionFactory.inExp(getExpression(), ids);
+    }
+
+    /**
+     * @param id object id
+     * @return An expression for finding object without given id.
+     */
+    public Expression notContainsId(Object id) {
+        return ExpressionFactory.noMatchExp(getExpression(), id);
+    }
+
+    /**
+     * @return An expression for finding objects without given id set.
+     */
+    public Expression notContainsId(Object firstId, Object... moreId) {
+
+        int moreValuesLength = moreId != null ? moreId.length : 0;
+
+        Object[] values = new Object[moreValuesLength + 1];
+        values[0] = firstId;
+
+        if (moreValuesLength > 0) {
+            System.arraycopy(moreId, 0, values, 1, moreValuesLength);
+        }
+
+        return ExpressionFactory.notInExp(getExpression(), values);
+    }
+
+    /**
+     * @return An expression for finding objects without given id set.
+     */
+    public Expression notContainsId(Collection<Object> ids) {
+        return ExpressionFactory.notInExp(getExpression(), ids);
+    }
+
+    /**
+     * @return An expression for finding objects with values not in the given set.
+     */
+    public Expression notContains(Collection<V> values) {
+        return ExpressionFactory.notInExp(getExpression(), values);
+    }
+
+    /**
+     * @return An expression for finding objects with values not in the given set.
+     */
+    @SafeVarargs
+    public final Expression notContains(V firstValue, V... moreValues) {
+
+        int moreValuesLength = moreValues != null ? moreValues.length : 0;
+
+        Object[] values = new Object[moreValuesLength + 1];
+        values[0] = firstValue;
+
+        if (moreValuesLength > 0) {
+            System.arraycopy(moreValues, 0, values, 1, moreValuesLength);
+        }
+
+        return ExpressionFactory.notInExp(getExpression(), values);
+    }
+
+    /**
+     * @return object entity type represented by this property
+     */
+    protected Class<V> getEntityType() {
+        return entityType;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2050c2e1/cayenne-server/src/main/java/org/apache/cayenne/exp/property/ComparableProperty.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/ComparableProperty.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/ComparableProperty.java
new file mode 100644
index 0000000..c1e15fc
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/ComparableProperty.java
@@ -0,0 +1,126 @@
+/*****************************************************************
+ *   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.property;
+
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
+import org.apache.cayenne.exp.FunctionExpressionFactory;
+
+/**
+ * Interface (or "Trait") that provides basic functionality for comparable properties.
+ *
+ * @see org.apache.cayenne.exp.property
+ * @since 4.2
+ */
+// TODO: bound <E> to <E extends Comparable> once deprecated Property class is removed
+public interface ComparableProperty<E> extends Property<E> {
+
+    /**
+     * @param lower The lower bound.
+     * @param upper The upper bound.
+     * @return An expression checking for objects between a lower and upper bound inclusive
+     */
+    default Expression between(E lower, E upper) {
+        return ExpressionFactory.betweenExp(getExpression(), lower, upper);
+    }
+
+    /**
+     * @param lower The lower bound.
+     * @param upper The upper bound.
+     * @return An expression checking for objects between a lower and upper bound inclusive
+     */
+    default Expression between(ComparableProperty<?> lower, ComparableProperty<?> upper) {
+        return ExpressionFactory.betweenExp(getExpression(), lower.getExpression(), upper.getExpression());
+    }
+
+    /**
+     * @return A greater than Expression.
+     */
+    default Expression gt(E value) {
+        return ExpressionFactory.greaterExp(getExpression(), value);
+    }
+
+    /**
+     * @return Represents a greater than relationship between two attributes
+     * (columns).
+     */
+    default Expression gt(ComparableProperty<?> value) {
+        return ExpressionFactory.greaterExp(getExpression(), value.getExpression());
+    }
+
+    /**
+     * @return A greater than or equal to Expression.
+     */
+    default Expression gte(E value) {
+        return ExpressionFactory.greaterOrEqualExp(getExpression(), value);
+    }
+
+    /**
+     * @return Represents a greater than or equal relationship between two
+     * attributes (columns).
+     */
+    default Expression gte(ComparableProperty<?> value) {
+        return ExpressionFactory.greaterOrEqualExp(getExpression(), value.getExpression());
+    }
+
+    /**
+     * @return A less than Expression.
+     */
+    default Expression lt(E value) {
+        return ExpressionFactory.lessExp(getExpression(), value);
+    }
+
+    /**
+     * @return Represents a less than relationship between two attributes
+     * (columns).
+     */
+    default Expression lt(ComparableProperty<?> value) {
+        return ExpressionFactory.lessExp(getExpression(), value.getExpression());
+    }
+
+    /**
+     * @return A less than or equal to Expression.
+     */
+    default Expression lte(E value) {
+        return ExpressionFactory.lessOrEqualExp(getExpression(), value);
+    }
+
+    /**
+     * @return Represents a less than or equal relationship between two
+     * attributes (columns).
+     */
+    default Expression lte(ComparableProperty<?> value) {
+        return ExpressionFactory.lessOrEqualExp(getExpression(), value.getExpression());
+    }
+
+    /**
+     * @see FunctionExpressionFactory#maxExp(Expression)
+     */
+    default BaseProperty<E> max() {
+        return PropertyFactory.createBase(FunctionExpressionFactory.maxExp(getExpression()), getType());
+    }
+
+    /**
+     * @see FunctionExpressionFactory#minExp(Expression)
+     */
+    default BaseProperty<E> min() {
+        return PropertyFactory.createBase(FunctionExpressionFactory.minExp(getExpression()), getType());
+    }
+}


Mime
View raw message