You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by nt...@apache.org on 2017/02/01 13:23:55 UTC

[07/10] cayenne git commit: CAY-2215 split cayenne-tools into cayenne-cgen and cayenne-ant

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c63b6be2/cayenne-cgen/src/main/java/org/apache/cayenne/gen/ClientDataMapArtifact.java
----------------------------------------------------------------------
diff --git a/cayenne-cgen/src/main/java/org/apache/cayenne/gen/ClientDataMapArtifact.java b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/ClientDataMapArtifact.java
new file mode 100644
index 0000000..a9d1dfe
--- /dev/null
+++ b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/ClientDataMapArtifact.java
@@ -0,0 +1,50 @@
+/*****************************************************************
+ *   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 org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.QueryDescriptor;
+import org.apache.cayenne.util.Util;
+
+import java.util.Collection;
+
+public class ClientDataMapArtifact extends DataMapArtifact {
+
+    public ClientDataMapArtifact(DataMap dataMap, Collection<QueryDescriptor> queries) {
+        super(dataMap, queries);
+
+    }
+
+    @Override
+    public String getQualifiedBaseClassName() {
+
+        return dataMap.getDefaultClientSuperclass();
+    }
+
+    @Override
+    public String getQualifiedClassName() {
+        String clientPrefix = "";
+        if (Util.nullSafeEquals(dataMap.getDefaultClientPackage(), dataMap.getDefaultPackage())) {
+            clientPrefix = "Client_";
+        }
+
+        return dataMap.getNameWithDefaultClientPackage(Util.underscoredToJava(clientPrefix + dataMap.getName(), true));
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c63b6be2/cayenne-cgen/src/main/java/org/apache/cayenne/gen/ClientEntityArtifact.java
----------------------------------------------------------------------
diff --git a/cayenne-cgen/src/main/java/org/apache/cayenne/gen/ClientEntityArtifact.java b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/ClientEntityArtifact.java
new file mode 100644
index 0000000..d20a2d4
--- /dev/null
+++ b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/ClientEntityArtifact.java
@@ -0,0 +1,45 @@
+/*****************************************************************
+ *   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 org.apache.cayenne.PersistentObject;
+import org.apache.cayenne.map.ObjEntity;
+
+/**
+ * Client code generation artifact based on ObjEntity.
+ * 
+ * @since 3.0
+ */
+public class ClientEntityArtifact extends EntityArtifact {
+
+    public ClientEntityArtifact(ObjEntity entity) {
+        super(entity);
+    }
+
+    @Override
+    public String getQualifiedBaseClassName() {
+        return (entity.getClientSuperClassName() != null) ? entity
+                .getClientSuperClassName() : PersistentObject.class.getName();
+    }
+
+    @Override
+    public String getQualifiedClassName() {
+        return entity.getClientClassName();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c63b6be2/cayenne-cgen/src/main/java/org/apache/cayenne/gen/DataMapArtifact.java
----------------------------------------------------------------------
diff --git a/cayenne-cgen/src/main/java/org/apache/cayenne/gen/DataMapArtifact.java b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/DataMapArtifact.java
new file mode 100644
index 0000000..8e60495
--- /dev/null
+++ b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/DataMapArtifact.java
@@ -0,0 +1,137 @@
+/*****************************************************************
+ *   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 org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.QueryDescriptor;
+import org.apache.cayenne.util.Util;
+import org.apache.velocity.VelocityContext;
+
+import java.util.Collection;
+import java.util.LinkedList;
+
+/**
+ * {@link Artifact} facade for a DataMap.
+ * 
+ * @since 3.0
+ */
+public class DataMapArtifact implements Artifact {
+
+    public static final String DATAMAP_UTILS_KEY = "dataMapUtils";
+
+    protected DataMap dataMap;
+    protected Collection<QueryDescriptor> selectQueries;
+    protected Collection<QueryDescriptor> sqlTemplateQueries;
+    protected Collection<QueryDescriptor> procedureQueries;
+    protected Collection<QueryDescriptor> ejbqlQueries;
+    protected Collection<String> queryNames;
+
+    public DataMapArtifact(DataMap dataMap, Collection<QueryDescriptor> queries) {
+        this.dataMap = dataMap;
+        selectQueries = new LinkedList<>();
+        sqlTemplateQueries = new LinkedList<>();
+        procedureQueries = new LinkedList<>();
+        ejbqlQueries = new LinkedList<>();
+        queryNames = new LinkedList<>();
+        addQueries(queries);
+    }
+
+    public String getQualifiedBaseClassName() {
+        return Object.class.getName();
+    }
+
+    public String getQualifiedClassName() {
+        return dataMap.getNameWithDefaultPackage(Util.underscoredToJava(dataMap.getName(), true));
+    }
+
+    public Object getObject() {
+        return this;
+    }
+
+    public void postInitContext(VelocityContext context) {
+        DataMapUtils dataMapUtils = new DataMapUtils();
+        context.put(DATAMAP_UTILS_KEY, dataMapUtils);
+    }
+
+    public TemplateType[] getTemplateTypes(ArtifactGenerationMode mode) {
+        switch (mode) {
+            case SINGLE_CLASS:
+                return new TemplateType[] {
+                    TemplateType.DATAMAP_SINGLE_CLASS
+                };
+            case GENERATION_GAP:
+                return new TemplateType[] {
+                        TemplateType.DATAMAP_SUPERCLASS, TemplateType.DATAMAP_SUBCLASS
+                };
+            default:
+                return new TemplateType[0];
+        }
+    }
+
+    private void addQueries(Collection<QueryDescriptor> queries) {
+        if (queries != null) {
+            for (QueryDescriptor query : queries) {
+                addQuery(query);
+            }
+        }
+    }
+
+    private void addQuery(QueryDescriptor query) {
+
+        switch (query.getType()) {
+            case QueryDescriptor.SELECT_QUERY:
+                selectQueries.add(query);
+                break;
+            case QueryDescriptor.PROCEDURE_QUERY:
+                procedureQueries.add(query);
+                break;
+            case QueryDescriptor.SQL_TEMPLATE:
+                sqlTemplateQueries.add(query);
+                break;
+            case QueryDescriptor.EJBQL_QUERY:
+                ejbqlQueries.add(query);
+                break;
+        }
+
+        if (query.getName() != null && !"".equals(query.getName())) {
+            queryNames.add(query.getName());
+        }
+    }
+
+    public Collection<QueryDescriptor> getSelectQueries() {
+        return selectQueries;
+    }
+
+    public boolean hasSelectQueries() {
+        return selectQueries.size() > 0;
+    }
+
+    public boolean hasQueryNames() {
+        return !queryNames.isEmpty();
+    }
+
+    public Collection<String> getQueryNames() {
+        return queryNames;
+    }
+
+    public DataMap getDataMap() {
+    	return dataMap;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c63b6be2/cayenne-cgen/src/main/java/org/apache/cayenne/gen/DataMapUtils.java
----------------------------------------------------------------------
diff --git a/cayenne-cgen/src/main/java/org/apache/cayenne/gen/DataMapUtils.java b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/DataMapUtils.java
new file mode 100644
index 0000000..a0013cc
--- /dev/null
+++ b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/DataMapUtils.java
@@ -0,0 +1,219 @@
+/*****************************************************************
+ *   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 org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionException;
+import org.apache.cayenne.exp.ExpressionParameter;
+import org.apache.cayenne.exp.parser.ASTList;
+import org.apache.cayenne.exp.parser.ASTObjPath;
+import org.apache.cayenne.map.Entity;
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.map.ObjRelationship;
+import org.apache.cayenne.map.PathComponent;
+import org.apache.cayenne.map.QueryDescriptor;
+import org.apache.cayenne.map.SelectQueryDescriptor;
+import org.apache.cayenne.query.Ordering;
+import org.apache.cayenne.util.CayenneMapEntry;
+import org.apache.cayenne.util.Util;
+import org.apache.commons.collections.set.ListOrderedSet;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Attributes and Methods for working with Queries.
+ *
+ * @since 3.0
+ */
+public class DataMapUtils {
+
+	Map<String, Map<String, String>> queriesMap = new HashMap<>();
+
+	/**
+	 * Return valid method name based on query name (replace all illegal
+	 * characters with underscore '_').
+	 * 
+	 * @param query
+	 * @return Method name that perform query.
+	 */
+	public String getQueryMethodName(QueryDescriptor query) {
+		return Util.underscoredToJava(query.getName(), true);
+	}
+
+	/**
+	 * Get all parameter names that used in query qualifier.
+	 *
+	 * @param query
+	 * @return Parameter names.
+	 */
+	public Collection getParameterNames(SelectQueryDescriptor query) {
+
+		if (query.getQualifier() == null) {
+			return Collections.EMPTY_SET;
+		}
+
+		Map<String, String> queryParameters = queriesMap.get(query.getName());
+
+		if (queryParameters == null) {
+			queryParameters = getParameterNames(query.getQualifier(), query.getRoot());
+			queriesMap.put(query.getName(), queryParameters);
+		}
+
+		return parseQualifier(query.getQualifier().toString());
+	}
+
+	public Boolean isValidParameterNames(SelectQueryDescriptor query) {
+
+		if (query.getQualifier() == null) {
+			return true;
+		}
+
+		Map<String, String> queryParameters = queriesMap.get(query.getName());
+
+		if (queryParameters == null) {
+			try {
+				queryParameters = getParameterNames(query.getQualifier(), query.getRoot());
+			} catch (Exception e) {
+				// if we have wrong path in queryParameters return false.
+				return false;
+			}
+		}
+
+		for (Ordering ordering : query.getOrderings()) {
+			// validate paths in ordering
+			String path = ordering.getSortSpecString();
+			Iterator<CayenneMapEntry> it = ((ObjEntity) query.getRoot()).resolvePathComponents(path);
+			while (it.hasNext()) {
+				try {
+					it.next();
+				} catch (ExpressionException e) {
+					// if we have wrong path in orderings return false.
+					return false;
+				}
+			}
+		}
+
+		return true;
+	}
+
+	/**
+	 * Get list of parameter names in the same order as in qualifier.
+	 * 
+	 * @param qualifierString
+	 *            to be parsed
+	 * @return List of parameter names.
+	 */
+	private Set parseQualifier(String qualifierString) {
+		Set result = new ListOrderedSet();
+		Pattern pattern = Pattern.compile("\\$[\\w]+");
+		Matcher matcher = pattern.matcher(qualifierString);
+		while (matcher.find()) {
+			String name = matcher.group();
+			result.add(Util.underscoredToJava(name.substring(1), false));
+		}
+
+		return result;
+	}
+
+	public boolean hasParameters(SelectQueryDescriptor query) {
+		Map queryParameters = queriesMap.get(query.getName());
+
+		if (queryParameters == null) {
+			return false;
+		}
+
+		return queryParameters.keySet().size() > 0;
+
+	}
+
+	/**
+	 * Get type of parameter for given name.
+	 *
+	 * @param query
+	 * @param name
+	 * @return Parameter type.
+	 */
+	public String getParameterType(SelectQueryDescriptor query, String name) {
+		return queriesMap.get(query.getName()).get(name);
+	}
+
+	private Map<String, String> getParameterNames(Expression expression, Object root) {
+		if (expression != null) {
+			Map<String, String> types = new HashMap<>();
+			String typeName = "";
+			List<String> names = new LinkedList<String>();
+
+			for (int i = 0; i < expression.getOperandCount(); i++) {
+				Object operand = expression.getOperand(i);
+
+				if (operand instanceof Expression) {
+					types.putAll(getParameterNames((Expression) operand, root));
+				}
+
+				if (operand instanceof ASTObjPath) {
+					PathComponent<ObjAttribute, ObjRelationship> component = ((Entity) root).lastPathComponent(
+							(ASTObjPath) operand, null);
+					ObjAttribute attribute = component.getAttribute();
+					if (attribute != null) {
+						typeName = attribute.getType();
+					} else {
+						ObjRelationship relationship = component.getRelationship();
+						if (relationship != null) {
+							typeName = ((ObjEntity) relationship.getTargetEntity()).getClassName();
+						} else {
+							typeName = "Object";
+						}
+					}
+				}
+
+				if (operand instanceof ASTList) {
+					Object[] values = (Object[]) ((ASTList) operand).getOperand(0);
+					for (Object value : values) {
+						if (value instanceof ExpressionParameter) {
+							names.add(((ExpressionParameter) value).getName());
+						}
+					}
+				}
+
+				if (operand instanceof ExpressionParameter) {
+					names.add(((ExpressionParameter) operand).getName());
+				}
+
+			}
+
+			for (String name : names) {
+				types.put(Util.underscoredToJava(name, false), typeName);
+			}
+
+			return types;
+		}
+		return Collections.EMPTY_MAP;
+	}
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c63b6be2/cayenne-cgen/src/main/java/org/apache/cayenne/gen/EmbeddableArtifact.java
----------------------------------------------------------------------
diff --git a/cayenne-cgen/src/main/java/org/apache/cayenne/gen/EmbeddableArtifact.java b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/EmbeddableArtifact.java
new file mode 100644
index 0000000..8e6ea43
--- /dev/null
+++ b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/EmbeddableArtifact.java
@@ -0,0 +1,68 @@
+/*****************************************************************
+ *   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 org.apache.cayenne.map.Embeddable;
+import org.apache.velocity.VelocityContext;
+
+/**
+ * {@link Artifact} facade for an {@link Embeddable}.
+ * 
+ * @since 3.0
+ */
+public class EmbeddableArtifact implements Artifact {
+
+    protected Embeddable embeddable;
+
+    public EmbeddableArtifact(Embeddable embeddable) {
+        this.embeddable = embeddable;
+    }
+
+    public Object getObject() {
+        return embeddable;
+    }
+
+    public String getQualifiedBaseClassName() {
+        return Object.class.getName();
+    }
+
+    public String getQualifiedClassName() {
+        return embeddable.getClassName();
+    }
+
+    public TemplateType[] getTemplateTypes(ArtifactGenerationMode mode) {
+        switch (mode) {
+            case SINGLE_CLASS:
+                return new TemplateType[] {
+                    TemplateType.EMBEDDABLE_SINGLE_CLASS
+                };
+            case GENERATION_GAP:
+                return new TemplateType[] {
+                        TemplateType.EMBEDDABLE_SUPERCLASS,
+                        TemplateType.EMBEDDABLE_SUBCLASS
+                };
+            default:
+                return new TemplateType[0];
+        }
+    }
+
+    public void postInitContext(VelocityContext context) {
+        // noop - no special keys...
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c63b6be2/cayenne-cgen/src/main/java/org/apache/cayenne/gen/EntityArtifact.java
----------------------------------------------------------------------
diff --git a/cayenne-cgen/src/main/java/org/apache/cayenne/gen/EntityArtifact.java b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/EntityArtifact.java
new file mode 100644
index 0000000..394304f
--- /dev/null
+++ b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/EntityArtifact.java
@@ -0,0 +1,98 @@
+/*****************************************************************
+ *   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 org.apache.cayenne.CayenneDataObject;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.velocity.VelocityContext;
+
+/**
+ * {@link Artifact} facade for an ObjEntity.
+ * 
+ * @since 3.0
+ */
+public class EntityArtifact implements Artifact {
+
+    public static String ENTITY_UTILS_KEY = "entityUtils";
+
+    protected ObjEntity entity;
+
+    public EntityArtifact(ObjEntity entity) {
+        this.entity = entity;
+    }
+
+    /**
+     * Returns ObjEntity.
+     */
+    public Object getObject() {
+        return entity;
+    }
+
+    public String getQualifiedBaseClassName() {
+        return (entity.getSuperClassName() != null)
+                ? entity.getSuperClassName()
+                : CayenneDataObject.class.getName();
+    }
+
+    public String getQualifiedClassName() {
+        return entity.getClassName();
+    }
+
+    public TemplateType getSingleClassType() {
+        return TemplateType.ENTITY_SINGLE_CLASS;
+    }
+
+    public TemplateType getSubclassType() {
+        return TemplateType.ENTITY_SUBCLASS;
+    }
+
+    public TemplateType getSuperClassType() {
+        return TemplateType.ENTITY_SUPERCLASS;
+    }
+
+    public TemplateType[] getTemplateTypes(ArtifactGenerationMode mode) {
+        switch (mode) {
+            case SINGLE_CLASS:
+                return new TemplateType[] {
+                    TemplateType.ENTITY_SINGLE_CLASS
+                };
+            case GENERATION_GAP:
+                return new TemplateType[] {
+                        TemplateType.ENTITY_SUPERCLASS, TemplateType.ENTITY_SUBCLASS
+                };
+            default:
+                return new TemplateType[0];
+        }
+    }
+
+    public void postInitContext(VelocityContext context) {
+        EntityUtils metadata = new EntityUtils(
+                entity.getDataMap(),
+                entity,
+                (String) context.get(BASE_CLASS_KEY),
+                (String) context.get(BASE_PACKAGE_KEY),
+                (String) context.get(SUPER_CLASS_KEY),
+                (String) context.get(SUPER_PACKAGE_KEY),
+                (String) context.get(SUB_CLASS_KEY),
+                (String) context.get(SUB_PACKAGE_KEY));
+
+        context.put(ENTITY_UTILS_KEY, metadata);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c63b6be2/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
new file mode 100644
index 0000000..ecf2a3f
--- /dev/null
+++ b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/EntityUtils.java
@@ -0,0 +1,274 @@
+/*****************************************************************
+ *   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.Collection;
+
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.ObjectId;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.MappingNamespace;
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.map.ObjRelationship;
+import org.apache.cayenne.map.Relationship;
+
+/**
+ * Attributes and Methods for working with ObjEntities.
+ * 
+ * @since 1.2
+ */
+public class EntityUtils {
+
+    // template substitution values
+    protected String subClassName;
+    protected String superClassName;
+    protected String baseClassName;
+    protected String subPackageName;
+    protected String superPackageName;
+    protected String basePackageName;
+
+    protected DataMap primaryDataMap;
+    protected ObjEntity objEntity;
+
+    protected Collection<String> callbackNames;
+
+    public EntityUtils(DataMap dataMap, ObjEntity objEntity, String fqnBaseClass, String fqnSuperClass,
+            String fqnSubClass) {
+
+        StringUtils stringUtils = StringUtils.getInstance();
+
+        this.baseClassName = stringUtils.stripPackageName(fqnBaseClass);
+        this.basePackageName = stringUtils.stripClass(fqnBaseClass);
+        this.superClassName = stringUtils.stripPackageName(fqnSuperClass);
+        this.superPackageName = stringUtils.stripClass(fqnSuperClass);
+        this.subClassName = stringUtils.stripPackageName(fqnSubClass);
+        this.subPackageName = stringUtils.stripClass(fqnSubClass);
+
+        this.primaryDataMap = dataMap;
+
+        this.objEntity = objEntity;
+        this.callbackNames = objEntity.getCallbackMethods();
+    }
+
+    EntityUtils(DataMap dataMap, ObjEntity objEntity, String baseClassName, String basePackageName,
+            String superClassName, String superPackageName, String subClassName, String subPackageName) {
+
+        this.baseClassName = baseClassName;
+        this.basePackageName = basePackageName;
+        this.superClassName = superClassName;
+        this.superPackageName = superPackageName;
+        this.subClassName = subClassName;
+        this.subPackageName = subPackageName;
+
+        this.primaryDataMap = dataMap;
+
+        this.objEntity = objEntity;
+        this.callbackNames = objEntity.getCallbackMethods();
+    }
+
+    /**
+     * @return Returns the primary DataMap.
+     * @since 1.2
+     */
+    public DataMap getPrimaryDataMap() {
+        return primaryDataMap;
+    }
+
+    /**
+     * Returns the EntityResolver for this set of DataMaps.
+     * 
+     * @since 1.2
+     */
+    public MappingNamespace getEntityResolver() {
+        return primaryDataMap.getNamespace();
+    }
+
+    /**
+     * Returns true if current ObjEntity is defined as abstract.
+     */
+    public boolean isAbstract() {
+        return isAbstract(objEntity);
+    }
+
+    /**
+     * Returns true if current ObjEntity is defined as abstract.
+     */
+    public boolean isAbstract(ObjEntity anObjEntity) {
+        return anObjEntity != null && anObjEntity.isAbstract();
+    }
+
+    /**
+     * Returns true if current ObjEntity contains at least one toMany
+     * relationship.
+     */
+    public boolean hasToManyRelationships() {
+        return hasToManyRelationships(objEntity);
+    }
+
+    /**
+     * Returns true if an ObjEntity contains at least one toMany relationship.
+     */
+    public boolean hasToManyRelationships(ObjEntity anObjEntity) {
+        if (anObjEntity == null) {
+            return false;
+        }
+
+        for (Relationship r : anObjEntity.getRelationships()) {
+            if (r.isToMany()) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns true if current ObjEntity contains at least one toMany
+     * relationship, ignoring those declared in superentities.
+     * 
+     * @since 1.2
+     */
+    public boolean hasToManyDeclaredRelationships() {
+        return hasToManyDeclaredRelationships(objEntity);
+    }
+
+    /**
+     * Returns true if an ObjEntity contains at least one toMany relationship,
+     * ignoring those declared in superentities.
+     * 
+     * @since 1.2
+     */
+    public boolean hasToManyDeclaredRelationships(ObjEntity anObjEntity) {
+        if (anObjEntity == null) {
+            return false;
+        }
+
+        for (Relationship r : anObjEntity.getDeclaredRelationships()) {
+            if (r.isToMany()) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns true if current ObjEntity contains at least one toOne
+     * relationship.
+     */
+    public boolean hasToOneRelationships() {
+        return hasToOneRelationships(objEntity);
+    }
+
+    /**
+     * Returns true if an ObjEntity contains at least one toOne relationship.
+     */
+    public boolean hasToOneRelationships(ObjEntity anObjEntity) {
+        if (anObjEntity == null) {
+            return false;
+        }
+
+        for (Relationship r : anObjEntity.getRelationships()) {
+            if (!r.isToMany()) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns true if current ObjEntity contains at least one toOne
+     * relationship, ignoring those declared in superentities.
+     */
+    public boolean hasToOneDeclaredRelationships() {
+        return hasToOneDeclaredRelationships(objEntity);
+    }
+
+    /**
+     * Returns true if an ObjEntity contains at least one toOne relationship,
+     * ignoring those declared in superentities.
+     */
+    public boolean hasToOneDeclaredRelationships(ObjEntity anObjEntity) {
+        if (anObjEntity == null) {
+            return false;
+        }
+
+        for (Relationship r : anObjEntity.getDeclaredRelationships()) {
+            if (!r.isToMany()) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns the map key type for a collection relationship of type
+     * java.util.Map.
+     * 
+     * @param relationship
+     *            The relationship to look up type information for.
+     * @return The type of the attribute keyed on.
+     */
+    public String getMapKeyType(final ObjRelationship relationship) {
+
+        ObjEntity targetEntity = (ObjEntity) relationship.getTargetEntity();
+
+        // 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.
+            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,
+            // 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.
+        ObjAttribute attribute = targetEntity.getAttribute(relationship.getMapKey());
+        if (attribute == null) {
+            throw new CayenneRuntimeException("Invalid map key '" + relationship.getMapKey()
+                    + "', no matching attribute found");
+        }
+
+        return attribute.getType();
+    }
+
+    /**
+     * @return the list of all callback names registered for the entity.
+     * @since 3.0
+     */
+    public Collection<String> getCallbackNames() {
+        return callbackNames;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c63b6be2/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
new file mode 100644
index 0000000..af40499
--- /dev/null
+++ b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/ImportUtils.java
@@ -0,0 +1,266 @@
+/*****************************************************************
+ *   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 org.apache.cayenne.util.Util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Methods for mangling strings.
+ * 
+ */
+public class ImportUtils {
+
+	public static final String importOrdering[] = new String[] { "java.", "javax.", "org.", "com." };
+
+	static final String primitives[] = new String[] { "long", "double", "byte", "boolean", "float", "short", "int",
+			"char" };
+
+	static final String primitiveClasses[] = new String[] { Long.class.getName(), Double.class.getName(),
+			Byte.class.getName(), Boolean.class.getName(), Float.class.getName(), Short.class.getName(),
+			Integer.class.getName(), Character.class.getName() };
+
+	static Map<String, String> classesForPrimitives = Util.toMap(primitives, primitiveClasses);
+	static Map<String, String> primitivesForClasses = Util.toMap(primitiveClasses, primitives);
+
+	protected Map<String, String> importTypesMap = new HashMap<>();
+
+	// Types forced to be FQN
+	protected Map<String, String> reservedImportTypesMap = new HashMap<>();
+
+	protected String packageName;
+
+	public ImportUtils() {
+		super();
+	}
+
+	protected boolean canRegisterType(String typeName) {
+		// Not sure why this would ever happen, but it did
+		if (null == typeName)
+			return false;
+
+		StringUtils stringUtils = StringUtils.getInstance();
+		String typeClassName = stringUtils.stripPackageName(typeName);
+		String typePackageName = stringUtils.stripClass(typeName);
+
+		if (typePackageName.length() == 0)
+			return false; // disallow non-packaged types (primitives, probably)
+		if ("java.lang".equals(typePackageName))
+			return false;
+
+		// Can only have one type -- rest must use fqn
+		if (reservedImportTypesMap.containsKey(typeClassName))
+			return false;
+		if (importTypesMap.containsKey(typeClassName))
+			return false;
+
+		return true;
+	}
+
+	/**
+	 * Reserve a fully-qualified data type class name so it cannot be used by
+	 * another class. No import statements will be generated for reserved types.
+	 * Typically, this is the fully-qualified class name of the class being
+	 * generated.
+	 * 
+	 * @param typeName
+	 *            FQ data type class name.
+	 */
+	public void addReservedType(String typeName) {
+		if (!canRegisterType(typeName))
+			return;
+
+		StringUtils stringUtils = StringUtils.getInstance();
+		String typeClassName = stringUtils.stripPackageName(typeName);
+
+		reservedImportTypesMap.put(typeClassName, typeName);
+	}
+
+	/**
+	 * Register a fully-qualified data type class name. For example,
+	 * org.apache.cayenne.CayenneDataObject.
+	 * 
+	 * @param typeName
+	 *            FQ data type class name.
+	 */
+	public void addType(String typeName) {
+		if (!canRegisterType(typeName))
+			return;
+
+		StringUtils stringUtils = StringUtils.getInstance();
+		String typePackageName = stringUtils.stripClass(typeName);
+		String typeClassName = stringUtils.stripPackageName(typeName);
+
+		if (typePackageName.equals(packageName))
+			return;
+
+		importTypesMap.put(typeClassName, typeName);
+	}
+
+	/**
+	 * Add the package name to use for this importUtil invocation.
+	 * 
+	 * @param packageName
+	 */
+	public void setPackage(String packageName) {
+		this.packageName = packageName;
+	}
+
+	/**
+	 * Performs processing similar to <code>formatJavaType(String)</code>, with
+	 * special handling of primitive types and their Java class counterparts.
+	 * This method allows users to make a decision whether to use primitives or
+	 * not, regardless of how type is mapped.
+	 */
+	public String formatJavaType(String typeName, boolean usePrimitives) {
+		if (usePrimitives) {
+			String primitive = primitivesForClasses.get(typeName);
+			return (primitive != null) ? primitive : formatJavaType(typeName);
+		} else {
+			String primitiveClass = classesForPrimitives.get(typeName);
+			return (primitiveClass != null) ? formatJavaType(primitiveClass) : formatJavaType(typeName);
+		}
+	}
+
+	/**
+	 * Removes registered package and non-reserved registered type name prefixes
+	 * from java types
+	 */
+	public String formatJavaType(String typeName) {
+		if (typeName != null) {
+			StringUtils stringUtils = StringUtils.getInstance();
+			String typeClassName = stringUtils.stripPackageName(typeName);
+
+			if (!reservedImportTypesMap.containsKey(typeClassName)) {
+				if (importTypesMap.containsKey(typeClassName)) {
+					if (typeName.equals(importTypesMap.get(typeClassName)))
+						return typeClassName;
+				}
+			}
+
+			String typePackageName = stringUtils.stripClass(typeName);
+			if ("java.lang".equals(typePackageName))
+				return typeClassName;
+			if ((null != packageName) && (packageName.equals(typePackageName)))
+				return typeClassName;
+		}
+
+		return typeName;
+	}
+
+	/**
+	 * @since 3.0
+	 */
+	public String formatJavaTypeAsNonBooleanPrimitive(String type) {
+		String value = ImportUtils.classesForPrimitives.get(type);
+		return formatJavaType(value != null ? value : type);
+	}
+
+	/**
+	 * @since 3.0
+	 */
+	public boolean isNonBooleanPrimitive(String type) {
+		return ImportUtils.classesForPrimitives.containsKey(type) && !isBoolean(type);
+	}
+
+	/**
+	 * @since 3.0
+	 */
+	public boolean isBoolean(String type) {
+		return "boolean".equals(type);
+	}
+
+	/**
+	 * Generate package and list of import statements based on the registered
+	 * types.
+	 */
+	public String generate() {
+		StringBuilder outputBuffer = new StringBuilder();
+
+		if (null != packageName) {
+			outputBuffer.append("package ");
+			outputBuffer.append(packageName);
+
+			// Using UNIX line endings intentionally - generated Java files
+			// should look
+			// the same regardless of platform to prevent developer teams
+			// working on
+			// multiple OS's to override each other's work
+			outputBuffer.append(";\n\n");
+		}
+
+		List<String> typesList = new ArrayList<>(importTypesMap.values());
+		Collections.sort(typesList, new Comparator<String>() {
+
+			public int compare(String s1, String s2) {
+
+				for (String ordering : importOrdering) {
+					if ((s1.startsWith(ordering)) && (!s2.startsWith(ordering))) {
+						return -1;
+					}
+					if ((!s1.startsWith(ordering)) && (s2.startsWith(ordering))) {
+						return 1;
+					}
+				}
+
+				return s1.compareTo(s2);
+			}
+		});
+
+		String lastStringPrefix = null;
+		boolean firstIteration = true;
+		for (String typeName : typesList) {
+
+			if (firstIteration) {
+				firstIteration = false;
+			} else {
+				outputBuffer.append('\n');
+			}
+			// Output another newline if we're in a different root package.
+			// Find root package
+			String thisStringPrefix = typeName;
+			int dotIndex = typeName.indexOf('.');
+			if (-1 != dotIndex) {
+				thisStringPrefix = typeName.substring(0, dotIndex);
+			}
+			// if this isn't the first import,
+			if (null != lastStringPrefix) {
+				// and it's different from the last import
+				if (false == thisStringPrefix.equals(lastStringPrefix)) {
+					// output a newline; force UNIX style per comment above
+					outputBuffer.append("\n");
+				}
+			}
+			lastStringPrefix = thisStringPrefix;
+
+			outputBuffer.append("import ");
+			outputBuffer.append(typeName);
+			outputBuffer.append(';');
+		}
+
+		return outputBuffer.toString();
+	}
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c63b6be2/cayenne-cgen/src/main/java/org/apache/cayenne/gen/StringUtils.java
----------------------------------------------------------------------
diff --git a/cayenne-cgen/src/main/java/org/apache/cayenne/gen/StringUtils.java b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/StringUtils.java
new file mode 100644
index 0000000..778a09b
--- /dev/null
+++ b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/StringUtils.java
@@ -0,0 +1,213 @@
+/*****************************************************************
+ *   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 org.apache.cayenne.project.validation.NameValidationHelper;
+import org.apache.cayenne.util.Util;
+
+/**
+ * Methods for mangling strings.
+ */
+public class StringUtils {
+
+    private static StringUtils sharedInstance;
+
+    public static StringUtils getInstance() {
+        if (null == sharedInstance) {
+            sharedInstance = new StringUtils();
+        }
+
+        return sharedInstance;
+    }
+
+    /**
+     * Prepends underscore to variable name if necessary to remove conflict with reserved
+     * keywords.
+     */
+    public String formatVariableName(String variableName) {
+        if (NameValidationHelper.getInstance().isReservedJavaKeyword(variableName)) {
+            return "_" + variableName;
+        }
+        else {
+            return variableName;
+        }
+    }
+
+    /**
+     * Removes package name, leaving base name.
+     * 
+     * @since 1.2
+     */
+    public String stripPackageName(String fullyQualifiedClassName) {
+        return Util.stripPackageName(fullyQualifiedClassName);
+    }
+
+    /**
+     * Removes base name, leaving package name.
+     * 
+     * @since 1.2
+     */
+    public String stripClass(String aString) {
+        if (aString == null || aString.length() == 0)
+            return aString;
+
+        int lastDot = aString.lastIndexOf('.');
+
+        if (-1 == lastDot)
+            return "";
+
+        return aString.substring(0, lastDot);
+    }
+
+    /**
+     * Capitalizes the first letter of the property name.
+     * 
+     * @since 1.1
+     */
+    public String capitalized(String name) {
+        if (name == null || name.length() == 0)
+            return name;
+
+        char c = Character.toUpperCase(name.charAt(0));
+        return (name.length() == 1) ? Character.toString(c) : c + name.substring(1);
+    }
+
+    /**
+     * Returns string with lowercased first letter
+     * 
+     * @since 1.2
+     */
+    public static String uncapitalized(String aString) {
+        if (aString == null || aString.length() == 0)
+            return aString;
+
+        char c = Character.toLowerCase(aString.charAt(0));
+        return (aString.length() == 1) ? Character.toString(c) : c + aString.substring(1);
+    }
+
+    /**
+     * Converts property name to Java constants naming convention.
+     * 
+     * @since 1.1
+     */
+    public String capitalizedAsConstant(String name) {
+        if (name == null || name.length() == 0)
+            return name;
+
+        // clear of non-java chars. While the method name implies that a passed identifier
+        // is pure Java, it is used to build pk columns names and such, so extra safety
+        // check is a good idea
+        name = Util.specialCharsToJava(name);
+
+        char charArray[] = name.toCharArray();
+        StringBuilder buffer = new StringBuilder();
+
+        for (int i = 0; i < charArray.length; i++) {
+            if ((Character.isUpperCase(charArray[i])) && (i != 0)) {
+
+                char prevChar = charArray[i - 1];
+                if ((Character.isLowerCase(prevChar))) {
+                    buffer.append("_");
+                }
+            }
+
+            buffer.append(Character.toUpperCase(charArray[i]));
+        }
+
+        return buffer.toString();
+    }
+
+    /**
+     * Converts entity or property name to a plural form. For example:
+     * <ul>
+     * <li>pluralize("Word") == "Words"</li>
+     * <li>pluralize("Status") == "Statuses"</li>
+     * <li>pluralize("Index") == "Indexes"</li>
+     * <li>pluralize("Factory") == "Factories"</li>
+     * </ul>
+     * <p>
+     * As of 3.1 this method is not used in bundled templates, and is present here for
+     * user templates convenience.
+     * 
+     * @since 3.1
+     */
+    public String pluralize(String str) {
+        if (str == null || str.length() == 0) {
+            return str;
+        }
+        else if (str.endsWith("s") || str.endsWith("x")) {
+            return str + "es";
+        }
+        else if (str.endsWith("y")) {
+            return str.substring(0, str.length() - 1) + "ies";
+        }
+        else {
+            return str + "s";
+        }
+    }
+
+    /**
+     * <p>
+     * Strip generic definition from string
+     * </p>
+     * <p>For example: List&gt;Integer&lt; == List</p>
+     * @since 4.0
+     */
+    public String stripGeneric(String str) {
+        if(str == null) {
+            return null;
+        }
+        int start = str.indexOf('<');
+        if(start == -1) {
+            return str;
+        }
+        int end = str.lastIndexOf('>');
+        if(end == -1) {
+            return str;
+        } else if(end == str.length() - 1) {
+            return str.substring(0, start);
+        }
+        return str.substring(0, start) + str.substring(end+1);
+    }
+
+    public String replaceWildcardInStringWithString(String wildcard, String pattern, String replacement) {
+        if (pattern == null || wildcard == null) {
+            return pattern;
+        }
+
+        StringBuilder buffer = new StringBuilder();
+        int lastPos = 0;
+        int wildCardPos = pattern.indexOf(wildcard);
+        while (wildCardPos != -1) {
+            if (lastPos != wildCardPos) {
+                buffer.append(pattern.substring(lastPos, wildCardPos));
+            }
+            buffer.append(replacement);
+            lastPos += wildCardPos + wildcard.length();
+            wildCardPos = pattern.indexOf(wildcard, lastPos);
+        }
+
+        if (lastPos < pattern.length()) {
+            buffer.append(pattern.substring(lastPos));
+        }
+
+        return buffer.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c63b6be2/cayenne-cgen/src/main/java/org/apache/cayenne/gen/TemplateType.java
----------------------------------------------------------------------
diff --git a/cayenne-cgen/src/main/java/org/apache/cayenne/gen/TemplateType.java b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/TemplateType.java
new file mode 100644
index 0000000..109627e
--- /dev/null
+++ b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/TemplateType.java
@@ -0,0 +1,55 @@
+/*****************************************************************
+ *   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;
+
+/**
+ * Defines class generation template types.
+ * 
+ * @since 3.0
+ */
+public enum TemplateType {
+
+    ENTITY_SINGLE_CLASS(false),
+
+    ENTITY_SUPERCLASS(true),
+
+    ENTITY_SUBCLASS(false),
+
+    EMBEDDABLE_SINGLE_CLASS(false),
+
+    EMBEDDABLE_SUPERCLASS(true),
+
+    EMBEDDABLE_SUBCLASS(false),
+
+    DATAMAP_SINGLE_CLASS(false),
+
+    DATAMAP_SUPERCLASS(true),
+
+    DATAMAP_SUBCLASS(false);
+
+    private boolean superclass;
+
+    private TemplateType(boolean superclass) {
+        this.superclass = superclass;
+    }
+
+    public boolean isSuperclass() {
+        return superclass;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c63b6be2/cayenne-cgen/src/main/java/org/apache/cayenne/gen/package.html
----------------------------------------------------------------------
diff --git a/cayenne-cgen/src/main/java/org/apache/cayenne/gen/package.html b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/package.html
new file mode 100644
index 0000000..69b8b7d
--- /dev/null
+++ b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/package.html
@@ -0,0 +1,28 @@
+<!--
+   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.
+-->
+<html>
+<body>
+Contains classes that provide Java source generation facility. 
+Source creation is based on a set of templates parsed 
+during generation process, using Jakarta Velocity template engine.
+
+<p><i>For more information see <a href="../../../../../../index.html"
+target="_top">Cayenne User Guide.</a></i></p>
+</body>
+</html>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c63b6be2/cayenne-cgen/src/main/java/org/apache/cayenne/tools/CayenneGeneratorEntityFilterAction.java
----------------------------------------------------------------------
diff --git a/cayenne-cgen/src/main/java/org/apache/cayenne/tools/CayenneGeneratorEntityFilterAction.java b/cayenne-cgen/src/main/java/org/apache/cayenne/tools/CayenneGeneratorEntityFilterAction.java
new file mode 100644
index 0000000..4d068d8
--- /dev/null
+++ b/cayenne-cgen/src/main/java/org/apache/cayenne/tools/CayenneGeneratorEntityFilterAction.java
@@ -0,0 +1,85 @@
+/*****************************************************************
+ *   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.tools;
+
+import org.apache.cayenne.dbsync.filter.NameFilter;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.Embeddable;
+import org.apache.cayenne.map.ObjEntity;
+
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+
+/**
+ * Performs entity filtering to build a collection of entities that should be used in
+ * class generation.
+ * 
+ * @since 3.0
+ */
+class CayenneGeneratorEntityFilterAction {
+
+    private NameFilter nameFilter;
+    private boolean client;
+
+    Collection<Embeddable> getFilteredEmbeddables(DataMap mainDataMap) {
+        Collection<Embeddable> embeddables = new ArrayList<>(mainDataMap.getEmbeddables());
+
+        // filter out excluded entities...
+        Iterator<Embeddable> it = embeddables.iterator();
+
+        while (it.hasNext()) {
+            Embeddable e = it.next();
+
+            // note that unlike entity, embeddable is matched by class name as it doesn't
+            // have a symbolic name...
+            if (!nameFilter.isIncluded(e.getClassName())) {
+                it.remove();
+            }
+        }
+
+        return embeddables;
+    }
+
+    Collection<ObjEntity> getFilteredEntities(DataMap mainDataMap)
+            throws MalformedURLException {
+
+        Collection<ObjEntity> entities = new ArrayList<>(mainDataMap.getObjEntities());
+
+        // filter out excluded entities...
+        Iterator<ObjEntity> it = entities.iterator();
+        while (it.hasNext()) {
+            ObjEntity e = it.next();
+            if (e.isGeneric() || client && !e.isClientAllowed() || !nameFilter.isIncluded(e.getName())) {
+                it.remove();
+            }
+        }
+
+        return entities;
+    }
+
+    void setClient(boolean client) {
+        this.client = client;
+    }
+
+    public void setNameFilter(NameFilter nameFilter) {
+        this.nameFilter = nameFilter;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c63b6be2/cayenne-cgen/src/main/java/org/apache/cayenne/tools/CayenneGeneratorMapLoaderAction.java
----------------------------------------------------------------------
diff --git a/cayenne-cgen/src/main/java/org/apache/cayenne/tools/CayenneGeneratorMapLoaderAction.java b/cayenne-cgen/src/main/java/org/apache/cayenne/tools/CayenneGeneratorMapLoaderAction.java
new file mode 100644
index 0000000..1a0a098
--- /dev/null
+++ b/cayenne-cgen/src/main/java/org/apache/cayenne/tools/CayenneGeneratorMapLoaderAction.java
@@ -0,0 +1,78 @@
+/*****************************************************************
+ *   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.tools;
+
+import java.io.File;
+import java.net.MalformedURLException;
+
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.map.MapLoader;
+import org.xml.sax.InputSource;
+
+/**
+ * Loads a DataMap and a shared entity namespace.
+ * 
+ * @since 3.0
+ */
+class CayenneGeneratorMapLoaderAction {
+
+    private File mainDataMapFile;
+    private File[] additionalDataMapFiles;
+    private DataMap mainDataMap;
+
+    DataMap getMainDataMap() throws MalformedURLException {
+        if (mainDataMap == null) {
+            MapLoader mapLoader = new MapLoader();
+
+            DataMap mainDataMap = loadDataMap(mapLoader, mainDataMapFile);
+
+            if (additionalDataMapFiles != null) {
+
+                EntityResolver entityResolver = new EntityResolver();
+                entityResolver.addDataMap(mainDataMap);
+                mainDataMap.setNamespace(entityResolver);
+
+                for (File additionalDataMapFile : additionalDataMapFiles) {
+
+                    DataMap dataMap = loadDataMap(mapLoader, additionalDataMapFile);
+                    entityResolver.addDataMap(dataMap);
+                    dataMap.setNamespace(entityResolver);
+                }
+            }
+
+            this.mainDataMap = mainDataMap;
+        }
+
+        return mainDataMap;
+    }
+
+    protected DataMap loadDataMap(MapLoader mapLoader, File dataMapFile) throws MalformedURLException {
+        InputSource in = new InputSource(dataMapFile.toURI().toURL().toString());
+        return mapLoader.loadDataMap(in);
+    }
+
+    void setMainDataMapFile(File mainDataMapFile) {
+        this.mainDataMapFile = mainDataMapFile;
+    }
+
+    void setAdditionalDataMapFiles(File[] additionalDataMapFiles) {
+        this.additionalDataMapFiles = additionalDataMapFiles;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c63b6be2/cayenne-cgen/src/main/resources/templates/v1_2/client-datamap-singleclass.vm
----------------------------------------------------------------------
diff --git a/cayenne-cgen/src/main/resources/templates/v1_2/client-datamap-singleclass.vm b/cayenne-cgen/src/main/resources/templates/v1_2/client-datamap-singleclass.vm
new file mode 100644
index 0000000..d0c7f6c
--- /dev/null
+++ b/cayenne-cgen/src/main/resources/templates/v1_2/client-datamap-singleclass.vm
@@ -0,0 +1,96 @@
+##   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.
+##
+##Terminology:
+##	Base class - super superclass of entity, ie, org.apache.cayenne.CayenneDataObject or MyBaseClass
+##  Super class - superclass of entity, ie,  org.apache.cayenne.art.auto._Artist
+##	Sub class - class of entity, ie, org.apache.cayenne.art.Artist
+##
+##  Classes available in template
+##    object (duplicated as 'objEntity') - the ObjEntity class: See org.apache.cayenne.map.ObjectEntity
+##    stringUtils - class for string "helper" functions: See org.apache.cayenne.gen.StringUtils
+##    dataMapUtils - class for query "helper" functions: See org.apache.cayenne.gen.DataMapUtils
+##    importUtils - class for import statement management: See org.apache.cayenne.gen.ImportUtils
+##    superClassName
+##    superPackageName
+##    subClassName
+##    subPackageName
+##    baseClassName
+##    basePackageName 
+##
+##
+${importUtils.setPackage($subPackageName)}##
+${importUtils.addReservedType("${subPackageName}.${subClassName}")}##
+${importUtils.addType("${basePackageName}.${baseClassName}")}##
+${importUtils.addType('java.util.List')}
+${importUtils.addType('java.util.Map')}
+${importUtils.addType('java.util.HashMap')}
+${importUtils.addType('org.apache.cayenne.ObjectContext')}
+#foreach( $selectQuery in ${object.SelectQueries})
+${importUtils.addType(${selectQuery.Root.ClassName})}
+#foreach( $parameter in ${dataMapUtils.getParameterNames(${selectQuery})})
+${importUtils.addType(${dataMapUtils.getParameterType(${selectQuery}, ${parameter})})}
+#end
+#end
+${importUtils.generate()}
+
+/**
+ * This class was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public class ${subClassName} {
+#if( ${object.hasQueryNames()})
+#foreach( $qname in ${object.QueryNames})
+
+    public static final String ${stringUtils.capitalizedAsConstant($qname)}_QUERYNAME = "$qname";
+#end
+#end
+
+private static ${subClassName} instance;
+
+    private ${subClassName}() {}
+
+    public ${subClassName} getInstance() {
+      if( instance == null) {
+        instance = new ${subClassName}();
+      }
+      return instance;
+    }
+
+#foreach( $selectQuery in ${object.SelectQueries})
+    public List<${stringUtils.stripPackageName($selectQuery.Root.ClassName)}> perform${dataMapUtils.getQueryMethodName(${selectQuery})}(ObjectContext context #foreach( $parameter in ${dataMapUtils.getParameterNames(${selectQuery})}), ${stringUtils.stripPackageName(${dataMapUtils.getParameterType(${selectQuery}, ${parameter})})} ${parameter} #end) {
+    #if(${dataMapUtils.hasParameters($selectQuery)})
+      String[] parameters = new String[] {
+      #foreach( $parameter in ${dataMapUtils.getParameterNames(${selectQuery})})
+      "${parameter}",
+      #end
+      };
+
+      Object[] values = new Object[] {
+      #foreach( $parameter in ${dataMapUtils.getParameterNames(${selectQuery})})
+      ${parameter},
+      #end
+      };
+    #end
+
+      NamedQuery query = new NamedQuery("${selectQuery.Name}"#if(${dataMapUtils.hasParameters($selectQuery)}), parameters, values#end);
+      return context.performQuery(query);
+    }
+#end
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c63b6be2/cayenne-cgen/src/main/resources/templates/v1_2/client-datamap-subclass.vm
----------------------------------------------------------------------
diff --git a/cayenne-cgen/src/main/resources/templates/v1_2/client-datamap-subclass.vm b/cayenne-cgen/src/main/resources/templates/v1_2/client-datamap-subclass.vm
new file mode 100644
index 0000000..f5e0474
--- /dev/null
+++ b/cayenne-cgen/src/main/resources/templates/v1_2/client-datamap-subclass.vm
@@ -0,0 +1,47 @@
+##   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.
+##
+##Terminology:
+##	Base class - super superclass of entity, ie, org.apache.cayenne.CayenneDataObject or MyBaseClass
+##  Super class - superclass of entity, ie,  org.apache.cayenne.art.auto._Artist
+##	Sub class - class of entity, ie, org.apache.cayenne.art.Artist
+##
+##  Classes available in template
+##    stringUtils - class for string "helper" functions: See org.apache.cayenne.gen.StringUtils
+##    dataMapUtils - class for query "helper" functions: See org.apache.cayenne.gen.dataMapUtils
+##    importUtils - class for import statement management: See org.apache.cayenne.gen.ImportUtils
+##
+##
+${importUtils.setPackage($subPackageName)}##
+${importUtils.addReservedType("${subPackageName}.${subClassName}")}##
+${importUtils.addType("${superPackageName}.${superClassName}")}##
+${importUtils.generate()}
+
+public class ${subClassName} extends ${superClassName} {
+
+    private static ${subClassName} instance;
+
+    private ${subClassName}() {}
+
+    public static ${subClassName} getInstance() {
+        if(instance == null) {
+            instance = new ${subClassName}();
+        }
+
+        return instance;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c63b6be2/cayenne-cgen/src/main/resources/templates/v1_2/client-datamap-superclass.vm
----------------------------------------------------------------------
diff --git a/cayenne-cgen/src/main/resources/templates/v1_2/client-datamap-superclass.vm b/cayenne-cgen/src/main/resources/templates/v1_2/client-datamap-superclass.vm
new file mode 100644
index 0000000..c196301
--- /dev/null
+++ b/cayenne-cgen/src/main/resources/templates/v1_2/client-datamap-superclass.vm
@@ -0,0 +1,83 @@
+##   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.
+##
+##Terminology:
+##	Base class - super superclass of entity, ie, org.apache.cayenne.CayenneDataObject or MyBaseClass
+##  Super class - superclass of entity, ie,  org.apache.cayenne.art.auto._Artist
+##	Sub class - class of entity, ie, org.apache.cayenne.art.Artist
+##
+##  Classes available in template
+##    stringUtils - class for string "helper" functions: See org.apache.cayenne.gen.StringUtils
+##    dataMapUtils - class for query "helper" functions: See org.apache.cayenne.gen.DataMapUtils
+##    importUtils - class for import statement management: See org.apache.cayenne.gen.ImportUtils
+##    superClassName
+##    superPackageName
+##    subClassName
+##    subPackageName
+##    baseClassName
+##    basePackageName
+##
+${importUtils.setPackage($superPackageName)}##
+#if(${superPackageName})${importUtils.addReservedType("${superPackageName}.${superClassName}")}#end##
+#if(${basePackageName})${importUtils.addType("${basePackageName}.${baseClassName}")}#end##
+#if( ${object.hasSelectQueries()} ) 
+${importUtils.addType('java.util.List')}##
+${importUtils.addType('org.apache.cayenne.ObjectContext')}##
+${importUtils.addType('org.apache.cayenne.query.NamedQuery')}##
+#foreach( $selectQuery in ${object.SelectQueries})
+${importUtils.addType(${selectQuery.Root.ClientClassName})}##
+#foreach( $parameter in ${dataMapUtils.getParameterNames(${selectQuery})})
+${importUtils.addType(${dataMapUtils.getParameterType(${selectQuery}, ${parameter})})}##
+#end    
+#end
+#end
+${importUtils.generate()}
+
+/**
+ * This class was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public class ${superClassName} {
+#if( ${object.hasQueryNames()})
+#foreach( $qname in ${object.QueryNames})
+
+    public static final String ${stringUtils.capitalizedAsConstant($qname)}_QUERYNAME = "$qname";
+#end
+#end
+#foreach( $selectQuery in ${object.SelectQueries})
+
+    public List<${stringUtils.stripPackageName($selectQuery.Root.ClientClassName)}> perform${dataMapUtils.getQueryMethodName(${selectQuery})}(ObjectContext context #foreach( $parameter in ${dataMapUtils.getParameterNames(${selectQuery})}), ${stringUtils.stripPackageName(${dataMapUtils.getParameterType(${selectQuery}, ${parameter})})} ${parameter}#end) {
+#if(${dataMapUtils.hasParameters($selectQuery)})
+        String[] parameters = {
+#foreach( $parameter in ${dataMapUtils.getParameterNames(${selectQuery})})
+            "${parameter}",
+#end
+        };
+
+        Object[] values = {
+#foreach( $parameter in ${dataMapUtils.getParameterNames(${selectQuery})})
+            ${parameter},
+#end
+        };
+
+#end
+        return context.performQuery(new NamedQuery("${selectQuery.Name}"#if(${dataMapUtils.hasParameters($selectQuery)}), parameters, values#end));
+    }
+#end
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c63b6be2/cayenne-cgen/src/main/resources/templates/v1_2/client-subclass.vm
----------------------------------------------------------------------
diff --git a/cayenne-cgen/src/main/resources/templates/v1_2/client-subclass.vm b/cayenne-cgen/src/main/resources/templates/v1_2/client-subclass.vm
new file mode 100644
index 0000000..d28de85
--- /dev/null
+++ b/cayenne-cgen/src/main/resources/templates/v1_2/client-subclass.vm
@@ -0,0 +1,57 @@
+##   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.
+##
+##  A default Cayenne template for a client-side subclass in a generated subclass/superclass pair.
+## 
+##  Terminology:
+##  Base class - super superclass of entity, ie, org.apache.cayenne.CayenneDataObject or MyBaseClass
+##  Super class - superclass of entity, ie,  org.apache.cayenne.art.auto._Artist
+##  Sub class - class of entity, ie, org.apache.cayenne.art.Artist
+##
+##  Classes available in template
+##    object (duplicated as 'objEntity') - the ObjEntity class: See org.apache.cayenne.map.ObjectEntity
+##    stringUtils - class for string "helper" functions: See org.apache.cayenne.gen.StringUtils
+##    entityUtils - class for entity "helper" functions: See org.apache.cayenne.gen.EntityUtils
+##    importUtils - class for import statement management: See org.apache.cayenne.gen.ImportUtils
+##    superClassName
+##    superPackageName
+##    subClassName
+##    subPackageName
+##    baseClassName
+##    basePackageName 
+##
+${importUtils.setPackage($subPackageName)}##
+${importUtils.addReservedType("${$subPackageName}.${subClassName}")}##
+${importUtils.addType("${superPackageName}.${superClassName}")}##
+${importUtils.generate()}
+
+/**
+ * A persistent class mapped as "${object.name}" Cayenne entity.
+ */
+public#if("true" == "${object.getIsAbstract()}") abstract#end class ${subClassName} extends ${superClassName} {
+
+     private static final long serialVersionUID = 1L; 
+     
+##callback methods
+#foreach( $cbname in ${entityUtils.callbackNames})
+    @Override
+    protected void ${cbname}() {
+        //TODO: Implement ${cbname}
+    }
+
+#end
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c63b6be2/cayenne-cgen/src/main/resources/templates/v1_2/client-superclass.vm
----------------------------------------------------------------------
diff --git a/cayenne-cgen/src/main/resources/templates/v1_2/client-superclass.vm b/cayenne-cgen/src/main/resources/templates/v1_2/client-superclass.vm
new file mode 100644
index 0000000..f8c9cbe
--- /dev/null
+++ b/cayenne-cgen/src/main/resources/templates/v1_2/client-superclass.vm
@@ -0,0 +1,248 @@
+##   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.
+##
+##Terminology:
+##	Base class - super superclass of entity, ie, org.apache.cayenne.PersistentObject or MyBaseClass
+##  Super class - superclass of entity, ie,  org.apache.cayenne.art.auto._Artist
+##	Sub class - class of entity, ie, org.apache.cayenne.art.Artist
+##
+##  Classes available in template
+##    object (duplicated as 'objEntity') - the ObjEntity class: See org.apache.cayenne.map.ObjectEntity
+##    stringUtils - class for string "helper" functions: See org.apache.cayenne.gen.StringUtils
+##    entityUtils - class for entity "helper" functions: See org.apache.cayenne.gen.EntityUtils
+##    importUtils - class for import statement management: See org.apache.cayenne.gen.ImportUtils
+##    superClassName
+##    superPackageName
+##    subClassName
+##    subPackageName
+##    baseClassName
+##    basePackageName
+##
+##
+${importUtils.setPackage($superPackageName)}##
+${importUtils.addReservedType("${$superPackageName}.${superClassName}")}##
+${importUtils.addType("${basePackageName}.${baseClassName}")}##
+#if((${object.DeclaredAttributes} && !${object.DeclaredAttributes.isEmpty()}) || (${object.DeclaredRelationships} && !${object.DeclaredRelationships.isEmpty()}))
+${importUtils.addType('org.apache.cayenne.exp.Property')}##
+#end
+#foreach( $attr in ${object.DeclaredAttributes} )
+$importUtils.addType(${attr.Type})##
+#end
+#foreach( $rel in ${object.DeclaredRelationships} )
+$importUtils.addType(${rel.TargetEntity.ClientClassName})##
+#if(${rel.CollectionType}) 
+$importUtils.addType(${rel.CollectionType})##
+#end
+#end
+#if( ${entityUtils.hasToOneDeclaredRelationships()} )
+${importUtils.addType('org.apache.cayenne.ValueHolder')}##
+${importUtils.addType('org.apache.cayenne.util.PersistentObjectHolder')}##
+#end
+#if( ${entityUtils.hasToManyDeclaredRelationships()} )
+${importUtils.addType('org.apache.cayenne.util.PersistentObjectList')}##
+#end
+${importUtils.generate()}
+
+/**
+ * A generated persistent class mapped as "${object.name}" Cayenne entity. It is a good idea to
+ * avoid changing this class manually, since it will be overwritten next time code is
+ * regenerated. If you need to make any customizations, put them in a subclass.
+ */
+public abstract class ${superClassName} extends ${baseClassName} {
+
+## Create ivars names
+#if( $createPropertyNames )
+#foreach( $attr in ${object.DeclaredAttributes} )
+    public static final String ${stringUtils.capitalizedAsConstant($attr.Name)}_PROPERTY = "${attr.Name}";
+#end
+#foreach( $rel in ${object.DeclaredRelationships} )
+    public static final String ${stringUtils.capitalizedAsConstant($rel.Name)}_PROPERTY = "${rel.Name}";
+#end
+
+#end
+## 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);
+#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.ClientClassName)>" )
+    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.ClientClassName)>" )
+    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
+#end
+
+## Create ivars
+#foreach( $attr in ${object.DeclaredAttributes} )
+    protected $importUtils.formatJavaType(${attr.Type}) ${attr.Name};
+#end
+#foreach( $rel in ${object.DeclaredRelationships} )
+#if( $rel.ToMany )
+#if ( ${rel.CollectionType} == "java.util.Map")
+    protected $importUtils.formatJavaType($rel.CollectionType)<$importUtils.formatJavaType($entityUtils.getMapKeyType($rel)), $importUtils.formatJavaType($rel.TargetEntity.ClientClassName)> ${rel.Name};
+#else
+    protected $importUtils.formatJavaType($rel.CollectionType)<$importUtils.formatJavaType($rel.TargetEntity.ClientClassName)> ${rel.Name};
+#end
+#else
+    protected ValueHolder ${rel.Name};
+#end
+#end
+
+## Create attribute set/get methods
+#foreach( $attr in ${object.DeclaredAttributes} )
+#if ( $importUtils.isBoolean(${attr.Type}) )
+    public boolean is${stringUtils.capitalized($attr.Name)}() {
+        if(objectContext != null) {
+            objectContext.prepareForAccess(this, "${attr.Name}", false);
+        }
+
+        return ${attr.Name};
+    }
+#else 
+    public $importUtils.formatJavaType(${attr.Type}) get${stringUtils.capitalized($attr.Name)}() {
+        if(objectContext != null) {
+            objectContext.prepareForAccess(this, "${attr.Name}", false);
+        }
+
+        return ${attr.Name};
+    }
+#end
+#if ("true" != "${object.isReadOnly()}")
+    public void set${stringUtils.capitalized($attr.Name)}($importUtils.formatJavaType(${attr.Type}) $stringUtils.formatVariableName(${attr.Name})) {
+        if(objectContext != null) {
+            objectContext.prepareForAccess(this, "${attr.Name}", false);
+        }
+
+        Object oldValue = this.${stringUtils.formatVariableName($attr.Name)};
+        // notify objectContext about simple property change
+        if(objectContext != null) {
+            objectContext.propertyChanged(this, "${attr.Name}", oldValue, $stringUtils.formatVariableName(${attr.Name}));
+        }
+        
+        this.${stringUtils.formatVariableName($attr.Name)} = ${stringUtils.formatVariableName($attr.Name)};
+    }
+#end
+
+#end
+##
+##
+## Create list add/remove/get methods
+#foreach( $rel in ${object.DeclaredRelationships} )
+#if( $rel.ToMany )
+#if ( ${rel.CollectionType} == "java.util.Map")
+    public $importUtils.formatJavaType($rel.CollectionType)<$importUtils.formatJavaType($entityUtils.getMapKeyType($rel)), $importUtils.formatJavaType($rel.TargetEntity.ClientClassName)> get${stringUtils.capitalized($rel.Name)}() {
+#else
+    public $importUtils.formatJavaType($rel.CollectionType)<$importUtils.formatJavaType($rel.TargetEntity.ClientClassName)> get${stringUtils.capitalized($rel.Name)}() {
+#end
+        if(objectContext != null) {
+            objectContext.prepareForAccess(this, "${rel.Name}", true);
+        } else if (this.$rel.Name == null) {
+#if ( ${rel.CollectionType} == "java.util.Map")
+        	throw new RuntimeException("Map relationships cannot be accessed for transient objects");
+#else
+        	this.$rel.Name = new PersistentObjectList(this, "${rel.Name}");
+#end
+		}
+
+        return ${rel.Name};
+    }
+#if ( ! $rel.ReadOnly )
+#if ( ${rel.CollectionType} == "java.util.Map")
+	public void addTo${stringUtils.capitalized($rel.Name)}($importUtils.formatJavaType(${rel.TargetEntity.ClientClassName}) object) {
+        if(objectContext != null) {
+            objectContext.prepareForAccess(this, "${rel.Name}", true);
+        } else if (this.$rel.Name == null) {
+        	throw new RuntimeException("Map relationships cannot be accessed for transient objects");        
+        }
+
+        this.${rel.Name}.put(getMapKey("${rel.Name}", object), object);
+    }
+    public void removeFrom${stringUtils.capitalized($rel.Name)}($importUtils.formatJavaType(${rel.TargetEntity.ClientClassName}) object) {
+        if(objectContext != null) {
+            objectContext.prepareForAccess(this, "${rel.Name}", true);
+        } else if (this.$rel.Name == null) {
+        	throw new RuntimeException("Map relationships cannot be accessed for transient objects");        
+        }
+
+        this.${rel.Name}.remove(getMapKey("${rel.Name}", object));
+    }
+#else
+    public void addTo${stringUtils.capitalized($rel.Name)}($importUtils.formatJavaType(${rel.TargetEntity.ClientClassName}) object) {
+        if(objectContext != null) {
+            objectContext.prepareForAccess(this, "${rel.Name}", true);
+        } else if (this.$rel.Name == null) {
+        	this.$rel.Name = new PersistentObjectList(this, "${rel.Name}");
+		}
+
+        this.${rel.Name}.add(object);
+    }
+    public void removeFrom${stringUtils.capitalized($rel.Name)}($importUtils.formatJavaType(${rel.TargetEntity.ClientClassName}) object) {
+        if(objectContext != null) {
+            objectContext.prepareForAccess(this, "${rel.Name}", true);
+        } else if (this.$rel.Name == null) {
+        	this.$rel.Name = new PersistentObjectList(this, "${rel.Name}");
+		}
+
+        this.${rel.Name}.remove(object);
+    }
+#end
+#end
+#else
+    public $importUtils.formatJavaType(${rel.TargetEntity.ClientClassName}) get${stringUtils.capitalized($rel.Name)}() {
+        if(objectContext != null) {
+            objectContext.prepareForAccess(this, "${rel.Name}", true);
+        } else if (this.$rel.Name == null) {
+        	this.$rel.Name = new PersistentObjectHolder(this, "$rel.Name");
+		}
+
+        return ($importUtils.formatJavaType(${rel.TargetEntity.ClientClassName})) ${rel.Name}.getValue();
+    }
+#if ( !${object.isReadOnly()} && !$rel.ReadOnly )
+    public void set${stringUtils.capitalized($rel.Name)}(${importUtils.formatJavaType($rel.TargetEntity.ClientClassName)} $stringUtils.formatVariableName(${rel.Name})) {
+        if(objectContext != null) {
+            objectContext.prepareForAccess(this, "${rel.Name}", true);
+        } else if (this.$rel.Name == null) {
+        	this.$rel.Name = new PersistentObjectHolder(this, "$rel.Name");
+		}
+
+        // note how we notify ObjectContext of change BEFORE the object is actually
+        // changed... this is needed to take a valid current snapshot
+        Object oldValue = this.${rel.Name}.getValueDirectly();
+        if (objectContext != null) {
+        	objectContext.propertyChanged(this, "$rel.Name", oldValue, $stringUtils.formatVariableName(${rel.Name}));
+        }
+        
+        this.${stringUtils.formatVariableName($rel.Name)}.setValue(${stringUtils.formatVariableName($rel.Name)});
+    }
+#end
+#end
+
+#end
+##callback methods
+#foreach( $cbname in ${entityUtils.callbackNames})
+    protected abstract void ${cbname}();
+
+#end
+}