Netbeans Template that creates multiple files

Generally speaking I want to create a Netbeans (7.4) File Wizard that creates multiple files.

In my case I want it to create JavaFX related files to use a convention over configuration approach when loading fxml files. (I took that Idea from Adam Bien: http://www.youtube.com/watch?v=mawFd4h1Or4)

More Specific: I want to enter a single String that defines a Basename and want the wizard to create 3 different files, located in the package where the Rightclick -> "New" wizard was invoked by the user.

  1. Basename.fxml
  2. BasenameController.java
  3. BasenameView.java

I tried to write my own Wizard for almost 3 days now, and it won't work. I have studied the (thin) Netbeans API and read all these tutorials (and more) carefully:

I tried more things then i can enumerate here, but these are some of the things that keep happining:

  • I never got it to work with a single @TemplateRegistration and multiple content entries.
  • So I registered multiple templates with @TemplateRegistrations, one Template for each file I wanted to create.
  • This creates multiple files, but only the first file has correct properties like ${package} set for the freemarker script. (Additionally this way I end up with multiple Templates ... but I could live with that.)
  • Then I tried to use other poperty names and set these by hand, But I can't figure out how to find the package the wizard was invoked on.

Related Questions, which did not really help me:

Any advice is very welcome. Thanks in advance!

UPDATE:

I managed to create the desired files. The problem was that you have to use a sparsely documented naming convention. If you want to correctly create a Basename.java file, you have to name the template file: Basename.java (which makes your compiler unhappy, as the template file is not a valid java class), or Basename.java.template. If you name it anything else like: Basename.java.templ (and manage the correct naming of the file yourself), it will not set the ${package} variable for you.

So now I'm very close to what I wanted, but there is still a problem left with correctly registering the Wizard.

Currently I'm using @TemplateRegistrations to register 3 templates (one for each file). But as the "folder" attribute is mandatory I now have 3 entries in the "New" Wizard of Netbeans. When i invoke one of them it will create all 3 files as desired, but I would like to only have 1 entry in the "New" Wizard.

@TemplateRegistrations({
    @TemplateRegistration(content = "FXML.fxml.template", scriptEngine = "freemarker", folder = "ASpecial", displayName = "#TemplateName", iconBase = "de/ekrnrw/fxmlview/view.png", description = "newFXMLView.html"),
    @TemplateRegistration(content = "View.java.template", scriptEngine = "freemarker", folder = "ASpecial"),
    @TemplateRegistration(content = "Controller.java.template", scriptEngine = "freemarker", folder = "ASpecial")
})

I tried to use a single @TemplateRegistration with multiple content entries, but this does not work with the code from the tutorial. It seems like only the fxml file is processed be freemarker, and the two java files are being just plain copies of the template files.

@TemplateRegistration(content = {"FXML.fxml.template","View.java.template","Controller.java.template"}, scriptEngine = "freemarker", folder = "ASpecial", displayName = "#TemplateName", iconBase = "de/ekrnrw/fxmlview/view.png", description = "newFXMLView.html")

I posted this question to the NetBeans mailing list here. And here is the relevant code snippet:

package de.ekrnrw.fxmlview;

import java.awt.Component;
...
import org.openide.util.NbBundle.Messages;

@TemplateRegistrations({
    @TemplateRegistration(content = "FXML.fxml.template", scriptEngine = "freemarker", folder = "ASpecial", displayName = "#TemplateName", iconBase = "de/ekrnrw/fxmlview/view.png", description = "newFXMLView.html"),
    @TemplateRegistration(content = "View.java.template", scriptEngine = "freemarker", folder = "ASpecial"),
    @TemplateRegistration(content = "Controller.java.template", scriptEngine = "freemarker", folder = "ASpecial")
})

// Would like to use this, but i can't make it work.
//@TemplateRegistration(content = {"FXML.fxml.template","View.java.template","Controller.java.template"}, scriptEngine = "freemarker", folder = "ASpecial", displayName = "#TemplateName", iconBase = "de/ekrnrw/fxmlview/view.png", description = "newFXMLView.html")

@Messages("TemplateName=FXMLView4")

public final class NewFXMLViewWizardIterator implements WizardDescriptor.InstantiatingIterator<WizardDescriptor> {

    private int index;

    private WizardDescriptor wizard;
    private List<WizardDescriptor.Panel<WizardDescriptor>> panels;

    private List<WizardDescriptor.Panel<WizardDescriptor>> getPanels() {
        if (panels == null) {
            panels = new ArrayList<>();

            // Change to default new file panel and add our panel at bottom
            Project p = Templates.getProject(wizard);
            SourceGroup[] groups = ProjectUtils.getSources(p).getSourceGroups(Sources.TYPE_GENERIC);

            // SimpleTargetChooser is the default new file panel
            WizardDescriptor.Panel<WizardDescriptor> advNewFilePanel = Templates.buildSimpleTargetChooser(p, groups).create();
            panels.add(advNewFilePanel);

            String[] steps = createSteps();
            for (int i = 0; i < panels.size(); i++) {
                Component c = panels.get(i).getComponent();
                if (steps[i] == null) {
                    // Default step name to component name of panel. Mainly
                    // useful for getting the name of the target chooser to
                    // appear in the list of steps.
                    steps[i] = c.getName();
                }
                if (c instanceof JComponent) { // assume Swing components
                    JComponent jc = (JComponent) c;
                    jc.putClientProperty(WizardDescriptor.PROP_CONTENT_SELECTED_INDEX, i);
                    jc.putClientProperty(WizardDescriptor.PROP_CONTENT_DATA, steps);
                    jc.putClientProperty(WizardDescriptor.PROP_AUTO_WIZARD_STYLE, true);
                    jc.putClientProperty(WizardDescriptor.PROP_CONTENT_DISPLAYED, true);
                    jc.putClientProperty(WizardDescriptor.PROP_CONTENT_NUMBERED, true);
                }
            }
        }
        return panels;
    }

    @Override
    public Set<?> instantiate() throws IOException {

        //Get the folder:
        FileObject dir = Templates.getTargetFolder(wizard);
        DataFolder df = DataFolder.findFolder(dir);

        //Get TargetName from File Wizard
        String targetName = Templates.getTargetName(wizard);

        // Read Title from wizard 
        String viewName = targetName + "View";
        String controllerName = targetName + "Controller";

        // FreeMarker Template will get its variables from HashMap.
        // HashMap key is the variable name.
        Map args = new HashMap();
        args.put("controllerName", controllerName);
        args.put("viewName", viewName);

        //Get Templates
        FileObject[] templates = Templates.getTemplate(wizard).getParent().getChildren();

        // Create all Files
        Set<DataObject> createdObjects = new HashSet<>();

        for (FileObject fileObject : templates) {

            DataObject dTemplate = DataObject.find(fileObject);
            String templateName = dTemplate.getName();

            // Sepcial treatment for the .fxml file.
            if (templateName.equals("FXML")) {
                templateName = "";
            }

            // Create file
            createdObjects.add(dTemplate.createFromTemplate(df, targetName + templateName , args));
        }
        return createdObjects;
    }    
    ...
}

Answers


Please ask at the mailing list dev@platform.netbeans.org and provide a non-working sample, so that the issue can be reproduced.


I tried your sample and I looked it up. You are not required to use the @TemplateRegistration annotation to use freemarker nor to use Templates.getTemplate(wizard).getParent().getChildren()

  1. Get each of your templates via FileObject template1 = FileUtil.getConfigFile("CustomerSalesTemplates/project1.txt"); DataObject dTemplate1 = DataObject.find(template1);
  2. and then use dTemplate1.createFromTemplate to invoke freemarker

See the articles * https://blogs.oracle.com/geertjan/entry/how_to_visually_diff_multiple * https://blogs.oracle.com/geertjan/entry/freemarker_baked_into_netbeans_ide1


All @TemplateRegistration annotations will be converted into file entries in a file called layer.xml at build time. In layer.xml, file templates are presented as follows:

<filesystem>
    <folder name="Templates">
        <folder name="javafx">
            <file name="FxmlNode.java" url="nbresloc:/tuupertunut/fxmltemplates/FxmlNode.java.template">
                <attr name="template" boolvalue="true"/>
                <attr name="displayName" bundlevalue="tuupertunut.fxmltemplates.Bundle#FxmlNode_displayName"/>
                <attr name="iconBase" stringvalue="org/netbeans/spi/java/project/support/ui/templates/class.png"/>
                <attr name="instantiatingWizardURL" urlvalue="nbresloc:/tuupertunut/fxmltemplates/FxmlNodeDescription.html"/>
                <attr name="instantiatingIterator" newvalue="tuupertunut.fxmltemplates.FxmlNodeIterator"/>
                <attr name="requireProject" boolvalue="true"/>
                <attr name="javax.script.ScriptEngine" stringvalue="freemarker"/>
            </file>
        </folder>
    </folder>
</filesystem>

You can see a file entry that has attr attributes. The relevant attributes regarding your question are template and javax.script.ScriptEngine.

  • Every file entry that has <attr name="template" boolvalue="true"/> will be shown as a separate entry in the "New file" dialog.
  • Every file entry that has <attr name="javax.script.ScriptEngine" stringvalue="freemarker"/> will be processed by freemarker before creating the file.

Case 1: Multiple @TemplateRegistrations

@TemplateRegistrations({
    @TemplateRegistration(content = "FXML.fxml.template", scriptEngine = "freemarker", folder = "ASpecial", ...),
    @TemplateRegistration(content = "View.java.template", scriptEngine = "freemarker", folder = "ASpecial"),
    @TemplateRegistration(content = "Controller.java.template", scriptEngine = "freemarker", folder = "ASpecial")
})

will convert to

<filesystem>
    <folder name="Templates">
        <folder name="ASpecial">
            <file name="FXML.fxml" url="nbresloc:/yourpackage/FXML.fxml.template">
                <attr name="template" boolvalue="true"/>
                <attr name="javax.script.ScriptEngine" stringvalue="freemarker"/>
                ...
            </file>
            <file name="View.java" url="nbresloc:/yourpackage/View.java.template">
                <attr name="template" boolvalue="true"/>
                <attr name="javax.script.ScriptEngine" stringvalue="freemarker"/>
                ...
            </file>
            <file name="Controller.java" url="nbresloc:/yourpackage/Controller.java.template">
                <attr name="template" boolvalue="true"/>
                <attr name="javax.script.ScriptEngine" stringvalue="freemarker"/>
                ...
            </file>
        </folder>
    </folder>
</filesystem>

As you can see, all three templates will show in the "New file" dialog and all three templates will be processed by freemarker.


Case 2: Multiple content in single @TemplateRegistration

@TemplateRegistration(content = {"FXML.fxml.template","View.java.template","Controller.java.template"}, scriptEngine = "freemarker", folder = "ASpecial", ...)

will convert to

<filesystem>
    <folder name="Templates">
        <folder name="ASpecial">
            <file name="FXML.fxml" url="nbresloc:/yourpackage/FXML.fxml.template">
                <attr name="template" boolvalue="true"/>
                <attr name="javax.script.ScriptEngine" stringvalue="freemarker"/>
                ...
            </file>
            <file name="View.java" url="nbresloc:/yourpackage/View.java.template">
                ...
            </file>
            <file name="Controller.java" url="nbresloc:/yourpackage/Controller.java.template">
                ...
            </file>
        </folder>
    </folder>
</filesystem>

Here only the first template will show in the "New file" dialog and also only the first will be processed by freemarker.


Solution

As the annotations won't produce the desired layer.xml, you must create one yourself. You should be able to find the automatically generated generated-layer.xml from your project's build/classes/META-INF directory. Create a new layer.xml file for your project from the "New file" dialog and copy-paste everything into it. Then you can edit those template and javax.script.ScriptEngine attributes by hand.

In the end, it should look something like this:

<filesystem>
    <folder name="Templates">
        <folder name="ASpecial">
            <file name="FXML.fxml" url="nbresloc:/yourpackage/FXML.fxml.template">
                <attr name="template" boolvalue="true"/>
                <attr name="javax.script.ScriptEngine" stringvalue="freemarker"/>
                ...
            </file>
            <file name="View.java" url="nbresloc:/yourpackage/View.java.template">
                <attr name="javax.script.ScriptEngine" stringvalue="freemarker"/>
                ...
            </file>
            <file name="Controller.java" url="nbresloc:/yourpackage/Controller.java.template">
                <attr name="javax.script.ScriptEngine" stringvalue="freemarker"/>
                ...
            </file>
        </folder>
    </folder>
</filesystem>

Where the first entry is the only one with template but all of them have javax.script.ScriptEngine

Don't forget to remove your @TemplateRegistration annotations, as the templates are now managed by your layer.xml.


Need Your Help

Filtering OData request with inherited entities causes casting exception

entity-framework linq-to-entities wcf-data-services odata

I feel like I'm asking a lot of questions, but I keep getting stuck. I am developing an OData service, and I want an entity that can have several user-designated name-value pairs associated, which...

Django virtual host setup. Apache mod_wsgi

django apache mod-wsgi python-2.7 redhat

I am hoping there is a simple answer to my question as I am not the most experienced with python and Apache.