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 2022/09/29 14:35:52 UTC

[cayenne] branch master updated (9a3cd2f46 -> feb1b89f9)

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

ntimofeev pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git


    from 9a3cd2f46 Merge pull request #520 from Ivan-nikitko/CAY-2758_refactoring_for_a_extended_TemplateType
     new 652ede964 template editor feature
     new 9cdf378e1 Merge remote-tracking branch 'parent/pr/522' into asf-master
     new feb1b89f9 CAY-2761 Modeler: Editor for the cgen templates  - minor code cleanup

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../apache/cayenne/gen/ClassGenerationAction.java  |   19 +-
 modeler/cayenne-modeler/pom.xml                    |    6 +
 .../modeler/dialog/pref/TemplatePreferences.java   |   26 +-
 .../dialog/pref/TemplatePreferencesView.java       |    7 +
 .../dialog/templateeditor/FileTemplateSaver.java   |   46 +-
 .../templateeditor/FindAndReplaceController.java   |  121 +++
 .../dialog/templateeditor/FindAndReplaceView.java  |   88 ++
 .../dialog/templateeditor/FindController.java      |  110 ++
 .../modeler/dialog/templateeditor/FindView.java    |  101 ++
 .../PreviewClassGenerationFactory.java             |   26 +-
 .../templateeditor/PreviewGenerationAction.java    |   45 +-
 .../templateeditor/TemplateEditorController.java   |  198 ++++
 .../dialog/templateeditor/TemplateEditorView.java  |  187 ++++
 .../dialog/templateeditor/TemplateLoader.java      |   58 ++
 .../dialog/templateeditor/VelocityTokenMaker.java  | 1065 ++++++++++++++++++++
 .../modeler/images/icon-find_and_replace.png       |  Bin 0 -> 620 bytes
 pom.xml                                            |    5 +
 17 files changed, 2053 insertions(+), 55 deletions(-)
 copy cayenne-server/src/main/java/org/apache/cayenne/configuration/xml/DefaultHandlerFactory.java => modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/FileTemplateSaver.java (50%)
 create mode 100644 modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/FindAndReplaceController.java
 create mode 100644 modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/FindAndReplaceView.java
 create mode 100644 modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/FindController.java
 create mode 100644 modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/FindView.java
 copy cayenne-cgen/src/main/java/org/apache/cayenne/gen/DefaultClassGenerationActionFactory.java => modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/PreviewClassGenerationFactory.java (61%)
 copy cayenne-cgen/src/test/java/org/apache/cayenne/gen/mock/TestClassGenerationAction.java => modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/PreviewGenerationAction.java (51%)
 create mode 100644 modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/TemplateEditorController.java
 create mode 100644 modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/TemplateEditorView.java
 create mode 100644 modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/TemplateLoader.java
 create mode 100644 modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/VelocityTokenMaker.java
 create mode 100644 modeler/cayenne-modeler/src/main/resources/org/apache/cayenne/modeler/images/icon-find_and_replace.png


[cayenne] 02/03: Merge remote-tracking branch 'parent/pr/522' into asf-master

Posted by nt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ntimofeev pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git

commit 9cdf378e1dace9f0964e2f4e7d15050ed6ce504b
Merge: 3b0e9a1fe 652ede964
Author: Nikita Timofeev <st...@gmail.com>
AuthorDate: Thu Sep 29 15:41:40 2022 +0300

    Merge remote-tracking branch 'parent/pr/522' into asf-master

 .../apache/cayenne/tools/CayenneGeneratorTask.java |    7 +-
 .../org/apache/cayenne/gen/CgenConfiguration.java  |   16 +-
 .../apache/cayenne/gen/ClassGenerationAction.java  |   57 +-
 .../cayenne/gen/SingleClassGenerationTest.java     |    8 +-
 .../cayenne/gen/SuperClassGenerationTest.java      |    8 +-
 .../java/org/apache/cayenne/tools/CgenTask.java    |    7 +-
 .../apache/cayenne/tools/CayenneGeneratorMojo.java |    7 +-
 modeler/cayenne-modeler/pom.xml                    |    6 +
 .../modeler/dialog/cgen/TemplateDialog.java        |    5 +-
 .../modeler/dialog/pref/TemplatePreferences.java   |    9 +-
 .../dialog/pref/TemplatePreferencesView.java       |    7 +
 .../dialog/templateeditor/FileTemplateSaver.java   |   60 ++
 .../templateeditor/FindAndReplaceController.java   |  121 +++
 .../dialog/templateeditor/FindAndReplaceView.java  |   88 ++
 .../dialog/templateeditor/FindController.java      |  110 ++
 .../modeler/dialog/templateeditor/FindView.java    |  101 ++
 .../PreviewClassGenerationFactory.java             |   55 +
 .../templateeditor/PreviewGenerationAction.java    |   64 ++
 .../templateeditor/TemplateEditorController.java   |  201 ++++
 .../dialog/templateeditor/TemplateEditorView.java  |  187 ++++
 .../dialog/templateeditor/TemplateLoader.java      |   58 ++
 .../dialog/templateeditor/VelocityTokenMaker.java  | 1065 ++++++++++++++++++++
 .../editor/cgen/StandardModeController.java        |   16 +-
 .../modeler/images/icon-find_and_replace.png       |  Bin 0 -> 620 bytes
 pom.xml                                            |    5 +
 25 files changed, 2187 insertions(+), 81 deletions(-)


[cayenne] 01/03: template editor feature

Posted by nt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ntimofeev pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git

commit 652ede9647d0db7508995b11304312d5b2cc4884
Author: Ivan Nikitka <70...@users.noreply.github.com>
AuthorDate: Fri Sep 23 16:35:04 2022 +0200

    template editor feature
---
 .../apache/cayenne/gen/ClassGenerationAction.java  |   19 +-
 modeler/cayenne-modeler/pom.xml                    |    6 +
 .../modeler/dialog/pref/TemplatePreferences.java   |    9 +-
 .../dialog/pref/TemplatePreferencesView.java       |    7 +
 .../dialog/templateeditor/FileTemplateSaver.java   |   60 ++
 .../templateeditor/FindAndReplaceController.java   |  121 +++
 .../dialog/templateeditor/FindAndReplaceView.java  |   88 ++
 .../dialog/templateeditor/FindController.java      |  110 ++
 .../modeler/dialog/templateeditor/FindView.java    |  101 ++
 .../PreviewClassGenerationFactory.java             |   55 +
 .../templateeditor/PreviewGenerationAction.java    |   64 ++
 .../templateeditor/TemplateEditorController.java   |  201 ++++
 .../dialog/templateeditor/TemplateEditorView.java  |  187 ++++
 .../dialog/templateeditor/TemplateLoader.java      |   58 ++
 .../dialog/templateeditor/VelocityTokenMaker.java  | 1065 ++++++++++++++++++++
 .../modeler/images/icon-find_and_replace.png       |  Bin 0 -> 620 bytes
 pom.xml                                            |    5 +
 17 files changed, 2147 insertions(+), 9 deletions(-)

