You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2016/10/02 18:31:32 UTC

[3/3] cayenne git commit: CAY-2116 Split schema synchronization code in a separate module

CAY-2116 Split schema synchronization code in a separate module

Refactoring: Sane name builder API


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

Branch: refs/heads/master
Commit: 38e4e7fda64c3793f3fd4fe6c0ca8dc29211e1f5
Parents: 827056e
Author: Andrus Adamchik <an...@objectstyle.com>
Authored: Sun Oct 2 16:04:12 2016 +0300
Committer: Andrus Adamchik <an...@objectstyle.com>
Committed: Sun Oct 2 21:27:04 2016 +0300

----------------------------------------------------------------------
 .../dbsync/merge/CreateTableToModel.java        |  22 +-
 .../dbsync/merge/DropRelationshipToDb.java      |   2 +-
 .../dbsync/merge/EntityMergeSupport.java        |  20 +-
 .../dbsync/naming/CallbackNameBuilder.java      |  74 +++++
 .../dbsync/naming/DeduplicationVisitor.java     | 277 +++++++++++++++++++
 .../dbsync/naming/DefaultBaseNameVisitor.java   | 122 ++++++++
 .../dbsync/naming/DuplicateNameResolver.java    |  68 -----
 .../cayenne/dbsync/naming/NameBuilder.java      |  84 ++++++
 .../cayenne/dbsync/naming/NameChecker.java      |  36 ---
 .../cayenne/dbsync/naming/NameCheckers.java     | 223 ---------------
 .../apache/cayenne/dbsync/naming/NameUtil.java  |  65 +++++
 .../dbsync/naming/NormalizationVisitor.java     | 123 ++++++++
 .../cayenne/dbsync/reverse/db/DbLoader.java     |  47 ++--
 .../reverse/db/DbRelationshipDetected.java      |  53 ++++
 .../reverse/db/ManyToManyCandidateEntity.java   |  11 +-
 .../cayenne/dbsync/naming/NameBuilderTest.java  | 272 ++++++++++++++++++
 .../cayenne/dbsync/naming/NameCheckersTest.java | 204 --------------
 .../apache/cayenne/map/CallbackDescriptor.java  |   2 +-
 .../cayenne/map/DbRelationshipDetected.java     |  48 ----
 .../modeler/action/CreateAttributeAction.java   | 106 +++----
 .../action/CreateCallbackMethodAction.java      |  47 ++--
 .../modeler/action/CreateDataMapAction.java     |  13 +-
 .../modeler/action/CreateDbEntityAction.java    | 116 ++++----
 .../modeler/action/CreateEmbeddableAction.java  |  13 +-
 .../modeler/action/CreateNodeAction.java        | 148 +++++-----
 .../modeler/action/CreateObjEntityAction.java   |  65 ++---
 .../modeler/action/CreateProcedureAction.java   |  89 +++---
 .../action/CreateProcedureParameterAction.java  | 102 +++----
 .../action/CreateRelationshipAction.java        | 217 ++++++++-------
 .../modeler/action/ImportDataMapAction.java     | 139 +++++-----
 .../modeler/action/ImportEOModelAction.java     |  38 +--
 .../modeler/action/NewProjectAction.java        |  27 +-
 .../cayenne/modeler/action/PasteAction.java     | 189 ++++++++-----
 .../action/ReverseEngineeringAction.java        |  24 +-
 .../dialog/ResolveDbRelationshipDialog.java     | 127 +++------
 .../modeler/dialog/db/DbLoaderHelper.java       |  43 ++-
 .../cayenne/modeler/dialog/query/QueryType.java |  19 +-
 .../cayenne/wocompat/EOModelProcessor.java      |  11 +-
 38 files changed, 1887 insertions(+), 1399 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/38e4e7fd/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/CreateTableToModel.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/CreateTableToModel.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/CreateTableToModel.java
index a8483b9..ba93d2c 100644
--- a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/CreateTableToModel.java
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/CreateTableToModel.java
@@ -19,8 +19,7 @@
 package org.apache.cayenne.dbsync.merge;
 
 import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
