cayenne-commits mailing list archives

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


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

Branch: refs/heads/master
Commit: 9ea878c02d760c42424588821828161d8f481ebb
Parents: 2050c2e
Author: Nikita Timofeev <stariy95@gmail.com>
Authored: Wed Dec 26 15:00:52 2018 +0300
Committer: Nikita Timofeev <stariy95@gmail.com>
Committed: Wed Dec 26 15:00:52 2018 +0300

----------------------------------------------------------------------
 .../java/org/apache/cayenne/gen/Artifact.java   |   1 +
 .../cayenne/gen/ClassGenerationAction.java      |   4 +-
 .../org/apache/cayenne/gen/EntityUtils.java     |  20 +-
 .../org/apache/cayenne/gen/ImportUtils.java     |   7 +
 .../org/apache/cayenne/gen/PropertyUtils.java   | 277 +++++++++++++++++++
 .../resources/templates/v4_1/singleclass.vm     |  30 +-
 .../main/resources/templates/v4_1/superclass.vm |  30 +-
 .../apache/cayenne/gen/PropertyUtilsTest.java   | 223 +++++++++++++++
 .../cayenne/gen/SingleClassGenerationTest.java  |  31 ++-
 .../cayenne/gen/SuperClassGenerationTest.java   |  37 ++-
 .../apache/cayenne/crypto/db/auto/_Table1.java  |  12 +-
 .../apache/cayenne/crypto/db/auto/_Table2.java  |   7 +-
 .../apache/cayenne/crypto/db/auto/_Table3.java  |   5 +-
 .../apache/cayenne/crypto/db/auto/_Table4.java  |   8 +-
 .../apache/cayenne/crypto/db/auto/_Table5.java  |   9 +-
 .../apache/cayenne/crypto/db/auto/_Table6.java  |   7 +-
 .../org/apache/cayenne/CayenneCompoundIT.java   |  14 +-
 .../cayenne/access/DataContextOrderingIT.java   |   5 +-
 .../cayenne/access/DataContextPrefetchIT.java   |   5 +-
 .../apache/cayenne/access/NumericTypesIT.java   |   7 +-
 .../java/org/apache/cayenne/access/UUIDIT.java  |   4 +-
 .../select/QualifierTranslatorIT.java           |  51 ++--
 .../apache/cayenne/exp/ExpressionFactoryIT.java |   9 +-
 .../apache/cayenne/exp/parser/ASTExtractIT.java |  11 +-
 .../exp/parser/ASTFunctionCallMathIT.java       |  19 +-
 .../exp/parser/ASTFunctionCallStringIT.java     |  72 ++---
 .../ExpressionCollectionEvaluationIT.java       |  17 +-
 .../parser/ExpressionEvaluateInMemoryTest.java  |  12 +-
 .../apache/cayenne/query/ColumnSelectIT.java    | 191 +++++++------
 .../apache/cayenne/query/ColumnSelectTest.java  |  43 +--
 .../cayenne/query/ObjectSelect_AggregateIT.java |  34 +--
 .../cayenne/query/ObjectSelect_CompileIT.java   |   4 +-
 .../query/ObjectSelect_PrimitiveColumnsIT.java  |  41 +--
 .../cayenne/query/ObjectSelect_RunIT.java       |  25 +-
 .../testdo/compound/auto/_CharFkTestEntity.java |   8 +-
 .../testdo/compound/auto/_CharPkTestEntity.java |  10 +-
 .../compound/auto/_CompoundFkTestEntity.java    |   8 +-
 .../testdo/compound/auto/_CompoundIntPk.java    | 107 ++++++-
 .../compound/auto/_CompoundPkTestEntity.java    |  12 +-
 .../testdo/date_time/auto/_CalendarEntity.java  |   5 +-
 .../testdo/date_time/auto/_DateTestEntity.java  |   9 +-
 .../testdo/map_to_many/auto/_IdMapToMany.java   |   5 +-
 .../map_to_many/auto/_IdMapToManyTarget.java    |   5 +-
 .../testdo/map_to_many/auto/_MapToMany.java     |   5 +-
 .../map_to_many/auto/_MapToManyTarget.java      |   8 +-
 .../primitive/auto/_PrimitivesTestEntity.java   |  10 +-
 .../auto/_SetToMany.java                        |   5 +-
 .../auto/_SetToManyTarget.java                  |   5 +-
 .../cayenne/testdo/testmap/auto/_ArtGroup.java  |  16 +-
 .../cayenne/testdo/testmap/auto/_Artist.java    |  18 +-
 .../testdo/testmap/auto/_ArtistCallback.java    |  11 +-
 .../testdo/testmap/auto/_ArtistExhibit.java     |  11 +-
 .../testdo/testmap/auto/_CompoundPainting.java  |  23 +-
 .../auto/_CompoundPaintingLongNames.java        |  27 +-
 .../cayenne/testdo/testmap/auto/_Exhibit.java   |  16 +-
 .../cayenne/testdo/testmap/auto/_Gallery.java   |  13 +-
 .../_MeaningfulGeneratedColumnTestEntity.java   |   9 +-
 .../testdo/testmap/auto/_NullTestEntity.java    |   8 +-
 .../cayenne/testdo/testmap/auto/_Painting.java  |  19 +-
 .../cayenne/testdo/testmap/auto/_Painting1.java |  13 +-
 .../testdo/testmap/auto/_PaintingInfo.java      |  14 +-
 .../cayenne/testdo/testmap/auto/_ROArtist.java  |  14 +-
 .../testdo/testmap/auto/_ROPainting.java        |  13 +-
 .../testmap/auto/_RWCompoundPainting.java       |  12 +-
 .../testdo/testmap/auto/_SubPainting.java       |   8 +-
 .../src/test/resources/cayenne-testmap.xml      |   4 +-
 .../src/test/resources/testmap.map.xml          |  65 +++--
 .../cayenne/tools/CayenneGeneratorMojoTest.java |   2 +-
 68 files changed, 1238 insertions(+), 552 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/9ea878c0/cayenne-cgen/src/main/java/org/apache/cayenne/gen/Artifact.java