diff --git a/cayenne-cgen/src/main/java/org/apache/cayenne/gen/ClassGenerationAction.java b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/ClassGenerationAction.java
index 8a5ca11b6..17315e01a 100644
--- a/cayenne-cgen/src/main/java/org/apache/cayenne/gen/ClassGenerationAction.java
+++ b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/ClassGenerationAction.java
@@ -270,13 +270,7 @@ public class ClassGenerationAction {
 
 		if (template == null) {
 			Properties props = new Properties();
-
-			props.put("resource.loaders", "cayenne");
-			props.put("resource.loader.cayenne.class", ClassGeneratorResourceLoader.class.getName());
-			props.put("resource.loader.cayenne.cache", "false");
-			if (cgenConfiguration.getRootPath() != null) {
-				props.put("resource.loader.cayenne.root", cgenConfiguration.getRootPath().toString());
-			}
+			initVelocityProperties(props);
 
 			VelocityEngine velocityEngine = new VelocityEngine();
 			velocityEngine.init(props);
@@ -288,12 +282,21 @@ public class ClassGenerationAction {
 		return template;
 	}
 
+	protected void initVelocityProperties(Properties props) {
+		props.put("resource.loaders", "cayenne");
+		props.put("resource.loader.cayenne.class", ClassGeneratorResourceLoader.class.getName());
+		props.put("resource.loader.cayenne.cache", "false");
+		if (cgenConfiguration.getRootPath() != null) {
+			props.put("resource.loader.cayenne.root", cgenConfiguration.getRootPath().toString());
+		}
+	}
+
 	/**
 	 * Validates the state of this class generator.
 	 * Throws CayenneRuntimeException if it is in an inconsistent state.
 	 * Called internally from "execute".
 	 */
-	private void validateAttributes() {
+	protected void validateAttributes() {
 		Path dir = cgenConfiguration.buildPath();
 		if (dir == null) {
 			throw new CayenneRuntimeException("'rootPath' attribute is missing.");
diff --git a/modeler/cayenne-modeler/pom.xml b/modeler/cayenne-modeler/pom.xml
index 4cff481c9..0a2bf1707 100644
--- a/modeler/cayenne-modeler/pom.xml
+++ b/modeler/cayenne-modeler/pom.xml
@@ -101,5 +101,11 @@
 			<artifactId>junit</artifactId>
 			<scope>test</scope>
 		</dependency>
+
+		<dependency>
+			<groupId>com.fifesoft</groupId>
+			<artifactId>rsyntaxtextarea</artifactId>
+		</dependency>
+
 	</dependencies>
 </project>
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/pref/TemplatePreferences.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/pref/TemplatePreferences.java
index d531be754..520a5cf59 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/pref/TemplatePreferences.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/pref/TemplatePreferences.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.modeler.dialog.pref;
 
 import org.apache.cayenne.modeler.CodeTemplateManager;
+import org.apache.cayenne.modeler.dialog.templateeditor.TemplateEditorController;
 import org.apache.cayenne.modeler.pref.FSPath;
 import org.apache.cayenne.modeler.util.CayenneController;
 import org.apache.cayenne.pref.CayennePreferenceEditor;
@@ -89,6 +90,7 @@ public class TemplatePreferences extends CayenneController {
         builder.bindToAction(view.getAddButton(), "addTemplateAction()");
         builder.bindToAction(view.getCreateFromDefaultButton(), "createFromDefaultAction()");
         builder.bindToAction(view.getRemoveButton(), "removeTemplateAction()");
+        builder.bindToAction(view.getTemplateEditorButton(), "templateEditorAction()");
 
         TableBindingBuilder tableBuilder = new TableBindingBuilder(builder);
 
@@ -123,6 +125,11 @@ public class TemplatePreferences extends CayenneController {
         addToTemplateEntries(path);
     }
 
+    @SuppressWarnings("unused")
+    public void templateEditorAction() {
+         new TemplateEditorController(this).startupAction();
+    }
+
     @SuppressWarnings("unused")
     public void createFromDefaultAction() {
         List<FSPath> paths = new CreateTemplateFromDefaultController(this).startupAction();
@@ -150,7 +157,7 @@ public class TemplatePreferences extends CayenneController {
         addToTemplateEntries(path);
     }
 
-    private void addToTemplateEntries(FSPath path) {
+    public void addToTemplateEntries(FSPath path) {
         if (path != null) {
             int len = templateEntries.size();
             templateEntries.add(path);
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/pref/TemplatePreferencesView.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/pref/TemplatePreferencesView.java
index 5e35f3fb7..71466287c 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/pref/TemplatePreferencesView.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/pref/TemplatePreferencesView.java
@@ -36,6 +36,7 @@ public class TemplatePreferencesView extends JPanel {
     protected JButton addButton;
     protected JButton createFromDefaultButton;
     protected JButton removeButton;
+    protected JButton templateEditorButton;
     protected JTable table;
 
     public TemplatePreferencesView() {
@@ -43,6 +44,7 @@ public class TemplatePreferencesView extends JPanel {
         // create widgets
         addButton = new JButton("Add Template");
         createFromDefaultButton = new JButton("Create from default");
+        templateEditorButton = new JButton("Edit template");
         removeButton = new JButton("Remove Template");
 
         table = new CayenneTable();
@@ -57,6 +59,7 @@ public class TemplatePreferencesView extends JPanel {
 
         builder.append(addButton);
         builder.append(createFromDefaultButton);
+        builder.append(templateEditorButton);
         builder.append(removeButton);
 
         setLayout(new BorderLayout());
@@ -81,4 +84,8 @@ public class TemplatePreferencesView extends JPanel {
     public JButton getRemoveButton() {
         return removeButton;
     }
+
+    public JButton getTemplateEditorButton() {
+        return templateEditorButton;
+    }
 }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/FileTemplateSaver.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/FileTemplateSaver.java
new file mode 100644
index 000000000..c735af63d
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/FileTemplateSaver.java
@@ -0,0 +1,60 @@
+/*****************************************************************
+ *   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
+ *
+ *    https://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.dialog.templateeditor;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.SwingConstants;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+
+/**
+ * since 4.3
+ */
+public class FileTemplateSaver  {
+
+    private static final Logger logger = LoggerFactory.getLogger(FileTemplateSaver.class);
+
+    public void save(String templateText, File dest, JDialog view) {
+        try {
+            Files.write(dest.toPath(), templateText.getBytes());
+        } catch (IOException e) {
+            JOptionPane.showMessageDialog(
+                    view,
+                    "File writing error \n" + dest,
+                    "Error",
+                    JOptionPane.WARNING_MESSAGE);
+            logger.warn("File writing error {}", dest);
+        }
+        JOptionPane.showMessageDialog(
+                view,
+                new JLabel("The changes in the \n"
+                        + dest
+                        + "\n have been saved", SwingConstants.CENTER),
+                "Message",
+                JOptionPane.PLAIN_MESSAGE);
+        logger.info("Change the template {}", dest);
+    }
+}
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/FindAndReplaceController.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/FindAndReplaceController.java
new file mode 100644
index 000000000..3b88e0306
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/FindAndReplaceController.java
@@ -0,0 +1,121 @@
+/*****************************************************************
+ *   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
+ *
+ *    https://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.dialog.templateeditor;
+
+import org.fife.ui.rtextarea.SearchContext;
+import org.fife.ui.rtextarea.SearchEngine;
+
+import javax.swing.JButton;
+import javax.swing.JOptionPane;
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+/**
+ * since 4.3
+ */
+public class FindAndReplaceController extends FindController implements ActionListener {
+
+    private  FindAndReplaceView view;
+    private  JButton nextButton;
+    private  JButton replaceButton;
+    private  JButton replaceAllButton;
+
+    public FindAndReplaceController(TemplateEditorController parent) {
+        super(parent);
+        initComponents();
+        initListeners();
+    }
+
+    @Override
+    protected void initComponents() {
+        this.view = new FindAndReplaceView();
+        this.nextButton = view.getNextButton();
+        this.replaceButton = view.getReplaceButton();
+        this.replaceAllButton = view.getReplaceAllButton();
+    }
+
+    @Override
+    protected void initListeners() {
+        nextButton.setActionCommand("findNext");
+        nextButton.addActionListener(this);
+        replaceButton.setActionCommand("replace");
+        replaceButton.addActionListener(this);
+        replaceAllButton.setActionCommand("replaceAll");
+        replaceAllButton.addActionListener(this);
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        String command = e.getActionCommand();
+        SearchContext context = getSearchContext(true,view);
+        if (context == null) return;
+        switch (command) {
+            case "findNext": {
+                boolean found = SearchEngine.find(parentView.getEditingTemplatePane(), context).wasFound();
+                if (!found) {
+                    JOptionPane.showMessageDialog(view, TEXT_NOT_FOUND_MSG);
+                }
+                break;
+            }
+            case "replace": {
+                boolean found = SearchEngine.replace(parentView.getEditingTemplatePane(), context).wasFound();
+                if (!found) {
+                    JOptionPane.showMessageDialog(view, TEXT_NOT_FOUND_MSG);
+                }
+                break;
+            }
+            case "replaceAll": {
+                boolean found = SearchEngine.replaceAll(parentView.getEditingTemplatePane(), context).wasFound();
+                if (!found) {
+                    JOptionPane.showMessageDialog(view, TEXT_NOT_FOUND_MSG);
+                }
+                break;
+            }
+            default:
+                break;
+        }
+    }
+
+    @Override
+    protected SearchContext getSearchContext(boolean forward, FindView view) {
+        SearchContext context = super.getSearchContext(forward,view);
+        context.setReplaceWith(this.view.getReplaceWithField().getText());
+        return context;
+    }
+
+    @Override
+    public Component getView() {
+        return view;
+    }
+
+    /**
+     * Pops up a dialog and blocks current thread until the dialog is closed.
+     */
+    @Override
+    public void startupAction() {
+        view.setModal(true);
+        view.pack();
+        view.setResizable(false);
+        makeCloseableOnEscape();
+        centerView();
+        view.setVisible(true);
+    }
+}
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/FindAndReplaceView.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/FindAndReplaceView.java
new file mode 100644
index 000000000..19a9b7eb0
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/FindAndReplaceView.java
@@ -0,0 +1,88 @@
+/*****************************************************************
+ *   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
+ *
+ *    https://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.dialog.templateeditor;
+
+import com.jgoodies.forms.builder.PanelBuilder;
+import com.jgoodies.forms.layout.CellConstraints;
+import com.jgoodies.forms.layout.FormLayout;
+
+import javax.swing.JButton;
+import javax.swing.JTextField;
+import java.awt.BorderLayout;
+
+/**
+ * since 4.3
+ */
+public class FindAndReplaceView extends FindView {
+
+    private JTextField replaceWithField;
+    private JButton replaceButton;
+    private JButton replaceAllButton;
+
+    public FindAndReplaceView() {
+        initComponents();
+        buildView();
+    }
+
+    @Override
+    protected void initComponents() {
+        super.initComponents();
+        this.replaceWithField = new JTextField();
+        this.replaceButton = new JButton("Replace");
+        this.replaceAllButton = new JButton("Replace all");
+    }
+
+    @Override
+    protected void buildView() {
+        this.setTitle("Find and replace dialog");
+        CellConstraints cc = new CellConstraints();
+        PanelBuilder builder = new PanelBuilder(new FormLayout(COLUMN_SPECS, ROW_SPECS));
+        builder.setDefaultDialogBorder();
+
+        builder.addLabel("Find what:", cc.xy(1, 1));
+        builder.add(searchField, cc.xyw(3, 1, 3));
+        builder.addLabel("Replace with:", cc.xy(1, 3));
+        builder.add(replaceWithField, cc.xyw(3, 3, 3));
+        builder.add(regexCB, cc.xy(1, 5));
+        builder.add(matchCaseCB, cc.xy(3, 5));
+        builder.add(wholeWordCB, cc.xy(5, 5));
+
+        builder.add(nextButton, cc.xy(7, 1));
+        builder.add(replaceButton, cc.xy(7, 3));
+        builder.add(replaceAllButton, cc.xy(7, 5));
+
+        getContentPane().setLayout(new BorderLayout());
+        getContentPane().add(builder.getPanel(), BorderLayout.CENTER);
+    }
+
+    public JTextField getReplaceWithField() {
+        return replaceWithField;
+    }
+
+    public JButton getReplaceButton() {
+        return replaceButton;
+    }
+
+    public JButton getReplaceAllButton() {
+        return replaceAllButton;
+    }
+
+
+}
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/FindController.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/FindController.java
new file mode 100644
index 000000000..8515f012e
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/FindController.java
@@ -0,0 +1,110 @@
+/*****************************************************************
+ *   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
+ *
+ *    https://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.dialog.templateeditor;
+
+import org.apache.cayenne.modeler.util.CayenneController;
+import org.apache.cayenne.util.Util;
+import org.fife.ui.rtextarea.SearchContext;
+import org.fife.ui.rtextarea.SearchEngine;
+
+import javax.swing.JButton;
+import javax.swing.JOptionPane;
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+/**
+ * since 4.3
+ */
+public class FindController extends CayenneController implements ActionListener {
+
+    private  FindView view;
+    protected TemplateEditorView parentView;
+    private  JButton nextButton;
+    private  JButton prevButton;
+    protected static final String TEXT_NOT_FOUND_MSG = "Text not found";
+
+    public FindController(TemplateEditorController parent) {
+        super(parent);
+        this.parentView = (TemplateEditorView) parent.getView();
+        initComponents();
+        initListeners();
+    }
+
+    protected void initComponents() {
+        this.view = new FindView();
+        this.nextButton = this.view.getNextButton();
+        this.prevButton = this.view.getPrevButton();
+    }
+
+    @Override
+    public Component getView() {
+        return view;
+    }
+
+    protected void initListeners() {
+        nextButton.setActionCommand("FindNext");
+        nextButton.addActionListener(this);
+        prevButton.setActionCommand("FindPrev");
+        prevButton.addActionListener(this);
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+
+        // "FindNext" => search forward, "FindPrev" => search backward
+        String command = e.getActionCommand();
+        boolean forward = "FindNext".equals(command);
+
+        SearchContext context = getSearchContext(forward, view);
+        if (context == null) return;
+
+        boolean found = SearchEngine.find(parentView.getEditingTemplatePane(), context).wasFound();
+        if (!found) {
+            JOptionPane.showMessageDialog(view, TEXT_NOT_FOUND_MSG);
+        }
+    }
+
+    protected SearchContext getSearchContext(boolean forward, FindView view) {
+        SearchContext context = new SearchContext();
+        String text = view.getSearchField().getText();
+        if (Util.isEmptyString(text)) {
+            return null;
+        }
+        context.setSearchFor(text);
+        context.setMatchCase(view.getMatchCaseCB().isSelected());
+        context.setRegularExpression(view.getRegexCB().isSelected());
+        context.setWholeWord(view.getWholeWordCB().isSelected());
+        context.setSearchForward(forward);
+        return context;
+    }
+
+    /**
+     * Pops up a dialog and blocks current thread until the dialog is closed.
+     */
+    public void startupAction() {
+        view.setModal(true);
+        view.pack();
+        view.setResizable(false);
+        makeCloseableOnEscape();
+        centerView();
+        view.setVisible(true);
+    }
+}
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/FindView.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/FindView.java
new file mode 100644
index 000000000..118d1e5a4
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/FindView.java
@@ -0,0 +1,101 @@
+/*****************************************************************
+ *   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
+ *
+ *    https://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.dialog.templateeditor;
+
+import com.jgoodies.forms.builder.PanelBuilder;
+import com.jgoodies.forms.layout.CellConstraints;
+import com.jgoodies.forms.layout.FormLayout;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JDialog;
+import javax.swing.JTextField;
+import java.awt.BorderLayout;
+
+/**
+ * since 4.3
+ */
+public class FindView extends JDialog {
+
+    protected JTextField searchField;
+    protected JCheckBox regexCB;
+    protected JCheckBox matchCaseCB;
+    protected JCheckBox wholeWordCB;
+    protected JButton nextButton;
+    private JButton prevButton;
+    protected static final String COLUMN_SPECS = "left:pref, 3dlu, left:pref,3dlu, 110dlu, 3dlu, fill:p:grow";
+    protected static final String ROW_SPECS = "4 * (p, 3dlu)";
+
+    public FindView() {
+        initComponents();
+        buildView();
+    }
+
+    protected void initComponents() {
+        this.searchField = new JTextField();
+        this.regexCB = new JCheckBox("Regex");
+        this.matchCaseCB = new JCheckBox("Match Case");
+        this.wholeWordCB = new JCheckBox("Whole word");
+        this.nextButton = new JButton("Find Next");
+        this.prevButton = new JButton("Find Previous");
+    }
+
+    protected void buildView() {
+        this.setTitle("Find dialog");
+        CellConstraints cc = new CellConstraints();
+        PanelBuilder builder = new PanelBuilder(new FormLayout(COLUMN_SPECS, ROW_SPECS));
+        builder.setDefaultDialogBorder();
+
+        builder.addLabel("Find what:", cc.xy(1, 1));
+        builder.add(searchField, cc.xyw(3, 1, 3));
+        builder.add(regexCB, cc.xy(1, 3));
+        builder.add(matchCaseCB, cc.xy(3, 3));
+        builder.add(wholeWordCB, cc.xy(5, 3));
+        builder.add(nextButton, cc.xy(7, 1));
+        builder.add(prevButton, cc.xy(7, 3));
+
+        getContentPane().setLayout(new BorderLayout());
+        getContentPane().add(builder.getPanel(), BorderLayout.CENTER);
+    }
+
+    public JTextField getSearchField() {
+        return searchField;
+    }
+
+    public JCheckBox getRegexCB() {
+        return regexCB;
+    }
+
+    public JCheckBox getMatchCaseCB() {
+        return matchCaseCB;
+    }
+
+    public JButton getNextButton() {
+        return nextButton;
+    }
+
+    public JButton getPrevButton() {
+        return prevButton;
+    }
+
+    public JCheckBox getWholeWordCB() {
+        return wholeWordCB;
+    }
+}
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/PreviewClassGenerationFactory.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/PreviewClassGenerationFactory.java
new file mode 100644
index 000000000..f8bba275b
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/PreviewClassGenerationFactory.java
@@ -0,0 +1,55 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    https://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.dialog.templateeditor;
+
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.gen.CgenConfiguration;
+import org.apache.cayenne.gen.ClassGenerationAction;
+import org.apache.cayenne.gen.ClassGenerationActionFactory;
+import org.apache.cayenne.gen.MetadataUtils;
+import org.apache.cayenne.gen.ToolsUtilsFactory;
+
+import java.io.StringWriter;
+
+
+/**
+ * @since 4.3
+ */
+public class PreviewClassGenerationFactory implements ClassGenerationActionFactory {
+
+    @Inject
+    private ToolsUtilsFactory utilsFactory;
+
+    @Inject
+    private MetadataUtils metadataUtils;
+
+    @Inject
+    private StringWriter writer;
+
+    @Override
+    public ClassGenerationAction createAction(CgenConfiguration cgenConfiguration) {
+        PreviewGenerationAction action = new PreviewGenerationAction(cgenConfiguration);
+        action.setUtilsFactory(utilsFactory);
+        action.setMetadataUtils(metadataUtils);
+        action.setWriter(writer);
+        return action;
+    }
+
+}
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/PreviewGenerationAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/PreviewGenerationAction.java
new file mode 100644
index 000000000..7be3de7aa
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/PreviewGenerationAction.java
@@ -0,0 +1,64 @@
+/*****************************************************************
+ *   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
+ *
+ *    https://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.dialog.templateeditor;
+
+
+import org.apache.cayenne.gen.CgenConfiguration;
+import org.apache.cayenne.gen.ClassGenerationAction;
+import org.apache.cayenne.gen.TemplateType;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.runtime.resource.loader.StringResourceLoader;
+
+import java.io.Writer;
+import java.util.Properties;
+
+/**
+ * @since 4.3
+ */
+public class PreviewGenerationAction extends ClassGenerationAction {
+
+    private Writer writer;
+
+    public PreviewGenerationAction(CgenConfiguration cgenConfig) {
+        super(cgenConfig);
+    }
+
+    @Override
+    protected void validateAttributes() {
+        //Mock
+    }
+
+    public void setWriter(Writer writer) {
+        this.writer = writer;
+    }
+
+    @Override
+    protected Writer openWriter(TemplateType templateType) {
+        return writer;
+    }
+
+    @Override
+    protected void initVelocityProperties(Properties props) {
+        props.setProperty(RuntimeConstants.RESOURCE_LOADERS, "string");
+        props.setProperty("resource.loader.string.class", StringResourceLoader.class.getName());
+        props.setProperty("resource.loader.string.repository.name",TemplateEditorController.TEMPLATE_EDITOR_REPO);
+    }
+
+}
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/TemplateEditorController.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/TemplateEditorController.java
new file mode 100644
index 000000000..8c20aac4b
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/TemplateEditorController.java
@@ -0,0 +1,201 @@
+/*****************************************************************
+ *   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
+ *
+ *    https://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.dialog.templateeditor;
+
+import org.apache.cayenne.configuration.xml.DataChannelMetaData;
+import org.apache.cayenne.di.Injector;
+import org.apache.cayenne.gen.CgenConfiguration;
+import org.apache.cayenne.gen.ClassGenerationAction;
+import org.apache.cayenne.gen.ClassGenerationActionFactory;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.modeler.dialog.pref.TemplatePreferences;
+import org.apache.cayenne.modeler.dialog.pref.TemplatePreferencesView;
+import org.apache.cayenne.modeler.util.CayenneController;
+import org.apache.cayenne.swing.BindingBuilder;
+import org.apache.cayenne.tools.ToolsInjectorBuilder;
+import org.apache.velocity.exception.ParseErrorException;
+import org.apache.velocity.runtime.resource.loader.StringResourceLoader;
+import org.apache.velocity.runtime.resource.util.StringResourceRepository;
+import org.apache.velocity.runtime.resource.util.StringResourceRepositoryImpl;
+
+import javax.swing.text.BadLocationException;
+import java.awt.Component;
+import java.io.File;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * since 4.3
+ */
+public class TemplateEditorController extends CayenneController {
+
+    protected TemplatePreferencesView preferencesView;
+    protected TemplateEditorView view;
+    protected boolean canceled;
+    private final Map<String, String> customTemplates;
+    private static final String EDITED_TEMPLATE = "editedTemplate";
+    public static final String TEMPLATE_EDITOR_REPO = "templateEditorRepo";
+    private final DataMap currentDataMap;
+
+    public TemplateEditorController(TemplatePreferences preferences) {
+        super(preferences);
+        this.preferencesView = (TemplatePreferencesView) preferences.getView();
+        this.customTemplates = application.getCodeTemplateManager().getCustomTemplates();
+        this.currentDataMap = application.getFrameController().getProjectController().getCurrentDataMap();
+        this.view = new TemplateEditorView( getEntityNames());
+        initBindings();
+    }
+
+    public void startupAction() {
+        this.view.setModal(true);
+        this.view.getEditingTemplatePane().setText(loadSelectedTemplateText());
+        this.view.editingTemplatePane.setCaretPosition(0);
+        this.view.pack();
+        makeCloseableOnEscape();
+        centerView();
+        this.view.setVisible(true);
+    }
+
+    private String loadSelectedTemplateText() {
+        TemplateLoader templateLoader = new TemplateLoader();
+        return templateLoader.load(view, getSelectedTemplate());
+    }
+
+    public Component getView() {
+        return view;
+    }
+
+    protected void initBindings() {
+        BindingBuilder builder = new BindingBuilder(getApplication().getBindingFactory(), this);
+        builder.bindToAction(view.getSaveButton(), "saveAction()");
+        builder.bindToAction(view.getPreviewButton(), "generateAction()");
+        builder.bindToAction(view.getFindButton(), "findAction()");
+        builder.bindToAction(view.getFindAndReplaceButton(), "findAndReplaceAction()");
+    }
+
+    @SuppressWarnings("unused")
+    public void saveAction() {
+        if (getSelectedTemplate() != null) {
+            File dest = new File(getSelectedTemplate());
+            FileTemplateSaver templateSaver = new FileTemplateSaver();
+            templateSaver.save(view.getTemplateText(),dest,view);
+        }
+    }
+
+    public String getSelectedTemplate() {
+        int selectedRow = preferencesView.getTable().getSelectedRow();
+        if (selectedRow != -1) {
+            Object key = preferencesView.getTable().getModel().getValueAt(selectedRow, 0);
+            return customTemplates.get(key.toString());
+        }
+        return null;
+    }
+
+    @SuppressWarnings("unused")
+    public void generateAction() throws Exception {
+        putTemplateTextInRepository();
+        Injector injector = getInjector();
+        ClassGenerationAction action =  injector
+                .getInstance(ClassGenerationActionFactory.class)
+                .createAction(getCgenConfiguration());
+        StringWriter writer = injector.getInstance(StringWriter.class);
+    action.addEntities(Collections.singleton(getSelectedEntity()));
+        int caretPosition = view.getEditingTemplatePane().getCaretPosition();
+        try {
+            action.execute();
+        } catch (Exception e) {
+            if (e instanceof ParseErrorException) {
+                caretPosition = getErrorCaretPosition((ParseErrorException) e);
+            }
+            writer.write(e.getMessage());
+        }
+        view.getEditingTemplatePane().setCaretPosition(caretPosition);
+        view.getClassPreviewPane().setText(writer.toString());
+        view.getClassPreviewPane().setCaretPosition(0);
+    }
+
+    private void putTemplateTextInRepository() {
+        StringResourceLoader.setRepository(TEMPLATE_EDITOR_REPO, new StringResourceRepositoryImpl());
+        StringResourceRepository repo = StringResourceLoader.getRepository(TEMPLATE_EDITOR_REPO);
+        repo.putStringResource(EDITED_TEMPLATE, view.getTemplateText());
+    }
+
+    private Injector getInjector() {
+        DataChannelMetaData metaData = getApplication().getMetaData();
+        return new ToolsInjectorBuilder()
+                .addModule(binder -> binder.bind(DataChannelMetaData.class).toInstance(metaData))
+                .addModule(binder -> binder.bind(ClassGenerationActionFactory.class).to(PreviewClassGenerationFactory.class))
+                .addModule(binder -> binder.bind(StringWriter.class).to(StringWriter.class))
+                .create();
+    }
+
+    private CgenConfiguration getCgenConfiguration() {
+        CgenConfiguration cgenConfiguration = new CgenConfiguration();
+        cgenConfiguration.setMakePairs(false);
+        cgenConfiguration.setTemplate(EDITED_TEMPLATE);
+        cgenConfiguration.setDataMap(currentDataMap);
+        return cgenConfiguration;
+    }
+
+    private int getErrorCaretPosition(ParseErrorException e) throws BadLocationException {
+        int errorLineNumber = e.getLineNumber();
+        return view.getEditingTemplatePane().getLineStartOffset(errorLineNumber - 1);
+    }
+
+    @SuppressWarnings("unused")
+    public void findAction() {
+        new FindController(this).startupAction();
+    }
+
+    @SuppressWarnings("unused")
+    public void findAndReplaceAction() {
+        new FindAndReplaceController(this).startupAction();
+    }
+
+    private ObjEntity getSelectedEntity() {
+        String selectedEntityName = view.getSelectedEntityName();
+        ObjEntity selectedObject = null;
+        Collection<ObjEntity> objEntities = currentDataMap.getObjEntities();
+        for (ObjEntity object : objEntities) {
+            if (selectedEntityName.equals(object.getName())) {
+                selectedObject = object;
+            }
+        }
+        return selectedObject;
+    }
+
+    private List<String> getEntityNames() {
+        ArrayList<String> names = new ArrayList<>();
+        if (currentDataMap != null) {
+            Object[] objEntities = currentDataMap.getObjEntities().toArray();
+            for (Object objEntity : objEntities) {
+                ObjEntity s = (ObjEntity) objEntity;
+                names.add(s.getName());
+            }
+        }
+        return names;
+    }
+
+}
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/TemplateEditorView.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/TemplateEditorView.java
new file mode 100644
index 000000000..4580dbd34
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/TemplateEditorView.java
@@ -0,0 +1,187 @@
+/*****************************************************************
+ *   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
+ *
+ *    https://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.dialog.templateeditor;
+
+import com.jgoodies.forms.builder.PanelBuilder;
+import com.jgoodies.forms.layout.CellConstraints;
+import com.jgoodies.forms.layout.FormLayout;
+import org.apache.cayenne.modeler.pref.ComponentGeometry;
+import org.apache.cayenne.modeler.util.ModelerUtil;
+import org.fife.ui.rsyntaxtextarea.AbstractTokenMakerFactory;
+import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
+import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
+import org.fife.ui.rsyntaxtextarea.TextEditorPane;
+import org.fife.ui.rsyntaxtextarea.TokenMakerFactory;
+import org.fife.ui.rtextarea.RTextScrollPane;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JDialog;
+import javax.swing.JPanel;
+import javax.swing.JSplitPane;
+import javax.swing.JToolBar;
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.util.List;
+
+/**
+ * since 4.3
+ */
+public class TemplateEditorView extends JDialog {
+
+    protected RSyntaxTextArea editingTemplatePane;
+    protected RSyntaxTextArea classPreviewPane;
+
+    protected JButton previewButton;
+    protected JButton saveButton;
+    protected JButton findButton;
+    protected JButton findAndReplaceButton;
+    protected JComboBox<Object> entityComboBox;
+    private JSplitPane split;
+    private JToolBar toolBar;
+    private JPanel topPanel;
+    static final String VELOCITY_KEY = "text/velocity";
+
+
+    public TemplateEditorView( List<String> entityNames) {
+        this.setTitle("Template editor");
+        this.editingTemplatePane = new TextEditorPane();
+        this.classPreviewPane = new RSyntaxTextArea();
+        initToolBoxComponents(entityNames);
+        mapVelocityTokenMaker();
+        buildView();
+        bindGeometry();
+    }
+
+    private void initToolBoxComponents(List<String> entityNames) {
+        this.saveButton = new JButton(ModelerUtil.buildIcon("icon-save.png"));
+        this.saveButton.setToolTipText("Save");
+        this.findButton = new JButton(ModelerUtil.buildIcon("icon-query.png"));
+        this.findButton.setToolTipText("Find");
+        this.findAndReplaceButton = new JButton(ModelerUtil.buildIcon("icon-find_and_replace.png"));
+        this.findAndReplaceButton.setToolTipText("Find and replace");
+        this.previewButton = new JButton(ModelerUtil.buildIcon("icon-edit.png"));
+        this.previewButton.setToolTipText("Generate preview");
+        this.entityComboBox = new JComboBox<>(entityNames.toArray());
+        this.entityComboBox.setToolTipText("Select an entity for the test");
+    }
+
+    private void buildView() {
+        initSplitPanel();
+        initToolBar();
+        initTopPanel();
+
+        getRootPane().setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
+        getContentPane().setLayout(new BorderLayout());
+        getContentPane().add(topPanel, BorderLayout.NORTH);
+        getContentPane().add(split, BorderLayout.CENTER);
+    }
+
+    private void initSplitPanel() {
+        editingTemplatePane.setSyntaxEditingStyle(VELOCITY_KEY);
+        editingTemplatePane.setMarkOccurrences(true);
+        RTextScrollPane leftPanel = new RTextScrollPane(editingTemplatePane);
+
+        classPreviewPane.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA);
+        classPreviewPane.setEnabled(false);
+        RTextScrollPane rightPanel = new RTextScrollPane(classPreviewPane);
+
+        split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, rightPanel);
+        split.setPreferredSize(new Dimension(1200, 700));
+        split.setDividerSize(6);
+        split.setDividerLocation(1.0);
+    }
+
+    private void initToolBar() {
+        toolBar = new JToolBar();
+        toolBar.setBorder(BorderFactory.createEmptyBorder());
+        toolBar.setFloatable(false);
+        toolBar.add(saveButton);
+        toolBar.addSeparator();
+        toolBar.add(findButton);
+        toolBar.add(findAndReplaceButton);
+        toolBar.addSeparator();
+        toolBar.add(previewButton);
+        toolBar.add(entityComboBox);
+    }
+
+    private void initTopPanel() {
+        CellConstraints constraintsTop = new CellConstraints();
+        PanelBuilder topPanelBuilder = new PanelBuilder(new FormLayout(
+                "left:pref:grow, right:pref", "p, 3dlu, p, 3dlu, p"));
+        topPanelBuilder.setDefaultDialogBorder();
+        topPanelBuilder.add(toolBar, constraintsTop.xy(1, 1));
+        topPanelBuilder.addSeparator("", constraintsTop.xyw(1, 3, 2));
+        topPanelBuilder.addLabel("Editing  template", constraintsTop.xy(1, 5));
+        topPanelBuilder.addLabel("Class preview", constraintsTop.xy(2, 5));
+        topPanel = topPanelBuilder.getPanel();
+    }
+
+    private void bindGeometry() {
+        ComponentGeometry geometry = new ComponentGeometry(this.getClass(), "split/divider");
+        geometry.bindIntProperty(split, JSplitPane.DIVIDER_LOCATION_PROPERTY, 600);
+        geometry.bind(this, 1200, 700, 0);
+    }
+
+    private void mapVelocityTokenMaker() {
+        AbstractTokenMakerFactory tokenMakerFactory = (AbstractTokenMakerFactory) TokenMakerFactory.getDefaultInstance();
+        tokenMakerFactory.putMapping(VELOCITY_KEY, VelocityTokenMaker.class.getName());
+    }
+
+    public String getSelectedEntityName() {
+        Object selectedItem = entityComboBox.getSelectedItem();
+        if (selectedItem != null) {
+            return selectedItem.toString();
+        }
+        return null;
+    }
+
+    public JButton getPreviewButton() {
+        return previewButton;
+    }
+
+    public JButton getSaveButton() {
+        return saveButton;
+    }
+
+    public JButton getFindButton() {
+        return findButton;
+    }
+
+    public JButton getFindAndReplaceButton() {
+        return findAndReplaceButton;
+    }
+
+    public String getTemplateText() {
+        return editingTemplatePane.getText();
+    }
+
+    public RSyntaxTextArea getEditingTemplatePane() {
+        return editingTemplatePane;
+    }
+
+    public RSyntaxTextArea getClassPreviewPane() {
+        return classPreviewPane;
+    }
+
+}
+
+
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/TemplateLoader.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/TemplateLoader.java
new file mode 100644
index 000000000..7e5727036
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/TemplateLoader.java
@@ -0,0 +1,58 @@
+/*****************************************************************
+ *   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
+ *
+ *    https://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.dialog.templateeditor;
+
+import org.apache.velocity.exception.ResourceNotFoundException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.swing.JOptionPane;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.List;
+
+/**
+ * since 4.3
+ */
+public class TemplateLoader {
+
+    private static final Logger logger = LoggerFactory.getLogger(TemplateLoader.class);
+
+    public String load(TemplateEditorView view, String selectedTemplate) {
+        StringBuilder stringBuilder = new StringBuilder();
+        if (selectedTemplate != null) {
+            try {
+                List<String> strings = Files.readAllLines(Paths.get(selectedTemplate));
+                for (String string : strings) {
+                    stringBuilder.append(string).append("\n");
+                }
+            } catch (IOException | ResourceNotFoundException e) {
+                JOptionPane.showMessageDialog(
+                        view,
+                        "File reading error \n" + selectedTemplate,
+                        "Error",
+                        JOptionPane.WARNING_MESSAGE);
+                logger.warn("File reading error {}", selectedTemplate);
+            }
+        }
+        return stringBuilder.toString();
+    }
+}
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/VelocityTokenMaker.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/VelocityTokenMaker.java
new file mode 100644
index 000000000..51b4104d2
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/VelocityTokenMaker.java
@@ -0,0 +1,1065 @@
+/*****************************************************************
+ *   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
+ *
+ *    https://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.dialog.templateeditor;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import javax.swing.text.Segment;
+
+import org.fife.ui.rsyntaxtextarea.AbstractJFlexCTokenMaker;
+import org.fife.ui.rsyntaxtextarea.Token;
+import org.fife.ui.rsyntaxtextarea.TokenImpl;
+
+
+/**
+ * since 4.3
+ */
+public class VelocityTokenMaker extends AbstractJFlexCTokenMaker {
+
+  /** This character denotes the end of file */
+  public static final int YYEOF = -1;
+
+  /** initial size of the lookahead buffer */
+  private static final int ZZ_BUFFERSIZE = 16384;
+
+  /** lexical states */
+  public static final int EOL_COMMENT = 2;
+  public static final int YYINITIAL = 0;
+  public static final int MLC = 1;
+
+  /** 
+   * Translates characters to character classes
+   */
+  private static final String ZZ_CMAP_PACKED = 
+    "\11\0\1\21\1\10\1\0\1\21\1\17\22\0\1\21\1\43\1\15"+
+    "\1\20\1\1\1\47\1\45\1\7\2\44\1\22\1\46\1\41\1\26"+
+    "\1\24\1\50\1\4\3\16\4\6\2\3\1\54\1\41\1\71\1\70"+
+    "\1\72\1\43\1\42\1\35\1\56\1\5\1\60\1\25\1\33\1\65"+
+    "\1\51\1\53\1\67\1\57\1\36\1\64\1\61\1\63\1\52\1\1"+
+    "\1\31\1\37\1\27\1\12\1\62\1\55\1\23\1\66\1\1\1\44"+
+    "\1\11\1\44\1\73\1\2\1\0\1\35\1\14\1\5\1\60\1\25"+
+    "\1\34\1\65\1\75\1\53\1\67\1\57\1\36\1\64\1\13\1\63"+
+    "\1\52\1\1\1\32\1\37\1\30\1\12\1\62\1\76\1\23\1\66"+
+    "\1\1\1\40\1\74\1\40\1\43\uff81\0";
+
+  /** 
+   * Translates characters to character classes
+   */
+  private static final char [] ZZ_CMAP = zzUnpackCMap(ZZ_CMAP_PACKED);
+
+  /** 
+   * Translates DFA states to action switch labels.
+   */
+  private static final int [] ZZ_ACTION = zzUnpackAction();
+
+  private static final String ZZ_ACTION_PACKED_0 =
+    "\3\0\2\1\2\2\1\3\1\4\1\1\1\5\1\1"+
+    "\1\6\1\7\2\1\1\7\3\1\1\10\3\7\2\1"+
+    "\3\7\1\11\1\12\10\11\1\13\6\11\1\0\1\14"+
+    "\1\0\2\14\1\3\1\15\1\0\1\3\1\1\2\5"+
+    "\1\16\1\0\1\17\1\20\7\0\1\21\7\1\1\22"+
+    "\2\11\2\0\2\11\2\0\2\11\2\0\2\11\3\0"+
+    "\1\21\1\0\1\23\1\3\1\24\2\3\1\15\1\3"+
+    "\1\1\1\5\1\25\1\5\11\0\1\26\2\0\10\1"+
+    "\2\11\2\0\2\11\2\0\2\11\2\0\2\11\3\0"+
+    "\1\3\1\5\11\0\1\1\1\27\6\1\1\11\1\0"+
+    "\2\11\1\30\1\0\1\11\1\0\2\11\1\31\2\0"+
+    "\1\3\1\5\1\0\1\26\6\0\6\1\1\11\2\0"+
+    "\1\11\3\0\1\3\1\5\4\0\3\1\1\32\2\0"+
+    "\14\1";
+
+  private static int [] zzUnpackAction() {
+    int [] result = new int[224];
+    int offset = 0;
+    offset = zzUnpackAction(ZZ_ACTION_PACKED_0, offset, result);
+    return result;
+  }
+
+  private static int zzUnpackAction(String packed, int offset, int [] result) {
+    int i = 0;       /* index in packed string  */
+    int j = offset;  /* index in unpacked array */
+    int l = packed.length();
+    while (i < l) {
+      int count = packed.charAt(i++);
+      int value = packed.charAt(i++);
+      do result[j++] = value; while (--count > 0);
+    }
+    return j;
+  }
+
+
+  /** 
+   * Translates a state to a row index in the transition table
+   */
+  private static final int [] ZZ_ROWMAP = zzUnpackRowMap();
+
+  private static final String ZZ_ROWMAP_PACKED_0 =
+    "\0\0\0\77\0\176\0\275\0\374\0\u013b\0\u017a\0\u01b9"+
+    "\0\275\0\u01f8\0\u0237\0\u0276\0\u02b5\0\u02f4\0\u0333\0\u0372"+
+    "\0\u03b1\0\u03f0\0\u042f\0\u046e\0\275\0\275\0\u04ad\0\u04ec"+
+    "\0\u052b\0\u056a\0\u05a9\0\u05e8\0\u0627\0\u0666\0\275\0\u06a5"+
+    "\0\u06e4\0\u0723\0\u0762\0\u07a1\0\u07e0\0\u081f\0\u085e\0\275"+
+    "\0\u089d\0\u08dc\0\u091b\0\u095a\0\u0999\0\u09d8\0\u0a17\0\u0a56"+
+    "\0\u0333\0\u0a95\0\u0ad4\0\u0b13\0\275\0\u0b52\0\u0b91\0\u0bd0"+
+    "\0\u0c0f\0\u0c4e\0\275\0\u0c8d\0\275\0\275\0\u0ccc\0\u0d0b"+
+    "\0\u0d4a\0\u0d89\0\u0dc8\0\u0e07\0\u0e46\0\u0e85\0\u0ec4\0\u0f03"+
+    "\0\u0f42\0\u0f81\0\u0fc0\0\u0fff\0\u103e\0\275\0\u107d\0\u10bc"+
+    "\0\u10fb\0\u113a\0\u1179\0\u11b8\0\u11f7\0\u1236\0\u1275\0\u12b4"+
+    "\0\u12f3\0\u1332\0\u1371\0\u13b0\0\u13ef\0\u142e\0\u146d\0\u14ac"+
+    "\0\u14eb\0\u0ad4\0\u152a\0\275\0\u1569\0\u15a8\0\u0b52\0\u15e7"+
+    "\0\u1626\0\u1665\0\275\0\u16a4\0\u16e3\0\u1722\0\u1761\0\u17a0"+
+    "\0\u17df\0\u181e\0\u185d\0\u189c\0\u18db\0\275\0\u191a\0\u1959"+
+    "\0\u1998\0\u19d7\0\u1a16\0\u1a55\0\u1a94\0\u1ad3\0\u1b12\0\u1b51"+
+    "\0\u1b90\0\u1bcf\0\u1c0e\0\u1c4d\0\u1c8c\0\u1ccb\0\u1d0a\0\u1d49"+
+    "\0\u1d88\0\u1dc7\0\u1e06\0\u1e45\0\u1e84\0\u1ec3\0\u1f02\0\u1f41"+
+    "\0\u1f80\0\u1fbf\0\u1ffe\0\u203d\0\u207c\0\u20bb\0\u20fa\0\u2139"+
+    "\0\u2178\0\u21b7\0\u21f6\0\u2235\0\u2274\0\374\0\u22b3\0\u22f2"+
+    "\0\u2331\0\u2370\0\u23af\0\u23ee\0\u242d\0\u246c\0\u24ab\0\u24ea"+
+    "\0\u2529\0\u2568\0\u25a7\0\u25e6\0\u2625\0\u2664\0\u26a3\0\u26e2"+
+    "\0\u2721\0\u2760\0\u279f\0\u27de\0\u281d\0\u285c\0\u289b\0\u28da"+
+    "\0\u2919\0\u2958\0\u2997\0\u29d6\0\u2a15\0\u2a54\0\u2a93\0\u2ad2"+
+    "\0\u2b11\0\u2b50\0\u2b8f\0\u2529\0\u2bce\0\u2c0d\0\u26a3\0\u2c4c"+
+    "\0\u2c8b\0\u2cca\0\u2d09\0\u2d48\0\u2d87\0\u2dc6\0\u2e05\0\u2e44"+
+    "\0\u2e83\0\374\0\u2ec2\0\u2f01\0\u2f40\0\u2f7f\0\u2fbe\0\u2ffd"+
+    "\0\u303c\0\u307b\0\u30ba\0\u30f9\0\u3138\0\u3177\0\u31b6\0\u31f5";
+
+  private static int [] zzUnpackRowMap() {
+    int [] result = new int[224];
+    int offset = 0;
+    offset = zzUnpackRowMap(ZZ_ROWMAP_PACKED_0, offset, result);
+    return result;
+  }
+
+  private static int zzUnpackRowMap(String packed, int offset, int [] result) {
+    int i = 0;  /* index in packed string  */
+    int j = offset;  /* index in unpacked array */
+    int l = packed.length();
+    while (i < l) {
+      int high = packed.charAt(i++) << 16;
+      result[j++] = high | packed.charAt(i++);
+    }
+    return j;
+  }
+
+  /** 
+   * The transition table of the DFA
+   */
+  private static final int [] ZZ_TRANS = zzUnpackTrans();
+
+  private static final String ZZ_TRANS_PACKED_0 =
+    "\1\4\2\5\1\6\1\7\1\5\1\6\1\10\1\11"+
+    "\1\4\2\5\1\12\1\13\1\6\1\4\1\14\1\15"+
+    "\1\16\1\5\1\17\1\20\1\21\2\22\2\5\2\23"+
+    "\2\5\1\24\1\25\2\4\1\26\1\25\1\27\1\30"+
+    "\2\16\2\5\1\31\1\26\1\5\1\12\4\5\1\32"+
+    "\4\5\1\16\1\33\1\34\1\26\1\35\2\5\10\36"+
+    "\1\37\11\36\1\40\10\36\1\41\1\42\14\36\1\43"+
+    "\3\36\1\44\17\36\1\45\1\46\10\47\1\50\22\47"+
+    "\1\51\1\52\14\47\1\53\3\47\1\54\17\47\1\55"+
+    "\1\56\100\0\6\5\2\0\1\57\3\5\1\0\1\5"+
+    "\4\0\1\5\1\0\1\5\1\0\11\5\11\0\3\5"+
+    "\1\0\13\5\5\0\2\5\3\60\2\6\1\60\1\6"+
+    "\2\0\4\60\1\0\1\6\1\0\1\60\2\0\1\60"+
+    "\1\61\1\62\1\0\11\60\2\0\1\60\6\0\3\60"+
+    "\1\0\13\60\5\0\5\60\2\6\1\60\1\6\2\0"+
+    "\4\60\1\0\1\6\1\0\1\60\2\0\1\63\1\61"+
+    "\1\62\1\0\11\60\2\0\1\60\6\0\3\60\1\0"+
+    "\13\60\5\0\2\60\7\64\1\65\1\66\1\67\65\64"+
+    "\1\0\6\5\2\0\1\57\3\5\1\0\1\5\4\0"+
+    "\1\5\1\0\1\5\1\0\6\5\1\70\2\5\11\0"+
+    "\3\5\1\0\13\5\5\0\2\5\10\13\1\71\1\72"+
+    "\3\13\1\73\61\13\14\0\1\74\3\0\1\75\1\0"+
+    "\1\76\2\0\1\77\5\0\2\100\2\0\1\101\12\0"+
+    "\1\102\1\103\2\0\1\74\1\0\1\104\3\0\1\105"+
+    "\33\0\1\15\145\0\1\26\11\0\2\106\1\0\1\106"+
+    "\7\0\1\106\61\0\6\5\2\0\1\57\1\5\1\107"+
+    "\1\5\1\0\1\5\4\0\1\5\1\0\1\5\1\0"+
+    "\11\5\11\0\3\5\1\0\4\5\1\107\6\5\5\0"+
+    "\2\5\26\0\1\26\41\0\1\26\7\0\6\5\2\0"+
+    "\1\57\3\5\1\0\1\5\4\0\1\5\1\0\1\5"+
+    "\1\0\2\5\2\110\5\5\11\0\3\5\1\0\13\5"+
+    "\5\0\2\5\1\0\6\5\2\0\1\57\3\5\1\0"+
+    "\1\5\4\0\1\5\1\0\1\5\1\0\6\5\1\111"+
+    "\2\5\11\0\3\5\1\0\13\5\5\0\2\5\1\0"+
+    "\6\5\2\0\1\57\1\112\2\5\1\0\1\5\4\0"+
+    "\1\5\1\0\1\5\1\0\2\113\7\5\11\0\3\5"+
+    "\1\0\13\5\5\0\2\5\45\0\1\26\77\0\1\26"+
+    "\21\0\1\26\7\0\6\5\2\0\1\57\3\5\1\0"+
+    "\1\5\4\0\1\5\1\0\1\5\1\0\11\5\11\0"+
+    "\3\5\1\0\7\5\1\114\3\5\5\0\2\5\1\0"+
+    "\6\5\2\0\1\57\2\5\1\115\1\0\1\5\4\0"+
+    "\1\5\1\0\1\5\1\0\11\5\11\0\3\5\1\0"+
+    "\1\5\1\115\11\5\5\0\2\5\71\0\1\16\77\0"+
+    "\1\16\100\0\1\26\2\0\10\36\1\0\11\36\1\0"+
+    "\11\36\1\0\40\36\22\0\1\116\56\0\10\36\1\0"+
+    "\11\36\1\0\4\36\2\117\3\36\1\0\16\36\1\120"+
+    "\21\36\31\0\2\121\22\0\1\122\23\0\10\36\1\0"+
+    "\11\36\1\0\4\36\2\123\3\36\1\0\40\36\2\0"+
+    "\10\36\1\0\11\36\1\0\11\36\1\0\20\36\1\124"+
+    "\17\36\1\0\1\125\27\0\2\126\123\0\1\125\20\0"+
+    "\1\125\10\47\1\0\23\47\1\0\40\47\2\0\10\47"+
+    "\1\0\16\47\2\127\3\47\1\0\16\47\1\130\21\47"+
+    "\31\0\2\131\22\0\1\132\23\0\10\47\1\0\16\47"+
+    "\2\133\3\47\1\0\40\47\2\0\10\47\1\0\23\47"+
+    "\1\0\20\47\1\134\17\47\1\0\1\135\27\0\2\136"+
+    "\123\0\1\135\20\0\1\135\12\0\1\137\64\0\7\60"+
+    "\2\0\4\60\1\0\1\60\1\0\1\60\2\0\1\60"+
+    "\1\0\1\60\1\0\11\60\2\0\1\60\6\0\3\60"+
+    "\1\0\13\60\5\0\5\60\2\140\1\60\1\140\2\0"+
+    "\4\60\1\0\1\140\1\0\1\60\2\0\1\60\1\0"+
+    "\1\60\1\141\11\60\2\0\1\60\3\0\1\141\2\0"+
+    "\3\60\1\0\13\60\5\0\5\60\4\142\2\0\3\60"+
+    "\1\142\1\0\1\142\1\0\1\60\2\0\1\60\1\0"+
+    "\1\142\1\0\4\60\3\142\2\60\2\0\1\60\6\0"+
+    "\3\60\1\0\1\60\1\142\1\60\1\142\7\60\5\0"+
+    "\2\60\7\143\1\144\1\0\66\143\7\0\1\144\67\0"+
+    "\4\143\1\145\1\143\1\146\1\147\1\0\1\64\1\150"+
+    "\3\64\1\145\11\143\1\64\1\143\1\64\1\143\1\64"+
+    "\42\143\1\0\6\5\2\0\1\57\3\5\1\0\1\5"+
+    "\4\0\1\5\1\0\1\5\1\0\10\5\1\151\11\0"+
+    "\3\5\1\0\13\5\5\0\2\5\11\71\1\152\3\71"+
+    "\1\153\65\71\1\13\1\71\2\13\1\0\1\13\1\154"+
+    "\4\13\11\71\1\13\1\71\1\13\1\71\1\13\42\71"+
+    "\31\0\2\155\57\0\1\156\22\0\1\157\22\0\1\156"+
+    "\1\160\77\0\1\161\40\0\1\162\1\0\2\163\103\0"+
+    "\1\164\54\0\1\165\17\0\2\166\24\0\1\165\42\0"+
+    "\1\167\106\0\1\170\41\0\3\60\2\106\1\60\1\106"+
+    "\2\0\4\60\1\0\1\106\1\0\1\60\2\0\1\60"+
+    "\1\0\1\62\1\0\11\60\2\0\1\60\6\0\3\60"+
+    "\1\0\13\60\5\0\2\60\1\0\6\5\2\0\1\57"+
+    "\3\5\1\0\1\5\4\0\1\5\1\0\1\5\1\0"+
+    "\2\171\7\5\11\0\3\5\1\0\13\5\5\0\2\5"+
+    "\1\0\6\5\2\0\1\57\1\172\2\5\1\0\1\5"+
+    "\4\0\1\5\1\0\1\5\1\0\11\5\11\0\3\5"+
+    "\1\0\13\5\5\0\2\5\1\0\6\5\2\0\1\57"+
+    "\3\5\1\0\1\5\4\0\1\5\1\0\1\5\1\0"+
+    "\7\5\1\173\1\5\11\0\3\5\1\0\13\5\5\0"+
+    "\2\5\1\0\6\5\2\0\1\57\2\5\1\174\1\0"+
+    "\1\5\4\0\1\5\1\0\1\5\1\0\11\5\11\0"+
+    "\1\5\1\175\1\5\1\0\1\5\1\174\11\5\5\0"+
+    "\2\5\1\0\6\5\2\0\1\57\3\5\1\0\1\5"+
+    "\4\0\1\5\1\0\1\5\1\0\2\5\2\176\5\5"+
+    "\11\0\3\5\1\0\13\5\5\0\2\5\1\0\6\5"+
+    "\2\0\1\57\3\5\1\0\1\5\4\0\1\5\1\0"+
+    "\1\5\1\0\11\5\11\0\1\5\1\177\1\5\1\0"+
+    "\13\5\5\0\2\5\1\0\6\5\2\0\1\57\3\5"+
+    "\1\0\1\5\4\0\1\5\1\0\1\5\1\0\11\5"+
+    "\11\0\3\5\1\0\12\5\1\200\5\0\2\5\10\36"+
+    "\1\0\11\36\1\0\11\36\1\0\15\36\1\201\22\36"+
+    "\2\0\10\36\1\0\11\36\1\0\11\36\1\0\1\36"+
+    "\1\202\36\36\54\0\1\203\62\0\1\204\40\0\10\36"+
+    "\1\0\11\36\1\0\4\36\2\205\3\36\1\0\40\36"+
+    "\2\0\10\36\1\0\11\36\1\0\11\36\1\0\20\36"+
+    "\1\206\17\36\1\0\1\207\55\0\1\207\20\0\1\207"+
+    "\27\0\2\210\46\0\10\47\1\0\23\47\1\0\15\47"+
+    "\1\211\22\47\2\0\10\47\1\0\23\47\1\0\1\47"+
+    "\1\212\36\47\54\0\1\213\62\0\1\214\40\0\10\47"+
+    "\1\0\16\47\2\215\3\47\1\0\40\47\2\0\10\47"+
+    "\1\0\23\47\1\0\20\47\1\216\17\47\1\0\1\217"+
+    "\55\0\1\217\20\0\1\217\27\0\2\220\51\0\4\221"+
+    "\5\0\1\221\1\0\1\221\6\0\1\221\5\0\3\221"+
+    "\20\0\1\221\1\0\1\221\16\0\3\60\2\140\1\60"+
+    "\1\140\2\0\4\60\1\0\1\140\1\0\1\60\2\0"+
+    "\1\60\1\0\1\60\1\0\11\60\2\0\1\60\6\0"+
+    "\3\60\1\0\13\60\5\0\2\60\3\0\2\140\1\0"+
+    "\1\140\7\0\1\140\60\0\7\143\1\65\1\0\72\143"+
+    "\1\146\1\143\1\146\1\144\1\0\5\143\1\146\64\143"+
+    "\1\64\1\143\1\64\1\144\1\0\5\143\1\64\63\143"+
+    "\4\222\1\65\1\0\3\143\1\222\1\143\1\222\6\143"+
+    "\1\222\5\143\3\222\20\143\1\222\1\143\1\222\16\143"+
+    "\1\0\6\5\2\0\1\57\3\5\1\0\1\5\4\0"+
+    "\1\5\1\0\1\174\1\0\11\5\11\0\3\5\1\0"+
+    "\13\5\5\0\2\5\10\71\1\0\71\71\4\223\2\71"+
+    "\1\152\2\71\1\223\1\153\1\223\6\71\1\223\5\71"+
+    "\3\223\20\71\1\223\1\71\1\223\16\71\25\0\1\224"+
+    "\131\0\1\166\55\0\1\225\74\0\1\226\72\0\2\227"+
+    "\73\0\2\166\131\0\1\230\44\0\2\231\51\0\1\232"+
+    "\124\0\2\233\47\0\1\234\72\0\6\5\2\0\1\57"+
+    "\3\5\1\0\1\5\4\0\1\5\1\0\1\5\1\0"+
+    "\11\5\11\0\2\5\1\235\1\0\13\5\5\0\2\5"+
+    "\1\0\6\5\2\0\1\57\3\5\1\0\1\5\4\0"+
+    "\1\5\1\0\1\236\1\0\11\5\11\0\3\5\1\0"+
+    "\13\5\5\0\2\5\1\0\6\5\2\0\1\57\3\5"+
+    "\1\0\1\5\4\0\1\5\1\0\1\5\1\0\10\5"+
+    "\1\172\11\0\3\5\1\0\13\5\5\0\2\5\1\0"+
+    "\4\5\1\237\1\5\2\0\1\57\3\5\1\0\1\5"+
+    "\4\0\1\5\1\0\1\5\1\0\11\5\11\0\1\5"+
+    "\1\240\1\5\1\0\13\5\5\0\2\5\1\0\6\5"+
+    "\2\0\1\57\3\5\1\0\1\5\4\0\1\5\1\0"+
+    "\1\241\1\0\11\5\11\0\3\5\1\0\13\5\5\0"+
+    "\2\5\1\0\6\5\2\0\1\57\3\5\1\0\1\5"+
+    "\4\0\1\5\1\0\1\5\1\0\11\5\11\0\2\5"+
+    "\1\242\1\0\13\5\5\0\2\5\1\0\6\5\2\0"+
+    "\1\57\3\5\1\0\1\5\4\0\1\5\1\0\1\5"+
+    "\1\0\11\5\11\0\3\5\1\0\6\5\1\243\4\5"+
+    "\5\0\2\5\1\0\6\5\2\0\1\57\3\5\1\0"+
+    "\1\5\4\0\1\5\1\0\1\244\1\0\11\5\11\0"+
+    "\3\5\1\0\13\5\5\0\2\5\10\36\1\0\11\36"+
+    "\1\0\11\36\1\0\17\36\1\245\20\36\2\0\10\36"+
+    "\1\0\11\36\1\0\2\36\1\201\6\36\1\0\40\36"+
+    "\56\0\1\246\47\0\1\203\51\0\10\36\1\0\11\36"+
+    "\1\0\11\36\1\0\15\36\1\247\22\36\2\0\10\36"+
+    "\1\0\11\36\1\0\1\36\1\250\7\36\1\0\40\36"+
+    "\26\0\1\251\124\0\1\252\24\0\10\47\1\0\23\47"+
+    "\1\0\17\47\1\253\20\47\2\0\10\47\1\0\14\47"+
+    "\1\211\6\47\1\0\40\47\56\0\1\254\47\0\1\213"+
+    "\51\0\10\47\1\0\23\47\1\0\15\47\1\255\22\47"+
+    "\2\0\10\47\1\0\13\47\1\256\7\47\1\0\40\47"+
+    "\26\0\1\257\124\0\1\260\27\0\4\261\5\0\1\261"+
+    "\1\0\1\261\6\0\1\261\5\0\3\261\20\0\1\261"+
+    "\1\0\1\261\16\0\3\143\4\262\1\65\1\0\3\143"+
+    "\1\262\1\143\1\262\6\143\1\262\5\143\3\262\20\143"+
+    "\1\262\1\143\1\262\16\143\3\71\4\263\2\71\1\152"+
+    "\2\71\1\263\1\153\1\263\6\71\1\263\5\71\3\263"+
+    "\20\71\1\263\1\71\1\263\16\71\35\0\1\264\66\0"+
+    "\1\265\107\0\1\266\65\0\1\267\123\0\1\166\63\0"+
+    "\1\270\75\0\1\271\113\0\1\272\54\0\2\273\45\0"+
+    "\6\5\2\0\1\57\3\5\1\0\1\5\4\0\1\5"+
+    "\1\0\1\5\1\0\2\274\7\5\11\0\3\5\1\0"+
+    "\13\5\5\0\2\5\1\0\6\5\2\0\1\57\3\5"+
+    "\1\0\1\5\4\0\1\5\1\0\1\5\1\0\7\5"+
+    "\1\275\1\5\11\0\3\5\1\0\13\5\5\0\2\5"+
+    "\1\0\6\5\2\0\1\57\3\5\1\0\1\5\4\0"+
+    "\1\5\1\0\1\5\1\0\6\5\1\276\2\5\11\0"+
+    "\3\5\1\0\13\5\5\0\2\5\1\0\6\5\2\0"+
+    "\1\57\3\5\1\0\1\5\4\0\1\5\1\0\1\5"+
+    "\1\0\2\5\2\174\5\5\11\0\3\5\1\0\13\5"+
+    "\5\0\2\5\1\0\6\5\2\0\1\57\1\5\1\277"+
+    "\1\5\1\0\1\5\4\0\1\5\1\0\1\5\1\0"+
+    "\11\5\11\0\3\5\1\0\4\5\1\277\6\5\5\0"+
+    "\2\5\1\0\6\5\2\0\1\57\3\5\1\0\1\5"+
+    "\4\0\1\5\1\0\1\5\1\0\2\5\2\300\5\5"+
+    "\11\0\3\5\1\0\13\5\5\0\2\5\1\0\4\5"+
+    "\1\301\1\5\2\0\1\57\3\5\1\0\1\5\4\0"+
+    "\1\5\1\0\1\5\1\0\11\5\11\0\3\5\1\0"+
+    "\13\5\5\0\2\5\10\36\1\0\11\36\1\0\11\36"+
+    "\1\0\13\36\1\302\24\36\52\0\1\303\26\0\10\36"+
+    "\1\0\11\36\1\0\11\36\1\0\2\36\1\201\14\36"+
+    "\1\245\20\36\2\0\1\36\7\250\1\0\1\36\3\250"+
+    "\1\36\1\250\1\36\1\250\1\36\1\304\11\250\1\251"+
+    "\3\250\1\36\30\250\4\36\2\251\1\0\1\251\1\304"+
+    "\4\251\1\304\2\0\3\251\1\0\1\251\1\0\1\304"+
+    "\1\0\1\304\1\251\1\304\1\251\1\304\11\251\1\0"+
+    "\7\304\4\251\1\304\13\251\1\304\4\0\2\251\37\0"+
+    "\1\203\14\0\1\246\22\0\10\47\1\0\23\47\1\0"+
+    "\13\47\1\305\24\47\52\0\1\306\26\0\10\47\1\0"+
+    "\23\47\1\0\2\47\1\211\14\47\1\253\20\47\2\0"+
+    "\1\47\7\256\1\0\1\47\3\256\1\47\1\256\1\47"+
+    "\1\256\1\47\12\256\1\257\3\256\1\47\30\256\4\47"+
+    "\2\257\1\0\1\257\1\307\4\257\1\307\2\0\3\257"+
+    "\1\0\1\257\1\0\1\307\1\0\1\307\1\257\1\307"+
+    "\1\257\1\307\11\257\1\0\7\307\4\257\1\307\13\257"+
+    "\1\307\4\0\2\257\37\0\1\213\14\0\1\254\25\0"+
+    "\4\310\5\0\1\310\1\0\1\310\6\0\1\310\5\0"+
+    "\3\310\20\0\1\310\1\0\1\310\16\0\3\143\4\311"+
+    "\1\65\1\0\3\143\1\311\1\143\1\311\6\143\1\311"+
+    "\5\143\3\311\20\143\1\311\1\143\1\311\16\143\3\71"+
+    "\4\312\2\71\1\152\2\71\1\312\1\153\1\312\6\71"+
+    "\1\312\5\71\3\312\20\71\1\312\1\71\1\312\16\71"+
+    "\57\0\1\166\72\0\1\313\35\0\1\314\121\0\1\315"+
+    "\66\0\1\166\63\0\1\316\77\0\1\270\45\0\1\270"+
+    "\100\0\1\166\14\0\6\5\2\0\1\57\3\5\1\0"+
+    "\1\5\4\0\1\5\1\0\1\5\1\0\11\5\11\0"+
+    "\3\5\1\0\11\5\1\317\1\5\5\0\2\5\1\0"+
+    "\6\5\2\0\1\57\3\5\1\0\1\5\4\0\1\5"+
+    "\1\0\1\5\1\0\6\5\1\320\2\5\11\0\3\5"+
+    "\1\0\13\5\5\0\2\5\1\0\4\5\1\321\1\5"+
+    "\2\0\1\57\3\5\1\0\1\5\4\0\1\5\1\0"+
+    "\1\5\1\0\11\5\11\0\3\5\1\0\13\5\5\0"+
+    "\2\5\1\0\6\5\2\0\1\57\3\5\1\0\1\5"+
+    "\4\0\1\5\1\0\1\5\1\0\11\5\11\0\3\5"+
+    "\1\0\10\5\1\317\2\5\5\0\2\5\1\0\6\5"+
+    "\2\0\1\57\3\5\1\0\1\5\4\0\1\5\1\0"+
+    "\1\5\1\0\2\317\7\5\11\0\3\5\1\0\13\5"+
+    "\5\0\2\5\1\0\6\5\2\0\1\57\3\5\1\0"+
+    "\1\5\4\0\1\5\1\0\1\5\1\0\2\322\7\5"+
+    "\11\0\3\5\1\0\13\5\5\0\2\5\10\36\1\0"+
+    "\11\36\1\0\11\36\1\0\13\36\1\250\24\36\52\0"+
+    "\1\251\26\0\10\47\1\0\23\47\1\0\13\47\1\256"+
+    "\24\47\52\0\1\257\31\0\4\5\5\0\1\5\1\0"+
+    "\1\5\6\0\1\5\5\0\3\5\20\0\1\5\1\0"+
+    "\1\5\16\0\3\143\4\64\1\65\1\0\3\143\1\64"+
+    "\1\143\1\64\6\143\1\64\5\143\3\64\20\143\1\64"+
+    "\1\143\1\64\16\143\3\71\4\13\2\71\1\152\2\71"+
+    "\1\13\1\153\1\13\6\71\1\13\5\71\3\13\20\71"+
+    "\1\13\1\71\1\13\16\71\33\0\2\166\77\0\1\323"+
+    "\46\0\1\324\151\0\1\270\17\0\6\5\2\0\1\57"+
+    "\1\325\2\5\1\0\1\5\4\0\1\5\1\0\1\5"+
+    "\1\0\11\5\11\0\3\5\1\0\13\5\5\0\2\5"+
+    "\1\0\6\5\2\0\1\57\3\5\1\0\1\5\4\0"+
+    "\1\5\1\0\1\5\1\0\10\5\1\326\11\0\3\5"+
+    "\1\0\13\5\5\0\2\5\1\0\6\5\2\0\1\57"+
+    "\3\5\1\0\1\5\4\0\1\5\1\0\1\5\1\0"+
+    "\11\5\11\0\3\5\1\0\2\5\1\327\10\5\5\0"+
+    "\2\5\27\0\2\270\117\0\1\166\23\0\1\166\2\0"+
+    "\6\5\2\0\1\57\3\5\1\0\1\5\4\0\1\5"+
+    "\1\0\1\5\1\0\2\330\7\5\11\0\3\5\1\0"+
+    "\13\5\5\0\2\5\1\0\6\5\2\0\1\57\3\5"+
+    "\1\0\1\5\4\0\1\5\1\0\1\5\1\0\10\5"+
+    "\1\331\11\0\3\5\1\0\13\5\5\0\2\5\1\0"+
+    "\6\5\2\0\1\57\3\5\1\0\1\5\4\0\1\5"+
+    "\1\0\1\5\1\0\6\5\1\332\2\5\11\0\3\5"+
+    "\1\0\13\5\5\0\2\5\1\0\6\5\2\0\1\57"+
+    "\3\5\1\0\1\5\4\0\1\5\1\0\1\5\1\0"+
+    "\11\5\11\0\2\5\1\333\1\0\13\5\5\0\2\5"+
+    "\1\0\6\5\2\0\1\57\1\5\1\334\1\5\1\0"+
+    "\1\5\4\0\1\5\1\0\1\5\1\0\11\5\11\0"+
+    "\3\5\1\0\4\5\1\334\6\5\5\0\2\5\1\0"+
+    "\6\5\2\0\1\57\3\5\1\0\1\5\4\0\1\5"+
+    "\1\0\1\5\1\0\11\5\11\0\3\5\1\0\10\5"+
+    "\1\335\2\5\5\0\2\5\1\0\6\5\2\0\1\57"+
+    "\3\5\1\0\1\5\4\0\1\5\1\0\1\5\1\0"+
+    "\7\5\1\336\1\5\11\0\3\5\1\0\13\5\5\0"+
+    "\2\5\1\0\6\5\2\0\1\57\3\5\1\0\1\5"+
+    "\4\0\1\5\1\0\1\5\1\0\6\5\1\337\2\5"+
+    "\11\0\3\5\1\0\13\5\5\0\2\5\1\0\6\5"+
+    "\2\0\1\57\3\5\1\0\1\5\4\0\1\5\1\0"+
+    "\1\331\1\0\11\5\11\0\3\5\1\0\13\5\5\0"+
+    "\2\5\1\0\6\5\2\0\1\57\3\5\1\0\1\5"+
+    "\4\0\1\5\1\0\1\5\1\0\10\5\1\322\11\0"+
+    "\3\5\1\0\13\5\5\0\2\5\1\0\6\5\2\0"+
+    "\1\57\3\5\1\0\1\5\4\0\1\5\1\0\1\5"+
+    "\1\0\11\5\11\0\3\5\1\0\7\5\1\340\3\5"+
+    "\5\0\2\5\1\0\6\5\2\0\1\57\3\5\1\0"+
+    "\1\5\4\0\1\5\1\0\1\322\1\0\11\5\11\0"+
+    "\3\5\1\0\13\5\5\0\2\5";
+
+  private static int [] zzUnpackTrans() {
+    int [] result = new int[12852];
+    int offset = 0;
+    offset = zzUnpackTrans(ZZ_TRANS_PACKED_0, offset, result);
+    return result;
+  }
+
+  private static int zzUnpackTrans(String packed, int offset, int [] result) {
+    int i = 0;       /* index in packed string  */
+    int j = offset;  /* index in unpacked array */
+    int l = packed.length();
+    while (i < l) {
+      int count = packed.charAt(i++);
+      int value = packed.charAt(i++);
+      value--;
+      do result[j++] = value; while (--count > 0);
+    }
+    return j;
+  }
+
+
+  /* error codes */
+  private static final int ZZ_UNKNOWN_ERROR = 0;
+  private static final int ZZ_NO_MATCH = 1;
+  private static final int ZZ_PUSHBACK_2BIG = 2;
+
+  /* error messages for the codes above */
+  private static final String ZZ_ERROR_MSG[] = {
+    "Unkown internal scanner error",
+    "Error: could not match input",
+    "Error: pushback value was too large"
+  };
+
+  /**
+   * ZZ_ATTRIBUTE[aState] contains the attributes of state <code>aState</code>
+   */
+  private static final int [] ZZ_ATTRIBUTE = zzUnpackAttribute();
+
+  private static final String ZZ_ATTRIBUTE_PACKED_0 =
+    "\3\0\1\11\4\1\1\11\13\1\2\11\10\1\1\11"+
+    "\10\1\1\11\6\1\1\0\1\1\1\0\3\1\1\11"+
+    "\1\0\4\1\1\11\1\0\2\11\7\0\10\1\1\11"+
+    "\2\1\2\0\2\1\2\0\2\1\2\0\2\1\3\0"+
+    "\1\1\1\0\2\1\1\11\6\1\1\11\1\1\11\0"+
+    "\1\11\2\0\12\1\2\0\2\1\2\0\2\1\2\0"+
+    "\2\1\3\0\2\1\11\0\11\1\1\0\3\1\1\0"+
+    "\1\1\1\0\3\1\2\0\2\1\1\0\1\1\6\0"+
+    "\7\1\2\0\1\1\3\0\2\1\4\0\4\1\2\0"+
+    "\14\1";
+
+  private static int [] zzUnpackAttribute() {
+    int [] result = new int[224];
+    int offset = 0;
+    offset = zzUnpackAttribute(ZZ_ATTRIBUTE_PACKED_0, offset, result);
+    return result;
+  }
+
+  private static int zzUnpackAttribute(String packed, int offset, int [] result) {
+    int i = 0;       /* index in packed string  */
+    int j = offset;  /* index in unpacked array */
+    int l = packed.length();
+    while (i < l) {
+      int count = packed.charAt(i++);
+      int value = packed.charAt(i++);
+      do result[j++] = value; while (--count > 0);
+    }
+    return j;
+  }
+
+  /** the input device */
+  private Reader zzReader;
+
+  /** the current state of the DFA */
+  private int zzState;
+
+  /** the current lexical state */
+  private int zzLexicalState = YYINITIAL;
+
+  /** this buffer contains the current text to be matched and is
+      the source of the yytext() string */
+  private char zzBuffer[];
+
+  /** the textposition at the last accepting state */
+  private int zzMarkedPos;
+
+  /** the textposition at the last state to be included in yytext */
+  private int zzPushbackPos;
+
+  /** the current text position in the buffer */
+  private int zzCurrentPos;
+
+  /** startRead marks the beginning of the yytext() string in the buffer */
+  private int zzStartRead;
+
+  /** endRead marks the last character in the buffer, that has been read
+      from input */
+  private int zzEndRead;
+
+  /** number of newlines encountered up to the start of the matched text */
+  private int yyline;
+
+  /** the number of characters up to the start of the matched text */
+  private int yychar;
+
+  /**
+   * the number of characters from the last newline up to the start of the 
+   * matched text
+   */
+  private int yycolumn;
+
+  /** 
+   * zzAtBOL == true <=> the scanner is currently at the beginning of a line
+   */
+  private boolean zzAtBOL = true;
+
+  /** zzAtEOF == true <=> the scanner is at the EOF */
+  private boolean zzAtEOF;
+
+  /* user code: */
+
+
+	/**
+	 * Constructor.  This must be here because JFlex does not generate a
+	 * no-parameter constructor.
+	 */
+	public VelocityTokenMaker() {
+	}
+
+
+	/**
+	 * Adds the token specified to the current linked list of tokens.
+	 *
+	 * @param tokenType The token's type.
+	 * @see #addToken(int, int, int)
+	 */
+	private void addHyperlinkToken(int start, int end, int tokenType) {
+		int so = start + offsetShift;
+		addToken(zzBuffer, start,end, tokenType, so, true);
+	}
+
+
+	/**
+	 * Adds the token specified to the current linked list of tokens.
+	 *
+	 * @param tokenType The token's type.
+	 */
+	private void addToken(int tokenType) {
+		addToken(zzStartRead, zzMarkedPos-1, tokenType);
+	}
+
+
+	/**
+	 * Adds the token specified to the current linked list of tokens.
+	 *
+	 * @param tokenType The token's type.
+	 * @see #addHyperlinkToken(int, int, int)
+	 */
+	private void addToken(int start, int end, int tokenType) {
+		int so = start + offsetShift;
+		addToken(zzBuffer, start,end, tokenType, so, false);
+	}
+
+
+	/**
+	 * Adds the token specified to the current linked list of tokens.
+	 *
+	 * @param array The character array.
+	 * @param start The starting offset in the array.
+	 * @param end The ending offset in the array.
+	 * @param tokenType The token's type.
+	 * @param startOffset The offset in the document at which this token
+	 *        occurs.
+	 * @param hyperlink Whether this token is a hyperlink.
+	 */
+	public void addToken(char[] array, int start, int end, int tokenType,
+						int startOffset, boolean hyperlink) {
+		super.addToken(array, start,end, tokenType, startOffset, hyperlink);
+		zzStartRead = zzMarkedPos;
+	}
+
+
+	/**
+	 * {@inheritDoc}
+	 */
+	public String[] getLineCommentStartAndEnd(int languageIndex) {
+		return new String[] { "##", null };
+	}
+
+
+	/**
+	 * Returns the first token in the linked list of tokens generated
+	 * from <code>text</code>.  This method must be implemented by
+	 * subclasses so they can correctly implement syntax highlighting.
+	 *
+	 * @param text The text from which to get tokens.
+	 * @param initialTokenType The token type we should start with.
+	 * @param startOffset The offset into the document at which
+	 *        <code>text</code> starts.
+	 * @return The first <code>Token</code> in a linked list representing
+	 *         the syntax highlighted text.
+	 */
+	public Token getTokenList(Segment text, int initialTokenType, int startOffset) {
+
+		resetTokenList();
+		this.offsetShift = -text.offset + startOffset;
+
+		// Start off in the proper state.
+		int state = Token.NULL;
+		switch (initialTokenType) {
+						case Token.COMMENT_MULTILINE:
+				state = MLC;
+				start = text.offset;
+				break;
+
+			/* No documentation comments */
+			default:
+				state = Token.NULL;
+		}
+
+		s = text;
+		try {
+			yyreset(zzReader);
+			yybegin(state);
+			return yylex();
+		} catch (IOException ioe) {
+			ioe.printStackTrace();
+			return new TokenImpl();
+		}
+
+	}
+
+
+	/**
+	 * Refills the input buffer.
+	 *
+	 * @return      <code>true</code> if EOF was reached, otherwise
+	 *              <code>false</code>.
+	 */
+	private boolean zzRefill() {
+		return zzCurrentPos>=s.offset+s.count;
+	}
+
+
+	/**
+	 * Resets the scanner to read from a new input stream.
+	 * Does not close the old reader.
+	 *
+	 * All internal variables are reset, the old input stream 
+	 * <b>cannot</b> be reused (internal buffer is discarded and lost).
+	 * Lexical state is set to <tt>YY_INITIAL</tt>.
+	 *
+	 * @param reader   the new input stream 
+	 */
+	public final void yyreset(Reader reader) {
+		// 's' has been updated.
+		zzBuffer = s.array;
+		/*
+		 * We replaced the line below with the two below it because zzRefill
+		 * no longer "refills" the buffer (since the way we do it, it's always
+		 * "full" the first time through, since it points to the segment's
+		 * array).  So, we assign zzEndRead here.
+		 */
+		//zzStartRead = zzEndRead = s.offset;
+		zzStartRead = s.offset;
+		zzEndRead = zzStartRead + s.count - 1;
+		zzCurrentPos = zzMarkedPos = zzPushbackPos = s.offset;
+		zzLexicalState = YYINITIAL;
+		zzReader = reader;
+		zzAtBOL  = true;
+		zzAtEOF  = false;
+	}
+
+
+
+
+  /**
+   * Creates a new scanner
+   * There is also a java.io.InputStream version of this constructor.
+   *
+   * @param   in  the java.io.Reader to read input from.
+   */
+  public VelocityTokenMaker(Reader in) {
+    this.zzReader = in;
+  }
+
+  /**
+   * Creates a new scanner.
+   * There is also java.io.Reader version of this constructor.
+   *
+   * @param   in  the java.io.Inputstream to read input from.
+   */
+  public VelocityTokenMaker(InputStream in) {
+    this(new InputStreamReader(in));
+  }
+
+  /** 
+   * Unpacks the compressed character translation table.
+   *
+   * @param packed   the packed character translation table
+   * @return         the unpacked character translation table
+   */
+  private static char [] zzUnpackCMap(String packed) {
+    char [] map = new char[0x10000];
+    int i = 0;  /* index in packed string  */
+    int j = 0;  /* index in unpacked array */
+    while (i < 192) {
+      int  count = packed.charAt(i++);
+      char value = packed.charAt(i++);
+      do map[j++] = value; while (--count > 0);
+    }
+    return map;
+  }
+
+
+  /**
+   * Closes the input stream.
+   */
+  public final void yyclose() throws IOException {
+    zzAtEOF = true;            /* indicate end of file */
+    zzEndRead = zzStartRead;  /* invalidate buffer    */
+
+    if (zzReader != null)
+      zzReader.close();
+  }
+
+
+  /**
+   * Enters a new lexical state
+   *
+   * @param newState the new lexical state
+   */
+  public final void yybegin(int newState) {
+    zzLexicalState = newState;
+  }
+
+
+  /**
+   * Returns the text matched by the current regular expression.
+   */
+  public final String yytext() {
+    return new String( zzBuffer, zzStartRead, zzMarkedPos-zzStartRead );
+  }
+
+
+  /**
+   * Returns the character at position <tt>pos</tt> from the 
+   * matched text. 
+   * 
+   * It is equivalent to yytext().charAt(pos), but faster
+   *
+   * @param pos the position of the character to fetch. 
+   *            A value from 0 to yylength()-1.
+   *
+   * @return the character at position pos
+   */
+  public final char yycharat(int pos) {
+    return zzBuffer[zzStartRead+pos];
+  }
+
+
+  /**
+   * Returns the length of the matched text region.
+   */
+  public final int yylength() {
+    return zzMarkedPos-zzStartRead;
+  }
+
+
+  /**
+   * Reports an error that occured while scanning.
+   *
+   * In a wellformed scanner (no or only correct usage of 
+   * yypushback(int) and a match-all fallback rule) this method 
+   * will only be called with things that "Can't Possibly Happen".
+   * If this method is called, something is seriously wrong
+   * (e.g. a JFlex bug producing a faulty scanner etc.).
+   *
+   * Usual syntax/scanner level error handling should be done
+   * in error fallback rules.
+   *
+   * @param   errorCode  the code of the errormessage to display
+   */
+  private void zzScanError(int errorCode) {
+    String message;
+    try {
+      message = ZZ_ERROR_MSG[errorCode];
+    }
+    catch (ArrayIndexOutOfBoundsException e) {
+      message = ZZ_ERROR_MSG[ZZ_UNKNOWN_ERROR];
+    }
+
+    throw new Error(message);
+  } 
+
+
+  /**
+   * Pushes the specified amount of characters back into the input stream.
+   *
+   * They will be read again by then next call of the scanning method
+   *
+   * @param number  the number of characters to be read again.
+   *                This number must not be greater than yylength()!
+   */
+  public void yypushback(int number)  {
+    if ( number > yylength() )
+      zzScanError(ZZ_PUSHBACK_2BIG);
+
+    zzMarkedPos -= number;
+  }
+
+
+  /**
+   * Resumes scanning until the next regular expression is matched,
+   * the end of input is encountered or an I/O-Error occurs.
+   *
+   * @return      the next token
+   * @exception   IOException  if any I/O-Error occurs
+   */
+  public Token yylex() throws IOException {
+    int zzInput;
+    int zzAction;
+
+    // cached fields:
+    int zzCurrentPosL;
+    int zzMarkedPosL;
+    int zzEndReadL = zzEndRead;
+    char [] zzBufferL = zzBuffer;
+    char [] zzCMapL = ZZ_CMAP;
+
+    int [] zzTransL = ZZ_TRANS;
+    int [] zzRowMapL = ZZ_ROWMAP;
+    int [] zzAttrL = ZZ_ATTRIBUTE;
+
+    while (true) {
+      zzMarkedPosL = zzMarkedPos;
+
+      zzAction = -1;
+
+      zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL;
+  
+      zzState = zzLexicalState;
+
+
+      zzForAction: {
+        while (true) {
+    
+          if (zzCurrentPosL < zzEndReadL)
+            zzInput = zzBufferL[zzCurrentPosL++];
+          else if (zzAtEOF) {
+            zzInput = YYEOF;
+            break zzForAction;
+          }
+          else {
+            // store back cached positions
+            zzCurrentPos  = zzCurrentPosL;
+            zzMarkedPos   = zzMarkedPosL;
+            boolean eof = zzRefill();
+            // get translated positions and possibly new buffer
+            zzCurrentPosL  = zzCurrentPos;
+            zzMarkedPosL   = zzMarkedPos;
+            zzBufferL      = zzBuffer;
+            zzEndReadL     = zzEndRead;
+            if (eof) {
+              zzInput = YYEOF;
+              break zzForAction;
+            }
+            else {
+              zzInput = zzBufferL[zzCurrentPosL++];
+            }
+          }
+          int zzNext = zzTransL[ zzRowMapL[zzState] + zzCMapL[zzInput] ];
+          if (zzNext == -1) break zzForAction;
+          zzState = zzNext;
+
+          int zzAttributes = zzAttrL[zzState];
+          if ( (zzAttributes & 1) == 1 ) {
+            zzAction = zzState;
+            zzMarkedPosL = zzCurrentPosL;
+            if ( (zzAttributes & 8) == 8 ) break zzForAction;
+          }
+
+        }
+      }
+
+      // store back cached position
+      zzMarkedPos = zzMarkedPosL;
+
+      switch (zzAction < 0 ? zzAction : ZZ_ACTION[zzAction]) {
+        case 4: 
+          { addNullToken(); return firstToken;
+          }
+        case 27: break;
+        case 20: 
+          { addToken(Token.LITERAL_CHAR);
+          }
+        case 28: break;
+        case 16: 
+          { start = zzMarkedPos-2; yybegin(MLC);
+          }
+        case 29: break;
+        case 6: 
+          { addToken(Token.WHITESPACE);
+          }
+        case 30: break;
+        case 19: 
+          { addToken(Token.LITERAL_NUMBER_HEXADECIMAL);
+          }
+        case 31: break;
+        case 21: 
+          { addToken(Token.ERROR_STRING_DOUBLE);
+          }
+        case 32: break;
+        case 17: 
+          { addToken(Token.LITERAL_NUMBER_FLOAT);
+          }
+        case 33: break;
+        case 22: 
+          { addToken(Token.RESERVED_WORD);
+          }
+        case 34: break;
+        case 8: 
+          { addToken(Token.SEPARATOR);
+          }
+        case 35: break;
+        case 1: 
+          { addToken(Token.IDENTIFIER);
+          }
+        case 36: break;
+        case 11: 
+          { addToken(start,zzStartRead-1, Token.COMMENT_EOL); addNullToken(); return firstToken;
+          }
+        case 37: break;
+        case 15: 
+          { start = zzMarkedPos-2; yybegin(EOL_COMMENT);
+          }
+        case 38: break;
+        case 26: 
+          { addToken(Token.FUNCTION);
+          }
+        case 39: break;
+        case 3: 
+          { addToken(Token.ERROR_CHAR); addNullToken(); return firstToken;
+          }
+        case 40: break;
+        case 5: 
+          { addToken(Token.ERROR_STRING_DOUBLE); addNullToken(); return firstToken;
+          }
+        case 41: break;
+        case 18: 
+          { yybegin(YYINITIAL); addToken(start,zzStartRead+2-1, Token.COMMENT_MULTILINE);
+          }
+        case 42: break;
+        case 13: 
+          { addToken(Token.ERROR_CHAR);
+          }
+        case 43: break;
+        case 23: 
+          { addToken(Token.LITERAL_BOOLEAN);
+          }
+        case 44: break;
+        case 14: 
+          { addToken(Token.LITERAL_STRING_DOUBLE_QUOTE);
+          }
+        case 45: break;
+        case 25: 
+          { int temp=zzStartRead; addToken(start,zzStartRead-1, Token.COMMENT_EOL); addHyperlinkToken(temp,zzMarkedPos-1, Token.COMMENT_EOL); start = zzMarkedPos;
+          }
+        case 46: break;
+        case 24: 
+          { int temp=zzStartRead; addToken(start,zzStartRead-1, Token.COMMENT_MULTILINE); addHyperlinkToken(temp,zzMarkedPos-1, Token.COMMENT_MULTILINE); start = zzMarkedPos;
+          }
+        case 47: break;
+        case 12: 
+          { addToken(Token.ERROR_NUMBER_FORMAT);
+          }
+        case 48: break;
+        case 2: 
+          { addToken(Token.LITERAL_NUMBER_DECIMAL_INT);
+          }
+        case 49: break;
+        case 7: 
+          { addToken(Token.OPERATOR);
+          }
+        case 50: break;
+        case 9: 
+          { 
+          }
+        case 51: break;
+        case 10: 
+          { addToken(start,zzStartRead-1, Token.COMMENT_MULTILINE); return firstToken;
+          }
+        case 52: break;
+        default: 
+          if (zzInput == YYEOF && zzStartRead == zzCurrentPos) {
+            zzAtEOF = true;
+            switch (zzLexicalState) {
+            case EOL_COMMENT: {
+              addToken(start,zzStartRead-1, Token.COMMENT_EOL); addNullToken(); return firstToken;
+            }
+            case 225: break;
+            case YYINITIAL: {
+              addNullToken(); return firstToken;
+            }
+            case 226: break;
+            case MLC: {
+              addToken(start,zzStartRead-1, Token.COMMENT_MULTILINE); return firstToken;
+            }
+            case 227: break;
+            default:
+            return null;
+            }
+          } 
+          else {
+            zzScanError(ZZ_NO_MATCH);
+          }
+      }
+    }
+  }
+
+
+}
diff --git a/modeler/cayenne-modeler/src/main/resources/org/apache/cayenne/modeler/images/icon-find_and_replace.png b/modeler/cayenne-modeler/src/main/resources/org/apache/cayenne/modeler/images/icon-find_and_replace.png
new file mode 100644
index 000000000..363b512aa
Binary files /dev/null and b/modeler/cayenne-modeler/src/main/resources/org/apache/cayenne/modeler/images/icon-find_and_replace.png differ
diff --git a/pom.xml b/pom.xml
index 4de1f92cf..9d37e65fa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -464,6 +464,11 @@
 				<version>${testcontainers.version}</version>
 				<scope>test</scope>
 			</dependency>
+			<dependency>
+				<groupId>com.fifesoft</groupId>
+				<artifactId>rsyntaxtextarea</artifactId>
+				<version>3.2.0</version>
+			</dependency>
 		</dependencies>
 	</dependencyManagement>
 


[cayenne] 03/03: CAY-2761 Modeler: Editor for the cgen templates - minor code cleanup

Posted by nt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ntimofeev pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git

commit feb1b89f9d3578950310d400746c26980d2056aa
Author: Nikita Timofeev <st...@gmail.com>
AuthorDate: Thu Sep 29 17:35:28 2022 +0300

    CAY-2761 Modeler: Editor for the cgen templates
     - minor code cleanup
---
 .../modeler/dialog/pref/TemplatePreferences.java   | 19 +++-----
 .../PreviewClassGenerationFactory.java             |  2 +-
 .../templateeditor/TemplateEditorController.java   | 57 ++++++++++------------
 .../dialog/templateeditor/TemplateEditorView.java  |  4 +-
 4 files changed, 38 insertions(+), 44 deletions(-)

diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/pref/TemplatePreferences.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/pref/TemplatePreferences.java
index 520a5cf59..f18886ebe 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/pref/TemplatePreferences.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/pref/TemplatePreferences.java
@@ -72,7 +72,7 @@ public class TemplatePreferences extends CayenneController {
         initBindings();
     }
 
-    public Component getView() {
+    public TemplatePreferencesView getView() {
         return view;
     }
 
@@ -127,7 +127,10 @@ public class TemplatePreferences extends CayenneController {
 
     @SuppressWarnings("unused")
     public void templateEditorAction() {
-         new TemplateEditorController(this).startupAction();
+        int selectedRow = view.getTable().getSelectedRow();
+        if(selectedRow != -1) {
+            new TemplateEditorController(this).startupAction();
+        }
     }
 
     @SuppressWarnings("unused")
@@ -161,9 +164,7 @@ public class TemplatePreferences extends CayenneController {
         if (path != null) {
             int len = templateEntries.size();
             templateEntries.add(path);
-            ((AbstractTableModel) view.getTable().getModel()).fireTableRowsInserted(
-                    len,
-                    len);
+            ((AbstractTableModel) view.getTable().getModel()).fireTableRowsInserted(len, len);
         }
     }
 
@@ -174,16 +175,12 @@ public class TemplatePreferences extends CayenneController {
             return;
         }
 
-        Object key = ((AbstractTableModel) view.getTable().getModel()).getValueAt(
-                selected,
-                0);
+        Object key = view.getTable().getModel().getValueAt(selected, 0);
 
         editor.getRemovedNode().add(getTemplatePreferences().node((String) key));
         templateEntries.remove(selected);
 
-        ((AbstractTableModel) view.getTable().getModel()).fireTableRowsDeleted(
-                selected,
-                selected);
+        ((AbstractTableModel) view.getTable().getModel()).fireTableRowsDeleted(selected, selected);
 
     }
 }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/PreviewClassGenerationFactory.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/PreviewClassGenerationFactory.java
index f8bba275b..e9d4c5631 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/PreviewClassGenerationFactory.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/PreviewClassGenerationFactory.java
@@ -40,7 +40,7 @@ public class PreviewClassGenerationFactory implements ClassGenerationActionFacto
     @Inject
     private MetadataUtils metadataUtils;
 
-    @Inject
+    @Inject(TemplateEditorController.TEMPLATE_EDITOR_WRITER)
     private StringWriter writer;
 
     @Override
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/TemplateEditorController.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/TemplateEditorController.java
index 8c20aac4b..ade5f1340 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/TemplateEditorController.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/TemplateEditorController.java
@@ -21,6 +21,7 @@ package org.apache.cayenne.modeler.dialog.templateeditor;
 
 import org.apache.cayenne.configuration.xml.DataChannelMetaData;
 import org.apache.cayenne.di.Injector;
+import org.apache.cayenne.di.Key;
 import org.apache.cayenne.gen.CgenConfiguration;
 import org.apache.cayenne.gen.ClassGenerationAction;
 import org.apache.cayenne.gen.ClassGenerationActionFactory;
@@ -40,31 +41,34 @@ import javax.swing.text.BadLocationException;
 import java.awt.Component;
 import java.io.File;
 import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 /**
  * since 4.3
  */
 public class TemplateEditorController extends CayenneController {
 
+    public static final String TEMPLATE_EDITOR_REPO = "templateEditorRepo";
+    public static final String TEMPLATE_EDITOR_WRITER = "tplEditorWriter";
+    private static final String EDITED_TEMPLATE = "editedTemplate";
+    private static final Key<StringWriter> TPL_EDITOR_WRITER = Key.get(StringWriter.class, TEMPLATE_EDITOR_WRITER);
+
+    private final Map<String, String> customTemplates;
+    private final DataMap currentDataMap;
+
     protected TemplatePreferencesView preferencesView;
     protected TemplateEditorView view;
     protected boolean canceled;
-    private final Map<String, String> customTemplates;
-    private static final String EDITED_TEMPLATE = "editedTemplate";
-    public static final String TEMPLATE_EDITOR_REPO = "templateEditorRepo";
-    private final DataMap currentDataMap;
 
     public TemplateEditorController(TemplatePreferences preferences) {
         super(preferences);
-        this.preferencesView = (TemplatePreferencesView) preferences.getView();
+        this.preferencesView = preferences.getView();
         this.customTemplates = application.getCodeTemplateManager().getCustomTemplates();
         this.currentDataMap = application.getFrameController().getProjectController().getCurrentDataMap();
-        this.view = new TemplateEditorView( getEntityNames());
+        this.view = new TemplateEditorView(getEntityNames());
         initBindings();
     }
 
@@ -117,18 +121,18 @@ public class TemplateEditorController extends CayenneController {
     public void generateAction() throws Exception {
         putTemplateTextInRepository();
         Injector injector = getInjector();
-        ClassGenerationAction action =  injector
+        ClassGenerationAction action = injector
                 .getInstance(ClassGenerationActionFactory.class)
                 .createAction(getCgenConfiguration());
-        StringWriter writer = injector.getInstance(StringWriter.class);
-    action.addEntities(Collections.singleton(getSelectedEntity()));
+        StringWriter writer = injector.getInstance(TPL_EDITOR_WRITER);
+        action.addEntities(Collections.singleton(getSelectedEntity()));
         int caretPosition = view.getEditingTemplatePane().getCaretPosition();
         try {
             action.execute();
+        } catch (ParseErrorException pe) {
+            caretPosition = getErrorCaretPosition(pe);
+            writer.write(pe.getMessage());
         } catch (Exception e) {
-            if (e instanceof ParseErrorException) {
-                caretPosition = getErrorCaretPosition((ParseErrorException) e);
-            }
             writer.write(e.getMessage());
         }
         view.getEditingTemplatePane().setCaretPosition(caretPosition);
@@ -147,7 +151,7 @@ public class TemplateEditorController extends CayenneController {
         return new ToolsInjectorBuilder()
                 .addModule(binder -> binder.bind(DataChannelMetaData.class).toInstance(metaData))
                 .addModule(binder -> binder.bind(ClassGenerationActionFactory.class).to(PreviewClassGenerationFactory.class))
-                .addModule(binder -> binder.bind(StringWriter.class).to(StringWriter.class))
+                .addModule(binder -> binder.bind(TPL_EDITOR_WRITER).to(StringWriter.class))
                 .create();
     }
 
@@ -176,26 +180,19 @@ public class TemplateEditorController extends CayenneController {
 
     private ObjEntity getSelectedEntity() {
         String selectedEntityName = view.getSelectedEntityName();
-        ObjEntity selectedObject = null;
-        Collection<ObjEntity> objEntities = currentDataMap.getObjEntities();
-        for (ObjEntity object : objEntities) {
-            if (selectedEntityName.equals(object.getName())) {
-                selectedObject = object;
-            }
+        if(currentDataMap == null) {
+            return null;
         }
-        return selectedObject;
+        return currentDataMap.getObjEntity(selectedEntityName);
     }
 
     private List<String> getEntityNames() {
-        ArrayList<String> names = new ArrayList<>();
-        if (currentDataMap != null) {
-            Object[] objEntities = currentDataMap.getObjEntities().toArray();
-            for (Object objEntity : objEntities) {
-                ObjEntity s = (ObjEntity) objEntity;
-                names.add(s.getName());
-            }
+        if(currentDataMap == null) {
+            return Collections.emptyList();
         }
-        return names;
+        return currentDataMap.getObjEntities().stream()
+                .map(ObjEntity::getName)
+                .collect(Collectors.toList());
     }
 
 }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/TemplateEditorView.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/TemplateEditorView.java
index 4580dbd34..a167cfd8f 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/TemplateEditorView.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/templateeditor/TemplateEditorView.java
@@ -54,7 +54,7 @@ public class TemplateEditorView extends JDialog {
     protected JButton saveButton;
     protected JButton findButton;
     protected JButton findAndReplaceButton;
-    protected JComboBox<Object> entityComboBox;
+    protected JComboBox<String> entityComboBox;
     private JSplitPane split;
     private JToolBar toolBar;
     private JPanel topPanel;
@@ -80,7 +80,7 @@ public class TemplateEditorView extends JDialog {
         this.findAndReplaceButton.setToolTipText("Find and replace");
         this.previewButton = new JButton(ModelerUtil.buildIcon("icon-edit.png"));
         this.previewButton.setToolTipText("Generate preview");
-        this.entityComboBox = new JComboBox<>(entityNames.toArray());
+        this.entityComboBox = new JComboBox<>(entityNames.toArray(new String[0]));
         this.entityComboBox.setToolTipText("Select an entity for the test");
     }