You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by nt...@apache.org on 2018/11/14 14:27:33 UTC

[02/32] cayenne git commit: cgen. create ui

cgen. create ui


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

Branch: refs/heads/master
Commit: 8119ffaa88f70787590df6b23bc86c031337b921
Parents: 831442c
Author: Arseni Bulatski <an...@gmail.com>
Authored: Thu Jun 14 16:57:22 2018 +0300
Committer: Arseni Bulatski <an...@gmail.com>
Committed: Wed Oct 24 13:39:05 2018 +0300

----------------------------------------------------------------------
 .../cayenne/gen/xml/CgenConfigHandler.java      |  23 +
 .../apache/cayenne/gen/xml/CgenExtension.java   |  32 ++
 .../cayenne/gen/xml/CgenLoaderDelegate.java     |  28 +
 .../cayenne/gen/xml/CgenSaverDelegate.java      |  20 +
 .../modeler/editor/DataMapTabbedView.java       |  10 +-
 .../cayenne/modeler/editor/EditorView.java      |   3 +-
 .../editor/cgen/ClassesTabController.java       | 123 ++++
 .../modeler/editor/cgen/ClassesTabPanel.java    |  84 +++
 .../editor/cgen/CodeGeneratorController.java    | 146 +++++
 .../cgen/CodeGeneratorControllerBase.java       | 289 ++++++++++
 .../modeler/editor/cgen/CodeGeneratorPane.java  |  76 +++
 .../editor/cgen/CustomModeController.java       | 238 ++++++++
 .../modeler/editor/cgen/CustomModePanel.java    | 189 ++++++
 .../editor/cgen/CustomPreferencesUpdater.java   | 193 +++++++
 .../editor/cgen/GeneratorController.java        | 568 +++++++++++++++++++
 .../editor/cgen/GeneratorControllerPanel.java   |  53 ++
 .../editor/cgen/GeneratorTabController.java     |  71 +++
 .../modeler/editor/cgen/GeneratorTabPanel.java  |  33 ++
 .../editor/cgen/StandardPanelComponent.java     |  79 +++
 .../modeler/init/CayenneModelerModule.java      |   4 +-
 20 files changed, 2256 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/8119ffaa/cayenne-cgen/src/main/java/org/apache/cayenne/gen/xml/CgenConfigHandler.java
