You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by ab...@apache.org on 2019/04/24 11:27:37 UTC

[cayenne] 01/02: CAY-2569 Custom 'Naming Strategy' in Cayenne Modeler

This is an automated email from the ASF dual-hosted git repository.

abulatski pushed a commit to branch STABLE-4.1
in repository https://gitbox.apache.org/repos/asf/cayenne.git

commit 87e340211cb737130c95ead4fce1b6a51bf633f5
Author: Arseni Bulatski <an...@gmail.com>
AuthorDate: Wed Apr 24 10:48:33 2019 +0300

    CAY-2569 Custom 'Naming Strategy' in Cayenne Modeler
    
    (cherry picked from commit e7193b42e15dc3226e8da9b2eef88115463fab19)
---
 RELEASE-NOTES.txt                                  |   1 +
 .../tools/utils/CustomObjectNameGenerator.java     | 140 +++++++++++++++++++++
 .../org/apache/cayenne/tools/build-schema.xml      |   3 +-
 .../tools/build-skip-primary-key-loading.xml       |   3 +-
 .../reverse/dbimport/DbImportConfiguration.java    |  13 +-
 .../cayenne/tools/dbimport_excludeRel.gradle       |   1 +
 .../cayenne/stubs/CustomObjectNameGenerator.java   | 140 +++++++++++++++++++++
 .../tools/dbimport/testDefaultPackage-pom.xml      |   1 +
 .../tools/dbimport/testExcludeRelationship-pom.xml |   1 +
 .../modeler/editor/dbimport/DbImportView.java      |  23 ++--
 .../dbimport/ReverseEngineeringConfigPanel.java    |  53 +++++---
 11 files changed, 342 insertions(+), 37 deletions(-)

diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index 539574a..e088f47 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -18,6 +18,7 @@ CAY-2559 Modeler: Warning dialog shows wrong information after changing target e
 CAY-2561 Modeler: cgen type combobox doesn't set templates
 CAY-2564 No import for Property after generation classes with pk properties
 CAY-2566 Flush action generates update for PK attribute in case of toDepPK relationship
+CAY-2569 Custom 'Naming Strategy' in Cayenne Modeler
 
 ----------------------------------
 Release: 4.1.B1