-import org.apache.cayenne.dbsync.naming.DuplicateNameResolver;
-import org.apache.cayenne.dbsync.naming.NameCheckers;
+import org.apache.cayenne.dbsync.naming.NameBuilder;
 import org.apache.cayenne.dbsync.reverse.naming.DefaultObjectNameGenerator;
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.DbEntity;
@@ -62,26 +61,21 @@ public class CreateTableToModel extends AbstractToModelToken.Entity {
 
         // create a ObjEntity
 
-        // TODO: proper name generator must be injected
+        // TODO: name generator must be injected...
+        // TODO: should we use DbEntity name as a basis instead of generic name like "ObjEntity1"?
+        String baseName = new DefaultObjectNameGenerator().createObjEntityName(dbEntity);
 
-        String objEntityName = new DefaultObjectNameGenerator().createObjEntityName(dbEntity);
-        objEntityName = DuplicateNameResolver.resolve(NameCheckers.objEntity, dbEntity.getDataMap(), objEntityName);
+        ObjEntity objEntity = new ObjEntity();
 
-        // this loop will terminate even if no valid name is found
-        // to prevent loader from looping forever (though such case is very unlikely)
-        String baseName = objEntityName;
-        for (int i = 1; i < 1000 && map.getObjEntity(objEntityName) != null; i++) {
-            objEntityName = baseName + i;
-        }
-
-        ObjEntity objEntity = new ObjEntity(objEntityName);
+        String name = NameBuilder.builder(objEntity, dbEntity.getDataMap()).baseName(baseName).name();
+        objEntity.setName(name);
         objEntity.setDbEntity(getEntity());
 
         // try to find a class name for the ObjEntity
         String className = objEntityClassName;
         if (className == null) {
             // we should generate a className based on the objEntityName
-            className = map.getNameWithDefaultPackage(objEntityName);
+            className = map.getNameWithDefaultPackage(name);
         }
 
         objEntity.setClassName(className);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38e4e7fd/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DropRelationshipToDb.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DropRelationshipToDb.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DropRelationshipToDb.java
index 3c7dd82..1579b25 100644
--- a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DropRelationshipToDb.java
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DropRelationshipToDb.java
@@ -23,7 +23,7 @@ import org.apache.cayenne.dba.QuotingStrategy;
 import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.DbRelationshipDetected;
+import org.apache.cayenne.dbsync.reverse.db.DbRelationshipDetected;
 
 import java.util.Collections;
 import java.util.List;

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38e4e7fd/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/EntityMergeSupport.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/EntityMergeSupport.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/EntityMergeSupport.java
index c7fc8de..e3692b0 100644
--- a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/EntityMergeSupport.java
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/EntityMergeSupport.java
@@ -20,8 +20,7 @@
 package org.apache.cayenne.dbsync.merge;
 
 import org.apache.cayenne.dba.TypesMapping;
-import org.apache.cayenne.dbsync.naming.DuplicateNameResolver;
-import org.apache.cayenne.dbsync.naming.NameCheckers;
+import org.apache.cayenne.dbsync.naming.NameBuilder;
 import org.apache.cayenne.dbsync.reverse.naming.ObjectNameGenerator;
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.DbAttribute;
@@ -199,10 +198,11 @@ public class EntityMergeSupport {
     }
 
     private boolean createObjRelationship(ObjEntity entity, DbRelationship dr, String targetEntityName) {
-        String relationshipName = nameGenerator.createObjRelationshipName(dr);
-        relationshipName = DuplicateNameResolver.resolve(NameCheckers.objRelationship, entity, relationshipName);
+        ObjRelationship or = new ObjRelationship();
+        or.setName(NameBuilder.builder(or, entity)
+                .baseName(nameGenerator.createObjRelationshipName(dr))
+                .name());
 
-        ObjRelationship or = new ObjRelationship(relationshipName);
         or.addDbRelationship(dr);
         Map<String, ObjEntity> objEntities = entity.getDataMap().getSubclassesForObjEntity(entity);
 
@@ -274,8 +274,11 @@ public class EntityMergeSupport {
     }
 
     private void addMissingAttribute(ObjEntity entity, DbAttribute da) {
-        String attrName = DuplicateNameResolver.resolve(NameCheckers.objAttribute, entity,
-                nameGenerator.createObjAttributeName(da));
+        ObjAttribute oa = new ObjAttribute();
+        oa.setName(NameBuilder.builder(oa, entity)
+                .baseName(nameGenerator.createObjAttributeName(da))
+                .name());
+        oa.setEntity(entity);
 
         String type = TypesMapping.getJavaBySqlType(da.getType());
         if (usePrimitives) {
@@ -284,8 +287,7 @@ public class EntityMergeSupport {
                 type = primitive;
             }
         }
-
-        ObjAttribute oa = new ObjAttribute(attrName, type, entity);
+        oa.setType(type);
         oa.setDbAttributePath(da.getName());
         entity.addAttribute(oa);
         fireAttributeAdded(oa);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38e4e7fd/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/CallbackNameBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/CallbackNameBuilder.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/CallbackNameBuilder.java
new file mode 100644
index 0000000..655841c
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/CallbackNameBuilder.java
@@ -0,0 +1,74 @@
+/*
+ *    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.dbsync.naming;
+
+import org.apache.cayenne.configuration.ConfigurationNode;
+import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
+import org.apache.cayenne.map.ObjEntity;
+
+/**
+ * @since 4.0
+ */
+// TODO: fold CallbackMethod to org.apache.cayenne.map package and make it a ConfigurationNode
+// then we can get rid off this fork...
+class CallbackNameBuilder extends NameBuilder {
+
+    public CallbackNameBuilder() {
+        super(new CallbackNode());
+    }
+
+    @Override
+    public String name() {
+        String baseName = this.baseName != null
+                ? this.baseName
+                : "onEvent";
+
+        return new DeduplicationVisitor(namingContext, baseName, dupesPattern).resolve(new DeduplicationVisitor.Predicate() {
+            @Override
+            public boolean isNameInUse(String name) {
+
+                ObjEntity entity = (ObjEntity) namingContext;
+
+                if (entity.getCallbackMethods().contains(name)) {
+                    return true;
+                }
+
+                if (name.startsWith("get")) {
+                    String conflictingProperty = NameUtil.uncapitalize(name.substring(3));
+
+                    // check if either attribute or relationship name matches...
+                    if (entity.getAttribute(conflictingProperty) != null
+                            || entity.getRelationship(conflictingProperty) != null) {
+                        return true;
+                    }
+                }
+
+                return false;
+            }
+        });
+    }
+
+    static class CallbackNode implements ConfigurationNode {
+
+        @Override
+        public <T> T acceptVisitor(ConfigurationNodeVisitor<T> visitor) {
+            return null;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38e4e7fd/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/DeduplicationVisitor.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/DeduplicationVisitor.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/DeduplicationVisitor.java
new file mode 100644
index 0000000..f809fd3
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/DeduplicationVisitor.java
@@ -0,0 +1,277 @@
+/*
+ *    Licensed to the Apache Software Foundation (ASF) under one
+ *    or more contributor license agreements.  See the NOTICE file
+ *    distributed with this work for additional information
+ *    regarding copyright ownership.  The ASF licenses this file
+ *    to you under the Apache License, Version 2.0 (the
+ *    "License"); you may not use this file except in compliance
+ *    with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an
+ *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *    KIND, either express or implied.  See the License for the
+ *    specific language governing permissions and limitations
+ *    under the License.
+ */
+package org.apache.cayenne.dbsync.naming;
+
+import org.apache.cayenne.configuration.ConfigurationNode;
+import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
+import org.apache.cayenne.configuration.DataChannelDescriptor;
+import org.apache.cayenne.configuration.DataNodeDescriptor;
+import org.apache.cayenne.dbimport.ReverseEngineering;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.Embeddable;
+import org.apache.cayenne.map.EmbeddableAttribute;
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.map.ObjRelationship;
+import org.apache.cayenne.map.Procedure;
+import org.apache.cayenne.map.ProcedureParameter;
+import org.apache.cayenne.map.QueryDescriptor;
+
+import java.util.Objects;
+
+/**
+ * @since 4.0
+ */
+// TODO: swap inner classes for lambdas when we are on java 8
+class DeduplicationVisitor implements ConfigurationNodeVisitor<String> {
+
+    private ConfigurationNode namingContext;
+    private String baseName;
+    private String dupesPattern;
+
+    DeduplicationVisitor(ConfigurationNode context, String baseName, String dupesPattern) {
+        this.namingContext = context;
+        this.baseName = Objects.requireNonNull(baseName);
+        this.dupesPattern = Objects.requireNonNull(dupesPattern);
+    }
+
+    @Override
+    public String visitDataChannelDescriptor(DataChannelDescriptor channelDescriptor) {
+        // DataChannelDescriptor is top-level. No context or naming conflicts are expected...
+        return baseName;
+    }
+
+    @Override
+    public String visitDataNodeDescriptor(DataNodeDescriptor nodeDescriptor) {
+        return resolve(new Predicate() {
+            @Override
+            public boolean isNameInUse(String name) {
+
+                DataChannelDescriptor dataChannelDescriptor = (DataChannelDescriptor) namingContext;
+                for (DataNodeDescriptor dataNodeDescriptor : dataChannelDescriptor.getNodeDescriptors()) {
+                    if (dataNodeDescriptor.getName().equals(name)) {
+                        return true;
+                    }
+                }
+
+                return false;
+            }
+        });
+    }
+
+    @Override
+    public String visitDataMap(DataMap dataMap) {
+        return resolve(new Predicate() {
+            @Override
+            public boolean isNameInUse(String name) {
+
+                // null context is a situation when DataMap is a
+                // top level object of the project
+                if (namingContext == null) {
+                    return false;
+                }
+
+                if (namingContext instanceof DataChannelDescriptor) {
+                    DataChannelDescriptor domain = (DataChannelDescriptor) namingContext;
+                    return domain.getDataMap(name) != null;
+                }
+                return false;
+            }
+        });
+    }
+
+    @Override
+    public String visitObjEntity(ObjEntity entity) {
+        return resolve(new Predicate() {
+            @Override
+            public boolean isNameInUse(String name) {
+                DataMap map = (DataMap) namingContext;
+                return map.getObjEntity(name) != null;
+            }
+        });
+    }
+
+    @Override
+    public String visitDbEntity(DbEntity entity) {
+        return resolve(new Predicate() {
+            @Override
+            public boolean isNameInUse(String name) {
+                DataMap map = (DataMap) namingContext;
+                return map.getDbEntity(name) != null;
+            }
+        });
+    }
+
+    @Override
+    public String visitEmbeddable(Embeddable embeddable) {
+        return resolve(new Predicate() {
+            @Override
+            public boolean isNameInUse(String name) {
+                DataMap map = (DataMap) namingContext;
+                return map.getEmbeddable(map.getNameWithDefaultPackage(name)) != null;
+            }
+        });
+    }
+
+    @Override
+    public String visitEmbeddableAttribute(EmbeddableAttribute attribute) {
+        return resolve(new Predicate() {
+            @Override
+            public boolean isNameInUse(String name) {
+                Embeddable emb = (Embeddable) namingContext;
+                return emb.getAttribute(name) != null;
+            }
+        });
+    }
+
+    @Override
+    public String visitObjAttribute(ObjAttribute attribute) {
+        return resolveObjEntityProperty();
+    }
+
+    @Override
+    public String visitDbAttribute(DbAttribute attribute) {
+        return resolveDbEntityProperty();
+    }
+
+    @Override
+    public String visitObjRelationship(ObjRelationship relationship) {
+        return resolveObjEntityProperty();
+    }
+
+    @Override
+    public String visitDbRelationship(DbRelationship relationship) {
+        return resolveDbEntityProperty();
+    }
+
+    @Override
+    public String visitProcedure(Procedure procedure) {
+        return resolve(new Predicate() {
+            @Override
+            public boolean isNameInUse(String name) {
+                DataMap map = (DataMap) namingContext;
+                return map.getProcedure(name) != null;
+            }
+        });
+    }
+
+    @Override
+    public String visitProcedureParameter(ProcedureParameter parameter) {
+        return resolve(new Predicate() {
+            @Override
+            public boolean isNameInUse(String name) {
+
+                // it doesn't matter if we create a parameter with a duplicate name.. parameters are positional anyway..
+                // still try to use unique names for visual consistency
+
+                Procedure procedure = (Procedure) namingContext;
+                for (ProcedureParameter parameter : procedure.getCallParameters()) {
+                    if (name.equals(parameter.getName())) {
+                        return true;
+                    }
+                }
+
+                return false;
+            }
+        });
+    }
+
+    @Override
+    public String visitQuery(QueryDescriptor query) {
+        return resolve(new Predicate() {
+            @Override
+            public boolean isNameInUse(String name) {
+                DataMap map = (DataMap) namingContext;
+                return map.getQueryDescriptor(name) != null;
+            }
+        });
+    }
+
+    @Override
+    public String visitReverseEngineering(ReverseEngineering reverseEngineering) {
+        return resolve(new Predicate() {
+            @Override
+            public boolean isNameInUse(String name) {
+
+                if (namingContext == null) {
+                    return false;
+                }
+
+                DataChannelDescriptor dataChannelDescriptor = (DataChannelDescriptor) namingContext;
+                for (DataMap dataMap : dataChannelDescriptor.getDataMaps()) {
+                    if (dataMap != null && dataMap.getReverseEngineering() != null &&
+                            dataMap.getReverseEngineering().getName() != null
+                            && dataMap.getReverseEngineering().getName().equals(name)) {
+                        return true;
+                    }
+                }
+                return false;
+            }
+        });
+    }
+
+    String resolve(Predicate nameChecker) {
+        int c = 1;
+        String name = baseName;
+        while (nameChecker.isNameInUse(name)) {
+            name = String.format(dupesPattern, baseName, c++);
+        }
+
+        return name;
+    }
+
+    private String resolveDbEntityProperty() {
+        return resolve(new Predicate() {
+            @Override
+            public boolean isNameInUse(String name) {
+
+                DbEntity entity = (DbEntity) namingContext;
+
+                // check if either attribute or relationship name matches...
+                return entity.getAttribute(name) != null || entity.getRelationship(name) != null;
+            }
+        });
+    }
+
+    private String resolveObjEntityProperty() {
+        return resolve(new Predicate() {
+            @Override
+            public boolean isNameInUse(String name) {
+
+                ObjEntity entity = (ObjEntity) namingContext;
+
+                // check if either attribute or relationship name matches...
+                if (entity.getAttribute(name) != null || entity.getRelationship(name) != null) {
+                    return true;
+                }
+
+                //  check if there's a callback method that shadows attribute getter (unlikely, but still)
+                String conflictingCallback = "get" + NameUtil.capitalize(name);
+                return entity.getCallbackMethods().contains(conflictingCallback);
+            }
+        });
+    }
+
+    interface Predicate {
+        boolean isNameInUse(String name);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38e4e7fd/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/DefaultBaseNameVisitor.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/DefaultBaseNameVisitor.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/DefaultBaseNameVisitor.java
new file mode 100644
index 0000000..ecc17f0
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/DefaultBaseNameVisitor.java
@@ -0,0 +1,122 @@
+/*
+ *    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.dbsync.naming;
+
+import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
+import org.apache.cayenne.configuration.DataChannelDescriptor;
+import org.apache.cayenne.configuration.DataNodeDescriptor;
+import org.apache.cayenne.dbimport.ReverseEngineering;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.Embeddable;
+import org.apache.cayenne.map.EmbeddableAttribute;
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.map.ObjRelationship;
+import org.apache.cayenne.map.Procedure;
+import org.apache.cayenne.map.ProcedureParameter;
+import org.apache.cayenne.map.QueryDescriptor;
+
+/**
+ * @since 4.0
+ */
+class DefaultBaseNameVisitor implements ConfigurationNodeVisitor<String> {
+
+    static final DefaultBaseNameVisitor INSTANCE = new DefaultBaseNameVisitor();
+
+    private DefaultBaseNameVisitor() {
+    }
+
+    @Override
+    public String visitDataChannelDescriptor(DataChannelDescriptor channelDescriptor) {
+        return "project";
+    }
+
+    @Override
+    public String visitDataNodeDescriptor(DataNodeDescriptor nodeDescriptor) {
+        return "datanode";
+    }
+
+    @Override
+    public String visitDataMap(DataMap dataMap) {
+        return "datamap";
+    }
+
+    @Override
+    public String visitObjEntity(ObjEntity entity) {
+        return "ObjEntity";
+    }
+
+    @Override
+    public String visitDbEntity(DbEntity entity) {
+        return "db_entity";
+    }
+
+    @Override
+    public String visitEmbeddable(Embeddable embeddable) {
+        return "Embeddable";
+    }
+
+    @Override
+    public String visitEmbeddableAttribute(EmbeddableAttribute attribute) {
+        return "untitledAttr";
+    }
+
+    @Override
+    public String visitObjAttribute(ObjAttribute attribute) {
+        return "untitledAttr";
+    }
+
+    @Override
+    public String visitDbAttribute(DbAttribute attribute) {
+        return "untitledAttr";
+    }
+
+    @Override
+    public String visitObjRelationship(ObjRelationship relationship) {
+        return "untitledRel";
+    }
+
+    @Override
+    public String visitDbRelationship(DbRelationship relationship) {
+        return "untitledRel";
+    }
+
+    @Override
+    public String visitProcedure(Procedure procedure) {
+        return "procedure";
+    }
+
+    @Override
+    public String visitProcedureParameter(ProcedureParameter parameter) {
+        return "UntitledProcedureParameter";
+    }
+
+    @Override
+    public String visitQuery(QueryDescriptor query) {
+        return "query";
+    }
+
+    @Override
+    public String visitReverseEngineering(ReverseEngineering reverseEngineering) {
+        return "reverseEngineering";
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38e4e7fd/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/DuplicateNameResolver.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/DuplicateNameResolver.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/DuplicateNameResolver.java
deleted file mode 100644
index 3f55d8f..0000000
--- a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/DuplicateNameResolver.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*****************************************************************
- *   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.dbsync.naming;
-
-import org.apache.cayenne.map.DataMap;
-
-/**
- * A generator of unique names for the various model objects.
- *
- * @since 4.0
- */
-public class DuplicateNameResolver {
-
-    private static final String DEFAULT_PATTERN = "%s%d";
-
-    public static String resolve(NameChecker checker) {
-        return resolve(checker, DEFAULT_PATTERN, null, null);
-    }
-
-    public static String resolve(NameChecker checker, Object context) {
-        return resolve(checker, DEFAULT_PATTERN, context, null);
-    }
-
-    public static String resolve(NameChecker checker, Object context, String baseName) {
-        return resolve(checker, DEFAULT_PATTERN, context, baseName);
-    }
-
-    public static String resolve(NameChecker nameChecker, String pattern, Object context, String baseName) {
-
-        if (baseName == null) {
-            baseName = nameChecker.baseName();
-        }
-
-        String resolved = doResolve(nameChecker, pattern, context, baseName);
-
-        // TODO ugly hack with cast... something more OO is in order
-        return (nameChecker == NameCheckers.embeddable)
-                ? ((DataMap) context).getNameWithDefaultPackage(resolved) : resolved;
-    }
-
-
-    private static String doResolve(NameChecker nameChecker, String pattern, Object namingContext, String baseName) {
-        int c = 1;
-        String name = baseName;
-        while (nameChecker.isNameInUse(namingContext, name)) {
-            name = String.format(pattern, baseName, c++);
-        }
-
-        return name;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38e4e7fd/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/NameBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/NameBuilder.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/NameBuilder.java
new file mode 100644
index 0000000..c791cbc
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/NameBuilder.java
@@ -0,0 +1,84 @@
+/*
+ *    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.dbsync.naming;
+
+import org.apache.cayenne.configuration.ConfigurationNode;
+import org.apache.cayenne.map.ObjEntity;
+
+import java.util.Objects;
+
+/**
+ * A
+ *
+ * @since 4.0
+ */
+public class NameBuilder {
+
+    protected ConfigurationNode nodeToName;
+    protected ConfigurationNode namingContext;
+    protected String dupesPattern;
+    protected String baseName;
+
+    protected NameBuilder(ConfigurationNode nodeToName) {
+        this.nodeToName = Objects.requireNonNull(nodeToName);
+        this.dupesPattern = "%s%d";
+    }
+
+    public static NameBuilder builder(ConfigurationNode node) {
+        return new NameBuilder(node);
+    }
+
+    public static NameBuilder builder(ConfigurationNode node, ConfigurationNode namingContext) {
+        return new NameBuilder(node).in(namingContext);
+    }
+
+    /**
+     * A special builder starter for callback methods. Eventually callback methods will be made into ConfigurationNodes,
+     * and we can use regular {@link #builder(ConfigurationNode)} methods to name them.
+     */
+    // TODO: fold CallbackMethod to org.apache.cayenne.map package and make it a ConfigurationNode
+    // then we can use normal API for it... for now have to keep a special one-off method...
+    public static NameBuilder builderForCallbackMethod(ObjEntity namingContext) {
+        return new CallbackNameBuilder().in(namingContext);
+    }
+
+    public NameBuilder in(ConfigurationNode namingContext) {
+        this.namingContext = Objects.requireNonNull(namingContext);
+        return this;
+    }
+
+    public NameBuilder dupesPattern(String dupesPattern) {
+        this.dupesPattern = Objects.requireNonNull(dupesPattern);
+        return this;
+    }
+
+    public NameBuilder baseName(String baseName) {
+        this.baseName = baseName;
+        return this;
+    }
+
+    public String name() {
+        String baseName = this.baseName != null && this.baseName.length() > 0
+                ? this.baseName
+                : nodeToName.acceptVisitor(DefaultBaseNameVisitor.INSTANCE);
+
+        String normalizedBaseName = nodeToName.acceptVisitor(new NormalizationVisitor(baseName));
+        return nodeToName.acceptVisitor(new DeduplicationVisitor(namingContext, normalizedBaseName, dupesPattern));
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38e4e7fd/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/NameChecker.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/NameChecker.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/NameChecker.java
deleted file mode 100644
index ade1ec5..0000000
--- a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/NameChecker.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*****************************************************************
- *   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.dbsync.naming;
-
-/**
- * @since 4.0
- */
-public interface NameChecker {
-
-	/**
-	 * Returns a base default name, like "UntitledEntity", etc.
-	 */
-	String baseName();
-
-	/**
-	 * Checks if the name is already taken by another sibling in the same
-	 * context.
-	 */
-	boolean isNameInUse(Object namingContext, String name);
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38e4e7fd/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/NameCheckers.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/NameCheckers.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/NameCheckers.java
deleted file mode 100644
index fca1734..0000000
--- a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/NameCheckers.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*****************************************************************
- *   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.dbsync.naming;
-
-import org.apache.cayenne.access.DataDomain;
-import org.apache.cayenne.configuration.DataChannelDescriptor;
-import org.apache.cayenne.configuration.DataNodeDescriptor;
-import org.apache.cayenne.map.DataMap;
-import org.apache.cayenne.map.Embeddable;
-import org.apache.cayenne.map.Entity;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.map.Procedure;
-import org.apache.cayenne.map.ProcedureParameter;
-import org.apache.commons.lang.StringUtils;
-
-/**
- * A set of default {@link NameChecker} objects for the known model objects.
- *
- * @since 4.0
- */
-public enum NameCheckers implements NameChecker {
-
-    dataChannelDescriptor("project") {
-        @Override
-        public boolean isNameInUse(Object namingContext, String name) {
-            return false;
-        }
-    },
-
-    dataMap("datamap") {
-        @Override
-        public boolean isNameInUse(Object namingContext, String name) {
-            // null context is a situation when DataMap is a
-            // top level object of the project
-            if (namingContext == null) {
-                return false;
-            }
-
-            if (namingContext instanceof DataDomain) {
-                DataDomain domain = (DataDomain) namingContext;
-                return domain.getDataMap(name) != null;
-            }
-
-            if (namingContext instanceof DataChannelDescriptor) {
-                DataChannelDescriptor domain = (DataChannelDescriptor) namingContext;
-                return domain.getDataMap(name) != null;
-            }
-            return false;
-        }
-    },
-
-    reverseEngineering("reverseEngineering") {
-        @Override
-        public boolean isNameInUse(Object namingContext, String name) {
-            if (namingContext == null) {
-                return false;
-            }
-
-            for (DataMap dataMap : ((DataChannelDescriptor) namingContext).getDataMaps()) {
-                if (dataMap != null && dataMap.getReverseEngineering() != null &&
-                        dataMap.getReverseEngineering().getName() != null && dataMap.getReverseEngineering().getName().equals(name)) {
-                    return true;
-                }
-            }
-            return false;
-        }
-    },
-
-    objEntity("ObjEntity") {
-        @Override
-        public boolean isNameInUse(Object namingContext, String name) {
-            DataMap map = (DataMap) namingContext;
-            return map.getObjEntity(name) != null;
-        }
-    },
-
-    embeddable("Embeddable") {
-        @Override
-        public boolean isNameInUse(Object namingContext, String name) {
-            DataMap map = (DataMap) namingContext;
-            return map.getEmbeddable(map.getNameWithDefaultPackage(name)) != null;
-        }
-    },
-
-    embeddableAttribute("untitledAttr") {
-        @Override
-        public boolean isNameInUse(Object namingContext, String name) {
-            Embeddable emb = (Embeddable) namingContext;
-            return emb.getAttribute(name) != null;
-        }
-    },
-
-    dbEntity("db_entity") {
-        @Override
-        public boolean isNameInUse(Object namingContext, String name) {
-            DataMap map = (DataMap) namingContext;
-            return map.getDbEntity(name) != null;
-        }
-    },
-
-    procedureParameter("UntitledProcedureParameter") {
-        @Override
-        public boolean isNameInUse(Object namingContext, String name) {
-
-            // it doesn't matter if we create a parameter with
-            // a duplicate name.. parameters are positional anyway..
-            // still try to use unique names for visual consistency
-            Procedure procedure = (Procedure) namingContext;
-            for (final ProcedureParameter parameter : procedure
-                    .getCallParameters()) {
-                if (name.equals(parameter.getName())) {
-                    return true;
-                }
-            }
-
-            return false;
-        }
-    },
-
-    procedure("procedure") {
-        @Override
-        public boolean isNameInUse(Object namingContext, String name) {
-            DataMap map = (DataMap) namingContext;
-            return map.getProcedure(name) != null;
-        }
-    },
-
-    query("query") {
-        @Override
-        public boolean isNameInUse(Object namingContext, String name) {
-            DataMap map = (DataMap) namingContext;
-            return map.getQueryDescriptor(name) != null;
-        }
-    },
-
-    objAttribute("untitledAttr") {
-        @Override
-        public boolean isNameInUse(Object namingContext, String name) {
-            return objRelationship.isNameInUse(namingContext, name);
-        }
-    },
-
-    dbAttribute("untitledAttr") {
-        @Override
-        public boolean isNameInUse(Object namingContext, String name) {
-            Entity ent = (Entity) namingContext;
-            return ent.getAttribute(name) != null
-                    || ent.getRelationship(name) != null;
-        }
-    },
-
-    dataNodeDescriptor("datanode") {
-        @Override
-        public boolean isNameInUse(Object namingContext, String name) {
-            DataChannelDescriptor domain = (DataChannelDescriptor) namingContext;
-            for (DataNodeDescriptor dataNodeDescriptor : domain
-                    .getNodeDescriptors()) {
-                if (dataNodeDescriptor.getName().equals(name)) {
-                    return true;
-                }
-            }
-            return false;
-        }
-    },
-
-    objRelationship("untitledRel") {
-        @Override
-        public boolean isNameInUse(Object namingContext, String name) {
-            ObjEntity ent = (ObjEntity) namingContext;
-            return dbAttribute.isNameInUse(namingContext, name)
-                    || ent.getCallbackMethods().contains(
-                    "get" + StringUtils.capitalize(name));
-        }
-    },
-
-    dbRelationship("untitledRel") {
-        @Override
-        public boolean isNameInUse(Object namingContext, String name) {
-            return dbAttribute.isNameInUse(namingContext, name);
-        }
-    },
-
-    objCallbackMethod("ObjCallbackMethod") {
-        @Override
-        public boolean isNameInUse(Object namingContext, String name) {
-            ObjEntity ent = (ObjEntity) namingContext;
-
-            return name.startsWith("get")
-                    && dbAttribute.isNameInUse(namingContext,
-                    StringUtils.uncapitalize(name.substring(3)))
-                    || ent.getCallbackMethods().contains(name);
-        }
-    };
-
-    private final String baseName;
-
-    NameCheckers(String baseName) {
-        this.baseName = baseName;
-    }
-
-    @Override
-    public String baseName() {
-        return baseName;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38e4e7fd/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/NameUtil.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/NameUtil.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/NameUtil.java
new file mode 100644
index 0000000..fb47bcb
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/NameUtil.java
@@ -0,0 +1,65 @@
+/*
+ *    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.dbsync.naming;
+
+/**
+ * @since 4.0
+ */
+final class NameUtil {
+    static String uncapitalize(String string) {
+        int len;
+        if (string == null || (len = string.length()) == 0) {
+            return string;
+        }
+
+        final char firstChar = string.charAt(0);
+        final char newChar = Character.toLowerCase(firstChar);
+        if (firstChar == newChar) {
+            // already capitalized
+            return string;
+        }
+
+        char[] newChars = new char[len];
+        newChars[0] = newChar;
+        string.getChars(1, len, newChars, 1);
+
+        return String.valueOf(newChars);
+    }
+
+    static String capitalize(String string) {
+        int len;
+        if (string == null || (len = string.length()) == 0) {
+            return string;
+        }
+
+        final char firstChar = string.charAt(0);
+        final char newChar = Character.toTitleCase(firstChar);
+        if (firstChar == newChar) {
+            // already capitalized
+            return string;
+        }
+
+        char[] newChars = new char[len];
+        newChars[0] = newChar;
+        string.getChars(1, len, newChars, 1);
+
+        return String.valueOf(newChars);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38e4e7fd/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/NormalizationVisitor.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/NormalizationVisitor.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/NormalizationVisitor.java
new file mode 100644
index 0000000..75d941d
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/naming/NormalizationVisitor.java
@@ -0,0 +1,123 @@
+/*
+ *    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.dbsync.naming;
+
+import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
+import org.apache.cayenne.configuration.DataChannelDescriptor;
+import org.apache.cayenne.configuration.DataNodeDescriptor;
+import org.apache.cayenne.dbimport.ReverseEngineering;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.Embeddable;
+import org.apache.cayenne.map.EmbeddableAttribute;
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.map.ObjRelationship;
+import org.apache.cayenne.map.Procedure;
+import org.apache.cayenne.map.ProcedureParameter;
+import org.apache.cayenne.map.QueryDescriptor;
+
+/**
+ * @since 4.0
+ */
+class NormalizationVisitor implements ConfigurationNodeVisitor<String> {
+
+    private String baseName;
+
+    public NormalizationVisitor(String baseName) {
+        this.baseName = baseName;
+    }
+
+    @Override
+    public String visitDataChannelDescriptor(DataChannelDescriptor channelDescriptor) {
+        return baseName;
+    }
+
+    @Override
+    public String visitDataNodeDescriptor(DataNodeDescriptor nodeDescriptor) {
+        return baseName;
+    }
+
+    @Override
+    public String visitDataMap(DataMap dataMap) {
+        return baseName;
+    }
+
+    @Override
+    public String visitObjEntity(ObjEntity entity) {
+        return NameUtil.capitalize(baseName);
+    }
+
+    @Override
+    public String visitDbEntity(DbEntity entity) {
+        return baseName;
+    }
+
+    @Override
+    public String visitEmbeddable(Embeddable embeddable) {
+        return NameUtil.capitalize(baseName);
+    }
+
+    @Override
+    public String visitEmbeddableAttribute(EmbeddableAttribute attribute) {
+        return NameUtil.uncapitalize(baseName);
+    }
+
+    @Override
+    public String visitObjAttribute(ObjAttribute attribute) {
+        return NameUtil.uncapitalize(baseName);
+    }
+
+    @Override
+    public String visitDbAttribute(DbAttribute attribute) {
+        return baseName;
+    }
+
+    @Override
+    public String visitObjRelationship(ObjRelationship relationship) {
+        return NameUtil.uncapitalize(baseName);
+    }
+
+    @Override
+    public String visitDbRelationship(DbRelationship relationship) {
+        return NameUtil.uncapitalize(baseName);
+    }
+
+    @Override
+    public String visitProcedure(Procedure procedure) {
+        return baseName;
+    }
+
+    @Override
+    public String visitProcedureParameter(ProcedureParameter parameter) {
+        return baseName;
+    }
+
+    @Override
+    public String visitQuery(QueryDescriptor query) {
+        return baseName;
+    }
+
+    @Override
+    public String visitReverseEngineering(ReverseEngineering reverseEngineering) {
+        return baseName;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38e4e7fd/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/db/DbLoader.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/db/DbLoader.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/db/DbLoader.java
index 44b9fb3..ecdfa70 100644
--- a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/db/DbLoader.java
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/db/DbLoader.java
@@ -21,23 +21,21 @@ package org.apache.cayenne.dbsync.reverse.db;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.dba.TypesMapping;
 import org.apache.cayenne.dbsync.merge.EntityMergeSupport;
+import org.apache.cayenne.dbsync.naming.NameBuilder;
 import org.apache.cayenne.dbsync.reverse.filters.CatalogFilter;
 import org.apache.cayenne.dbsync.reverse.filters.FiltersConfig;
 import org.apache.cayenne.dbsync.reverse.filters.SchemaFilter;
 import org.apache.cayenne.dbsync.reverse.filters.TableFilter;
+import org.apache.cayenne.dbsync.reverse.naming.LegacyObjectNameGenerator;
+import org.apache.cayenne.dbsync.reverse.naming.ObjectNameGenerator;
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.DbJoin;
 import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.DbRelationshipDetected;
 import org.apache.cayenne.map.ObjEntity;
 import org.apache.cayenne.map.Procedure;
 import org.apache.cayenne.map.ProcedureParameter;
-import org.apache.cayenne.dbsync.naming.DuplicateNameResolver;
-import org.apache.cayenne.dbsync.reverse.naming.LegacyObjectNameGenerator;
-import org.apache.cayenne.dbsync.naming.NameCheckers;
-import org.apache.cayenne.dbsync.reverse.naming.ObjectNameGenerator;
 import org.apache.cayenne.util.EqualsBuilder;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
@@ -133,10 +131,12 @@ public class DbLoader {
                 continue;
             }
 
-            String objEntityName = DuplicateNameResolver.resolve(NameCheckers.objEntity, map,
-                    nameGenerator.createObjEntityName(dbEntity));
+            ObjEntity objEntity = new ObjEntity();
+            objEntity.setName(NameBuilder
+                    .builder(objEntity, map)
+                    .baseName(nameGenerator.createObjEntityName(dbEntity))
+                    .name());
 
-            ObjEntity objEntity = new ObjEntity(objEntityName);
             objEntity.setDbEntity(dbEntity);
             objEntity.setClassName(config.getGenericClassName() != null ? config.getGenericClassName() : map
                     .getNameWithDefaultPackage(objEntity.getName()));
@@ -315,13 +315,26 @@ public class DbLoader {
             }
 
             // forwardRelationship is a reference from table with primary key
-            DbRelationship forwardRelationship = new DbRelationship(generateName(pkEntity, key, true));
+            DbRelationship forwardRelationship = new DbRelationship();
+            forwardRelationship.setName(NameBuilder
+                    .builder(forwardRelationship, pkEntity)
+                    .baseName(nameGenerator.createDbRelationshipName(key, true))
+                    .name());
+
             forwardRelationship.setSourceEntity(pkEntity);
             forwardRelationship.setTargetEntityName(fkEntity);
 
             // forwardRelationship is a reference from table with foreign key,
             // it is what exactly we load from db
-            DbRelationshipDetected reverseRelationship = new DbRelationshipDetected(generateName(fkEntity, key, false));
+
+            // TODO: dirty and non-transparent... using DbRelationshipDetected for the benefit of the merge package.
+            // This info is available from joins....
+            DbRelationshipDetected reverseRelationship = new DbRelationshipDetected();
+            reverseRelationship.setName(NameBuilder
+                    .builder(reverseRelationship, fkEntity)
+                    .baseName(nameGenerator.createDbRelationshipName(key, false))
+                    .name());
+
             reverseRelationship.setFkName(key.getFKName());
             reverseRelationship.setSourceEntity(fkEntity);
             reverseRelationship.setTargetEntityName(pkEntity);
@@ -336,7 +349,12 @@ public class DbLoader {
                     && fkEntity.getPrimaryKeys().size() == forwardRelationship.getJoins().size();
 
             forwardRelationship.setToMany(!isOneToOne);
-            forwardRelationship.setName(generateName(pkEntity, key, !isOneToOne));
+
+            // TODO: can we avoid resetting the name twice? Do we need a placeholder name above?
+            forwardRelationship.setName(NameBuilder
+                            .builder(forwardRelationship, pkEntity)
+                            .baseName(nameGenerator.createDbRelationshipName(key, !isOneToOne))
+                            .name());
 
             if (delegate.dbRelationshipLoaded(fkEntity, reverseRelationship)) {
                 fkEntity.addRelationship(reverseRelationship);
@@ -358,7 +376,7 @@ public class DbLoader {
     }
 
     private void createAndAppendJoins(Set<ExportedKey> exportedKeys, DbEntity pkEntity, DbEntity fkEntity,
-                                      DbRelationship forwardRelationship, DbRelationshipDetected reverseRelationship) {
+                                      DbRelationship forwardRelationship, DbRelationship reverseRelationship) {
         for (ExportedKey exportedKey : exportedKeys) {
             // Create and append joins
             String pkName = exportedKey.getPKColumnName();
@@ -437,11 +455,6 @@ public class DbLoader {
         LOGGER.info("Skip relation: '" + key + "' because table '" + tableName + "' not found");
     }
 
-    private String generateName(DbEntity entity, ExportedKey key, boolean toMany) {
-        String forwardPreferredName = nameGenerator.createDbRelationshipName(key, toMany);
-        return DuplicateNameResolver.resolve(NameCheckers.dbRelationship, entity, forwardPreferredName);
-    }
-
     private void fireObjEntitiesAddedEvents(Collection<ObjEntity> loadedObjEntities) {
         for (ObjEntity curEntity : loadedObjEntities) {
             // notify delegate

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38e4e7fd/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/db/DbRelationshipDetected.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/db/DbRelationshipDetected.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/db/DbRelationshipDetected.java
new file mode 100644
index 0000000..dfad14f
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/db/DbRelationshipDetected.java
@@ -0,0 +1,53 @@
+/*****************************************************************
+ *   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.dbsync.reverse.db;
+
+
+import org.apache.cayenne.map.DbRelationship;
+
+/**
+ * A subclass of {@link DbRelationship} to hold some extra runtime information.
+ */
+// TODO: dirty ... can we lookup fkName via joins?
+public class DbRelationshipDetected extends DbRelationship {
+
+    private String fkName;
+
+    public DbRelationshipDetected(String uniqueRelName) {
+        super(uniqueRelName);
+    }
+
+    public DbRelationshipDetected() {
+    }
+
+    /**
+     * Get the name of the underlying foreign key. Typically FK_NAME from jdbc metadata.
+     */
+    public String getFkName() {
+        return fkName;
+    }
+
+    /**
+     * Set the name of the underlying foreign key. Typically FK_NAME from jdbc metadata.
+     */
+    public void setFkName(String fkName) {
+        this.fkName = fkName;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38e4e7fd/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/db/ManyToManyCandidateEntity.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/db/ManyToManyCandidateEntity.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/db/ManyToManyCandidateEntity.java
index 85af6a4..7f2f815 100644
--- a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/db/ManyToManyCandidateEntity.java
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/db/ManyToManyCandidateEntity.java
@@ -18,12 +18,11 @@
  ****************************************************************/
 package org.apache.cayenne.dbsync.reverse.db;
 
+import org.apache.cayenne.dbsync.naming.NameBuilder;
+import org.apache.cayenne.dbsync.reverse.naming.ObjectNameGenerator;
 import org.apache.cayenne.map.DbRelationship;
 import org.apache.cayenne.map.ObjEntity;
 import org.apache.cayenne.map.ObjRelationship;
-import org.apache.cayenne.dbsync.naming.DuplicateNameResolver;
-import org.apache.cayenne.dbsync.naming.NameCheckers;
-import org.apache.cayenne.dbsync.reverse.naming.ObjectNameGenerator;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
@@ -113,8 +112,10 @@ class ManyToManyCandidateEntity {
                 (short) 1);
 
         ObjRelationship newRelationship = new ObjRelationship();
-        newRelationship.setName(DuplicateNameResolver.resolve(NameCheckers.objRelationship, srcEntity,
-                nameGenerator.createDbRelationshipName(key, true)));
+        newRelationship.setName(NameBuilder
+                .builder(newRelationship, srcEntity)
+                .baseName(nameGenerator.createDbRelationshipName(key, true))
+                .name());
 
         newRelationship.setSourceEntity(srcEntity);
         newRelationship.setTargetEntityName(dstEntity);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38e4e7fd/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/naming/NameBuilderTest.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/naming/NameBuilderTest.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/naming/NameBuilderTest.java
new file mode 100644
index 0000000..8aa21c3
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/naming/NameBuilderTest.java
@@ -0,0 +1,272 @@
+/*****************************************************************
+ *   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.dbsync.naming;
+
+import org.apache.cayenne.configuration.DataChannelDescriptor;
+import org.apache.cayenne.configuration.DataNodeDescriptor;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.Embeddable;
+import org.apache.cayenne.map.EmbeddableAttribute;
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.map.ObjRelationship;
+import org.apache.cayenne.map.Procedure;
+import org.apache.cayenne.map.ProcedureParameter;
+import org.apache.cayenne.map.QueryDescriptor;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class NameBuilderTest {
+
+    @Test
+    public void testName_Root() {
+        assertEquals("project", NameBuilder.builder(new DataChannelDescriptor()).name());
+    }
+
+    @Test
+    public void testName_DataChannelDescriptorContext() throws Exception {
+        DataChannelDescriptor descriptor = new DataChannelDescriptor();
+
+        DataMap m0 = new DataMap();
+        m0.setName(NameBuilder.builder(m0).in(descriptor).name());
+        assertEquals("datamap", m0.getName());
+        descriptor.getDataMaps().add(m0);
+
+        DataMap m1 = new DataMap();
+        m1.setName(NameBuilder.builder(m1).in(descriptor).name());
+        assertEquals("datamap1", m1.getName());
+        descriptor.getDataMaps().add(m1);
+
+        DataNodeDescriptor nd0 = new DataNodeDescriptor();
+        nd0.setName(NameBuilder.builder(nd0).in(descriptor).name());
+        assertEquals("datanode", nd0.getName());
+        descriptor.getNodeDescriptors().add(nd0);
+
+        DataNodeDescriptor nd1 = new DataNodeDescriptor();
+        nd1.setName(NameBuilder.builder(nd1).in(descriptor).name());
+        assertEquals("datanode1", nd1.getName());
+        descriptor.getNodeDescriptors().add(nd1);
+    }
+
+
+    @Test
+    public void testName_DataMapContext() {
+        DataMap map = new DataMap();
+        map.setDefaultPackage("com.foo");
+
+        DbEntity de0 = new DbEntity();
+        de0.setName(NameBuilder.builder(de0).in(map).name());
+        assertEquals("db_entity", de0.getName());
+        map.addDbEntity(de0);
+
+        DbEntity de1 = new DbEntity();
+        de1.setName(NameBuilder.builder(de1).in(map).name());
+        assertEquals("db_entity1", de1.getName());
+        map.addDbEntity(de1);
+
+        ObjEntity oe0 = new ObjEntity();
+        oe0.setName(NameBuilder.builder(oe0).in(map).name());
+        assertEquals("ObjEntity", oe0.getName());
+        map.addObjEntity(oe0);
+
+        ObjEntity oe1 = new ObjEntity();
+        oe1.setName(NameBuilder.builder(oe1).in(map).name());
+        assertEquals("ObjEntity1", oe1.getName());
+        map.addObjEntity(oe1);
+
+        ObjEntity oe2 = new ObjEntity();
+        oe2.setName(NameBuilder.builder(oe0).in(map).baseName("db_entity").name());
+        assertEquals("Should not conflict with similarly named DbEntity", "Db_entity", oe2.getName());
+        map.addObjEntity(oe2);
+
+        Procedure p0 = new Procedure();
+        p0.setName(NameBuilder.builder(p0).in(map).name());
+        assertEquals("procedure", p0.getName());
+        map.addProcedure(p0);
+
+        Procedure p1 = new Procedure();
+        p1.setName(NameBuilder.builder(p1).in(map).name());
+        assertEquals("procedure1", p1.getName());
+        map.addProcedure(p1);
+
+        Procedure p2 = new Procedure();
+        p2.setName(NameBuilder.builder(p1).in(map).baseName("db_enity").name());
+        assertEquals("Should not conflict with similarly named DbEntity", "db_enity", p2.getName());
+        map.addProcedure(p2);
+
+        QueryDescriptor q0 = QueryDescriptor.selectQueryDescriptor();
+        q0.setName(NameBuilder.builder(q0).in(map).name());
+        assertEquals("query", q0.getName());
+        map.addQueryDescriptor(q0);
+
+        QueryDescriptor q1 = QueryDescriptor.ejbqlQueryDescriptor();
+        q1.setName(NameBuilder.builder(q1).in(map).name());
+        assertEquals("query1", q1.getName());
+        map.addQueryDescriptor(q1);
+
+        Embeddable e0 = new Embeddable();
+        e0.setClassName("com.foo." + NameBuilder.builder(e0).in(map).name());
+        assertEquals("com.foo.Embeddable", e0.getClassName());
+        map.addEmbeddable(e0);
+
+        Embeddable e1 = new Embeddable();
+        e1.setClassName("com.foo." + NameBuilder.builder(e1).in(map).name());
+        assertEquals("com.foo.Embeddable1", e1.getClassName());
+        map.addEmbeddable(e1);
+    }
+
+    @Test
+    public void testName_ObjEntityContext() {
+
+        ObjEntity entity = new ObjEntity();
+
+        entity.getCallbackMap().getPostAdd().addCallbackMethod("getMe");
+
+        ObjAttribute a0 = new ObjAttribute();
+        String na0 = NameBuilder.builder(a0).in(entity).name();
+        assertEquals("untitledAttr", na0);
+        a0.setName(na0);
+        entity.addAttribute(a0);
+
+        ObjAttribute a1 = new ObjAttribute();
+        String na1 = NameBuilder.builder(a1).in(entity).name();
+        assertEquals("untitledAttr1", na1);
+        a1.setName(na1);
+        entity.addAttribute(a1);
+
+        ObjAttribute a2 = new ObjAttribute();
+        String na2 = NameBuilder.builder(a2).in(entity).baseName("me").name();
+        assertEquals("Conflict with callback method was not detected", "me1", na2);
+        a2.setName(na2);
+        entity.addAttribute(a2);
+
+        ObjRelationship r0 = new ObjRelationship();
+        String nr0 = NameBuilder.builder(r0).in(entity).name();
+        assertEquals("untitledRel", nr0);
+        r0.setName(nr0);
+        entity.addRelationship(r0);
+
+        ObjRelationship r1 = new ObjRelationship();
+        String nr1 = NameBuilder.builder(r1).in(entity).name();
+        assertEquals("untitledRel1", nr1);
+        r1.setName(nr1);
+        entity.addRelationship(r1);
+    }
+
+    @Test
+    public void testName_DbEntityContext() {
+        DbEntity entity = new DbEntity();
+
+        DbAttribute a0 = new DbAttribute();
+        String na0 = NameBuilder.builder(a0).in(entity).name();
+        assertEquals("untitledAttr", na0);
+        a0.setName(na0);
+        entity.addAttribute(a0);
+
+        DbAttribute a1 = new DbAttribute();
+        String na1 = NameBuilder.builder(a1).in(entity).name();
+        assertEquals("untitledAttr1", na1);
+        a1.setName(na1);
+        entity.addAttribute(a1);
+
+        DbRelationship r0 = new DbRelationship();
+        String nr0 = NameBuilder.builder(r0).in(entity).name();
+        assertEquals("untitledRel", nr0);
+        r0.setName(nr0);
+        entity.addRelationship(r0);
+
+        DbRelationship r1 = new DbRelationship();
+        String nr1 = NameBuilder.builder(r1).in(entity).name();
+        assertEquals("untitledRel1", nr1);
+        r1.setName(nr1);
+        entity.addRelationship(r1);
+    }
+
+    @Test
+    public void testName_ProcedureContext() {
+        Procedure procedure = new Procedure();
+
+        ProcedureParameter p0 = new ProcedureParameter();
+        p0.setName(NameBuilder.builder(p0).in(procedure).name());
+        assertEquals("UntitledProcedureParameter", p0.getName());
+        procedure.addCallParameter(p0);
+
+        ProcedureParameter p1 = new ProcedureParameter();
+        p1.setName(NameBuilder.builder(p1).in(procedure).name());
+        assertEquals("UntitledProcedureParameter1", p1.getName());
+        procedure.addCallParameter(p1);
+    }
+
+    @Test
+    public void testName_EmbeddableContext() {
+        Embeddable embeddable = new Embeddable();
+
+        EmbeddableAttribute ea0 = new EmbeddableAttribute();
+        ea0.setName(NameBuilder.builder(ea0).in(embeddable).name());
+        assertEquals("untitledAttr", ea0.getName());
+        embeddable.addAttribute(ea0);
+
+        EmbeddableAttribute ea1 = new EmbeddableAttribute();
+        ea1.setName(NameBuilder.builder(ea1).in(embeddable).name());
+        assertEquals("untitledAttr1", ea1.getName());
+        embeddable.addAttribute(ea1);
+    }
+
+    @Test
+    public void testName_UncapitalizeAttributeNames() throws Exception {
+
+        ObjEntity entity = new ObjEntity();
+
+        ObjAttribute a0 = new ObjAttribute();
+        String na0 = NameBuilder.builder(a0).in(entity).baseName("myName").name();
+        assertEquals("myName", na0);
+        a0.setName(na0);
+        entity.addAttribute(a0);
+
+        ObjAttribute a1 = new ObjAttribute();
+        String na1 = NameBuilder.builder(a1).in(entity).baseName("MyName").name();
+        assertEquals("myName1", na1);
+        a1.setName(na1);
+        entity.addAttribute(a1);
+    }
+
+    @Test
+    public void testName_Callbacks_ObjEntityContext() {
+
+        ObjEntity entity = new ObjEntity();
+
+        String c0 = NameBuilder.builderForCallbackMethod(entity).name();
+        assertEquals("onEvent", c0);
+        entity.getCallbackMap().getPostAdd().addCallbackMethod(c0);
+
+        String c1 = NameBuilder.builderForCallbackMethod(entity).name();
+        assertEquals("onEvent1", c1);
+        entity.getCallbackMap().getPostAdd().addCallbackMethod(c1);
+
+        entity.addAttribute(new ObjAttribute("untitledAttr"));
+
+        String c3 = NameBuilder.builderForCallbackMethod(entity).baseName("getUntitledAttr").name();
+        assertEquals("getUntitledAttr1", c3);
+        entity.getCallbackMap().getPostAdd().addCallbackMethod(c3);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38e4e7fd/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/naming/NameCheckersTest.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/naming/NameCheckersTest.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/naming/NameCheckersTest.java
deleted file mode 100644
index 8131160..0000000
--- a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/naming/NameCheckersTest.java
+++ /dev/null
@@ -1,204 +0,0 @@
-/*****************************************************************
- *   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.dbsync.naming;
-
-import org.apache.cayenne.access.DataDomain;
-import org.apache.cayenne.configuration.DataChannelDescriptor;
-import org.apache.cayenne.configuration.DataNodeDescriptor;
-import org.apache.cayenne.map.DataMap;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.Embeddable;
-import org.apache.cayenne.map.EmbeddableAttribute;
-import org.apache.cayenne.map.ObjAttribute;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.map.ObjRelationship;
-import org.apache.cayenne.map.Procedure;
-import org.apache.cayenne.map.ProcedureParameter;
-import org.apache.cayenne.map.QueryDescriptor;
-import org.junit.Assert;
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-public class NameCheckersTest {
-
-    @Test
-    public void testObjEntityAttributes() throws Exception {
-        NameCheckers maker = NameCheckers.objAttribute;
-        ObjEntity namingContainer = new ObjEntity();
-
-        String baseName = maker.baseName();
-        String name = DuplicateNameResolver.resolve(maker, namingContainer);
-        assertEquals(baseName, name);
-        namingContainer.addAttribute(new ObjAttribute(name));
-
-        name = DuplicateNameResolver.resolve(maker, namingContainer);
-        assertEquals(baseName + "1", name);
-        namingContainer.addAttribute(new ObjAttribute(name));
-
-        name = DuplicateNameResolver.resolve(maker, namingContainer);
-        assertEquals(baseName + "2", name);
-        namingContainer.addAttribute(new ObjAttribute(name));
-
-        name = DuplicateNameResolver.resolve(maker, namingContainer);
-        assertEquals(baseName + "3", name);
-        namingContainer.addAttribute(new ObjAttribute(name));
-
-        maker = NameCheckers.objRelationship;
-        baseName = maker.baseName();
-        name = DuplicateNameResolver.resolve(maker, namingContainer);
-        assertEquals(baseName, name);
-        namingContainer.addRelationship(new ObjRelationship(name));
-
-        name = DuplicateNameResolver.resolve(maker, namingContainer);
-        assertEquals(baseName + "1", name);
-        namingContainer.addRelationship(new ObjRelationship(name));
-
-        maker = NameCheckers.objCallbackMethod;
-        baseName = maker.baseName();
-        name = DuplicateNameResolver.resolve(maker, namingContainer);
-        assertEquals(baseName, name);
-        namingContainer.addRelationship(new ObjRelationship(name));
-    }
-
-    @Test
-    public void testEntity () {
-        DataMap map = new DataMap();
-
-        map.addDbEntity(new DbEntity("name"));
-        checkNameAndOther(map, NameCheckers.dbEntity, "name");
-
-        map.addObjEntity(new ObjEntity("name"));
-        checkNameAndOther(map, NameCheckers.objEntity, "name");
-
-        map.addProcedure(new Procedure("name"));
-        checkNameAndOther(map, NameCheckers.procedure, "name");
-
-        QueryDescriptor query = QueryDescriptor.selectQueryDescriptor();
-        query.setName("name");
-        map.addQueryDescriptor(query);
-        checkNameAndOther(map, NameCheckers.query, "name");
-    }
-
-    @Test
-    public void testProject() throws Exception {
-        assertFalse(NameCheckers.dataChannelDescriptor.isNameInUse(null, null));
-    }
-
-    @Test
-    public void testDbEntity() throws Exception {
-        DbEntity dbEntity = new DbEntity();
-
-        dbEntity.addRelationship(new DbRelationship("name"));
-        checkNameAndOther(dbEntity, NameCheckers.dbRelationship, "name");
-    }
-
-    @Test
-    public void testProcedureAttr() throws Exception {
-        Procedure procedure = new Procedure();
-
-        procedure.addCallParameter(new ProcedureParameter("name"));
-        checkNameAndOther(procedure, NameCheckers.procedureParameter, "name");
-    }
-
-    @Test
-    public void testEmbeddableAttr() throws Exception {
-        Embeddable embeddable = new Embeddable();
-
-        embeddable.addAttribute(new EmbeddableAttribute("name"));
-        checkNameAndOther(embeddable, NameCheckers.embeddableAttribute, "name");
-    }
-
-    @Test
-    public void testDatanode() throws Exception {
-        DataChannelDescriptor descriptor = new DataChannelDescriptor();
-
-        descriptor.getDataMaps().add(new DataMap("name"));
-        checkNameAndOther(descriptor, NameCheckers.dataMap, "name");
-
-        descriptor.getNodeDescriptors().add(new DataNodeDescriptor("name"));
-        checkNameAndOther(descriptor, NameCheckers.dataNodeDescriptor, "name");
-    }
-
-    @Test
-    public void testDataMap() throws Exception {
-        DataDomain dataDomain = new DataDomain("name");
-
-        dataDomain.addDataMap(new DataMap("name"));
-        checkNameAndOther(dataDomain, NameCheckers.dataMap, "name");
-
-        assertFalse(NameCheckers.dataMap.isNameInUse(null, "name"));
-        assertFalse(NameCheckers.dataMap.isNameInUse(1, "name"));
-    }
-
-    private void checkNameAndOther(Object namingContainer, NameCheckers maker, String newName) {
-        assertTrue(maker.isNameInUse(namingContainer, newName));
-        assertEquals(newName + "1", DuplicateNameResolver.resolve(maker,namingContainer, newName));
-        assertEquals("other" + newName, DuplicateNameResolver.resolve(maker,namingContainer, "other" + newName));
-    }
-
-    @Test
-    public void testOverlappingAttributeAndCallbackNames() throws Exception {
-        ObjEntity namingContainer = new ObjEntity();
-
-        namingContainer.addAttribute(new ObjAttribute("myName"));
-        assertEquals("getMyName1", DuplicateNameResolver.resolve(NameCheckers.objCallbackMethod, namingContainer, "getMyName"));
-
-        namingContainer.getCallbackMap().getPostAdd().addCallbackMethod("getSecondName");
-        assertEquals("SecondName1", DuplicateNameResolver.resolve(NameCheckers.objAttribute, namingContainer, "SecondName"));
-        assertEquals("secondName1", DuplicateNameResolver.resolve(NameCheckers.objAttribute, namingContainer, "secondName"));
-        assertEquals("SecondName1", DuplicateNameResolver.resolve(NameCheckers.objRelationship, namingContainer, "SecondName"));
-        assertEquals("secondName1", DuplicateNameResolver.resolve(NameCheckers.objRelationship, namingContainer, "secondName"));
-    }
-
-    @Test
-    public void testAttributeDifferentInFirstLetterCases() throws Exception {
-        ObjEntity namingContainer = new ObjEntity();
-
-        namingContainer.addAttribute(new ObjAttribute("myName"));
-        Assert.assertTrue(NameCheckers.objAttribute.isNameInUse(namingContainer, "myName"));
-        Assert.assertFalse(NameCheckers.objAttribute.isNameInUse(namingContainer, "MyName"));
-
-        namingContainer.getCallbackMap().getPostAdd().addCallbackMethod("getSecondName");
-        assertEquals("SecondName1", DuplicateNameResolver.resolve(NameCheckers.objAttribute, namingContainer, "SecondName"));
-        assertEquals("secondName1", DuplicateNameResolver.resolve(NameCheckers.objAttribute, namingContainer, "secondName"));
-    }
-
-    @Test
-    public void testEmbeddable() {
-        DataMap map = new DataMap();
-
-        map.addEmbeddable(new Embeddable("name"));
-        Assert.assertTrue(NameCheckers.embeddable.isNameInUse(map, "name"));
-        assertEquals("name1", DuplicateNameResolver.resolve(NameCheckers.embeddable, map, "name"));
-        Assert.assertFalse(NameCheckers.embeddable.isNameInUse(map, "other-name"));
-
-        map.setDefaultPackage("package");
-        Assert.assertFalse(NameCheckers.embeddable.isNameInUse(map, "name"));
-        assertEquals("package.name", DuplicateNameResolver.resolve(NameCheckers.embeddable, map, "name"));
-        map.addEmbeddable(new Embeddable("package.name"));
-
-        Assert.assertTrue(NameCheckers.embeddable.isNameInUse(map, "name"));
-        assertEquals("package.name1", DuplicateNameResolver.resolve(NameCheckers.embeddable, map, "name"));
-        Assert.assertFalse(NameCheckers.embeddable.isNameInUse(map, "other-name"));
-    }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38e4e7fd/cayenne-server/src/main/java/org/apache/cayenne/map/CallbackDescriptor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/map/CallbackDescriptor.java b/cayenne-server/src/main/java/org/apache/cayenne/map/CallbackDescriptor.java
index d76f38c..c1a75f1 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/map/CallbackDescriptor.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/map/CallbackDescriptor.java
@@ -74,7 +74,7 @@ public class CallbackDescriptor implements Serializable {
     }
 
     /**
-     * moves specified callback method to the specified position
+     * Moves specified callback method to the specified position
      * 
      * @param callbackMethod callbacm method name (should exist)
      * @param destinationIndex destinationi index (should be valid)

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38e4e7fd/cayenne-server/src/main/java/org/apache/cayenne/map/DbRelationshipDetected.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/map/DbRelationshipDetected.java b/cayenne-server/src/main/java/org/apache/cayenne/map/DbRelationshipDetected.java
deleted file mode 100644
index 8a610e3..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/map/DbRelationshipDetected.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*****************************************************************
- *   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.map;
-
-
-/**
- * A subclass of {@link DbRelationship} to hold some extra runtime information.
- * 
- */
-public class DbRelationshipDetected extends DbRelationship {
-
-    private String fkName;
-
-    public DbRelationshipDetected(String uniqueRelName) {
-        super(uniqueRelName);
-    }
-
-    /**
-     * Set the name of the underlying foreign key. Typically FK_NAME from jdbc metadata.
-     */
-    public void setFkName(String fkName) {
-        this.fkName = fkName;
-    }
-
-    /**
-     * Get the name of the underlying foreign key. Typically FK_NAME from jdbc metadata.
-     */
-    public String getFkName() {
-        return fkName;
-    }
-
-}