----------------------------------------------------------------------
diff --git a/cayenne-cgen/src/main/java/org/apache/cayenne/gen/xml/CgenConfigHandler.java b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/xml/CgenConfigHandler.java
new file mode 100644
index 0000000..23f0ff3
--- /dev/null
+++ b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/xml/CgenConfigHandler.java
@@ -0,0 +1,23 @@
+package org.apache.cayenne.gen.xml;
+
+import org.apache.cayenne.configuration.xml.DataChannelMetaData;
+import org.apache.cayenne.configuration.xml.NamespaceAwareNestedTagHandler;
+import org.apache.cayenne.dbsync.xml.DbImportExtension;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+public class CgenConfigHandler extends NamespaceAwareNestedTagHandler{
+
+    private DataChannelMetaData metaData;
+
+    CgenConfigHandler(NamespaceAwareNestedTagHandler parentHandler, DataChannelMetaData metaData) {
+        super(parentHandler);
+        this.metaData = metaData;
+        this.targetNamespace = DbImportExtension.NAMESPACE;
+    }
+
+    @Override
+    protected boolean processElement(String namespaceURI, String localName, Attributes attributes) throws SAXException {
+        return false;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/8119ffaa/cayenne-cgen/src/main/java/org/apache/cayenne/gen/xml/CgenExtension.java
----------------------------------------------------------------------
diff --git a/cayenne-cgen/src/main/java/org/apache/cayenne/gen/xml/CgenExtension.java b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/xml/CgenExtension.java
new file mode 100644
index 0000000..619bffc
--- /dev/null
+++ b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/xml/CgenExtension.java
@@ -0,0 +1,32 @@
+package org.apache.cayenne.gen.xml;
+
+import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
+import org.apache.cayenne.configuration.xml.DataChannelMetaData;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.project.Project;
+import org.apache.cayenne.project.extension.LoaderDelegate;
+import org.apache.cayenne.project.extension.ProjectExtension;
+import org.apache.cayenne.project.extension.SaverDelegate;
+
+public class CgenExtension implements ProjectExtension {
+
+    public static final String NAMESPACE = "http://cayenne.apache.org/schema/" + Project.VERSION + "/cgen";
+
+    @Inject
+    private DataChannelMetaData metaData;
+
+    @Override
+    public LoaderDelegate createLoaderDelegate() {
+        return null;
+    }
+
+    @Override
+    public SaverDelegate createSaverDelegate() {
+        return new CgenSaverDelegate(metaData);
+    }
+
+    @Override
+    public ConfigurationNodeVisitor<String> createNamingDelegate() {
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/8119ffaa/cayenne-cgen/src/main/java/org/apache/cayenne/gen/xml/CgenLoaderDelegate.java
----------------------------------------------------------------------
diff --git a/cayenne-cgen/src/main/java/org/apache/cayenne/gen/xml/CgenLoaderDelegate.java b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/xml/CgenLoaderDelegate.java
new file mode 100644
index 0000000..44438e3
--- /dev/null
+++ b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/xml/CgenLoaderDelegate.java
@@ -0,0 +1,28 @@
+package org.apache.cayenne.gen.xml;
+
+import org.apache.cayenne.configuration.xml.DataChannelMetaData;
+import org.apache.cayenne.configuration.xml.NamespaceAwareNestedTagHandler;
+
+import org.apache.cayenne.project.extension.LoaderDelegate;
+
+public class CgenLoaderDelegate implements LoaderDelegate {
+
+    private DataChannelMetaData metaData;
+
+    CgenLoaderDelegate(DataChannelMetaData metaData){
+        this.metaData = metaData;
+    }
+
+    @Override
+    public String getTargetNamespace() {
+        return CgenExtension.NAMESPACE;
+    }
+
+    @Override
+    public NamespaceAwareNestedTagHandler createHandler(NamespaceAwareNestedTagHandler parent, String tag) {
+//        if(CgenConfigHandler.CONFIG_TAG.equals(tag)) {
+//            return new CgenConfigHandler(parent, metaData);
+//        }
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/8119ffaa/cayenne-cgen/src/main/java/org/apache/cayenne/gen/xml/CgenSaverDelegate.java
----------------------------------------------------------------------
diff --git a/cayenne-cgen/src/main/java/org/apache/cayenne/gen/xml/CgenSaverDelegate.java b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/xml/CgenSaverDelegate.java
new file mode 100644
index 0000000..f9c7173
--- /dev/null
+++ b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/xml/CgenSaverDelegate.java
@@ -0,0 +1,20 @@
+package org.apache.cayenne.gen.xml;
+
+import org.apache.cayenne.configuration.xml.DataChannelMetaData;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.project.extension.BaseSaverDelegate;
+
+public class CgenSaverDelegate extends BaseSaverDelegate{
+
+    private DataChannelMetaData metaData;
+
+    CgenSaverDelegate(DataChannelMetaData metaData){
+        this.metaData = metaData;
+    }
+
+    @Override
+    public Void visitDataMap(DataMap dataMap) {
+
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/8119ffaa/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/DataMapTabbedView.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/DataMapTabbedView.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/DataMapTabbedView.java
index ed1b6f1..bd9afc5 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/DataMapTabbedView.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/DataMapTabbedView.java
@@ -18,12 +18,13 @@
  ****************************************************************/
 package org.apache.cayenne.modeler.editor;
 
-import javax.swing.JScrollPane;
-import javax.swing.JTabbedPane;
-
+import org.apache.cayenne.modeler.Application;
 import org.apache.cayenne.modeler.ProjectController;
+import org.apache.cayenne.modeler.editor.cgen.CodeGeneratorController;
 import org.apache.cayenne.modeler.editor.dbimport.DbImportView;
 
+import javax.swing.*;
+
 
 /**
  * Data map editing tabs container
@@ -55,8 +56,11 @@ public class DataMapTabbedView extends JTabbedPane {
         // must be wrapped in a scroll pane
         JScrollPane dataMapView = new JScrollPane(new DataMapView(mediator));
         JScrollPane dbImportView = new JScrollPane(new DbImportView(mediator));
+        CodeGeneratorController codeGeneratorController = new CodeGeneratorController(Application.getInstance().getFrameController(), mediator);
+        JScrollPane cgenView = new JScrollPane(codeGeneratorController.getView());
         addTab("DataMap", dataMapView);
         addTab("DbImport", dbImportView);
+        addTab("Cgen", cgenView);
     }
 }
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/8119ffaa/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/EditorView.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/EditorView.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/EditorView.java
index e9b3290..5b4675a 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/EditorView.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/EditorView.java
@@ -103,7 +103,6 @@ public class EditorView extends JPanel implements ObjEntityDisplayListener,
     private SQLTemplateTabbedView sqlTemplateView;
     private EjbqlTabbedView ejbqlQueryView;
     private JTabbedPane dataNodeView;
-    
 
     protected ActionManager actionManager;
 	private FilterController filterController;
@@ -162,7 +161,7 @@ public class EditorView extends JPanel implements ObjEntityDisplayListener,
 
     public EditorView(ProjectController eventController) {
         this.eventController = eventController;
-        this.actionManager= eventController.getApplication().getActionManager();
+        this.actionManager = eventController.getApplication().getActionManager();
         initView();
         initController();
        

http://git-wip-us.apache.org/repos/asf/cayenne/blob/8119ffaa/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/ClassesTabController.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/ClassesTabController.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/ClassesTabController.java
new file mode 100644
index 0000000..2b478ac
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/ClassesTabController.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.modeler.editor.cgen;
+
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.modeler.util.CayenneController;
+import org.apache.cayenne.swing.BindingBuilder;
+import org.apache.cayenne.swing.ImageRendererColumn;
+import org.apache.cayenne.swing.ObjectBinding;
+import org.apache.cayenne.swing.TableBindingBuilder;
+
+import javax.swing.*;
+import java.awt.*;
+
+public class ClassesTabController extends CayenneController {
+
+    public static final String GENERATE_PROPERTY = "generate";
+
+    protected ClassesTabPanel view;
+    protected ObjectBinding tableBinding;
+
+    public ClassesTabController(CodeGeneratorControllerBase parent) {
+        super(parent);
+
+        this.view = new ClassesTabPanel();
+    }
+
+    public void startup(DataMap dataMap){
+        initBindings();
+    }
+
+    protected CodeGeneratorControllerBase getParentController() {
+        return (CodeGeneratorControllerBase) getParent();
+    }
+
+    public Component getView() {
+        return view;
+    }
+
+    protected void initBindings() {
+
+        BindingBuilder builder = new BindingBuilder(
+                getApplication().getBindingFactory(),
+                this);
+
+        builder.bindToAction(view.getCheckAll(), "checkAllAction()");
+
+        TableBindingBuilder tableBuilder = new TableBindingBuilder(builder);
+
+        tableBuilder.addColumn(
+                "",
+                "parent.setCurrentClass(#item), selected",
+                Boolean.class,
+                true,
+                Boolean.TRUE);
+        tableBuilder.addColumn(
+                "Class",
+                "parent.getItemName(#item)",
+                JLabel.class,
+                false,
+                "XXXXXXXXXXXXXX");
+
+        tableBuilder.addColumn(
+                "Comments, Warnings",
+                "parent.getProblem(#item)",
+                String.class,
+                false,
+                "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
+
+        this.tableBinding = tableBuilder.bindToTable(view.getTable(), "parent.classes");
+        view.getTable().getColumnModel().getColumn(1).setCellRenderer(new ImageRendererColumn());
+    }
+
+    public boolean isSelected() {
+        return getParentController().isSelected();
+    }
+
+    public void setSelected(boolean selected) {
+        getParentController().setSelected(selected);
+        classSelectedAction();
+    }
+
+    /**
+     * A callback action that updates the state of Select All checkbox.
+     */
+    public void classSelectedAction() {
+        int selectedCount = getParentController().getSelectedEntitiesSize() + getParentController().getSelectedEmbeddablesSize() ;
+
+        if (selectedCount == 0) {
+            view.getCheckAll().setSelected(false);
+        }
+        else if (selectedCount == getParentController().getClasses().size()) {
+            view.getCheckAll().setSelected(true);
+        }
+    }
+
+    /**
+     * An action that updates entity check boxes in response to the Select All state
+     * change.
+     */
+    public void checkAllAction() {
+        if (getParentController().updateSelection(view.getCheckAll().isSelected() ? o -> true : o -> false)) {
+            tableBinding.updateView();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/8119ffaa/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/ClassesTabPanel.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/ClassesTabPanel.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/ClassesTabPanel.java
new file mode 100644
index 0000000..e4fc20c
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/ClassesTabPanel.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.modeler.editor.cgen;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+
+/**
+ */
+public class ClassesTabPanel extends JPanel {
+
+    protected JTable table;
+    protected JCheckBox checkAll;
+    protected JLabel checkAllLabel;
+
+    public ClassesTabPanel() {
+
+        this.table = new JTable();
+        this.table.setRowHeight(22);
+
+        // TODO: andrus 04/07/2006 - is there an easy way to stick that checkbox in the
+        // table header????
+        this.checkAll = new JCheckBox();
+        this.checkAllLabel = new JLabel("Check All Classes");
+
+        checkAll.addItemListener(new ItemListener() {
+
+            public void itemStateChanged(ItemEvent event) {
+                if (checkAll.isSelected()) {
+                    checkAllLabel.setText("Uncheck All Classess");
+                }
+                else {
+                    checkAllLabel.setText("Check All Classes");
+                }
+            }
+        });
+
+        // assemble
+        JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
+        topPanel.setBorder(UIManager.getBorder("ToolBar.border"));
+        topPanel.add(checkAll);
+        topPanel.add(checkAllLabel);
+
+        JScrollPane tablePanel = new JScrollPane(
+                table,
+                ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
+                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
+
+        // set some minimal preferred size, so that it is smaller than other forms used in
+        // the dialog... this way we get the right automated overall size
+        tablePanel.setPreferredSize(new Dimension(300, 200));
+
+        setLayout(new BorderLayout());
+        add(topPanel, BorderLayout.NORTH);
+        add(tablePanel, BorderLayout.CENTER);
+    }
+
+    public JTable getTable() {
+        return table;
+    }
+
+    public JCheckBox getCheckAll() {
+        return checkAll;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/8119ffaa/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/CodeGeneratorController.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/CodeGeneratorController.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/CodeGeneratorController.java
new file mode 100644
index 0000000..8163acb
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/CodeGeneratorController.java
@@ -0,0 +1,146 @@
+/*****************************************************************
+ *   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.modeler.editor.cgen;
+
+import org.apache.cayenne.gen.ClassGenerationAction;
+import org.apache.cayenne.modeler.ProjectController;
+import org.apache.cayenne.modeler.dialog.ErrorDebugDialog;
+import org.apache.cayenne.modeler.util.CayenneController;
+import org.apache.cayenne.swing.BindingBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.swing.*;
+import java.awt.*;
+import java.util.Collection;
+import java.util.function.Predicate;
+
+/**
+ * A controller for the class generator dialog.
+ */
+public class CodeGeneratorController extends CodeGeneratorControllerBase {
+    /**
+     * Logger to print stack traces
+     */
+    private static Logger logObj = LoggerFactory.getLogger(ErrorDebugDialog.class);
+
+    protected CodeGeneratorPane view;
+
+    protected ClassesTabController classesSelector;
+    protected GeneratorTabController generatorSelector;
+
+    public CodeGeneratorController(CayenneController parent, ProjectController projectController) {
+        super(parent, projectController);
+        initListeners();
+        this.classesSelector = new ClassesTabController(this);
+        this.generatorSelector = new GeneratorTabController(this);
+        view = new CodeGeneratorPane(generatorSelector.getView(), classesSelector.getView());
+        initBindings();
+    }
+
+    private void initListeners(){
+        projectController.addDataMapDisplayListener(e -> {
+            super.startup(e.getDataMap());
+            classesSelector.startup(e.getDataMap());
+            generatorSelector.startup(e.getDataMap());
+        });
+    }
+
+    @Override
+    public Component getView() {
+        return view;
+    }
+
+    protected void initBindings() {
+        BindingBuilder builder = new BindingBuilder(
+                getApplication().getBindingFactory(),
+                this);
+
+        builder.bindToAction(view.getGenerateButton(), "generateAction()");
+        builder.bindToAction(this, "classesSelectedAction()", SELECTED_PROPERTY);
+        builder.bindToAction(generatorSelector, "generatorSelectedAction()",
+                GeneratorTabController.GENERATOR_PROPERTY);
+
+        generatorSelectedAction();
+    }
+
+    public void generatorSelectedAction() {
+        GeneratorController controller = generatorSelector.getGeneratorController();
+        validate(controller);
+
+        Predicate<Object> predicate = controller != null
+                ? controller.getDefaultClassFilter()
+                : o -> false;
+
+        updateSelection(predicate);
+        classesSelector.classSelectedAction();
+    }
+
+    public void classesSelectedAction() {
+        int size = getSelectedEntitiesSize();
+        String label;
+
+        if (size == 0) {
+            label = "No entities selected";
+        }
+        else if (size == 1) {
+            label = "One entity selected";
+        }
+        else {
+            label = size + " entities selected";
+        }
+
+        label = label.concat("; ");
+        
+        int sizeEmb = getSelectedEmbeddablesSize();
+
+        if (sizeEmb == 0) {
+            label = label + "No embeddables selected";
+        }
+        else if (sizeEmb == 1) {
+            label = label + "One embeddable selected";
+        }
+        else {
+            label =label + sizeEmb + " embeddables selected";
+        }
+        
+        view.getClassesCount().setText(label);
+    }
+
+    public void generateAction() {
+        Collection<ClassGenerationAction> generators = generatorSelector.getGenerator();
+
+        if (generators != null) {
+            try {
+                for (ClassGenerationAction generator : generators) {
+                    generator.execute();
+                }
+                JOptionPane.showMessageDialog(
+                        getView(),
+                        "Class generation finished");
+            } catch (Exception e) {
+                logObj.error("Error generating classes", e);
+                JOptionPane.showMessageDialog(
+                        getView(),
+                        "Error generating classes - " + e.getMessage());
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/8119ffaa/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/CodeGeneratorControllerBase.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/CodeGeneratorControllerBase.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/CodeGeneratorControllerBase.java
new file mode 100644
index 0000000..17196fa
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/CodeGeneratorControllerBase.java
@@ -0,0 +1,289 @@
+/*****************************************************************
+ *   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.modeler.editor.cgen;
+
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.Embeddable;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.modeler.ProjectController;
+import org.apache.cayenne.modeler.util.CayenneController;
+import org.apache.cayenne.modeler.util.CellRenderers;
+import org.apache.cayenne.validation.ValidationFailure;
+import org.apache.cayenne.validation.ValidationResult;
+
+import javax.swing.*;
+import java.awt.*;
+import java.util.*;
+import java.util.List;
+import java.util.function.Predicate;
+
+/**
+ * A base superclass of a top controller for the code generator. Defines all common model
+ * parts used in class generation.
+ *
+ */
+public abstract class CodeGeneratorControllerBase extends CayenneController {
+
+    public static final String SELECTED_PROPERTY = "selected";
+
+    protected Collection<DataMap> dataMaps;
+
+    protected DataMap dataMap;
+
+    protected ValidationResult validation;
+
+    protected List<Object> classes;
+
+    protected Set<String> selectedEntities;
+    protected Set<String> selectedEmbeddables;
+
+    protected transient Object currentClass;
+
+    protected ProjectController projectController;
+
+    public CodeGeneratorControllerBase(CayenneController parent, ProjectController projectController) {
+        super(parent);
+        this.projectController = projectController;
+        this.classes = new ArrayList<>();
+
+        this.selectedEntities = new HashSet<>();
+        this.selectedEmbeddables = new HashSet<>();
+    }
+
+    public void startup(DataMap dataMap){
+        this.dataMap = dataMap;
+        classes.clear();
+        this.classes.addAll(dataMap.getObjEntities());
+        this.classes.addAll(dataMap.getEmbeddables());
+    }
+
+    public List<Object> getClasses() {
+        return classes;
+    }
+
+    public abstract Component getView();
+
+    public void validate(GeneratorController validator) {
+
+        ValidationResult validationBuffer = new ValidationResult();
+
+        if (validator != null) {
+            for (Object classObj : classes) {
+                if (classObj instanceof ObjEntity) {
+                    validator.validateEntity(
+                            validationBuffer,
+                            (ObjEntity) classObj,
+                            false);
+                }
+                else if (classObj instanceof Embeddable) {
+                    validator.validateEmbeddable(validationBuffer, (Embeddable) classObj);
+                }
+            }
+
+        }
+
+        this.validation = validationBuffer;
+    }
+
+    public boolean updateSelection(Predicate<Object> predicate) {
+
+        boolean modified = false;
+
+        for (Object classObj : classes) {
+            boolean select = predicate.test(classObj);
+            if (classObj instanceof ObjEntity) {
+
+                if (select) {
+                    if (selectedEntities.add(((ObjEntity) classObj).getName())) {
+                        modified = true;
+                    }
+                }
+                else {
+                    if (selectedEntities.remove(((ObjEntity) classObj).getName())) {
+                        modified = true;
+                    }
+                }
+            }
+            else if (classObj instanceof Embeddable) {
+                if (select) {
+                    if (selectedEmbeddables.add(((Embeddable) classObj).getClassName())) {
+                        modified = true;
+                    }
+                }
+                else {
+                    if (selectedEmbeddables
+                            .remove(((Embeddable) classObj).getClassName())) {
+                        modified = true;
+                    }
+                }
+            }
+
+        }
+
+        if (modified) {
+            firePropertyChange(SELECTED_PROPERTY, null, null);
+        }
+
+        return modified;
+    }
+
+    public List<Embeddable> getSelectedEmbeddables() {
+
+        List<Embeddable> selected = new ArrayList<>(selectedEmbeddables.size());
+
+        for (Object classObj : classes) {
+            if (classObj instanceof Embeddable
+                    && selectedEmbeddables.contains(((Embeddable) classObj)
+                            .getClassName())) {
+                selected.add((Embeddable) classObj);
+            }
+        }
+
+        return selected;
+    }
+
+    public List<ObjEntity> getSelectedEntities() {
+        List<ObjEntity> selected = new ArrayList<>(selectedEntities.size());
+        for (Object classObj : classes) {
+            if (classObj instanceof ObjEntity
+                    && selectedEntities.contains(((ObjEntity) classObj).getName())) {
+                selected.add(((ObjEntity) classObj));
+            }
+        }
+
+        return selected;
+    }
+
+    public int getSelectedEntitiesSize() {
+        return selectedEntities.size();
+    }
+
+    public int getSelectedEmbeddablesSize() {
+        return selectedEmbeddables.size();
+    }
+
+    /**
+     * Returns the first encountered validation problem for an antity matching the name or
+     * null if the entity is valid or the entity is not present.
+     */
+    public String getProblem(Object obj) {
+
+        String name = null;
+        
+        if (obj instanceof ObjEntity) {
+            name = ((ObjEntity) obj).getName();
+        }
+        else if (obj instanceof Embeddable) {
+            name = ((Embeddable) obj).getClassName();
+        }
+        
+        if (validation == null) {
+            return null;
+        }
+
+        List failures = validation.getFailures(name);
+        if (failures.isEmpty()) {
+            return null;
+        }
+
+        return ((ValidationFailure) failures.get(0)).getDescription();
+    }
+
+    public boolean isSelected() {
+        if (currentClass instanceof ObjEntity) {
+            return selectedEntities
+                    .contains(((ObjEntity) currentClass).getName());
+        }
+        if (currentClass instanceof Embeddable) {
+            return selectedEmbeddables
+                    .contains(((Embeddable) currentClass).getClassName());
+        }
+        return false;
+
+    }
+
+    public void setSelected(boolean selectedFlag) {
+        if (currentClass == null) {
+            return;
+        }
+        if (currentClass instanceof ObjEntity) {
+            if (selectedFlag) {
+                if (selectedEntities.add(((ObjEntity) currentClass).getName())) {
+                    firePropertyChange(SELECTED_PROPERTY, null, null);
+                }
+            }
+            else {
+                if (selectedEntities.remove(((ObjEntity) currentClass).getName())) {
+                    firePropertyChange(SELECTED_PROPERTY, null, null);
+                }
+            }
+        }
+        if (currentClass instanceof Embeddable) {
+            if (selectedFlag) {
+                if (selectedEmbeddables.add(((Embeddable) currentClass).getClassName())) {
+                    firePropertyChange(SELECTED_PROPERTY, null, null);
+                }
+            }
+            else {
+                if (selectedEmbeddables
+                        .remove(((Embeddable) currentClass).getClassName())) {
+                    firePropertyChange(SELECTED_PROPERTY, null, null);
+                }
+            }
+        }
+    }
+
+    public Object getCurrentClass() {
+        return currentClass;
+    }
+
+    public void setCurrentClass(Object currentClass) {
+        this.currentClass = currentClass;
+    }
+
+    public Collection<DataMap> getDataMaps() {
+        return dataMaps;
+    }
+
+    public JLabel getItemName(Object obj) {
+        String className;
+        Icon icon;
+        if (obj instanceof Embeddable) {
+            className = ((Embeddable) obj).getClassName();
+            icon = CellRenderers.iconForObject(new Embeddable());
+        } else {
+            className = ((ObjEntity) obj).getName();
+            icon = CellRenderers.iconForObject(new ObjEntity());
+        }
+        JLabel labelIcon = new JLabel();
+        labelIcon.setIcon(icon);
+        labelIcon.setVisible(true);
+        labelIcon.setText(className);
+        return labelIcon;
+    }
+
+    public DataMap getDataMap() {
+        return dataMap;
+    }
+
+    public ProjectController getProjectController() {
+        return projectController;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/8119ffaa/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/CodeGeneratorPane.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/CodeGeneratorPane.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/CodeGeneratorPane.java
new file mode 100644
index 0000000..334931a
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/CodeGeneratorPane.java
@@ -0,0 +1,76 @@
+/*****************************************************************
+ *   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.modeler.editor.cgen;
+
+import org.apache.cayenne.swing.components.TopBorder;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ */
+public class CodeGeneratorPane extends JSplitPane {
+
+    protected JSplitPane splitPane;
+
+    protected JButton generateButton;
+    protected JLabel classesCount;
+
+    public CodeGeneratorPane(Component generatorPanel, Component entitySelectorPanel) {
+        super();
+
+        splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
+
+        this.generateButton = new JButton("Generate");
+        this.classesCount = new JLabel("No classes selected");
+        classesCount.setFont(classesCount.getFont().deriveFont(10f));
+
+        JScrollPane scrollPane = new JScrollPane(
+                generatorPanel,
+                ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
+                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+        scrollPane.setPreferredSize(new Dimension(800, 400));
+
+        // assemble
+        splitPane.setRightComponent(scrollPane);
+        splitPane.setLeftComponent(entitySelectorPanel);
+
+        JPanel messages = new JPanel(new BorderLayout());
+        messages.add(classesCount, BorderLayout.WEST);
+
+        JPanel buttons = new JPanel(new FlowLayout(FlowLayout.RIGHT));
+        buttons.setBorder(TopBorder.create());
+        buttons.add(classesCount);
+        buttons.add(Box.createHorizontalStrut(50));
+        buttons.add(generateButton);
+
+        setLayout(new BorderLayout());
+        add(splitPane, BorderLayout.CENTER);
+        add(buttons, BorderLayout.SOUTH);
+    }
+
+    public JButton getGenerateButton() {
+        return generateButton;
+    }
+
+    public JLabel getClassesCount() {
+        return classesCount;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/8119ffaa/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/CustomModeController.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/CustomModeController.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/CustomModeController.java
new file mode 100644
index 0000000..8f4dac9
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/CustomModeController.java
@@ -0,0 +1,238 @@
+/*****************************************************************
+ *   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.modeler.editor.cgen;
+
+import org.apache.cayenne.gen.ClassGenerationAction;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.modeler.CodeTemplateManager;
+import org.apache.cayenne.modeler.dialog.pref.PreferenceDialog;
+import org.apache.cayenne.modeler.pref.DataMapDefaults;
+import org.apache.cayenne.swing.BindingBuilder;
+import org.apache.cayenne.swing.ObjectBinding;
+import org.apache.cayenne.util.Util;
+
+import javax.swing.*;
+import java.awt.*;
+import java.util.*;
+import java.util.List;
+
+/**
+ * A controller for the custom generation mode.
+ */
+public class CustomModeController extends GeneratorController {
+
+    // correspond to non-public constants on MapClassGenerator.
+    static final String MODE_DATAMAP = "datamap";
+    static final String MODE_ENTITY = "entity";
+    static final String MODE_ALL = "all";
+
+    static final String DATA_MAP_MODE_LABEL = "DataMap generation";
+    static final String ENTITY_MODE_LABEL = "Entity and Embeddable generation";
+    static final String ALL_MODE_LABEL = "Generate all";
+
+    static final Map<String, String> modesByLabel = new HashMap<>();
+
+    static {
+        modesByLabel.put(DATA_MAP_MODE_LABEL, MODE_DATAMAP);
+        modesByLabel.put(ENTITY_MODE_LABEL, MODE_ENTITY);
+        modesByLabel.put(ALL_MODE_LABEL, MODE_ALL);
+    }
+
+    protected CustomModePanel view;
+    protected CodeTemplateManager templateManager;
+
+    protected ObjectBinding superTemplate;
+    protected ObjectBinding subTemplate;
+
+    private BindingBuilder builder;
+
+    private CustomPreferencesUpdater preferencesUpdater;
+
+    public CustomPreferencesUpdater getCustomPreferencesUpdater() {
+        return preferencesUpdater;
+    }
+
+    public CustomModeController(CodeGeneratorControllerBase parent) {
+        super(parent);
+        this.view = new CustomModePanel();
+        bind();
+    }
+
+    private void bind() {
+        initBindings(new BindingBuilder(getApplication().getBindingFactory(), this));
+        builder = new BindingBuilder(getApplication().getBindingFactory(), this);
+        builder.bindToAction(view.getManageTemplatesLink(), "popPreferencesAction()");
+
+        Object[] modeChoices = new Object[]{ENTITY_MODE_LABEL, DATA_MAP_MODE_LABEL, ALL_MODE_LABEL};
+        view.getGenerationMode().setModel(new DefaultComboBoxModel(modeChoices));
+    }
+
+    public void startup(DataMap dataMap) {
+        super.startup(dataMap);
+
+        // bind preferences and init defaults...
+        DataMapDefaults dataMapDefaults = getMapPreferences().get(getParentController().getDataMap());
+
+        if (Util.isEmptyString(dataMapDefaults.getSuperclassTemplate())) {
+            dataMapDefaults.setSuperclassTemplate(CodeTemplateManager.STANDARD_SERVER_SUPERCLASS);
+        }
+
+        if (Util.isEmptyString(dataMapDefaults.getSubclassTemplate())) {
+            dataMapDefaults.setSubclassTemplate(CodeTemplateManager.STANDARD_SERVER_SUBCLASS);
+        }
+
+        if (Util.isEmptyString(dataMapDefaults.getProperty("mode"))) {
+            dataMapDefaults.setProperty("mode", MODE_ENTITY);
+        }
+
+        if (Util.isEmptyString(dataMapDefaults.getProperty("overwrite"))) {
+            dataMapDefaults.setBooleanProperty("overwrite", false);
+        }
+
+        if (Util.isEmptyString(dataMapDefaults.getProperty("pairs"))) {
+            dataMapDefaults.setBooleanProperty("pairs", true);
+        }
+
+        if (Util.isEmptyString(dataMapDefaults.getProperty("usePackagePath"))) {
+            dataMapDefaults.setBooleanProperty("usePackagePath", true);
+        }
+
+        if (Util.isEmptyString(dataMapDefaults.getProperty("outputPattern"))) {
+            dataMapDefaults.setProperty("outputPattern", "*.java");
+        }
+
+        builder.bindToComboSelection(view.getGenerationMode(), "customPreferencesUpdater.mode").updateView();
+
+        builder.bindToStateChange(view.getOverwrite(), "customPreferencesUpdater.overwrite").updateView();
+
+        builder.bindToStateChange(view.getPairs(), "customPreferencesUpdater.pairs").updateView();
+
+        builder.bindToStateChange(view.getUsePackagePath(), "customPreferencesUpdater.usePackagePath").updateView();
+
+        subTemplate = builder.bindToComboSelection(view.getSubclassTemplate(),
+                "customPreferencesUpdater.subclassTemplate");
+
+        superTemplate = builder.bindToComboSelection(view.getSuperclassTemplate(),
+                "customPreferencesUpdater.superclassTemplate");
+
+        builder.bindToTextField(view.getOutputPattern(), "customPreferencesUpdater.outputPattern").updateView();
+
+        builder.bindToStateChange(view.getCreatePropertyNames(), "customPreferencesUpdater.createPropertyNames")
+                .updateView();
+
+        updateTemplates();
+    }
+
+    protected void createDefaults() {
+        TreeMap<DataMap, DataMapDefaults> map = new TreeMap<>();
+        DataMap dataMap = getParentController().getDataMap();
+        DataMapDefaults preferences;
+        preferences = getApplication().getFrameController().getProjectController()
+                .getDataMapPreferences(this.getClass().getName().replace(".", "/"), dataMap);
+        preferences.setSuperclassPackage("");
+        preferences.updateSuperclassPackage(dataMap, false);
+
+        map.put(dataMap, preferences);
+
+        if (getOutputPath() == null) {
+            setOutputPath(preferences.getOutputPath());
+        }
+
+        setMapPreferences(map);
+        preferencesUpdater = new CustomPreferencesUpdater(map);
+    }
+
+    protected GeneratorControllerPanel createView() {
+        if (getParentController().getDataMap() != view.getStandardPanelComponent().getDataMap()) {
+            DataMapDefaults dataMapDefaults = getMapPreferences().get(getParentController().getDataMap());
+            view.getStandardPanelComponent().setDataMap(getParentController().getDataMap());
+            view.getStandardPanelComponent().setPreferences(dataMapDefaults);
+            view.getStandardPanelComponent().getDataMapName().setText(view.getStandardPanelComponent().getDataMap().getName());
+            BindingBuilder builder = new BindingBuilder(getApplication().getBindingFactory(), view.getStandardPanelComponent());
+            builder.bindToTextField(view.getStandardPanelComponent().getSuperclassPackage(), "preferences.superclassPackage").updateView();
+        }
+        return view;
+    }
+
+    protected void updateTemplates() {
+        this.templateManager = getApplication().getCodeTemplateManager();
+
+        List<String> customTemplates = new ArrayList<>(templateManager.getCustomTemplates().keySet());
+        Collections.sort(customTemplates);
+
+        List<String> superTemplates = new ArrayList<>(templateManager.getStandardSuperclassTemplates());
+        Collections.sort(superTemplates);
+        superTemplates.addAll(customTemplates);
+
+        List<String> subTemplates = new ArrayList<>(templateManager.getStandardSubclassTemplates());
+        Collections.sort(subTemplates);
+        subTemplates.addAll(customTemplates);
+
+        this.view.getSubclassTemplate().setModel(new DefaultComboBoxModel(subTemplates.toArray()));
+        this.view.getSuperclassTemplate().setModel(new DefaultComboBoxModel(superTemplates.toArray()));
+
+        superTemplate.updateView();
+        subTemplate.updateView();
+    }
+
+    public Component getView() {
+        return view;
+    }
+
+    public Collection<ClassGenerationAction> createGenerator() {
+
+        mode = modesByLabel.get(view.getGenerationMode().getSelectedItem()).toString();
+
+        Collection<ClassGenerationAction> generators = super.createGenerator();
+
+        String superKey = view.getSuperclassTemplate().getSelectedItem().toString();
+        String superTemplate = templateManager.getTemplatePath(superKey);
+
+        String subKey = view.getSubclassTemplate().getSelectedItem().toString();
+        String subTemplate = templateManager.getTemplatePath(subKey);
+
+        for (ClassGenerationAction generator : generators) {
+            generator.setSuperTemplate(superTemplate);
+            generator.setTemplate(subTemplate);
+            generator.setOverwrite(view.getOverwrite().isSelected());
+            generator.setUsePkgPath(view.getUsePackagePath().isSelected());
+            generator.setMakePairs(view.getPairs().isSelected());
+            generator.setCreatePropertyNames(view.getCreatePropertyNames().isSelected());
+
+            if (!Util.isEmptyString(view.getOutputPattern().getText())) {
+                generator.setOutputPattern(view.getOutputPattern().getText());
+            }
+        }
+
+        return generators;
+    }
+
+    public void popPreferencesAction() {
+        new PreferenceDialog(getApplication().getFrameController()).startupAction(PreferenceDialog.TEMPLATES_KEY);
+        updateTemplates();
+    }
+
+    @Override
+    protected ClassGenerationAction newGenerator() {
+        ClassGenerationAction action = new ClassGenerationAction();
+        getApplication().getInjector().injectMembers(action);
+        return action;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/8119ffaa/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/CustomModePanel.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/CustomModePanel.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/CustomModePanel.java
new file mode 100644
index 0000000..538ff64
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/CustomModePanel.java
@@ -0,0 +1,189 @@
+/*****************************************************************
+ *   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.modeler.editor.cgen;
+
+import com.jgoodies.forms.builder.DefaultFormBuilder;
+import com.jgoodies.forms.layout.FormLayout;
+import org.apache.cayenne.swing.control.ActionLink;
+
+import javax.swing.*;
+import java.awt.*;
+
+public class CustomModePanel extends GeneratorControllerPanel {
+
+    protected JComboBox generationMode;
+    protected JComboBox subclassTemplate;
+    protected JComboBox superclassTemplate;
+    protected JCheckBox pairs;
+    protected JCheckBox overwrite;
+    protected JCheckBox usePackagePath;
+    protected JTextField outputPattern;
+    protected JCheckBox createPropertyNames;
+
+    private JTextField additionalMaps;
+    private JButton selectAdditionalMaps;
+    private JCheckBox client;
+    private JTextField encoding;
+    private JComboBox embeddableTemplate;
+    private JComboBox embeddableSuperTemplate;
+    private JLabel dataMapName;
+
+    private DefaultFormBuilder builder;
+
+    protected ActionLink manageTemplatesLink;
+
+    private StandardPanelComponent standardPanelComponent;
+
+    public CustomModePanel() {
+
+        this.generationMode = new JComboBox();
+        this.superclassTemplate = new JComboBox();
+        this.subclassTemplate = new JComboBox();
+        this.pairs = new JCheckBox();
+        this.overwrite = new JCheckBox();
+        this.usePackagePath = new JCheckBox();
+        this.outputPattern = new JTextField();
+        this.createPropertyNames = new JCheckBox();
+        this.manageTemplatesLink = new ActionLink("Customize Templates...");
+        manageTemplatesLink.setFont(manageTemplatesLink.getFont().deriveFont(10f));
+
+        this.standardPanelComponent = new StandardPanelComponent();
+
+        this.additionalMaps = new JTextField();
+        this.selectAdditionalMaps = new JButton("Select");
+        this.client = new JCheckBox();
+        this.encoding = new JTextField();
+        this.embeddableTemplate = new JComboBox();
+        this.embeddableSuperTemplate = new JComboBox();
+        this.dataMapName = new JLabel();
+        this.dataMapName.setFont(dataMapName.getFont().deriveFont(1));
+
+        pairs.addChangeListener(e -> {
+            superclassTemplate.setEnabled(pairs.isSelected());
+            overwrite.setEnabled(!pairs.isSelected());
+        });
+
+        // assemble
+
+        FormLayout layout = new FormLayout(
+                "right:77dlu, 3dlu, fill:200:grow, 6dlu, fill:50dlu, 3dlu", "");
+        builder = new DefaultFormBuilder(layout);
+        builder.setDefaultDialogBorder();
+
+        builder.append("Output Directory:", outputFolder, selectOutputFolder);
+        builder.nextLine();
+
+        builder.append("Additional DataMaps", additionalMaps, selectAdditionalMaps);
+        builder.nextLine();
+
+        builder.append("Generation Mode:", generationMode);
+        builder.nextLine();
+
+        builder.append("Subclass Template:", subclassTemplate);
+        builder.nextLine();
+
+        builder.append("Superclass Template:", superclassTemplate);
+        builder.nextLine();
+
+        builder.append("Embeddable Template", embeddableTemplate);
+        builder.nextLine();
+
+        builder.append("Embeddable Super Template", embeddableSuperTemplate);
+        builder.nextLine();
+
+        builder.append("Output Pattern:", outputPattern);
+        builder.nextLine();
+
+        builder.append("Encoding", encoding);
+        builder.nextLine();
+
+        builder.append("Make Pairs:", pairs);
+        builder.nextLine();
+
+        builder.append("Use Package Path:", usePackagePath);
+        builder.nextLine();
+
+        builder.append("Overwrite Subclasses:", overwrite);
+        builder.nextLine();
+
+        builder.append("Create Property Names:", createPropertyNames);
+        builder.nextLine();
+
+        builder.append("Client", client);
+        builder.nextLine();
+
+        builder.append(standardPanelComponent, 4);
+
+        setLayout(new BorderLayout());
+        add(builder.getPanel(), BorderLayout.CENTER);
+
+        JPanel links = new JPanel(new FlowLayout(FlowLayout.TRAILING));
+        links.add(manageTemplatesLink);
+        add(links, BorderLayout.SOUTH);
+
+        add(builder.getPanel(), BorderLayout.CENTER);
+    }
+
+    public void addDataMapLine(StandardPanelComponent dataMapLine) {
+        dataMapLines.add(dataMapLine);
+        builder.append(dataMapLine, 4);
+        builder.nextLine();
+    }
+
+    public JComboBox getGenerationMode() {
+        return generationMode;
+    }
+
+    public ActionLink getManageTemplatesLink() {
+        return manageTemplatesLink;
+    }
+
+    public JComboBox getSubclassTemplate() {
+        return subclassTemplate;
+    }
+
+    public JComboBox getSuperclassTemplate() {
+        return superclassTemplate;
+    }
+
+    public JCheckBox getOverwrite() {
+        return overwrite;
+    }
+
+    public JCheckBox getPairs() {
+        return pairs;
+    }
+
+    public JCheckBox getUsePackagePath() {
+        return usePackagePath;
+    }
+
+    public JTextField getOutputPattern() {
+        return outputPattern;
+    }
+
+    public JCheckBox getCreatePropertyNames() {
+        return createPropertyNames;
+    }
+
+    public StandardPanelComponent getStandardPanelComponent() {
+        return standardPanelComponent;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/8119ffaa/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/CustomPreferencesUpdater.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/CustomPreferencesUpdater.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/CustomPreferencesUpdater.java
new file mode 100644
index 0000000..469e594
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/CustomPreferencesUpdater.java
@@ -0,0 +1,193 @@
+/*****************************************************************
+ *   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.modeler.editor.cgen;
+
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.modeler.pref.DataMapDefaults;
+
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class CustomPreferencesUpdater {
+
+    enum Property {
+        SUBCLASS_TEMPLATE,
+        SUPERCLASS_TEMPLATE,
+        OVERWRITE,
+        PAIRS,
+        USE_PACKAGE_PATH,
+        MODE,
+        OUTPUT_PATTERN,
+        CREATE_PROPERTY_NAMES
+    }
+
+    private static final String OVERWRITE = "overwrite";
+    private static final String PAIRS = "pairs";
+    private static final String USE_PACKAGE_PATH = "usePackagePath";
+    private static final String MODE = "mode";
+    private static final String OUTPUT_PATTERN = "outputPattern";
+    private static final String CREATE_PROPERTY_NAMES = "createPropertyNames";
+
+    private Map<DataMap, DataMapDefaults> mapPreferences;
+
+
+    public CustomPreferencesUpdater(Map<DataMap, DataMapDefaults> mapPreferences) {
+        this.mapPreferences = mapPreferences;
+    }
+
+    public String getMode() {
+        return (String) getProperty(Property.MODE);
+    }
+
+    public void setMode(String mode) {
+        updatePreferences(Property.MODE, mode);
+    }
+
+    public String getSubclassTemplate() {
+        return (String) getProperty(Property.SUBCLASS_TEMPLATE);
+    }
+
+    public void setSubclassTemplate(String subclassTemplate) {
+        updatePreferences(Property.SUBCLASS_TEMPLATE, subclassTemplate);
+    }
+
+    public String getSuperclassTemplate() {
+        return (String) getProperty(Property.SUPERCLASS_TEMPLATE);
+    }
+
+    public void setSuperclassTemplate(String superclassTemplate) {
+        updatePreferences(Property.SUPERCLASS_TEMPLATE, superclassTemplate);
+    }
+
+    public Boolean getOverwrite() {
+        return (Boolean) getProperty(Property.OVERWRITE);
+    }
+
+    public void setOverwrite(Boolean overwrite) {
+        updatePreferences(Property.OVERWRITE, overwrite);
+    }
+
+    public Boolean getPairs() {
+        return (Boolean) getProperty(Property.PAIRS);
+    }
+
+    public void setPairs(Boolean pairs) {
+        updatePreferences(Property.PAIRS, pairs);
+    }
+
+    public Boolean getUsePackagePath() {
+        return (Boolean) getProperty(Property.USE_PACKAGE_PATH);
+    }
+
+    public void setUsePackagePath(Boolean usePackagePath) {
+        updatePreferences(Property.USE_PACKAGE_PATH, usePackagePath);
+    }
+
+    public String getOutputPattern() {
+        return (String) getProperty(Property.OUTPUT_PATTERN);
+    }
+
+    public void setOutputPattern(String outputPattern) {
+        updatePreferences(Property.OUTPUT_PATTERN, outputPattern);
+    }
+
+    public Boolean getCreatePropertyNames() {
+        return (Boolean) getProperty(Property.CREATE_PROPERTY_NAMES);
+    }
+
+    public void setCreatePropertyNames(Boolean createPropertyNames) {
+        updatePreferences(Property.CREATE_PROPERTY_NAMES, createPropertyNames);
+    }
+
+    private Object getProperty(Property property) {
+        Object obj = null;
+
+        Set<Entry<DataMap, DataMapDefaults>> entities = mapPreferences.entrySet();
+        for (Entry<DataMap, DataMapDefaults> entry : entities) {
+
+            switch (property) {
+                case MODE:
+                    obj = entry.getValue().getProperty(MODE);
+                    break;
+                case OUTPUT_PATTERN:
+                    obj = entry.getValue().getProperty(OUTPUT_PATTERN);
+                    break;
+                case SUBCLASS_TEMPLATE:
+                    obj = entry.getValue().getSubclassTemplate();
+                    break;
+                case SUPERCLASS_TEMPLATE:
+                    obj = entry.getValue().getSuperclassTemplate();
+                    break;
+                case OVERWRITE:
+                    obj = entry.getValue().getBooleanProperty(OVERWRITE);
+                    break;
+                case PAIRS:
+                    obj = entry.getValue().getBooleanProperty(PAIRS);
+                    break;
+                case USE_PACKAGE_PATH:
+                    obj = entry.getValue().getBooleanProperty(USE_PACKAGE_PATH);
+                    break;
+                case CREATE_PROPERTY_NAMES:
+                    obj = entry.getValue().getBooleanProperty(CREATE_PROPERTY_NAMES);
+                    break;
+                default:
+                    throw new IllegalArgumentException("Bad type property: " + property);
+            }
+
+        }
+        return obj;
+    }
+
+    private void updatePreferences(Property property, Object value) {
+        Set<Entry<DataMap, DataMapDefaults>> entities = mapPreferences.entrySet();
+        for (Entry<DataMap, DataMapDefaults> entry : entities) {
+
+            switch (property) {
+                case MODE:
+                    entry.getValue().setProperty(MODE, (String) value);
+                    break;
+                case OUTPUT_PATTERN:
+                    entry.getValue().setProperty(OUTPUT_PATTERN, (String) value);
+                    break;
+                case SUBCLASS_TEMPLATE:
+                    entry.getValue().setSubclassTemplate((String) value);
+                    break;
+                case SUPERCLASS_TEMPLATE:
+                    entry.getValue().setSuperclassTemplate((String) value);
+                    break;
+                case OVERWRITE:
+                    entry.getValue().setBooleanProperty(OVERWRITE, (Boolean) value);
+                    break;
+                case PAIRS:
+                    entry.getValue().setBooleanProperty(PAIRS, (Boolean) value);
+                    break;
+                case USE_PACKAGE_PATH:
+                    entry.getValue().setBooleanProperty(USE_PACKAGE_PATH, (Boolean) value);
+                    break;
+                case CREATE_PROPERTY_NAMES:
+                    entry.getValue().setBooleanProperty(CREATE_PROPERTY_NAMES, (Boolean) value);
+                    break;
+                default:
+                    throw new IllegalArgumentException("Bad type property: " + property);
+            }
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/8119ffaa/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/GeneratorController.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/GeneratorController.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/GeneratorController.java
new file mode 100644
index 0000000..9370e3b
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/GeneratorController.java
@@ -0,0 +1,568 @@
+/*****************************************************************
+ *   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.modeler.editor.cgen;
+
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.gen.ArtifactsGenerationMode;
+import org.apache.cayenne.gen.ClassGenerationAction;
+import org.apache.cayenne.map.*;
+import org.apache.cayenne.modeler.Application;
+import org.apache.cayenne.modeler.dialog.pref.GeneralPreferences;
+import org.apache.cayenne.modeler.pref.DataMapDefaults;
+import org.apache.cayenne.modeler.pref.FSPath;
+import org.apache.cayenne.modeler.util.CayenneController;
+import org.apache.cayenne.modeler.util.CodeValidationUtil;
+import org.apache.cayenne.swing.BindingBuilder;
+import org.apache.cayenne.util.Util;
+import org.apache.cayenne.validation.BeanValidationFailure;
+import org.apache.cayenne.validation.SimpleValidationFailure;
+import org.apache.cayenne.validation.ValidationFailure;
+import org.apache.cayenne.validation.ValidationResult;
+
+import javax.swing.*;
+import java.io.File;
+import java.util.*;
+import java.util.function.Predicate;
+import java.util.prefs.Preferences;
+
+/**
+ * A mode-specific part of the code generation dialog.
+ * 
+ */
+public abstract class GeneratorController extends CayenneController {
+
+    protected String mode = ArtifactsGenerationMode.ALL.getLabel();
+    protected Map<DataMap, DataMapDefaults> mapPreferences;
+    private String outputPath;
+
+    public GeneratorController(CodeGeneratorControllerBase parent) {
+        super(parent);
+    }
+
+    public void startup(DataMap dataMap){
+        createDefaults();
+        createView();
+//        initBindings(new BindingBuilder(getApplication().getBindingFactory(), this));
+    }
+
+    public String getOutputPath() {
+        return outputPath;
+    }
+
+    public void setOutputPath(String path) {
+        String old = this.outputPath;
+        this.outputPath = path;
+        if (this.outputPath != null && !this.outputPath.equals(old)) {
+            updatePreferences(path);
+        }
+    }
+
+    public void updatePreferences(String path) {
+        if (mapPreferences == null)
+            return;
+        Set<DataMap> keys = mapPreferences.keySet();
+        for (DataMap key : keys) {
+            mapPreferences
+                    .get(key)
+                    .setOutputPath(path);
+        }
+    }
+
+    public void setMapPreferences(Map<DataMap, DataMapDefaults> mapPreferences) {
+        this.mapPreferences = mapPreferences;
+    }
+
+    public Map<DataMap, DataMapDefaults> getMapPreferences() {
+        return this.mapPreferences;
+    }
+
+    protected void initBindings(BindingBuilder bindingBuilder) {
+
+        initOutputFolder();
+
+        JTextField outputFolder = ((GeneratorControllerPanel) getView()).getOutputFolder();
+        JButton outputSelect = ((GeneratorControllerPanel) getView()).getSelectOutputFolder();
+
+        outputFolder.setText(getOutputPath());
+        bindingBuilder.bindToAction(outputSelect, "selectOutputFolderAction()");
+        bindingBuilder.bindToTextField(outputFolder, "outputPath");
+    }
+
+    protected CodeGeneratorControllerBase getParentController() {
+        return (CodeGeneratorControllerBase) getParent();
+    }
+
+    protected abstract GeneratorControllerPanel createView();
+
+    protected abstract void createDefaults();
+
+    /**
+     * Creates an appropriate subclass of {@link ClassGenerationAction},
+     * returning it in an unconfigured state. Configuration is performed by
+     * {@link #createGenerator()} method.
+     */
+    protected abstract ClassGenerationAction newGenerator();
+
+    /**
+     * Creates a class generator for provided selections.
+     */
+    public Collection<ClassGenerationAction> createGenerator() {
+
+        File outputDir = getOutputDir();
+
+        // no destination folder
+        if (outputDir == null) {
+            JOptionPane.showMessageDialog(this.getView(), "Select directory for source files.");
+            return null;
+        }
+
+        // no such folder
+        if (!outputDir.exists() && !outputDir.mkdirs()) {
+            JOptionPane.showMessageDialog(this.getView(), "Can't create directory " + outputDir
+                    + ". Select a different one.");
+            return null;
+        }
+
+        // not a directory
+        if (!outputDir.isDirectory()) {
+            JOptionPane.showMessageDialog(this.getView(), outputDir + " is not a valid directory.");
+            return null;
+        }
+
+        // remove generic entities...
+        Collection<ObjEntity> selectedEntities = new ArrayList<>(getParentController().getSelectedEntities());
+        selectedEntities.removeIf(ObjEntity::isGeneric);
+
+        Collection<ClassGenerationAction> generators = new ArrayList<>();
+        Collection<StandardPanelComponent> dataMapLines = ((GeneratorControllerPanel) getView()).getDataMapLines();
+        DataMap map = getParentController().getDataMap();
+        try {
+            ClassGenerationAction generator = newGenerator();
+            generator.setArtifactsGenerationMode(mode);
+            generator.setDataMap(map);
+
+            LinkedList<ObjEntity> objEntities = new LinkedList<>(map.getObjEntities());
+            objEntities.retainAll(selectedEntities);
+            generator.addEntities(objEntities);
+
+            LinkedList<Embeddable> embeddables = new LinkedList<>(map.getEmbeddables());
+            embeddables.retainAll(getParentController().getSelectedEmbeddables());
+            generator.addEmbeddables(embeddables);
+
+            generator.addQueries(map.getQueryDescriptors());
+
+            Preferences preferences = application.getPreferencesNode(GeneralPreferences.class, "");
+
+            if (preferences != null) {
+                generator.setEncoding(preferences.get(GeneralPreferences.ENCODING_PREFERENCE, null));
+            }
+
+            generator.setDestDir(outputDir);
+            generator.setMakePairs(true);
+            generator.setForce(true);
+
+            for (StandardPanelComponent dataMapLine : dataMapLines) {
+                if (dataMapLine.getDataMap() == map && !Util.isEmptyString(dataMapLine.getSuperclassPackage().getText())) {
+                    generator.setSuperPkg(dataMapLine.getSuperclassPackage().getText());
+                    break;
+                }
+            }
+
+                generators.add(generator);
+            } catch (CayenneRuntimeException exception) {
+                JOptionPane.showMessageDialog(this.getView(), exception.getUnlabeledMessage());
+                return null;
+            }
+
+
+        return generators;
+    }
+
+    public void validateEmbeddable(ValidationResult validationBuffer, Embeddable embeddable) {
+        ValidationFailure embeddableFailure = validateEmbeddable(embeddable);
+        if (embeddableFailure != null) {
+            validationBuffer.addFailure(embeddableFailure);
+            return;
+        }
+
+        for (EmbeddableAttribute attribute : embeddable.getAttributes()) {
+            ValidationFailure failure = validateEmbeddableAttribute(attribute);
+            if (failure != null) {
+                validationBuffer.addFailure(failure);
+                return;
+            }
+        }
+    }
+
+    private ValidationFailure validateEmbeddableAttribute(EmbeddableAttribute attribute) {
+        String name = attribute.getEmbeddable().getClassName();
+
+        ValidationFailure emptyName = BeanValidationFailure.validateNotEmpty(name, "attribute.name",
+                attribute.getName());
+        if (emptyName != null) {
+            return emptyName;
+        }
+
+        ValidationFailure badName = CodeValidationUtil.validateJavaIdentifier(name, "attribute.name",
+                attribute.getName());
+        if (badName != null) {
+            return badName;
+        }
+
+        ValidationFailure emptyType = BeanValidationFailure.validateNotEmpty(name, "attribute.type",
+                attribute.getType());
+        if (emptyType != null) {
+            return emptyType;
+        }
+
+        ValidationFailure badType = BeanValidationFailure.validateJavaClassName(name, "attribute.type",
+                attribute.getType());
+        if (badType != null) {
+            return badType;
+        }
+
+        return null;
+    }
+
+    protected ValidationFailure validateEmbeddable(Embeddable embeddable) {
+
+        String name = embeddable.getClassName();
+
+        ValidationFailure emptyClass = BeanValidationFailure.validateNotEmpty(name, "className",
+                embeddable.getClassName());
+        if (emptyClass != null) {
+            return emptyClass;
+        }
+
+        ValidationFailure badClass = BeanValidationFailure.validateJavaClassName(name, "className",
+                embeddable.getClassName());
+        if (badClass != null) {
+            return badClass;
+        }
+
+        return null;
+    }
+
+    public void validateEntity(ValidationResult validationBuffer, ObjEntity entity, boolean clientValidation) {
+
+        ValidationFailure entityFailure = validateEntity(clientValidation ? entity.getClientEntity() : entity);
+        if (entityFailure != null) {
+            validationBuffer.addFailure(entityFailure);
+            return;
+        }
+
+        for (ObjAttribute attribute : entity.getAttributes()) {
+            if (attribute instanceof EmbeddedAttribute) {
+                EmbeddedAttribute embeddedAttribute = (EmbeddedAttribute) attribute;
+                for (ObjAttribute subAttribute : embeddedAttribute.getAttributes()) {
+                    ValidationFailure failure = validateEmbeddedAttribute(subAttribute);
+                    if (failure != null) {
+                        validationBuffer.addFailure(failure);
+                        return;
+                    }
+                }
+            } else {
+
+                ValidationFailure failure = validateAttribute(attribute);
+                if (failure != null) {
+                    validationBuffer.addFailure(failure);
+                    return;
+                }
+            }
+        }
+
+        for (ObjRelationship rel : entity.getRelationships()) {
+            ValidationFailure failure = validateRelationship(rel, clientValidation);
+            if (failure != null) {
+                validationBuffer.addFailure(failure);
+                return;
+            }
+        }
+    }
+
+    protected ValidationFailure validateEntity(ObjEntity entity) {
+
+        String name = entity.getName();
+
+        if (entity.isGeneric()) {
+            return new SimpleValidationFailure(name, "Generic class");
+        }
+
+        ValidationFailure emptyClass = BeanValidationFailure.validateNotEmpty(name, "className", entity.getClassName());
+        if (emptyClass != null) {
+            return emptyClass;
+        }
+
+        ValidationFailure badClass = BeanValidationFailure.validateJavaClassName(name, "className",
+                entity.getClassName());
+        if (badClass != null) {
+            return badClass;
+        }
+
+        if (entity.getSuperClassName() != null) {
+            ValidationFailure badSuperClass = BeanValidationFailure.validateJavaClassName(name, "superClassName",
+                    entity.getSuperClassName());
+            if (badSuperClass != null) {
+                return badSuperClass;
+            }
+        }
+
+        return null;
+    }
+
+    protected ValidationFailure validateAttribute(ObjAttribute attribute) {
+
+        String name = attribute.getEntity().getName();
+
+        ValidationFailure emptyName = BeanValidationFailure.validateNotEmpty(name, "attribute.name",
+                attribute.getName());
+        if (emptyName != null) {
+            return emptyName;
+        }
+
+        ValidationFailure badName = CodeValidationUtil.validateJavaIdentifier(name, "attribute.name",
+                attribute.getName());
+        if (badName != null) {
+            return badName;
+        }
+
+        ValidationFailure emptyType = BeanValidationFailure.validateNotEmpty(name, "attribute.type",
+                attribute.getType());
+        if (emptyType != null) {
+            return emptyType;
+        }
+
+        ValidationFailure badType = BeanValidationFailure.validateJavaClassName(name, "attribute.type",
+                attribute.getType());
+        if (badType != null) {
+            return badType;
+        }
+
+        return null;
+    }
+
+    protected ValidationFailure validateEmbeddedAttribute(ObjAttribute attribute) {
+
+        String name = attribute.getEntity().getName();
+
+        // validate embeddedAttribute and attribute names
+        // embeddedAttribute returned attibute as
+        // [name_embeddedAttribute].[name_attribute]
+        String[] attributes = attribute.getName().split("\\.");
+        String nameEmbeddedAttribute = attributes[0];
+        int beginIndex = attributes[0].length();
+        String attr = attribute.getName().substring(beginIndex + 1);
+
+        ValidationFailure emptyEmbeddedName = BeanValidationFailure.validateNotEmpty(name, "attribute.name",
+                nameEmbeddedAttribute);
+        if (emptyEmbeddedName != null) {
+            return emptyEmbeddedName;
+        }
+
+        ValidationFailure badEmbeddedName = CodeValidationUtil.validateJavaIdentifier(name, "attribute.name",
+                nameEmbeddedAttribute);
+        if (badEmbeddedName != null) {
+            return badEmbeddedName;
+        }
+
+        ValidationFailure emptyName = BeanValidationFailure.validateNotEmpty(name, "attribute.name", attr);
+        if (emptyName != null) {
+            return emptyName;
+        }
+
+        ValidationFailure badName = CodeValidationUtil.validateJavaIdentifier(name, "attribute.name", attr);
+        if (badName != null) {
+            return badName;
+        }
+
+        ValidationFailure emptyType = BeanValidationFailure.validateNotEmpty(name, "attribute.type",
+                attribute.getType());
+        if (emptyType != null) {
+            return emptyType;
+        }
+
+        ValidationFailure badType = BeanValidationFailure.validateJavaClassName(name, "attribute.type",
+                attribute.getType());
+        if (badType != null) {
+            return badType;
+        }
+
+        return null;
+    }
+
+    protected ValidationFailure validateRelationship(ObjRelationship relationship, boolean clientValidation) {
+
+        String name = relationship.getSourceEntity().getName();
+
+        ValidationFailure emptyName = BeanValidationFailure.validateNotEmpty(name, "relationship.name",
+                relationship.getName());
+        if (emptyName != null) {
+            return emptyName;
+        }
+
+        ValidationFailure badName = CodeValidationUtil.validateJavaIdentifier(name, "relationship.name",
+                relationship.getName());
+        if (badName != null) {
+            return badName;
+        }
+
+        if (!relationship.isToMany()) {
+
+            ObjEntity targetEntity = relationship.getTargetEntity();
+
+            if (clientValidation && targetEntity != null) {
+                targetEntity = targetEntity.getClientEntity();
+            }
+
+            if (targetEntity == null) {
+
+                return new BeanValidationFailure(name, "relationship.targetEntity", "No target entity");
+            } else if (!targetEntity.isGeneric()) {
+                ValidationFailure emptyClass = BeanValidationFailure.validateNotEmpty(name,
+                        "relationship.targetEntity.className", targetEntity.getClassName());
+                if (emptyClass != null) {
+                    return emptyClass;
+                }
+
+                ValidationFailure badClass = BeanValidationFailure.validateJavaClassName(name,
+                        "relationship.targetEntity.className", targetEntity.getClassName());
+                if (badClass != null) {
+                    return badClass;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns a predicate for default entity selection in a given mode.
+     */
+    public Predicate getDefaultClassFilter() {
+        final ObjEntity selectedEntity = Application.getInstance().getFrameController().getProjectController()
+                .getCurrentObjEntity();
+
+        final Embeddable selectedEmbeddable = Application.getInstance().getFrameController().getProjectController()
+                .getCurrentEmbeddable();
+
+        if (selectedEntity != null) {
+            // select a single entity
+            final boolean hasProblem = getParentController().getProblem(selectedEntity.getName()) != null;
+            return object -> !hasProblem && object == selectedEntity;
+        } else if (selectedEmbeddable != null) {
+            // select a single embeddable
+            final boolean hasProblem = getParentController().getProblem(selectedEmbeddable.getClassName()) != null;
+            return object -> !hasProblem && object == selectedEmbeddable;
+        } else {
+            // select all entities
+            return object -> {
+                if (object instanceof ObjEntity) {
+                    return getParentController().getProblem(((ObjEntity) object).getName()) == null;
+                }
+
+                if (object instanceof Embeddable) {
+                    return getParentController().getProblem(((Embeddable) object).getClassName()) == null;
+                }
+
+                return false;
+            };
+        }
+    }
+
+    public File getOutputDir() {
+        String dir = ((GeneratorControllerPanel) getView()).getOutputFolder().getText();
+        return dir != null ? new File(dir) : new File(System.getProperty("user.dir"));
+    }
+
+    /**
+     * An action method that pops up a file chooser dialog to pick the
+     * generation directory.
+     */
+    public void selectOutputFolderAction() {
+
+        JTextField outputFolder = ((GeneratorControllerPanel) getView()).getOutputFolder();
+
+        String currentDir = outputFolder.getText();
+
+        JFileChooser chooser = new JFileChooser();
+        chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+        chooser.setDialogType(JFileChooser.OPEN_DIALOG);
+
+        // guess start directory
+        if (!Util.isEmptyString(currentDir)) {
+            chooser.setCurrentDirectory(new File(currentDir));
+        } else {
+            FSPath lastDir = Application.getInstance().getFrameController().getLastDirectory();
+            lastDir.updateChooser(chooser);
+        }
+
+        int result = chooser.showOpenDialog(getView());
+        if (result == JFileChooser.APPROVE_OPTION) {
+            File selected = chooser.getSelectedFile();
+
+            // update model
+            String path = selected.getAbsolutePath();
+            outputFolder.setText(path);
+            setOutputPath(path);
+        }
+    }
+
+    private void initOutputFolder() {
+        String path;
+        if (getOutputPath() == null) {
+            if (System.getProperty("cayenne.cgen.destdir") != null) {
+                setOutputPath(System.getProperty("cayenne.cgen.destdir"));
+            } else {
+                // init default directory..
+                FSPath lastPath = Application.getInstance().getFrameController().getLastDirectory();
+
+                path = checkDefaultMavenResourceDir(lastPath, "test");
+
+                if (path != null || (path = checkDefaultMavenResourceDir(lastPath, "main")) != null) {
+                    setOutputPath(path);
+                } else {
+                    File lastDir = (lastPath != null) ? lastPath.getExistingDirectory(false) : null;
+                    setOutputPath(lastDir != null ? lastDir.getAbsolutePath() : null);
+                }
+            }
+        }
+    }
+
+    private String checkDefaultMavenResourceDir(FSPath lastPath, String dirType) {
+        String path = lastPath.getPath();
+        String resourcePath = buildFilePath("src", dirType, "resources");
+        int idx = path.indexOf(resourcePath);
+        if (idx < 0) {
+            return null;
+        }
+        return path.substring(0, idx) + buildFilePath("src", dirType, "java");
+    }
+
+    private static String buildFilePath(String... pathElements) {
+        if (pathElements.length == 0) {
+            return "";
+        }
+        StringBuilder path = new StringBuilder(pathElements[0]);
+        for (int i = 1; i < pathElements.length; i++) {
+            path.append(File.separator).append(pathElements[i]);
+        }
+        return path.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/8119ffaa/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/GeneratorControllerPanel.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/GeneratorControllerPanel.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/GeneratorControllerPanel.java
new file mode 100644
index 0000000..9b836bd
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/cgen/GeneratorControllerPanel.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.modeler.editor.cgen;
+
+import javax.swing.*;
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * A generic panel that is a superclass of generator panels, defining common fields.
+ * 
+ */
+public class GeneratorControllerPanel extends JPanel {
+
+    protected Collection<StandardPanelComponent> dataMapLines;
+    protected JTextField outputFolder;
+    protected JButton selectOutputFolder;
+
+    public GeneratorControllerPanel() {
+        this.dataMapLines = new ArrayList<>();
+        this.outputFolder = new JTextField();
+        this.selectOutputFolder = new JButton("Select");
+    }
+
+    public JTextField getOutputFolder() {
+        return outputFolder;
+    }
+
+    public JButton getSelectOutputFolder() {
+        return selectOutputFolder;
+    }
+
+    public Collection<StandardPanelComponent> getDataMapLines() {
+        return dataMapLines;
+    }
+}