diff --git a/cayenne-ant/src/test/java/org/apache/cayenne/tools/utils/CustomObjectNameGenerator.java b/cayenne-ant/src/test/java/org/apache/cayenne/tools/utils/CustomObjectNameGenerator.java
new file mode 100644
index 0000000..8305c19
--- /dev/null
+++ b/cayenne-ant/src/test/java/org/apache/cayenne/tools/utils/CustomObjectNameGenerator.java
@@ -0,0 +1,140 @@
+/*****************************************************************
+ *   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.utils;
+
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+
+import org.apache.cayenne.dbsync.naming.DbEntityNameStemmer;
+import org.apache.cayenne.dbsync.naming.NoStemStemmer;
+import org.apache.cayenne.dbsync.naming.ObjectNameGenerator;
+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.util.Util;
+import org.jvnet.inflector.Noun;
+
+/**
+ * @since 4.2
+ */
+public class CustomObjectNameGenerator implements ObjectNameGenerator {
+
+    private DbEntityNameStemmer dbEntityNameStemmer;
+
+    public CustomObjectNameGenerator() {
+        this.dbEntityNameStemmer = NoStemStemmer.getInstance();
+    }
+
+    public CustomObjectNameGenerator(DbEntityNameStemmer dbEntityNameStemmer) {
+        this.dbEntityNameStemmer = dbEntityNameStemmer;
+    }
+
+    @Override
+    public String relationshipName(DbRelationship... relationshipChain) {
+
+        if (relationshipChain == null || relationshipChain.length < 1) {
+            throw new IllegalArgumentException("At least on relationship is expected: " + relationshipChain);
+        }
+
+        // ignore the name of DbRelationship itself (FWIW we may be generating a new name for it here)...
+        // generate the name based on join semantics...
+
+        String name = isToMany(relationshipChain)
+                ? toManyRelationshipName(relationshipChain)
+                : toOneRelationshipName(relationshipChain);
+
+        return Util.underscoredToJava(name, false);
+    }
+
+    protected boolean isToMany(DbRelationship... relationshipChain) {
+
+        for (DbRelationship r : relationshipChain) {
+            if (r.isToMany()) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    protected String stemmed(String dbEntityName) {
+        return dbEntityNameStemmer.stem(Objects.requireNonNull(dbEntityName));
+    }
+
+    protected String toManyRelationshipName(DbRelationship... relationshipChain) {
+
+        DbRelationship last = relationshipChain[relationshipChain.length - 1];
+
+        String baseName = stemmed(last.getTargetEntityName());
+
+        try {
+            // by default we use English rules here...
+            return Noun.pluralOf(baseName.toLowerCase(), Locale.ENGLISH);
+        } catch (Exception inflectorError) {
+            //  seems that Inflector cannot be trusted. For instance, it
+            // throws an exception when invoked for word "ADDRESS" (although
+            // lower case works fine). To feel safe, we use superclass'
+            // behavior if something's gone wrong
+            return baseName;
+        }
+    }
+
+    protected String toOneRelationshipName(DbRelationship... relationshipChain) {
+
+        DbRelationship first = relationshipChain[0];
+        DbRelationship last = relationshipChain[relationshipChain.length - 1];
+
+        List<DbJoin> joins = first.getJoins();
+        if (joins.isEmpty()) {
+            // In case, when uses EditRelationship button, relationship doesn't exist => it doesn't have joins
+            // and just return targetName
+            return stemmed(last.getTargetEntityName());
+        }
+
+        DbJoin join1 = joins.get(0);
+
+        // TODO: multi-join relationships
+
+        // return the name of the FK column sans ID
+        String fkColName = join1.getSourceName();
+        if (fkColName == null) {
+            return stemmed(last.getTargetEntityName());
+        } else if (fkColName.toUpperCase().endsWith("_ID") && fkColName.length() > 3) {
+            return fkColName.substring(0, fkColName.length() - 3);
+        } else if (fkColName.toUpperCase().endsWith("ID") && fkColName.length() > 2) {
+            return fkColName.substring(0, fkColName.length() - 2);
+        } else {
+            return stemmed(last.getTargetEntityName());
+        }
+    }
+
+    @Override
+    public String objEntityName(DbEntity dbEntity) {
+        String baseName = stemmed(dbEntity.getName());
+        return Util.underscoredToJava(baseName, true);
+    }
+
+    @Override
+    public String objAttributeName(DbAttribute attr) {
+        return Util.underscoredToJava(attr.getName(), false);
+    }
+}
diff --git a/cayenne-ant/src/test/resources/org/apache/cayenne/tools/build-schema.xml b/cayenne-ant/src/test/resources/org/apache/cayenne/tools/build-schema.xml
index 911af23..eb1db2b 100644
--- a/cayenne-ant/src/test/resources/org/apache/cayenne/tools/build-schema.xml
+++ b/cayenne-ant/src/test/resources/org/apache/cayenne/tools/build-schema.xml
@@ -27,7 +27,8 @@
                    adapter="org.apache.cayenne.dba.hsqldb.HSQLDBAdapter"
                    driver="org.hsqldb.jdbcDriver"
                    url="jdbc:hsqldb:hsql://localhost/bookmarker"
-                   username="sa">
+                   username="sa"
+                   namingStrategy="org.apache.cayenne.dbsync.naming.DefaultObjectNameGenerator">
 
                 <schema>schema-name-01</schema>
 
diff --git a/cayenne-ant/src/test/resources/org/apache/cayenne/tools/build-skip-primary-key-loading.xml b/cayenne-ant/src/test/resources/org/apache/cayenne/tools/build-skip-primary-key-loading.xml
index 396b603..32d01b8 100644
--- a/cayenne-ant/src/test/resources/org/apache/cayenne/tools/build-skip-primary-key-loading.xml
+++ b/cayenne-ant/src/test/resources/org/apache/cayenne/tools/build-skip-primary-key-loading.xml
@@ -27,7 +27,8 @@
                    adapter="org.apache.cayenne.dba.hsqldb.HSQLDBAdapter"
                    driver="org.hsqldb.jdbcDriver"
                    url="jdbc:hsqldb:hsql://localhost/bookmarker"
-                   username="sa" skipPrimaryKeyLoading="true">
+                   username="sa" skipPrimaryKeyLoading="true"
+                   namingStrategy="org.apache.cayenne.tools.utils.CustomObjectNameGenerator">
         </cdbimport>
     </target>
 
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/dbimport/DbImportConfiguration.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/dbimport/DbImportConfiguration.java
index 87d249f..83678cc 100644
--- a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/dbimport/DbImportConfiguration.java
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/dbimport/DbImportConfiguration.java
@@ -18,14 +18,15 @@
  ****************************************************************/
 package org.apache.cayenne.dbsync.reverse.dbimport;
 
+import java.io.File;
+import java.util.regex.Pattern;
+
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.configuration.DataNodeDescriptor;
 import org.apache.cayenne.conn.DataSourceInfo;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.dbsync.filter.NameFilter;
 import org.apache.cayenne.dbsync.filter.NamePatternMatcher;
-import org.apache.cayenne.dbsync.reverse.dbload.DefaultModelMergeDelegate;
-import org.apache.cayenne.dbsync.reverse.dbload.ModelMergeDelegate;
 import org.apache.cayenne.dbsync.naming.DbEntityNameStemmer;
 import org.apache.cayenne.dbsync.naming.DefaultObjectNameGenerator;
 import org.apache.cayenne.dbsync.naming.NoStemStemmer;
@@ -34,13 +35,12 @@ import org.apache.cayenne.dbsync.naming.PatternStemmer;
 import org.apache.cayenne.dbsync.reverse.dbload.DbLoaderConfiguration;
 import org.apache.cayenne.dbsync.reverse.dbload.DbLoaderDelegate;
 import org.apache.cayenne.dbsync.reverse.dbload.DefaultDbLoaderDelegate;
+import org.apache.cayenne.dbsync.reverse.dbload.DefaultModelMergeDelegate;
 import org.apache.cayenne.dbsync.reverse.dbload.LoggingDbLoaderDelegate;
+import org.apache.cayenne.dbsync.reverse.dbload.ModelMergeDelegate;
 import org.apache.cayenne.dbsync.reverse.filters.FiltersConfig;
 import org.slf4j.Logger;
 
-import java.io.File;
-import java.util.regex.Pattern;
-
 /**
  * @since 4.0
  */
@@ -190,7 +190,8 @@ public class DbImportConfiguration {
         String namingStrategy = getNamingStrategy();
         if (namingStrategy != null && !namingStrategy.equals(DefaultObjectNameGenerator.class.getName())) {
             try {
-                return (ObjectNameGenerator) Class.forName(namingStrategy).newInstance();
+                Class<?> generatorClass = Thread.currentThread().getContextClassLoader().loadClass(namingStrategy);
+                return (ObjectNameGenerator) generatorClass.newInstance();
             } catch (Exception e) {
                 throw new CayenneRuntimeException("Error creating name generator: " + namingStrategy, e);
             }
diff --git a/cayenne-gradle-plugin/src/test/resources/org/apache/cayenne/tools/dbimport_excludeRel.gradle b/cayenne-gradle-plugin/src/test/resources/org/apache/cayenne/tools/dbimport_excludeRel.gradle
index 79b41db..9eb400f 100644
--- a/cayenne-gradle-plugin/src/test/resources/org/apache/cayenne/tools/dbimport_excludeRel.gradle
+++ b/cayenne-gradle-plugin/src/test/resources/org/apache/cayenne/tools/dbimport_excludeRel.gradle
@@ -37,6 +37,7 @@ cdbimport {
             name 'SCHEMA_01'
             excludeRelationship 'test2s'
             excludeRelationship 'test1'
+            namingStrategy 'org.apache.cayenne.dbsync.naming.DefaultObjectNameGenerator'
         }
 
     }
diff --git a/maven-plugins/cayenne-maven-plugin/src/test/java/org/apache/cayenne/stubs/CustomObjectNameGenerator.java b/maven-plugins/cayenne-maven-plugin/src/test/java/org/apache/cayenne/stubs/CustomObjectNameGenerator.java
new file mode 100644
index 0000000..55f51f6
--- /dev/null
+++ b/maven-plugins/cayenne-maven-plugin/src/test/java/org/apache/cayenne/stubs/CustomObjectNameGenerator.java
@@ -0,0 +1,140 @@
+/*****************************************************************
+ *   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.stubs;
+
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+
+import org.apache.cayenne.dbsync.naming.DbEntityNameStemmer;
+import org.apache.cayenne.dbsync.naming.NoStemStemmer;
+import org.apache.cayenne.dbsync.naming.ObjectNameGenerator;
+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.util.Util;
+import org.jvnet.inflector.Noun;
+
+/**
+ * @since 4.2
+ */
+public class CustomObjectNameGenerator implements ObjectNameGenerator {
+
+    private DbEntityNameStemmer dbEntityNameStemmer;
+
+    public CustomObjectNameGenerator() {
+        this.dbEntityNameStemmer = NoStemStemmer.getInstance();
+    }
+
+    public CustomObjectNameGenerator(DbEntityNameStemmer dbEntityNameStemmer) {
+        this.dbEntityNameStemmer = dbEntityNameStemmer;
+    }
+
+    @Override
+    public String relationshipName(DbRelationship... relationshipChain) {
+
+        if (relationshipChain == null || relationshipChain.length < 1) {
+            throw new IllegalArgumentException("At least on relationship is expected: " + relationshipChain);
+        }
+
+        // ignore the name of DbRelationship itself (FWIW we may be generating a new name for it here)...
+        // generate the name based on join semantics...
+
+        String name = isToMany(relationshipChain)
+                ? toManyRelationshipName(relationshipChain)
+                : toOneRelationshipName(relationshipChain);
+
+        return Util.underscoredToJava(name, false);
+    }
+
+    protected boolean isToMany(DbRelationship... relationshipChain) {
+
+        for (DbRelationship r : relationshipChain) {
+            if (r.isToMany()) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    protected String stemmed(String dbEntityName) {
+        return dbEntityNameStemmer.stem(Objects.requireNonNull(dbEntityName));
+    }
+
+    protected String toManyRelationshipName(DbRelationship... relationshipChain) {
+
+        DbRelationship last = relationshipChain[relationshipChain.length - 1];
+
+        String baseName = stemmed(last.getTargetEntityName());
+
+        try {
+            // by default we use English rules here...
+            return Noun.pluralOf(baseName.toLowerCase(), Locale.ENGLISH);
+        } catch (Exception inflectorError) {
+            //  seems that Inflector cannot be trusted. For instance, it
+            // throws an exception when invoked for word "ADDRESS" (although
+            // lower case works fine). To feel safe, we use superclass'
+            // behavior if something's gone wrong
+            return baseName;
+        }
+    }
+
+    protected String toOneRelationshipName(DbRelationship... relationshipChain) {
+
+        DbRelationship first = relationshipChain[0];
+        DbRelationship last = relationshipChain[relationshipChain.length - 1];
+
+        List<DbJoin> joins = first.getJoins();
+        if (joins.isEmpty()) {
+            // In case, when uses EditRelationship button, relationship doesn't exist => it doesn't have joins
+            // and just return targetName
+            return stemmed(last.getTargetEntityName());
+        }
+
+        DbJoin join1 = joins.get(0);
+
+        // TODO: multi-join relationships
+
+        // return the name of the FK column sans ID
+        String fkColName = join1.getSourceName();
+        if (fkColName == null) {
+            return stemmed(last.getTargetEntityName());
+        } else if (fkColName.toUpperCase().endsWith("_ID") && fkColName.length() > 3) {
+            return fkColName.substring(0, fkColName.length() - 3);
+        } else if (fkColName.toUpperCase().endsWith("ID") && fkColName.length() > 2) {
+            return fkColName.substring(0, fkColName.length() - 2);
+        } else {
+            return stemmed(last.getTargetEntityName());
+        }
+    }
+
+    @Override
+    public String objEntityName(DbEntity dbEntity) {
+        String baseName = stemmed(dbEntity.getName());
+        return Util.underscoredToJava(baseName, true);
+    }
+
+    @Override
+    public String objAttributeName(DbAttribute attr) {
+        return Util.underscoredToJava(attr.getName(), false);
+    }
+}
diff --git a/maven-plugins/cayenne-maven-plugin/src/test/resources/org/apache/cayenne/tools/dbimport/testDefaultPackage-pom.xml b/maven-plugins/cayenne-maven-plugin/src/test/resources/org/apache/cayenne/tools/dbimport/testDefaultPackage-pom.xml
index b4bc513..f8bcebb 100644
--- a/maven-plugins/cayenne-maven-plugin/src/test/resources/org/apache/cayenne/tools/dbimport/testDefaultPackage-pom.xml
+++ b/maven-plugins/cayenne-maven-plugin/src/test/resources/org/apache/cayenne/tools/dbimport/testDefaultPackage-pom.xml
@@ -42,6 +42,7 @@
                     <dbimport>
                         <defaultPackage>com.example.test</defaultPackage>
                         <schema>SCHEMA_01</schema>
+                        <namingStrategy>org.apache.cayenne.stubs.CustomObjectNameGenerator</namingStrategy>
                     </dbimport>
                     <project implementation="org.apache.cayenne.stubs.CayenneProjectStub"/>
 
diff --git a/maven-plugins/cayenne-maven-plugin/src/test/resources/org/apache/cayenne/tools/dbimport/testExcludeRelationship-pom.xml b/maven-plugins/cayenne-maven-plugin/src/test/resources/org/apache/cayenne/tools/dbimport/testExcludeRelationship-pom.xml
index 184326e..368addc 100644
--- a/maven-plugins/cayenne-maven-plugin/src/test/resources/org/apache/cayenne/tools/dbimport/testExcludeRelationship-pom.xml
+++ b/maven-plugins/cayenne-maven-plugin/src/test/resources/org/apache/cayenne/tools/dbimport/testExcludeRelationship-pom.xml
@@ -50,6 +50,7 @@
                             <excludeRelationship>test12s</excludeRelationship>
                             <excludeRelationship>test11</excludeRelationship>
                         </schema>
+                        <namingStrategy>org.apache.cayenne.dbsync.naming.DefaultObjectNameGenerator</namingStrategy>
                     </dbimport>
                     <project implementation="org.apache.cayenne.stubs.CayenneProjectStub"/>
 
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/DbImportView.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/DbImportView.java
index f1a33b6..f0fc44e 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/DbImportView.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/DbImportView.java
@@ -18,6 +18,17 @@
  ****************************************************************/
 package org.apache.cayenne.modeler.editor.dbimport;
 
+import javax.swing.AbstractAction;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JProgressBar;
+import javax.swing.border.EmptyBorder;
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+
 import com.jgoodies.forms.builder.DefaultFormBuilder;
 import com.jgoodies.forms.layout.FormLayout;
 import org.apache.cayenne.dbsync.reverse.dbimport.ReverseEngineering;
@@ -30,17 +41,6 @@ import org.apache.cayenne.modeler.dialog.db.load.TransferableNode;
 import org.apache.cayenne.modeler.util.CayenneAction;
 import org.apache.cayenne.modeler.util.ModelerUtil;
 
-import javax.swing.AbstractAction;
-import javax.swing.ImageIcon;
-import javax.swing.JButton;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-import javax.swing.JProgressBar;
-import javax.swing.border.EmptyBorder;
-import java.awt.BorderLayout;
-import java.awt.FlowLayout;
-import java.awt.event.ActionEvent;
-
 /**
  * @since 4.1
  */
@@ -202,6 +202,7 @@ public class DbImportView extends JPanel {
             }
             configPanel.fillCheckboxes(reverseEngineering);
             configPanel.initializeTextFields(reverseEngineering);
+            configPanel.initStrategy(reverseEngineering);
             treePanel.updateTree();
             DbImportTreeNode root = draggableTreePanel.getSourceTree().getRootNode();
             root.removeAllChildren();
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/ReverseEngineeringConfigPanel.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/ReverseEngineeringConfigPanel.java
index 8f3993a..dc40cff 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/ReverseEngineeringConfigPanel.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbimport/ReverseEngineeringConfigPanel.java
@@ -19,20 +19,23 @@
 
 package org.apache.cayenne.modeler.editor.dbimport;
 
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import java.util.Vector;
+
 import com.jgoodies.forms.builder.DefaultFormBuilder;
 import com.jgoodies.forms.layout.FormLayout;
 import org.apache.cayenne.dbsync.reverse.dbimport.ReverseEngineering;
 import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.modeler.Application;
 import org.apache.cayenne.modeler.ProjectController;
 import org.apache.cayenne.modeler.util.NameGeneratorPreferences;
 import org.apache.cayenne.modeler.util.TextAdapter;
-
-import javax.swing.DefaultComboBoxModel;
-import javax.swing.JCheckBox;
-import javax.swing.JComboBox;
-import javax.swing.JPanel;
-import javax.swing.JTextField;
-import java.util.Vector;
+import org.apache.cayenne.modeler.util.combo.AutoCompletion;
 
 /**
  * @since 4.1
@@ -73,7 +76,7 @@ public class ReverseEngineeringConfigPanel extends JPanel {
         panelBuilder.append("Force datamap schema:", forceDataMapSchema);
         panelBuilder.append("Use Java primitive types:", usePrimitives);
         panelBuilder.append("Use java.util.Date type:", useJava7Types);
-        panelBuilder.append(strategyCombo);
+        panelBuilder.append("Naming strategy:", strategyCombo);
 
         add(panelBuilder.getPanel());
     }
@@ -97,22 +100,18 @@ public class ReverseEngineeringConfigPanel extends JPanel {
         return projectController.getApplication().getMetaData().get(dataMap, ReverseEngineering.class);
     }
 
-    private void initStrategy() {
+    void initStrategy(ReverseEngineering reverseEngineering) {
         Vector<String> arr = NameGeneratorPreferences
                 .getInstance()
                 .getLastUsedStrategies();
         strategyCombo.setModel(new DefaultComboBoxModel<>(arr));
+        strategyCombo.setSelectedItem(reverseEngineering.getNamingStrategy());
     }
 
     private void initFormElements() {
-        strategyCombo = new JComboBox<>();
-        strategyCombo.addActionListener(e -> {
-            getReverseEngineeringBySelectedMap().setNamingStrategy(
-                    (String) ReverseEngineeringConfigPanel.this.getStrategyCombo().getSelectedItem()
-            );
-            projectController.setDirty(true);
-        });
-        strategyCombo.setVisible(false);
+        strategyCombo = Application.getWidgetFactory().createComboBox();
+        AutoCompletion.enable(strategyCombo, false, true);
+        strategyCombo.setToolTipText("Naming strategy to use");
 
         JTextField meaningfulPkField = new JTextField();
         meaningfulPkField.setToolTipText("<html>Regular expression to filter tables with meaningful primary keys.<br>" +
@@ -152,7 +151,6 @@ public class ReverseEngineeringConfigPanel extends JPanel {
                 "By default <b>java.time.*</b> types will be used.</html>");
         usePrimitives = new JCheckBox();
         usePrimitives.setToolTipText("<html>Use primitive types (e.g. int) or Object types (e.g. java.lang.Integer)</html>");
-        initStrategy();
     }
 
     private void initListeners() {
@@ -180,6 +178,25 @@ public class ReverseEngineeringConfigPanel extends JPanel {
             getReverseEngineeringBySelectedMap().setUseJava7Types(useJava7Types.isSelected());
             projectController.setDirty(true);
         });
+        strategyCombo.addActionListener(e -> {
+            String strategy = (String) ReverseEngineeringConfigPanel.this.getStrategyCombo().getSelectedItem();
+            checkStrategy(strategy);
+            getReverseEngineeringBySelectedMap().setNamingStrategy(strategy);
+            NameGeneratorPreferences.getInstance().addToLastUsedStrategies(strategy);
+            projectController.setDirty(true);
+        });
+    }
+
+    private void checkStrategy(String strategy) {
+        try{
+            Thread.currentThread().getContextClassLoader().loadClass(strategy);
+        } catch(Exception ex) {
+            JOptionPane.showMessageDialog(
+                    this,
+                    strategy + " not found. Please, add naming strategy to classpath.",
+                    "Error",
+                    JOptionPane.WARNING_MESSAGE);
+        }
     }
 
     JComboBox<String> getStrategyCombo() {