----------------------------------------------------------------------
diff --git a/cayenne-cgen/src/main/java/org/apache/cayenne/gen/Artifact.java b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/Artifact.java
index d16bf6e..fa48382 100644
--- a/cayenne-cgen/src/main/java/org/apache/cayenne/gen/Artifact.java
+++ b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/Artifact.java
@@ -43,6 +43,7 @@ public interface Artifact {
     String BASE_PACKAGE_KEY = "basePackageName";
     String CREATE_PROPERTY_NAMES = "createPropertyNames";
     String CREATE_PK_PROPERTIES = "createPKProperties";
+    String PROPERTY_UTILS_KEY = "propertyUtils";
 
     TemplateType[] getTemplateTypes(ArtifactGenerationMode mode);
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/9ea878c0/cayenne-cgen/src/main/java/org/apache/cayenne/gen/ClassGenerationAction.java
----------------------------------------------------------------------
diff --git a/cayenne-cgen/src/main/java/org/apache/cayenne/gen/ClassGenerationAction.java b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/ClassGenerationAction.java
index 68897ef..008fc3b 100644
--- a/cayenne-cgen/src/main/java/org/apache/cayenne/gen/ClassGenerationAction.java
+++ b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/ClassGenerationAction.java
@@ -169,7 +169,9 @@ public class ClassGenerationAction {
 	 * template type combination.
 	 */
 	void resetContextForArtifactTemplate(Artifact artifact, TemplateType templateType) {
-		context.put(Artifact.IMPORT_UTILS_KEY, new ImportUtils());
+        ImportUtils importUtils = new ImportUtils();
+        context.put(Artifact.IMPORT_UTILS_KEY, importUtils);
+		context.put(Artifact.PROPERTY_UTILS_KEY, new PropertyUtils(importUtils));
 		artifact.postInitContext(context);
 	}
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/9ea878c0/cayenne-cgen/src/main/java/org/apache/cayenne/gen/EntityUtils.java
----------------------------------------------------------------------
diff --git a/cayenne-cgen/src/main/java/org/apache/cayenne/gen/EntityUtils.java b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/EntityUtils.java
index 2923b98..3d5fc61 100644
--- a/cayenne-cgen/src/main/java/org/apache/cayenne/gen/EntityUtils.java
+++ b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/EntityUtils.java
@@ -232,30 +232,28 @@ public class EntityUtils {
      * @return The type of the attribute keyed on.
      */
     public String getMapKeyType(final ObjRelationship relationship) {
+        return getMapKeyTypeInternal(relationship);
+    }
+
+    static String getMapKeyTypeInternal(final ObjRelationship relationship) {
 
-        ObjEntity targetEntity = (ObjEntity) relationship.getTargetEntity();
+        ObjEntity targetEntity = relationship.getTargetEntity();
 
-        // If the map key is null, then we're doing look-ups by actual object
-        // key.
+        // If the map key is null, then we're doing look-ups by actual object key.
         if (relationship.getMapKey() == null) {
 
-            // If it's a multi-column key, then the return type is always
-            // ObjectId.
+            // If it's a multi-column key, then the return type is always ObjectId.
             DbEntity dbEntity = targetEntity.getDbEntity();
             if ((dbEntity != null) && (dbEntity.getPrimaryKeys().size() > 1)) {
                 return ObjectId.class.getName();
             }
 
-            // If it's a single column key or no key exists at all, then we
-            // really don't
-            // know what the key type is,
+            // If it's a single column key or no key exists at all, then we really don't know what the key type is,
             // so default to Object.
             return Object.class.getName();
         }
 
-        // If the map key is a non-default attribute, then fetch the attribute
-        // and return
-        // its type.
+        // If the map key is a non-default attribute, then fetch the attribute and return its type.
         ObjAttribute attribute = targetEntity.getAttribute(relationship.getMapKey());
         if (attribute == null) {
             throw new CayenneRuntimeException("Invalid map key '%s', no matching attribute found", relationship.getMapKey());

http://git-wip-us.apache.org/repos/asf/cayenne/blob/9ea878c0/cayenne-cgen/src/main/java/org/apache/cayenne/gen/ImportUtils.java
----------------------------------------------------------------------
diff --git a/cayenne-cgen/src/main/java/org/apache/cayenne/gen/ImportUtils.java b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/ImportUtils.java
index 6072356..d1af2f5 100644
--- a/cayenne-cgen/src/main/java/org/apache/cayenne/gen/ImportUtils.java
+++ b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/ImportUtils.java
@@ -184,6 +184,13 @@ public class ImportUtils {
 	}
 
 	/**
+	 * @since 4.2
+	 */
+	public boolean isNumericPrimitive(String type) {
+		return isNonBooleanPrimitive(type) && !"char".equals(type);
+	}
+
+	/**
 	 * @since 3.0
 	 */
 	public boolean isBoolean(String type) {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/9ea878c0/cayenne-cgen/src/main/java/org/apache/cayenne/gen/PropertyUtils.java
----------------------------------------------------------------------
diff --git a/cayenne-cgen/src/main/java/org/apache/cayenne/gen/PropertyUtils.java b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/PropertyUtils.java
new file mode 100644
index 0000000..4a92dcd
--- /dev/null
+++ b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/PropertyUtils.java
@@ -0,0 +1,277 @@
+/*****************************************************************
+ *   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.gen;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.cayenne.dba.TypesMapping;
+import org.apache.cayenne.exp.ExpressionFactory;
+import org.apache.cayenne.exp.property.BaseProperty;
+import org.apache.cayenne.exp.property.DateProperty;
+import org.apache.cayenne.exp.property.EntityProperty;
+import org.apache.cayenne.exp.property.ListProperty;
+import org.apache.cayenne.exp.property.MapProperty;
+import org.apache.cayenne.exp.property.NumericProperty;
+import org.apache.cayenne.exp.property.PropertyFactory;
+import org.apache.cayenne.exp.property.SetProperty;
+import org.apache.cayenne.exp.property.StringProperty;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjRelationship;
+
+/**
+ * @since 4.2
+ */
+public class PropertyUtils {
+
+    private static final String PK_PROPERTY_SUFFIX = "_PK_PROPERTY";
+    private static final Map<String, String> FACTORY_METHODS = new HashMap<>();
+
+    static {
+        FACTORY_METHODS.put(BaseProperty.class.getName(), "createBase");
+        FACTORY_METHODS.put(NumericProperty.class.getName(), "createNumeric");
+        FACTORY_METHODS.put(StringProperty.class.getName(), "createString");
+        FACTORY_METHODS.put(DateProperty.class.getName(), "createDate");
+        FACTORY_METHODS.put(ListProperty.class.getName(), "createList");
+        FACTORY_METHODS.put(SetProperty.class.getName(), "createSet");
+        FACTORY_METHODS.put(MapProperty.class.getName(), "createMap");
+    }
+
+    private static final List<Class<?>> JAVA_DATE_TYPES = Arrays.asList(
+            java.util.Date.class,
+            java.time.LocalDate.class,
+            java.time.LocalTime.class,
+            java.time.LocalDateTime.class,
+            java.sql.Date.class,
+            java.sql.Time.class,
+            java.sql.Timestamp.class
+    );
+
+    private final ImportUtils importUtils;
+
+    public PropertyUtils(ImportUtils importUtils) {
+        this.importUtils = importUtils;
+    }
+
+    public void addImportForPK(DbEntity entity) throws ClassNotFoundException {
+        importUtils.addType(PropertyFactory.class.getName());
+        importUtils.addType(ExpressionFactory.class.getName());
+        for(DbAttribute attribute : entity.getPrimaryKeys()) {
+            importUtils.addType(TypesMapping.getJavaBySqlType(attribute.getType()));
+            importUtils.addType(getPropertyTypeForAttribute(attribute));
+        }
+    }
+
+    public void addImport(ObjAttribute attribute) {
+        importUtils.addType(PropertyFactory.class.getName());
+        importUtils.addType(attribute.getType());
+        importUtils.addType(getPropertyTypeForAttribute(attribute));
+    }
+
+    public void addImport(ObjRelationship relationship) {
+        importUtils.addType(PropertyFactory.class.getName());
+        if (relationship.getTargetEntityName() != null) {
+            importUtils.addType(relationship.getTargetEntity().getClassName());
+        }
+        importUtils.addType(getPropertyTypeForJavaClass(relationship));
+        if (relationship.isToMany()) {
+            importUtils.addType(relationship.getCollectionType());
+        }
+    }
+
+    public String propertyDefinition(DbAttribute attribute) throws ClassNotFoundException {
+        StringUtils utils = StringUtils.getInstance();
+
+        String attributeType = TypesMapping.getJavaBySqlType(attribute.getType());
+        String propertyType = getPropertyTypeForAttribute(attribute);
+        String propertyFactoryMethod = factoryMethodForPropertyType(propertyType);
+        attributeType = importUtils.formatJavaType(attributeType, false);
+
+        return String.format("public static final %s<%s> %s = PropertyFactory.%s(ExpressionFactory.dbPathExp(\"%s\"), %s.class);",
+                importUtils.formatJavaType(propertyType),
+                attributeType,
+                utils.capitalizedAsConstant(attribute.getName() + PK_PROPERTY_SUFFIX),
+                propertyFactoryMethod,
+                attribute.getName(),
+                attributeType
+        );
+    }
+
+    private String getPropertyTypeForAttribute(DbAttribute attribute) throws ClassNotFoundException {
+        String attributeType = TypesMapping.getJavaBySqlType(attribute.getType());
+        Class<?> javaClass = Class.forName(attributeType);
+        if (Number.class.isAssignableFrom(javaClass)) {
+            return NumericProperty.class.getName();
+        }
+
+        if (CharSequence.class.isAssignableFrom(javaClass)) {
+            return StringProperty.class.getName();
+        }
+
+        if (JAVA_DATE_TYPES.contains(javaClass)) {
+            return DateProperty.class.getName();
+        }
+
+        return BaseProperty.class.getName();
+    }
+
+    public String propertyDefinition(ObjAttribute attribute) {
+        StringUtils utils = StringUtils.getInstance();
+
+        String propertyType = getPropertyTypeForAttribute(attribute);
+        String propertyFactoryMethod = factoryMethodForPropertyType(propertyType);
+        String attributeType = utils.stripGeneric(importUtils.formatJavaType(attribute.getType(), false));
+
+        return String.format("public static final %s<%s> %s = PropertyFactory.%s(\"%s\", %s.class);",
+                importUtils.formatJavaType(propertyType),
+                attributeType,
+                utils.capitalizedAsConstant(attribute.getName()),
+                propertyFactoryMethod,
+                attribute.getName(),
+                attributeType
+        );
+    }
+
+    public String propertyDefinition(ObjRelationship relationship) {
+        if (relationship.isToMany()) {
+            return toManyRelationshipDefinition(relationship);
+        } else {
+            return toOneRelationshipDefinition(relationship);
+        }
+    }
+
+    private String toManyRelationshipDefinition(ObjRelationship relationship) {
+        if (Map.class.getName().equals(relationship.getCollectionType())) {
+            return mapRelationshipDefinition(relationship);
+        } else {
+            return collectionRelationshipDefinition(relationship);
+        }
+    }
+
+    private String mapRelationshipDefinition(ObjRelationship relationship) {
+        StringUtils utils = StringUtils.getInstance();
+
+        String propertyType = getPropertyTypeForJavaClass(relationship);
+        String propertyFactoryMethod = factoryMethodForPropertyType(propertyType);
+        String mapKeyType = EntityUtils.getMapKeyTypeInternal(relationship);
+        String attributeType = relationship.getTargetEntity().getClassName();
+
+        return String.format("public static final %s<%s, %s> %s = PropertyFactory.%s(\"%s\", %s.class, %s.class);",
+                importUtils.formatJavaType(propertyType),
+                importUtils.formatJavaType(mapKeyType),
+                importUtils.formatJavaType(attributeType),
+                utils.capitalizedAsConstant(relationship.getName()),
+                propertyFactoryMethod,
+                relationship.getName(),
+                importUtils.formatJavaType(mapKeyType),
+                importUtils.formatJavaType(attributeType)
+        );
+    }
+
+    private String collectionRelationshipDefinition(ObjRelationship relationship) {
+        StringUtils utils = StringUtils.getInstance();
+
+        String propertyType = getPropertyTypeForJavaClass(relationship);
+        String propertyFactoryMethod = factoryMethodForPropertyType(propertyType);
+        String entityType = importUtils.formatJavaType(relationship.getTargetEntity().getClassName());
+
+        return String.format("public static final %s<%s> %s = PropertyFactory.%s(\"%s\", %s.class);",
+                importUtils.formatJavaType(propertyType),
+                entityType,
+                utils.capitalizedAsConstant(relationship.getName()),
+                propertyFactoryMethod,
+                relationship.getName(),
+                entityType
+        );
+    }
+
+    private String toOneRelationshipDefinition(ObjRelationship relationship) {
+        StringUtils utils = StringUtils.getInstance();
+
+        String propertyType = EntityProperty.class.getName();
+        String propertyFactoryMethod = "createEntity";
+        String attributeType = relationship.getTargetEntityName() == null
+                ? "Object"
+                : importUtils.formatJavaType(relationship.getTargetEntity().getClassName());
+
+        return String.format("public static final %s<%s> %s = PropertyFactory.%s(\"%s\", %s.class);",
+                importUtils.formatJavaType(propertyType),
+                attributeType,
+                utils.capitalizedAsConstant(relationship.getName()),
+                propertyFactoryMethod,
+                relationship.getName(),
+                attributeType
+        );
+    }
+
+    private String factoryMethodForPropertyType(String propertyType) {
+        return FACTORY_METHODS.get(propertyType);
+    }
+
+    private String getPropertyTypeForJavaClass(ObjRelationship relationship) {
+        if (relationship.isToMany()) {
+            String collectionType = relationship.getCollectionType();
+            if (java.util.Map.class.getName().equals(collectionType)) {
+                return MapProperty.class.getName();
+            }
+
+            if (java.util.List.class.getName().equals(collectionType)) {
+                return ListProperty.class.getName();
+            }
+
+            return SetProperty.class.getName();
+        }
+
+        return EntityProperty.class.getName();
+    }
+
+    private String getPropertyTypeForAttribute(ObjAttribute attribute) {
+        String attributeType = attribute.getType();
+
+        if (importUtils.isNumericPrimitive(attributeType)) {
+            return NumericProperty.class.getName();
+        }
+
+        Class<?> javaClass;
+        if (importUtils.isPrimitive(attributeType) || attributeType.startsWith("java.")) {
+            javaClass = attribute.getJavaClass();
+        } else {
+            return BaseProperty.class.getName();
+        }
+
+        if (Number.class.isAssignableFrom(javaClass)) {
+            return NumericProperty.class.getName();
+        }
+
+        if (CharSequence.class.isAssignableFrom(javaClass)) {
+            return StringProperty.class.getName();
+        }
+
+        if (JAVA_DATE_TYPES.contains(javaClass)) {
+            return DateProperty.class.getName();
+        }
+
+        return BaseProperty.class.getName();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/9ea878c0/cayenne-cgen/src/main/resources/templates/v4_1/singleclass.vm
----------------------------------------------------------------------
diff --git a/cayenne-cgen/src/main/resources/templates/v4_1/singleclass.vm b/cayenne-cgen/src/main/resources/templates/v4_1/singleclass.vm
index 0f4d3e7..1d5199f 100644
--- a/cayenne-cgen/src/main/resources/templates/v4_1/singleclass.vm
+++ b/cayenne-cgen/src/main/resources/templates/v4_1/singleclass.vm
@@ -40,19 +40,13 @@ ${importUtils.addType("java.io.IOException")}##
 ${importUtils.addType("java.io.ObjectInputStream")}##
 ${importUtils.addType("java.io.ObjectOutputStream")}##
 #if( $createPKProperties )
-${importUtils.addType("org.apache.cayenne.exp.ExpressionFactory")}##
-#end
-#if((${object.DeclaredAttributes} && !${object.DeclaredAttributes.isEmpty()}) || (${object.DeclaredRelationships} && !${object.DeclaredRelationships.isEmpty()}))
-${importUtils.addType('org.apache.cayenne.exp.Property')}##
+$propertyUtils.addImportForPK($object.DbEntity)##
 #end
 #foreach( $attr in ${object.DeclaredAttributes} )
-$importUtils.addType(${attr.Type})##
+$propertyUtils.addImport($attr)##
 #end
 #foreach( $rel in ${object.DeclaredRelationships} )
-$importUtils.addType(${rel.TargetEntity.ClassName})##
-#if(${rel.CollectionType})
-$importUtils.addType(${rel.CollectionType})##
-#end
+$propertyUtils.addImport($rel)##
 #end
 ${importUtils.generate()}
 
@@ -79,7 +73,7 @@ public#if("true" == "${object.isAbstract()}") abstract#end class ${subClassName}
     #foreach( $idAttr in ${object.DbEntity.PrimaryKeys} )
         #if( $createPKProperties && !${entityUtils.declaresDbAttribute($idAttr)})
             #set ( $type = "$importUtils.dbAttributeToJavaType($idAttr)")
-    public static final Property<$type> ${stringUtils.capitalizedAsConstant($idAttr.Name)}_PK_COLUMN = Property.create(ExpressionFactory.dbPathExp("${idAttr.Name}"), ${type}.class});
+    $propertyUtils.propertyDefinition($idAttr)
         #else
     public static final String ${stringUtils.capitalizedAsConstant($idAttr.Name)}_PK_COLUMN = "${idAttr.Name}";
         #end
@@ -90,22 +84,10 @@ public#if("true" == "${object.isAbstract()}") abstract#end class ${subClassName}
 ## Create Properties ##
 #######################
 #foreach( $attr in ${object.DeclaredAttributes} )
-#set ( $type = "$importUtils.formatJavaType(${attr.Type}, false)")
-    public static final Property<$type> ${stringUtils.capitalizedAsConstant($attr.Name)} = Property.create("${attr.Name}", ${stringUtils.stripGeneric($type)}.class);
+    $propertyUtils.propertyDefinition($attr)
 #end
 #foreach( $rel in ${object.DeclaredRelationships} )
-#if( $rel.ToMany )
-#if ( ${rel.CollectionType} == "java.util.Map")
-#set( $type = "$importUtils.formatJavaType($rel.CollectionType)<$importUtils.formatJavaType($entityUtils.getMapKeyType($rel)), $importUtils.formatJavaType($rel.TargetEntity.ClassName)>" )
-    public static final Property<$type> ${stringUtils.capitalizedAsConstant($rel.Name)} = Property.create("${rel.Name}", ${stringUtils.stripGeneric($type)}.class);
-#else
-#set( $type = "$importUtils.formatJavaType($rel.CollectionType)<$importUtils.formatJavaType($rel.TargetEntity.ClassName)>" )
-    public static final Property<$type> ${stringUtils.capitalizedAsConstant($rel.Name)} = Property.create("${rel.Name}", ${stringUtils.stripGeneric($type)}.class);
-#end
-#else
-    #set( $type = "$importUtils.formatJavaType(${rel.TargetEntity.ClassName})" )
-    public static final Property<$type> ${stringUtils.capitalizedAsConstant($rel.Name)} = Property.create("${rel.Name}", ${stringUtils.stripGeneric($type)}.class);
-#end
+    $propertyUtils.propertyDefinition($rel)
 #end
 
 ###################

http://git-wip-us.apache.org/repos/asf/cayenne/blob/9ea878c0/cayenne-cgen/src/main/resources/templates/v4_1/superclass.vm
----------------------------------------------------------------------
diff --git a/cayenne-cgen/src/main/resources/templates/v4_1/superclass.vm b/cayenne-cgen/src/main/resources/templates/v4_1/superclass.vm
index e275c0f..96dd580 100644
--- a/cayenne-cgen/src/main/resources/templates/v4_1/superclass.vm
+++ b/cayenne-cgen/src/main/resources/templates/v4_1/superclass.vm
@@ -42,19 +42,13 @@ ${importUtils.addType("java.io.IOException")}##
 ${importUtils.addType("java.io.ObjectInputStream")}##
 ${importUtils.addType("java.io.ObjectOutputStream")}##
 #if( $createPKProperties )
-${importUtils.addType("org.apache.cayenne.exp.ExpressionFactory")}##
-#end
-#if((${object.DeclaredAttributes} && !${object.DeclaredAttributes.isEmpty()}) || (${object.DeclaredRelationships} && !${object.DeclaredRelationships.isEmpty()}))
-${importUtils.addType('org.apache.cayenne.exp.Property')}##
+$propertyUtils.addImportForPK($object.DbEntity)##
 #end
 #foreach( $attr in ${object.DeclaredAttributes} )
-$importUtils.addType(${attr.Type})##
+$propertyUtils.addImport($attr)##
 #end
 #foreach( $rel in ${object.DeclaredRelationships} )
-$importUtils.addType(${rel.TargetEntity.ClassName})##
-#if(${rel.CollectionType}) 
-$importUtils.addType(${rel.CollectionType})##
-#end
+$propertyUtils.addImport($rel)##
 #end
 ${importUtils.generate()}
 
@@ -87,7 +81,7 @@ public abstract class ${superClassName} extends ${baseClassName} {
     #foreach( $idAttr in ${object.DbEntity.PrimaryKeys} )
         #if( $createPKProperties && !${entityUtils.declaresDbAttribute($idAttr)})
             #set ( $type = "$importUtils.dbAttributeToJavaType($idAttr)")
-    public static final Property<$type> ${stringUtils.capitalizedAsConstant($idAttr.Name)}_PK_PROPERTY = Property.create(ExpressionFactory.dbPathExp("${idAttr.Name}"), ${type}.class);
+    $propertyUtils.propertyDefinition($idAttr)
         #end
     public static final String ${stringUtils.capitalizedAsConstant($idAttr.Name)}_PK_COLUMN = "${idAttr.Name}";
     #end
@@ -97,22 +91,10 @@ public abstract class ${superClassName} extends ${baseClassName} {
 ## Create Properties ##
 #######################
 #foreach( $attr in ${object.DeclaredAttributes} )
-    #set ( $type = "$importUtils.formatJavaType(${attr.Type}, false)")
-    public static final Property<$type> ${stringUtils.capitalizedAsConstant($attr.Name)} = Property.create("${attr.Name}", ${stringUtils.stripGeneric($type)}.class);
+    $propertyUtils.propertyDefinition($attr)
 #end
 #foreach( $rel in ${object.DeclaredRelationships} )
-#if( $rel.ToMany )
-#if ( ${rel.CollectionType} == "java.util.Map")
-    #set( $type = "$importUtils.formatJavaType($rel.CollectionType)<$importUtils.formatJavaType($entityUtils.getMapKeyType($rel)), $importUtils.formatJavaType($rel.TargetEntity.ClassName)>" )
-    public static final Property<$type> ${stringUtils.capitalizedAsConstant($rel.Name)} = Property.create("${rel.Name}", ${stringUtils.stripGeneric($type)}.class);
-#else
-    #set( $type = "$importUtils.formatJavaType($rel.CollectionType)<$importUtils.formatJavaType($rel.TargetEntity.ClassName)>" )
-    public static final Property<$type> ${stringUtils.capitalizedAsConstant($rel.Name)} = Property.create("${rel.Name}", ${stringUtils.stripGeneric($type)}.class);
-#end
-#else
-    #set( $type = "$importUtils.formatJavaType(${rel.TargetEntity.ClassName})" )
-    public static final Property<$type> ${stringUtils.capitalizedAsConstant($rel.Name)} = Property.create("${rel.Name}", ${stringUtils.stripGeneric($type)}.class);
-#end
+    $propertyUtils.propertyDefinition($rel)
 #end
 
 ###################

http://git-wip-us.apache.org/repos/asf/cayenne/blob/9ea878c0/cayenne-cgen/src/test/java/org/apache/cayenne/gen/PropertyUtilsTest.java
----------------------------------------------------------------------
diff --git a/cayenne-cgen/src/test/java/org/apache/cayenne/gen/PropertyUtilsTest.java b/cayenne-cgen/src/test/java/org/apache/cayenne/gen/PropertyUtilsTest.java
new file mode 100644
index 0000000..84cfc1a
--- /dev/null
+++ b/cayenne-cgen/src/test/java/org/apache/cayenne/gen/PropertyUtilsTest.java
@@ -0,0 +1,223 @@
+/*****************************************************************
+ *   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.gen;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.cayenne.exp.property.DateProperty;
+import org.apache.cayenne.exp.property.EntityProperty;
+import org.apache.cayenne.exp.property.ListProperty;
+import org.apache.cayenne.exp.property.MapProperty;
+import org.apache.cayenne.exp.property.NumericProperty;
+import org.apache.cayenne.exp.property.PropertyFactory;
+import org.apache.cayenne.exp.property.SetProperty;
+import org.apache.cayenne.exp.property.StringProperty;
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.map.ObjRelationship;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * @since 4.2
+ */
+public class PropertyUtilsTest {
+
+    PropertyUtils propertyUtils;
+    ImportUtils importUtils;
+
+    @Before
+    public void setup() {
+        importUtils = new ImportUtils();
+        propertyUtils = new PropertyUtils(importUtils);
+    }
+
+    @Test
+    public void testImportAttribute() {
+        ObjAttribute attribute = new ObjAttribute();
+        attribute.setName("test");
+        attribute.setType(java.util.Date.class.getName());
+
+        Map<String, String> importTypesMap = importUtils.importTypesMap;
+        assertEquals(0, importTypesMap.size());
+
+        propertyUtils.addImport(attribute);
+
+        assertEquals(3, importTypesMap.size());
+        assertEquals(java.util.Date.class.getName(), importTypesMap.get("Date"));
+        assertEquals(DateProperty.class.getName(), importTypesMap.get("DateProperty"));
+        assertEquals(PropertyFactory.class.getName(), importTypesMap.get("PropertyFactory"));
+    }
+
+    @Test
+    public void testImportRelationship() {
+        String typeName = "org.example.model.TargetEntity";
+        ObjEntity entity = mock(ObjEntity.class);
+        when(entity.getClassName()).thenReturn(typeName);
+        when(entity.getName()).thenReturn("target");
+
+        ObjRelationship relationship = mock(ObjRelationship.class);
+        when(relationship.isToMany()).thenReturn(true);
+        when(relationship.getCollectionType()).thenReturn("java.util.List");
+        when(relationship.getTargetEntityName()).thenReturn("target");
+        when(relationship.getName()).thenReturn("list_rel");
+        when(relationship.getTargetEntity()).thenReturn(entity);
+
+        Map<String, String> importTypesMap = importUtils.importTypesMap;
+        assertEquals(0, importTypesMap.size());
+
+        propertyUtils.addImport(relationship);
+
+        assertEquals(4, importTypesMap.size());
+        assertEquals(typeName, importTypesMap.get("TargetEntity"));
+        assertEquals(List.class.getName(), importTypesMap.get("List"));
+        assertEquals(ListProperty.class.getName(), importTypesMap.get("ListProperty"));
+        assertEquals(PropertyFactory.class.getName(), importTypesMap.get("PropertyFactory"));
+    }
+
+    @Test
+    public void simpleNumericDefinition() {
+        importUtils.addType(NumericProperty.class.getName());
+
+        ObjAttribute attribute = new ObjAttribute();
+        attribute.setName("test");
+        attribute.setType("int");
+
+        String definition = propertyUtils.propertyDefinition(attribute);
+        assertEquals("public static final NumericProperty<Integer> TEST = PropertyFactory.createNumeric(\"test\", Integer.class);",
+                definition);
+    }
+
+    @Test
+    public void simpleStringDefinition() {
+        importUtils.addType(StringProperty.class.getName());
+
+        ObjAttribute attribute = new ObjAttribute();
+        attribute.setName("test");
+        attribute.setType("java.lang.String");
+
+        String definition = propertyUtils.propertyDefinition(attribute);
+        assertEquals("public static final StringProperty<String> TEST = PropertyFactory.createString(\"test\", String.class);",
+                definition);
+    }
+
+    @Test
+    public void toOneRelationshipDefinition() {
+
+        String typeName = "org.example.model.TargetEntity";
+        importUtils.addType(EntityProperty.class.getName());
+        importUtils.addType(typeName);
+
+        ObjEntity entity = mock(ObjEntity.class);
+        when(entity.getClassName()).thenReturn(typeName);
+        when(entity.getName()).thenReturn("target");
+
+        ObjRelationship relationship = mock(ObjRelationship.class);
+        when(relationship.isToMany()).thenReturn(false);
+        when(relationship.getTargetEntityName()).thenReturn("target");
+        when(relationship.getName()).thenReturn("to_one_rel");
+        when(relationship.getTargetEntity()).thenReturn(entity);
+
+        String definition = propertyUtils.propertyDefinition(relationship);
+        assertEquals("public static final EntityProperty<TargetEntity> TO_ONE_REL = PropertyFactory.createEntity(\"to_one_rel\", TargetEntity.class);",
+                definition);
+    }
+
+    @Test
+    public void listRelationshipDefinition() {
+
+        String typeName = "org.example.model.TargetEntity";
+        importUtils.addType(ListProperty.class.getName());
+        importUtils.addType(typeName);
+
+        ObjEntity entity = mock(ObjEntity.class);
+        when(entity.getClassName()).thenReturn(typeName);
+        when(entity.getName()).thenReturn("target");
+
+        ObjRelationship relationship = mock(ObjRelationship.class);
+        when(relationship.isToMany()).thenReturn(true);
+        when(relationship.getCollectionType()).thenReturn("java.util.List");
+        when(relationship.getTargetEntityName()).thenReturn("target");
+        when(relationship.getName()).thenReturn("list_rel");
+        when(relationship.getTargetEntity()).thenReturn(entity);
+
+        String definition = propertyUtils.propertyDefinition(relationship);
+        assertEquals("public static final ListProperty<TargetEntity> LIST_REL = PropertyFactory.createList(\"list_rel\", TargetEntity.class);",
+                definition);
+    }
+
+    @Test
+    public void setRelationshipDefinition() {
+
+        String typeName = "org.example.model.TargetEntity";
+        importUtils.addType(SetProperty.class.getName());
+        importUtils.addType(typeName);
+
+        ObjEntity entity = mock(ObjEntity.class);
+        when(entity.getClassName()).thenReturn(typeName);
+        when(entity.getName()).thenReturn("target");
+
+        ObjRelationship relationship = mock(ObjRelationship.class);
+        when(relationship.isToMany()).thenReturn(true);
+        when(relationship.getCollectionType()).thenReturn("java.util.Set");
+        when(relationship.getTargetEntityName()).thenReturn("target");
+        when(relationship.getName()).thenReturn("set_rel");
+        when(relationship.getTargetEntity()).thenReturn(entity);
+
+        String definition = propertyUtils.propertyDefinition(relationship);
+        assertEquals("public static final SetProperty<TargetEntity> SET_REL = PropertyFactory.createSet(\"set_rel\", TargetEntity.class);",
+                definition);
+    }
+
+    @Test
+    public void mapRelationshipDefinition() {
+
+        String typeName = "org.example.model.TargetEntity";
+        importUtils.addType(MapProperty.class.getName());
+        importUtils.addType(typeName);
+
+        ObjAttribute attribute = mock(ObjAttribute.class);
+        when(attribute.getType()).thenReturn(String.class.getName());
+
+        ObjEntity entity = mock(ObjEntity.class);
+        when(entity.getClassName()).thenReturn(typeName);
+        when(entity.getName()).thenReturn("target");
+        when(entity.getAttribute(anyString())).thenReturn(attribute);
+
+        ObjRelationship relationship = mock(ObjRelationship.class);
+        when(relationship.isToMany()).thenReturn(true);
+        when(relationship.getCollectionType()).thenReturn("java.util.Map");
+        when(relationship.getMapKey()).thenReturn("key");
+        when(relationship.getTargetEntityName()).thenReturn("target");
+        when(relationship.getName()).thenReturn("map_rel");
+        when(relationship.getTargetEntity()).thenReturn(entity);
+
+        String definition = propertyUtils.propertyDefinition(relationship);
+        assertEquals("public static final MapProperty<String, TargetEntity> MAP_REL = PropertyFactory.createMap(\"map_rel\", String.class, TargetEntity.class);",
+                definition);
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/9ea878c0/cayenne-cgen/src/test/java/org/apache/cayenne/gen/SingleClassGenerationTest.java
----------------------------------------------------------------------
diff --git a/cayenne-cgen/src/test/java/org/apache/cayenne/gen/SingleClassGenerationTest.java b/cayenne-cgen/src/test/java/org/apache/cayenne/gen/SingleClassGenerationTest.java
index 0bbe9e9..38eab41 100644
--- a/cayenne-cgen/src/test/java/org/apache/cayenne/gen/SingleClassGenerationTest.java
+++ b/cayenne-cgen/src/test/java/org/apache/cayenne/gen/SingleClassGenerationTest.java
@@ -35,23 +35,34 @@ public class SingleClassGenerationTest extends ClassGenerationCase {
         ObjEntity objEntity = new ObjEntity("TEST1");
 
         VelocityContext context = new VelocityContext();
+        ImportUtils importUtils = new ImportUtils();
         context.put(Artifact.OBJECT_KEY, objEntity);
+        context.put(Artifact.IMPORT_UTILS_KEY, importUtils);
+        context.put(Artifact.STRING_UTILS_KEY, StringUtils.getInstance());
+        context.put(Artifact.PROPERTY_UTILS_KEY, new PropertyUtils(importUtils));
 
         String res = renderTemplate(ClassGenerationAction.SINGLE_CLASS_TEMPLATE, context);
-        assertFalse(res.contains("org.apache.cayenne.exp.Property"));
+        assertFalse(res.contains("org.apache.cayenne.exp.property.NumericProperty"));
+        assertFalse(res.contains("org.apache.cayenne.exp.property.EntityProperty"));
     }
 
     @Test
     public void testContainsPropertyImportForAttributes() throws Exception {
         ObjEntity objEntity = new ObjEntity("TEST1");
         ObjAttribute attr = new ObjAttribute("attr");
+        attr.setType("java.lang.Integer");
         objEntity.addAttribute(attr);
 
         VelocityContext context = new VelocityContext();
+        ImportUtils importUtils = new ImportUtils();
         context.put(Artifact.OBJECT_KEY, objEntity);
+        context.put(Artifact.IMPORT_UTILS_KEY, importUtils);
+        context.put(Artifact.STRING_UTILS_KEY, StringUtils.getInstance());
+        context.put(Artifact.PROPERTY_UTILS_KEY, new PropertyUtils(importUtils));
 
         String res = renderTemplate(ClassGenerationAction.SINGLE_CLASS_TEMPLATE, context);
-        assertTrue(res.contains("org.apache.cayenne.exp.Property"));
+        assertTrue(res.contains("org.apache.cayenne.exp.property.NumericProperty"));
+        assertFalse(res.contains("org.apache.cayenne.exp.property.EntityProperty"));
     }
 
     @Test
@@ -61,26 +72,38 @@ public class SingleClassGenerationTest extends ClassGenerationCase {
         objEntity.addRelationship(rel);
 
         VelocityContext context = new VelocityContext();
+        ImportUtils importUtils = new ImportUtils();
         context.put(Artifact.OBJECT_KEY, objEntity);
+        context.put(Artifact.IMPORT_UTILS_KEY, importUtils);
+        context.put(Artifact.STRING_UTILS_KEY, StringUtils.getInstance());
+        context.put(Artifact.PROPERTY_UTILS_KEY, new PropertyUtils(importUtils));
 
         String res = renderTemplate(ClassGenerationAction.SINGLE_CLASS_TEMPLATE, context);
-        assertTrue(res.contains("org.apache.cayenne.exp.Property"));
+        assertFalse(res.contains("org.apache.cayenne.exp.property.NumericProperty"));
+        assertTrue(res.contains("org.apache.cayenne.exp.property.EntityProperty"));
     }
 
     @Test
     public void testContainsPropertyImport() throws Exception {
         ObjEntity objEntity = new ObjEntity("TEST1");
         ObjAttribute attr = new ObjAttribute("attr");
+        attr.setType("java.lang.Integer");
+
         ObjRelationship rel = new ObjRelationship("rel");
 
         objEntity.addAttribute(attr);
         objEntity.addRelationship(rel);
 
         VelocityContext context = new VelocityContext();
+        ImportUtils importUtils = new ImportUtils();
         context.put(Artifact.OBJECT_KEY, objEntity);
+        context.put(Artifact.IMPORT_UTILS_KEY, importUtils);
+        context.put(Artifact.STRING_UTILS_KEY, StringUtils.getInstance());
+        context.put(Artifact.PROPERTY_UTILS_KEY, new PropertyUtils(importUtils));
 
         String res = renderTemplate(ClassGenerationAction.SINGLE_CLASS_TEMPLATE, context);
-        assertTrue(res.contains("org.apache.cayenne.exp.Property"));
+        assertTrue(res.contains("org.apache.cayenne.exp.property.NumericProperty"));
+        assertTrue(res.contains("org.apache.cayenne.exp.property.EntityProperty"));
     }
 
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/9ea878c0/cayenne-cgen/src/test/java/org/apache/cayenne/gen/SuperClassGenerationTest.java
----------------------------------------------------------------------
diff --git a/cayenne-cgen/src/test/java/org/apache/cayenne/gen/SuperClassGenerationTest.java b/cayenne-cgen/src/test/java/org/apache/cayenne/gen/SuperClassGenerationTest.java
index 0a47d63..8acaba3 100644
--- a/cayenne-cgen/src/test/java/org/apache/cayenne/gen/SuperClassGenerationTest.java
+++ b/cayenne-cgen/src/test/java/org/apache/cayenne/gen/SuperClassGenerationTest.java
@@ -19,6 +19,8 @@
 
 package org.apache.cayenne.gen;
 
+import org.apache.cayenne.exp.property.EntityProperty;
+import org.apache.cayenne.exp.property.NumericProperty;
 import org.apache.cayenne.map.ObjAttribute;
 import org.apache.cayenne.map.ObjEntity;
 import org.apache.cayenne.map.ObjRelationship;
@@ -36,22 +38,35 @@ public class SuperClassGenerationTest extends ClassGenerationCase {
 
         VelocityContext context = new VelocityContext();
         context.put(Artifact.OBJECT_KEY, objEntity);
+        ImportUtils importUtils = new ImportUtils();
+        context.put(Artifact.OBJECT_KEY, objEntity);
+        context.put(Artifact.IMPORT_UTILS_KEY, importUtils);
+        context.put(Artifact.STRING_UTILS_KEY, StringUtils.getInstance());
+        context.put(Artifact.PROPERTY_UTILS_KEY, new PropertyUtils(importUtils));
 
         String res = renderTemplate(ClassGenerationAction.SUPERCLASS_TEMPLATE, context);
-        assertFalse(res.contains("org.apache.cayenne.exp.Property"));
+        assertFalse(res.contains(NumericProperty.class.getName()));
+        assertFalse(res.contains(EntityProperty.class.getName()));
     }
 
     @Test
     public void testContainsPropertyImportForAttributes() throws Exception {
         ObjEntity objEntity = new ObjEntity("TEST1");
         ObjAttribute attr = new ObjAttribute("attr");
+        attr.setType("int");
         objEntity.addAttribute(attr);
 
         VelocityContext context = new VelocityContext();
         context.put(Artifact.OBJECT_KEY, objEntity);
+        ImportUtils importUtils = new ImportUtils();
+        context.put(Artifact.OBJECT_KEY, objEntity);
+        context.put(Artifact.IMPORT_UTILS_KEY, importUtils);
+        context.put(Artifact.STRING_UTILS_KEY, StringUtils.getInstance());
+        context.put(Artifact.PROPERTY_UTILS_KEY, new PropertyUtils(importUtils));
 
         String res = renderTemplate(ClassGenerationAction.SUPERCLASS_TEMPLATE, context);
-        assertTrue(res.contains("org.apache.cayenne.exp.Property"));
+        assertTrue(res.contains(NumericProperty.class.getName()));
+        assertFalse(res.contains(EntityProperty.class.getName()));
     }
 
     @Test
@@ -62,25 +77,39 @@ public class SuperClassGenerationTest extends ClassGenerationCase {
 
         VelocityContext context = new VelocityContext();
         context.put(Artifact.OBJECT_KEY, objEntity);
+        ImportUtils importUtils = new ImportUtils();
+        context.put(Artifact.OBJECT_KEY, objEntity);
+        context.put(Artifact.IMPORT_UTILS_KEY, importUtils);
+        context.put(Artifact.STRING_UTILS_KEY, StringUtils.getInstance());
+        context.put(Artifact.PROPERTY_UTILS_KEY, new PropertyUtils(importUtils));
 
         String res = renderTemplate(ClassGenerationAction.SUPERCLASS_TEMPLATE, context);
-        assertTrue(res.contains("org.apache.cayenne.exp.Property"));
+        assertFalse(res.contains(NumericProperty.class.getName()));
+        assertTrue(res.contains(EntityProperty.class.getName()));
     }
 
     @Test
     public void testContainsPropertyImport() throws Exception {
         ObjEntity objEntity = new ObjEntity("TEST1");
+
         ObjAttribute attr = new ObjAttribute("attr");
+        attr.setType("java.lang.Integer");
+
         ObjRelationship rel = new ObjRelationship("rel");
 
         objEntity.addAttribute(attr);
         objEntity.addRelationship(rel);
 
         VelocityContext context = new VelocityContext();
+        ImportUtils importUtils = new ImportUtils();
         context.put(Artifact.OBJECT_KEY, objEntity);
+        context.put(Artifact.IMPORT_UTILS_KEY, importUtils);
+        context.put(Artifact.STRING_UTILS_KEY, StringUtils.getInstance());
+        context.put(Artifact.PROPERTY_UTILS_KEY, new PropertyUtils(importUtils));
 
         String res = renderTemplate(ClassGenerationAction.SUPERCLASS_TEMPLATE, context);
-        assertTrue(res.contains("org.apache.cayenne.exp.Property"));
+        assertTrue(res.contains(NumericProperty.class.getName()));
+        assertTrue(res.contains(EntityProperty.class.getName()));
     }
 
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/9ea878c0/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/db/auto/_Table1.java
----------------------------------------------------------------------
diff --git a/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/db/auto/_Table1.java b/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/db/auto/_Table1.java
index bf34752..0d32a17 100644
--- a/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/db/auto/_Table1.java
+++ b/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/db/auto/_Table1.java
@@ -5,7 +5,9 @@ import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 
 import org.apache.cayenne.BaseDataObject;
-import org.apache.cayenne.exp.Property;
+import org.apache.cayenne.exp.property.NumericProperty;
+import org.apache.cayenne.exp.property.PropertyFactory;
+import org.apache.cayenne.exp.property.StringProperty;
 
 /**
  * Class _Table1 was generated by Cayenne.
@@ -19,10 +21,10 @@ public abstract class _Table1 extends BaseDataObject {
 
     public static final String ID_PK_COLUMN = "ID";
 
-    public static final Property<Integer> CRYPTO_INT = Property.create("cryptoInt", Integer.class);
-    public static final Property<String> CRYPTO_STRING = Property.create("cryptoString", String.class);
-    public static final Property<Integer> PLAIN_INT = Property.create("plainInt", Integer.class);
-    public static final Property<String> PLAIN_STRING = Property.create("plainString", String.class);
+    public static final NumericProperty<Integer> CRYPTO_INT = PropertyFactory.createNumeric("cryptoInt", Integer.class);
+    public static final StringProperty<String> CRYPTO_STRING = PropertyFactory.createString("cryptoString", String.class);
+    public static final NumericProperty<Integer> PLAIN_INT = PropertyFactory.createNumeric("plainInt", Integer.class);
+    public static final StringProperty<String> PLAIN_STRING = PropertyFactory.createString("plainString", String.class);
 
     protected Integer cryptoInt;
     protected String cryptoString;

http://git-wip-us.apache.org/repos/asf/cayenne/blob/9ea878c0/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/db/auto/_Table2.java
----------------------------------------------------------------------
diff --git a/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/db/auto/_Table2.java b/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/db/auto/_Table2.java
index 5d0e518..ebb619c 100644
--- a/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/db/auto/_Table2.java
+++ b/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/db/auto/_Table2.java
@@ -5,7 +5,8 @@ import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 
 import org.apache.cayenne.BaseDataObject;
-import org.apache.cayenne.exp.Property;
+import org.apache.cayenne.exp.property.BaseProperty;
+import org.apache.cayenne.exp.property.PropertyFactory;
 
 /**
  * Class _Table2 was generated by Cayenne.
@@ -19,8 +20,8 @@ public abstract class _Table2 extends BaseDataObject {
 
     public static final String ID_PK_COLUMN = "ID";
 
-    public static final Property<byte[]> CRYPTO_BYTES = Property.create("cryptoBytes", byte[].class);
-    public static final Property<byte[]> PLAIN_BYTES = Property.create("plainBytes", byte[].class);
+    public static final BaseProperty<byte[]> CRYPTO_BYTES = PropertyFactory.createBase("cryptoBytes", byte[].class);
+    public static final BaseProperty<byte[]> PLAIN_BYTES = PropertyFactory.createBase("plainBytes", byte[].class);
 
     protected byte[] cryptoBytes;
     protected byte[] plainBytes;

http://git-wip-us.apache.org/repos/asf/cayenne/blob/9ea878c0/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/db/auto/_Table3.java
----------------------------------------------------------------------
diff --git a/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/db/auto/_Table3.java b/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/db/auto/_Table3.java
index ce3913d..a25a778 100644
--- a/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/db/auto/_Table3.java
+++ b/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/db/auto/_Table3.java
@@ -5,7 +5,8 @@ import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 
 import org.apache.cayenne.BaseDataObject;
-import org.apache.cayenne.exp.Property;
+import org.apache.cayenne.exp.property.PropertyFactory;
+import org.apache.cayenne.exp.property.StringProperty;
 
 /**
  * Class _Table3 was generated by Cayenne.
@@ -19,7 +20,7 @@ public abstract class _Table3 extends BaseDataObject {
 
     public static final String ID_PK_COLUMN = "ID";
 
-    public static final Property<String> CRYPTO_STRING = Property.create("cryptoString", String.class);
+    public static final StringProperty<String> CRYPTO_STRING = PropertyFactory.createString("cryptoString", String.class);
 
     protected String cryptoString;
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/9ea878c0/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/db/auto/_Table4.java
----------------------------------------------------------------------
diff --git a/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/db/auto/_Table4.java b/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/db/auto/_Table4.java
index 779c530..b2dd4ff 100644
--- a/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/db/auto/_Table4.java
+++ b/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/db/auto/_Table4.java
@@ -5,7 +5,9 @@ import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 
 import org.apache.cayenne.BaseDataObject;
-import org.apache.cayenne.exp.Property;
+import org.apache.cayenne.exp.property.NumericProperty;
+import org.apache.cayenne.exp.property.PropertyFactory;
+import org.apache.cayenne.exp.property.StringProperty;
 
 /**
  * Class _Table4 was generated by Cayenne.
@@ -19,8 +21,8 @@ public abstract class _Table4 extends BaseDataObject {
 
     public static final String ID_PK_COLUMN = "ID";
 
-    public static final Property<Integer> PLAIN_INT = Property.create("plainInt", Integer.class);
-    public static final Property<String> PLAIN_STRING = Property.create("plainString", String.class);
+    public static final NumericProperty<Integer> PLAIN_INT = PropertyFactory.createNumeric("plainInt", Integer.class);
+    public static final StringProperty<String> PLAIN_STRING = PropertyFactory.createString("plainString", String.class);
 
     protected Integer plainInt;
     protected String plainString;

http://git-wip-us.apache.org/repos/asf/cayenne/blob/9ea878c0/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/db/auto/_Table5.java
----------------------------------------------------------------------
diff --git a/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/db/auto/_Table5.java b/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/db/auto/_Table5.java
index 0fec43f..e4523e6 100644
--- a/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/db/auto/_Table5.java
+++ b/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/db/auto/_Table5.java
@@ -5,7 +5,8 @@ import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 
 import org.apache.cayenne.BaseDataObject;
-import org.apache.cayenne.exp.Property;
+import org.apache.cayenne.exp.property.NumericProperty;
+import org.apache.cayenne.exp.property.PropertyFactory;
 
 /**
  * Class _Table5 was generated by Cayenne.
@@ -19,9 +20,9 @@ public abstract class _Table5 extends BaseDataObject {
 
     public static final String ID_PK_COLUMN = "ID";
 
-    public static final Property<Integer> CRYPTO_INT1 = Property.create("cryptoInt1", Integer.class);
-    public static final Property<Integer> CRYPTO_INT3 = Property.create("cryptoInt3", Integer.class);
-    public static final Property<Integer> CRYPTO_INT4 = Property.create("cryptoInt4", Integer.class);
+    public static final NumericProperty<Integer> CRYPTO_INT1 = PropertyFactory.createNumeric("cryptoInt1", Integer.class);
+    public static final NumericProperty<Integer> CRYPTO_INT3 = PropertyFactory.createNumeric("cryptoInt3", Integer.class);
+    public static final NumericProperty<Integer> CRYPTO_INT4 = PropertyFactory.createNumeric("cryptoInt4", Integer.class);
 
     protected Integer cryptoInt1;
     protected Integer cryptoInt3;

http://git-wip-us.apache.org/repos/asf/cayenne/blob/9ea878c0/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/db/auto/_Table6.java
----------------------------------------------------------------------
diff --git a/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/db/auto/_Table6.java b/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/db/auto/_Table6.java
index 76cee43..5255744 100644
--- a/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/db/auto/_Table6.java
+++ b/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/db/auto/_Table6.java
@@ -5,7 +5,8 @@ import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 
 import org.apache.cayenne.BaseDataObject;
-import org.apache.cayenne.exp.Property;
+import org.apache.cayenne.exp.property.NumericProperty;
+import org.apache.cayenne.exp.property.PropertyFactory;
 
 /**
  * Class _Table6 was generated by Cayenne.
@@ -19,8 +20,8 @@ public abstract class _Table6 extends BaseDataObject {
 
     public static final String ID_PK_COLUMN = "ID";
 
-    public static final Property<Long> CRYPTO_INT1 = Property.create("cryptoInt1", Long.class);
-    public static final Property<Integer> CRYPTO_INT4 = Property.create("cryptoInt4", Integer.class);
+    public static final NumericProperty<Long> CRYPTO_INT1 = PropertyFactory.createNumeric("cryptoInt1", Long.class);
+    public static final NumericProperty<Integer> CRYPTO_INT4 = PropertyFactory.createNumeric("cryptoInt4", Integer.class);
 
     protected Long cryptoInt1;
     protected Integer cryptoInt4;

http://git-wip-us.apache.org/repos/asf/cayenne/blob/9ea878c0/cayenne-server/src/test/java/org/apache/cayenne/CayenneCompoundIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/CayenneCompoundIT.java b/cayenne-server/src/test/java/org/apache/cayenne/CayenneCompoundIT.java
index ea822d0..b3e4ce3 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/CayenneCompoundIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/CayenneCompoundIT.java
@@ -20,7 +20,7 @@
 package org.apache.cayenne;
 
 import org.apache.cayenne.di.Inject;
-import org.apache.cayenne.exp.Property;
+import org.apache.cayenne.exp.property.PropertyFactory;
 import org.apache.cayenne.query.EJBQLQuery;
 import org.apache.cayenne.query.ObjectSelect;
 import org.apache.cayenne.query.SelectQuery;
@@ -101,7 +101,7 @@ public class CayenneCompoundIT extends ServerCase {
 	public void testCompoundPKForObject() throws Exception {
 		createOneCompoundPK();
 
-		List<?> objects = context.performQuery(new SelectQuery(CompoundPkTestEntity.class));
+		List<?> objects = context.performQuery(new SelectQuery<>(CompoundPkTestEntity.class));
 		assertEquals(1, objects.size());
 		DataObject object = (DataObject) objects.get(0);
 
@@ -116,7 +116,7 @@ public class CayenneCompoundIT extends ServerCase {
 	public void testIntPKForObjectFailureForCompound() throws Exception {
 		createOneCompoundPK();
 
-		List<?> objects = context.performQuery(new SelectQuery(CompoundPkTestEntity.class));
+		List<?> objects = context.performQuery(new SelectQuery<>(CompoundPkTestEntity.class));
 		assertEquals(1, objects.size());
 		DataObject object = (DataObject) objects.get(0);
 
@@ -132,7 +132,7 @@ public class CayenneCompoundIT extends ServerCase {
 	public void testIntPKForObjectFailureForNonNumeric() throws Exception {
 		createOneCharPK();
 
-		List<?> objects = context.performQuery(new SelectQuery(CharPkTestEntity.class));
+		List<?> objects = context.performQuery(new SelectQuery<>(CharPkTestEntity.class));
 		assertEquals(1, objects.size());
 		DataObject object = (DataObject) objects.get(0);
 
@@ -148,7 +148,7 @@ public class CayenneCompoundIT extends ServerCase {
 	public void testPKForObjectFailureForCompound() throws Exception {
 		createOneCompoundPK();
 
-		List<?> objects = context.performQuery(new SelectQuery(CompoundPkTestEntity.class));
+		List<?> objects = context.performQuery(new SelectQuery<>(CompoundPkTestEntity.class));
 		assertEquals(1, objects.size());
 		DataObject object = (DataObject) objects.get(0);
 
@@ -164,7 +164,7 @@ public class CayenneCompoundIT extends ServerCase {
 	public void testIntPKForObjectNonNumeric() throws Exception {
 		createOneCharPK();
 
-		List<?> objects = context.performQuery(new SelectQuery(CharPkTestEntity.class));
+		List<?> objects = context.performQuery(new SelectQuery<>(CharPkTestEntity.class));
 		assertEquals(1, objects.size());
 		DataObject object = (DataObject) objects.get(0);
 
@@ -177,7 +177,7 @@ public class CayenneCompoundIT extends ServerCase {
 		createCompoundPKs(20);
 
 		List<Object[]> result = ObjectSelect.query(CompoundPkTestEntity.class)
-				.columns(CompoundPkTestEntity.NAME, Property.createSelf(CompoundPkTestEntity.class))
+				.columns(CompoundPkTestEntity.NAME, PropertyFactory.createSelf(CompoundPkTestEntity.class))
 				.pageSize(7)
 				.select(context);
 		assertEquals(20, result.size());

http://git-wip-us.apache.org/repos/asf/cayenne/blob/9ea878c0/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextOrderingIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextOrderingIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextOrderingIT.java
index 974d641..bb56dbb 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextOrderingIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextOrderingIT.java
@@ -137,11 +137,8 @@ public class DataContextOrderingIT extends ServerCase {
 
         context.commitChanges();
 
-        Expression exp = FunctionExpressionFactory.substringExp(Artist.ARTIST_NAME.path(), 2, 1);
-        Property<String> nameSubstr = Property.create("name", exp, String.class);
-
         SelectQuery<Artist> query = new SelectQuery<>(Artist.class);
-        query.addOrdering(nameSubstr.desc());
+        query.addOrdering(Artist.ARTIST_NAME.substring(2, 1).desc());
 
         List<Artist> list = query.select(context);
         assertEquals(3, list.size());

http://git-wip-us.apache.org/repos/asf/cayenne/blob/9ea878c0/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextPrefetchIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextPrefetchIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextPrefetchIT.java
index c52f322..7a624d5 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextPrefetchIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextPrefetchIT.java
@@ -26,7 +26,7 @@ import org.apache.cayenne.ValueHolder;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.exp.Expression;
 import org.apache.cayenne.exp.ExpressionFactory;
-import org.apache.cayenne.exp.Property;
+import org.apache.cayenne.exp.property.PropertyFactory;
 import org.apache.cayenne.map.ObjEntity;
 import org.apache.cayenne.map.ObjRelationship;
 import org.apache.cayenne.query.ObjectSelect;
@@ -488,7 +488,8 @@ public class DataContextPrefetchIT extends ServerCase {
 		tArtistGroup.insert(101, 1);
 
 		// OUTER join part intentionally doesn't match anything
-		Expression exp = Property.create("groupArray+.name", String.class).eq("XX").orExp(Artist.ARTIST_NAME.eq("artist2"));
+		Expression exp = PropertyFactory.createBase("groupArray+.name", String.class)
+				.eq("XX").orExp(Artist.ARTIST_NAME.eq("artist2"));
 
 		SelectQuery<Artist> q = new SelectQuery<>(Artist.class, exp);
 		q.addPrefetch(Artist.PAINTING_ARRAY.disjoint());

http://git-wip-us.apache.org/repos/asf/cayenne/blob/9ea878c0/cayenne-server/src/test/java/org/apache/cayenne/access/NumericTypesIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/NumericTypesIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/NumericTypesIT.java
index 29107b1..24196e8 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/NumericTypesIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/NumericTypesIT.java
@@ -24,7 +24,8 @@ import org.apache.cayenne.configuration.server.ServerRuntime;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.exp.Expression;
 import org.apache.cayenne.exp.ExpressionFactory;
-import org.apache.cayenne.exp.Property;
+import org.apache.cayenne.exp.property.NumericProperty;
+import org.apache.cayenne.exp.property.PropertyFactory;
 import org.apache.cayenne.query.ObjectSelect;
 import org.apache.cayenne.query.SelectQuery;
 import org.apache.cayenne.test.jdbc.DBHelper;
@@ -320,8 +321,8 @@ public class NumericTypesIT extends ServerCase {
 
         assertEquals(i, readValue);
 
-        Property<BigInteger> calculated =
-                Property.create(ExpressionFactory.exp("bigIntegerField + 1"), BigInteger.class);
+        NumericProperty<BigInteger> calculated =
+                PropertyFactory.createNumeric(ExpressionFactory.exp("bigIntegerField + 1"), BigInteger.class);
 
         BigInteger readValue2 = ObjectSelect.query(BigIntegerEntity.class)
                 .column(calculated).selectOne(context);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/9ea878c0/cayenne-server/src/test/java/org/apache/cayenne/access/UUIDIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/UUIDIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/UUIDIT.java
index 37e3785..411a8c2 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/UUIDIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/UUIDIT.java
@@ -22,7 +22,7 @@ import org.apache.cayenne.Cayenne;
 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.exp.ExpressionFactory;
-import org.apache.cayenne.exp.Property;
+import org.apache.cayenne.exp.property.PropertyFactory;
 import org.apache.cayenne.query.ObjectSelect;
 import org.apache.cayenne.query.SelectQuery;
 import org.apache.cayenne.test.jdbc.DBHelper;
@@ -113,7 +113,7 @@ public class UUIDIT extends ServerCase {
         assertEquals(id, readValue);
 
         UUID readValue2 = ObjectSelect.query(UuidTestEntity.class)
-                .column(Property.create(ExpressionFactory.dbPathExp("UUID"), UUID.class)).selectOne(context);
+                .column(PropertyFactory.createBase(ExpressionFactory.dbPathExp("UUID"), UUID.class)).selectOne(context);
 
         assertEquals(id, readValue2);
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/9ea878c0/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/QualifierTranslatorIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/QualifierTranslatorIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/QualifierTranslatorIT.java
index 9db81a1..2f2c71d 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/QualifierTranslatorIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/QualifierTranslatorIT.java
@@ -29,8 +29,6 @@ import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.exp.Expression;
 import org.apache.cayenne.exp.ExpressionFactory;
-import org.apache.cayenne.exp.FunctionExpressionFactory;
-import org.apache.cayenne.exp.Property;
 import org.apache.cayenne.query.MockQuery;
 import org.apache.cayenne.query.SelectQuery;
 import org.apache.cayenne.testdo.testmap.Artist;
@@ -54,7 +52,7 @@ public class QualifierTranslatorIT extends ServerCase {
 
 	// TODO: not an integration test; extract into *Test
 	@Test
-	public void testNonQualifiedQuery() throws Exception {
+	public void testNonQualifiedQuery() {
 		TstQueryAssembler qa = new TstQueryAssembler(new MockQuery(), node.getAdapter(), node.getEntityResolver());
 
 		try {
@@ -67,8 +65,8 @@ public class QualifierTranslatorIT extends ServerCase {
 
 	// TODO: not an integration test; extract into *Test
 	@Test
-	public void testNullQualifier() throws Exception {
-		TstQueryAssembler qa = new TstQueryAssembler(new SelectQuery<Object>(), node.getAdapter(),
+	public void testNullQualifier() {
+		TstQueryAssembler qa = new TstQueryAssembler(new SelectQuery<>(), node.getAdapter(),
 				node.getEntityResolver());
 
 		StringBuilder out = new StringBuilder();
@@ -77,51 +75,51 @@ public class QualifierTranslatorIT extends ServerCase {
 	}
 
 	@Test
-	public void testBinary_In1() throws Exception {
+	public void testBinary_In1() {
 		doExpressionTest(Exhibit.class, "toGallery.galleryName in ('g1', 'g2', 'g3')", "ta.GALLERY_NAME IN (?, ?, ?)");
 	}
 
 	@Test
-	public void testBinary_In2() throws Exception {
+	public void testBinary_In2() {
 		Expression exp = ExpressionFactory.inExp("toGallery.galleryName",
-				Arrays.asList(new Object[] { "g1", "g2", "g3" }));
+				Arrays.asList("g1", "g2", "g3"));
 		doExpressionTest(Exhibit.class, exp, "ta.GALLERY_NAME IN (?, ?, ?)");
 	}
 
 	@Test
-	public void testBinary_In3() throws Exception {
-		Expression exp = ExpressionFactory.inExp("toGallery.galleryName", new Object[] { "g1", "g2", "g3" });
+	public void testBinary_In3() {
+		Expression exp = ExpressionFactory.inExp("toGallery.galleryName", "g1", "g2", "g3");
 		doExpressionTest(Exhibit.class, exp, "ta.GALLERY_NAME IN (?, ?, ?)");
 	}
 
 	@Test
-	public void testBinary_Like() throws Exception {
+	public void testBinary_Like() {
 		doExpressionTest(Exhibit.class, "toGallery.galleryName like 'a%'", "ta.GALLERY_NAME LIKE ?");
 	}
 
 	@Test
-	public void testBinary_LikeIgnoreCase() throws Exception {
+	public void testBinary_LikeIgnoreCase() {
 		doExpressionTest(Exhibit.class, "toGallery.galleryName likeIgnoreCase 'a%'",
 				"UPPER(ta.GALLERY_NAME) LIKE UPPER(?)");
 	}
 
 	@Test
-	public void testBinary_IsNull() throws Exception {
+	public void testBinary_IsNull() {
 		doExpressionTest(Exhibit.class, "toGallery.galleryName = null", "ta.GALLERY_NAME IS NULL");
 	}
 
 	@Test
-	public void testBinary_IsNotNull() throws Exception {
+	public void testBinary_IsNotNull() {
 		doExpressionTest(Exhibit.class, "toGallery.galleryName != null", "ta.GALLERY_NAME IS NOT NULL");
 	}
 
 	@Test
-	public void testTernary_Between() throws Exception {
+	public void testTernary_Between() {
 		doExpressionTest(Painting.class, "estimatedPrice between 3000 and 15000", "ta.ESTIMATED_PRICE BETWEEN ? AND ?");
 	}
 
 	@Test
-	public void testExtras() throws Exception {
+	public void testExtras() {
 		ObjectId oid1 = new ObjectId("Gallery", "GALLERY_ID", 1);
 		ObjectId oid2 = new ObjectId("Gallery", "GALLERY_ID", 2);
 		Gallery g1 = new Gallery();
@@ -136,27 +134,22 @@ public class QualifierTranslatorIT extends ServerCase {
 	}
 
 	@Test
-	public void testTrim() throws Exception {
-		Expression exp = FunctionExpressionFactory.trimExp(Artist.ARTIST_NAME.path());
-		Property<String> property = Property.create("trimmedName", exp, String.class);
-
-		doExpressionTest(Artist.class, property.like("P%"), "TRIM(ta.ARTIST_NAME) LIKE ?");
+	public void testTrim() {
+		doExpressionTest(Artist.class, Artist.ARTIST_NAME.trim().like("P%"),
+				"TRIM(ta.ARTIST_NAME) LIKE ?");
 	}
 
 	@Test
-	public void testConcat() throws Exception {
-		Expression exp = FunctionExpressionFactory.concatExp("artistName", "dateOfBirth");
-
-		Property<String> property = Property.create("concatNameAndDate", exp, String.class);
-
-		doExpressionTest(Artist.class, property.like("P%"), "CONCAT(ta.ARTIST_NAME, ta.DATE_OF_BIRTH) LIKE ?");
+	public void testConcat() {
+		doExpressionTest(Artist.class, Artist.ARTIST_NAME.concat(Artist.DATE_OF_BIRTH).like("P%"),
+				"CONCAT(ta.ARTIST_NAME, ta.DATE_OF_BIRTH) LIKE ?");
 	}
 
-	private void doExpressionTest(Class<?> queryType, String qualifier, String expectedSQL) throws Exception {
+	private void doExpressionTest(Class<?> queryType, String qualifier, String expectedSQL) {
 		doExpressionTest(queryType, ExpressionFactory.exp(qualifier), expectedSQL);
 	}
 
-	private void doExpressionTest(Class<?> queryType, Expression qualifier, String expectedSQL) throws Exception {
+	private void doExpressionTest(Class<?> queryType, Expression qualifier, String expectedSQL) {
 
 		SelectQuery<?> q = new SelectQuery<>(queryType);
 		q.setQualifier(qualifier);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/9ea878c0/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionFactoryIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionFactoryIT.java b/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionFactoryIT.java
index c1b2ae2..af23be3 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionFactoryIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionFactoryIT.java
@@ -156,16 +156,15 @@ public class ExpressionFactoryIT extends ServerCase {
 
 		// Second version via FunctionExpressionFactory API
 		Expression exp2 = greaterExp(
-				lengthExp(substringExp(Artist.ARTIST_NAME.path(), 1, 3)),
-				lengthExp(trimExp(Artist.ARTIST_NAME.path()))
+				lengthExp(substringExp(Artist.ARTIST_NAME.getExpression(), 1, 3)),
+				lengthExp(trimExp(Artist.ARTIST_NAME.getExpression()))
 		);
 		res = ObjectSelect.query(Artist.class, exp2).select(context);
 		assertEquals(0, res.size());
 
 		// Third version via Property API
-		Property<Integer> lengthSub = Property.create(lengthExp(substringExp(Artist.ARTIST_NAME.path(), 1, 3)), Integer.class);
-		Property<Integer> length = Property.create(lengthExp(trimExp(Artist.ARTIST_NAME.path())), Integer.class);
-		Expression exp3 = lengthSub.gt(length);
+		Expression exp3 = Artist.ARTIST_NAME.substring(1, 3).length()
+				.gt(Artist.ARTIST_NAME.trim().length());
 		res = ObjectSelect.query(Artist.class, exp3).select(context);
 		assertEquals(0, res.size());
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/9ea878c0/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTExtractIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTExtractIT.java b/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTExtractIT.java
index 7ebbfba..0a1e1eb 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTExtractIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTExtractIT.java
@@ -27,8 +27,6 @@ import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.exp.Expression;
 import org.apache.cayenne.exp.ExpressionFactory;
-import org.apache.cayenne.exp.FunctionExpressionFactory;
-import org.apache.cayenne.exp.Property;
 import org.apache.cayenne.query.ObjectSelect;
 import org.apache.cayenne.test.jdbc.DBHelper;
 import org.apache.cayenne.testdo.date_time.DateTestEntity;
@@ -40,6 +38,7 @@ import org.junit.Before;
 import org.junit.Test;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 /**
  * @since 4.0
@@ -147,12 +146,12 @@ public class ASTExtractIT extends ServerCase {
 
     @Test
     public void testDayOfYearSelect() {
-        Property<Integer> dayOfYear = Property.create(
-                FunctionExpressionFactory.yearExp(DateTestEntity.DATE_COLUMN.path()), Integer.class);
-
         try {
-            List<Integer> res = ObjectSelect.query(DateTestEntity.class).column(dayOfYear).select(context);
+            List<Integer> res = ObjectSelect.query(DateTestEntity.class)
+                    .column(DateTestEntity.DATE_COLUMN.dayOfYear()).select(context);
             assertEquals(2, res.size());
+            assertTrue(res.contains(59));
+            assertTrue(res.contains(89));
         } catch (CayenneRuntimeException e) {
             if(unitDbAdapter.supportsExtractPart(ASTExtract.DateTimePart.DAY_OF_YEAR)) {
                 throw e;

http://git-wip-us.apache.org/repos/asf/cayenne/blob/9ea878c0/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTFunctionCallMathIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTFunctionCallMathIT.java b/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTFunctionCallMathIT.java
index a5ab9fd..b08a57d 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTFunctionCallMathIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTFunctionCallMathIT.java
@@ -25,7 +25,6 @@ import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.exp.Expression;
 import org.apache.cayenne.exp.ExpressionFactory;
-import org.apache.cayenne.exp.Property;
 import org.apache.cayenne.query.ObjectSelect;
 import org.apache.cayenne.testdo.table_primitives.TablePrimitives;
 import org.apache.cayenne.unit.di.server.CayenneProjects;
@@ -55,10 +54,8 @@ public class ASTFunctionCallMathIT extends ServerCase {
     public void testASTAbs() throws Exception {
         TablePrimitives p1 = createPrimitives(-10);
 
-        ASTAbs exp = new ASTAbs(TablePrimitives.INT_COLUMN.path());
-        Property<Integer> intColumn = Property.create("intColumn", exp, Integer.class);
-
-        TablePrimitives p2 = ObjectSelect.query(TablePrimitives.class).where(intColumn.eq(10)).selectOne(context);
+        TablePrimitives p2 = ObjectSelect.query(TablePrimitives.class)
+                .where(TablePrimitives.INT_COLUMN.abs().eq(10)).selectOne(context);
         assertEquals(p1, p2);
     }
 
@@ -66,10 +63,8 @@ public class ASTFunctionCallMathIT extends ServerCase {
     public void testASTSqrt() throws Exception {
         TablePrimitives p1 = createPrimitives(9);
 
-        ASTSqrt exp = new ASTSqrt(TablePrimitives.INT_COLUMN.path());
-        Property<Integer> intColumn = Property.create("intColumn", exp, Integer.class);
-
-        TablePrimitives p2 = ObjectSelect.query(TablePrimitives.class).where(intColumn.eq(3)).selectOne(context);
+        TablePrimitives p2 = ObjectSelect.query(TablePrimitives.class)
+                .where(TablePrimitives.INT_COLUMN.sqrt().eq(3)).selectOne(context);
         assertEquals(p1, p2);
     }
 
@@ -77,10 +72,8 @@ public class ASTFunctionCallMathIT extends ServerCase {
     public void testASTMod() throws Exception {
         TablePrimitives p1 = createPrimitives(10);
 
-        ASTMod exp = new ASTMod(TablePrimitives.INT_COLUMN.path(), new ASTScalar((Integer)3));
-        Property<Integer> intColumn = Property.create("intColumn", exp, Integer.class);
-
-        TablePrimitives p2 = ObjectSelect.query(TablePrimitives.class).where(intColumn.eq(1)).selectOne(context);
+        TablePrimitives p2 = ObjectSelect.query(TablePrimitives.class)
+                .where(TablePrimitives.INT_COLUMN.mod(3).eq(1)).selectOne(context);
         assertEquals(p1, p2);
     }
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/9ea878c0/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTFunctionCallStringIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTFunctionCallStringIT.java b/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTFunctionCallStringIT.java
index 57e3254..3a8742d 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTFunctionCallStringIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTFunctionCallStringIT.java
@@ -25,7 +25,6 @@ import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.exp.Expression;
 import org.apache.cayenne.exp.ExpressionFactory;
-import org.apache.cayenne.exp.Property;
 import org.apache.cayenne.query.ObjectSelect;
 import org.apache.cayenne.testdo.testmap.Artist;
 import org.apache.cayenne.unit.di.server.CayenneProjects;
@@ -34,6 +33,7 @@ import org.apache.cayenne.unit.di.server.UseServerRuntime;
 import org.junit.Test;
 
 import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNull;
 
 /**
  * @since 4.0
@@ -55,58 +55,40 @@ public class ASTFunctionCallStringIT extends ServerCase {
     @Test
     public void testASTTrimInWhere() throws Exception {
         Artist a1 = createArtist("  name  ");
-
-        ASTTrim exp = new ASTTrim(Artist.ARTIST_NAME.path());
-        Property<String> trimmedName = Property.create("trimmedName", exp, String.class);
-
-        Artist a2 = ObjectSelect.query(Artist.class).where(trimmedName.eq("name")).selectOne(context);
+        Artist a2 = ObjectSelect.query(Artist.class)
+                .where(Artist.ARTIST_NAME.trim().eq("name")).selectOne(context);
         assertEquals(a1, a2);
     }
 
     @Test
     public void testASTUpperInWhere() throws Exception {
         Artist a1 = createArtist("name");
-
-        ASTUpper exp = new ASTUpper(Artist.ARTIST_NAME.path());
-        Property<String> upperName = Property.create("upperName", exp, String.class);
-
-        Artist a2 = ObjectSelect.query(Artist.class).where(upperName.eq("NAME")).selectOne(context);
+        Artist a2 = ObjectSelect.query(Artist.class)
+                .where(Artist.ARTIST_NAME.upper().eq("NAME")).selectOne(context);
         assertEquals(a1, a2);
     }
 
     @Test
     public void testASTLowerInWhere() throws Exception {
         Artist a1 = createArtist("NAME");
-
-        ASTLower exp = new ASTLower(Artist.ARTIST_NAME.path());
-        Property<String> lowerName = Property.create("lowerName", exp, String.class);
-
-        Artist a2 = ObjectSelect.query(Artist.class).where(lowerName.eq("name")).selectOne(context);
+        Artist a2 = ObjectSelect.query(Artist.class)
+                .where(Artist.ARTIST_NAME.lower().eq("name")).selectOne(context);
         assertEquals(a1, a2);
     }
 
     @Test
     public void testASTSubstringInWhere() throws Exception {
         Artist a1 = createArtist("1234567890xyz");
-
-        ASTSubstring exp = new ASTSubstring(Artist.ARTIST_NAME.path(), new ASTScalar((Integer)2), new ASTScalar((Integer)8));
-        Property<String> substrName = Property.create("substrName", exp, String.class);
-
-        Artist a2 = ObjectSelect.query(Artist.class).where(substrName.eq("23456789")).selectOne(context);
+        Artist a2 = ObjectSelect.query(Artist.class)
+                .where(Artist.ARTIST_NAME.substring(2, 8).eq("23456789")).selectOne(context);
         assertEquals(a1, a2);
     }
 
     @Test
     public void testASTConcat() throws Exception {
         Artist a1 = createArtist("Pablo");
-
-        ASTScalar scalar1 = new ASTScalar(" ");
-        ASTScalar scalar2 = new ASTScalar("Picasso");
-
-        ASTConcat exp = new ASTConcat(Artist.ARTIST_NAME.path(), scalar1, scalar2);
-        Property<String> concatName = Property.create("concatName", exp, String.class);
-
-        Artist a2 = ObjectSelect.query(Artist.class).where(concatName.eq("Pablo Picasso")).selectOne(context);
+        Artist a2 = ObjectSelect.query(Artist.class)
+                .where(Artist.ARTIST_NAME.concat(" ", "Picasso").eq("Pablo Picasso")).selectOne(context);
         assertEquals(a1, a2);
     }
 
@@ -114,43 +96,27 @@ public class ASTFunctionCallStringIT extends ServerCase {
     public void testASTLength() throws Exception {
         Artist a1 = createArtist("123456");
 
-        ASTLength exp = new ASTLength(Artist.ARTIST_NAME.path());
-        Property<Integer> nameLength = Property.create("nameLength", exp, Integer.class);
-
-        Artist a2 = ObjectSelect.query(Artist.class).where(nameLength.gt(5)).selectOne(context);
+        Artist a2 = ObjectSelect.query(Artist.class).where(Artist.ARTIST_NAME.length().gt(5)).selectOne(context);
         assertEquals(a1, a2);
 
-        Artist a3 = ObjectSelect.query(Artist.class).where(nameLength.lt(5)).selectOne(context);
-        assertEquals(null, a3);
+        Artist a3 = ObjectSelect.query(Artist.class).where(Artist.ARTIST_NAME.length().lt(5)).selectOne(context);
+        assertNull(a3);
     }
 
     @Test
     public void testASTLocate() throws Exception {
         Artist a1 = createArtist("1267834567890abc");
-
-        ASTScalar substr = new ASTScalar("678");
-//        ASTScalar offset = new ASTScalar((Integer)5); // not all DBs support offset parameter, so skip it
-        ASTLocate exp = new ASTLocate(substr, Artist.ARTIST_NAME.path());
-        Property<Integer> nameLoc = Property.create("nameLoc", exp, Integer.class);
-
-        Artist a2 = ObjectSelect.query(Artist.class).where(nameLoc.eq(3)).selectOne(context);
+        Artist a2 = ObjectSelect.query(Artist.class)
+                .where(Artist.ARTIST_NAME.locate("678").eq(3)).selectOne(context);
         assertEquals(a1, a2);
     }
 
     @Test
     public void testCombinedFunction() throws Exception {
         Artist a1 = createArtist("absdefghij  klmnopq"); // substring with length 10 from 3 is "sdefghij  "
-
-        ASTSubstring substring = new ASTSubstring(
-                Artist.ARTIST_NAME.path(),
-                new ASTScalar((Integer)3),
-                new ASTScalar((Integer)10));
-        ASTTrim trim = new ASTTrim(substring);
-        ASTUpper upper = new ASTUpper(trim);
-        ASTConcat concat = new ASTConcat(upper, new ASTScalar(" "), new ASTScalar("test"));
-
-        Property<String> name = Property.create("substrName", concat, String.class);
-        Artist a2 = ObjectSelect.query(Artist.class).where(name.eq("SDEFGHIJ test")).selectOne(context);
+        Artist a2 = ObjectSelect.query(Artist.class)
+                .where(Artist.ARTIST_NAME.substring(3, 10).trim().upper().concat(" ", "test").eq("SDEFGHIJ test"))
+                .selectOne(context);
         assertEquals(a1, a2);
     }
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/9ea878c0/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ExpressionCollectionEvaluationIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ExpressionCollectionEvaluationIT.java b/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ExpressionCollectionEvaluationIT.java
index b788a27..179f852 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ExpressionCollectionEvaluationIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ExpressionCollectionEvaluationIT.java
@@ -27,7 +27,7 @@ import org.apache.cayenne.access.DataContext;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.exp.Expression;
 import org.apache.cayenne.exp.ExpressionFactory;
-import org.apache.cayenne.exp.Property;
+import org.apache.cayenne.exp.property.PropertyFactory;
 import org.apache.cayenne.query.ObjectSelect;
 import org.apache.cayenne.test.jdbc.DBHelper;
 import org.apache.cayenne.test.jdbc.TableHelper;
@@ -107,12 +107,19 @@ public class ExpressionCollectionEvaluationIT extends ServerCase {
         testExpression("paintingArray.estimatedPrice + 2", BigDecimal.class);
     }
 
+    @SuppressWarnings("unchecked")
     private <T extends Comparable<T>> void testExpression(String expStr, Class<T> tClass) {
         Expression exp = ExpressionFactory.exp(expStr);
-        Object res = exp.evaluate(ObjectSelect.query(Artist.class).prefetch(Artist.PAINTING_ARRAY.disjoint()).selectOne(context));
-        List<T> sqlResult = ObjectSelect.query(Artist.class).column(Property.create(exp, tClass)).orderBy("db:paintingArray.PAINTING_ID").select(context);
-
-        Collections.sort((List)res);
+        Object res = exp.evaluate(ObjectSelect
+                .query(Artist.class)
+                .prefetch(Artist.PAINTING_ARRAY.disjoint())
+                .selectOne(context));
+        List<T> sqlResult = ObjectSelect.query(Artist.class)
+                .column(PropertyFactory.createBase(exp, tClass))
+                .orderBy("db:paintingArray.PAINTING_ID")
+                .select(context);
+
+        Collections.sort((List<T>)res);
         Collections.sort(sqlResult);
 
         assertEquals(3, sqlResult.size());


Mime
View raw message