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
+}

Reply via email to