Creating a Better Java Class Wizard

At the end of my recent Javalobby article, Overview of NetBeans Java API Samples in Plugin Portal, two users asked for a more advanced wizard for Java classes, one that would let the user specify superclasses and interfaces right in the wizard. Fine. Let's create exactly that. If you work through everything in this blog entry, you will end up with a new template in the New File dialog:

When you click Next, you'll get everything as usual, except also the possibility to fill in superclasses and interfaces (all on the same panel, hurray):

On clicking Finish, you'll have a new class, with the superclasses and interfaces you specified generated automatically right into the class, and lightbulbs to click through to generate the import statements and required methods:

If you need this, or something like that, follow along below:

  1. Create a new module, called "FancyJavaClassTemplate", with code name base "org.netbeans.modules.fancy".

  2. In the New File dialog, use the Wizard wizard to create stubs for a new wizard, with "New File" under "Registration Type" and "1" in "Number of Wizard Panels":

  3. Type values in the final panel and click Finish. Then look at the generated sources. First look at the layer.xml file, then at the Java classes, and then at nbproject/project.xml.

  4. Use FancyVisualPanel1 to design the extra panel that you want to show, making sure that you call the first field "extField" and the second "impField" (because the code later on in this blog entry will assume that you've done so):

    Don't worry about the top part of what you see in the second screenshot above, because that's simply reused from the NetBeans internals, as you will see.

  5. Set dependencies on 'Datasystems API', 'Filesystem API', 'Java project Support', 'Nodes API', 'Project API', and 'Project UI API'.

  6. Open the class called "FancyWizardIterator.java". Rewrite the start of the getPanels() method as follows and make sure to also declare the packageChooserPanel, as shown below:

    private WizardDescriptor.Panel packageChooserPanel;
    
    private WizardDescriptor.Panel[] getPanels() {
        Project project = Templates.getProject(wizard);
        Sources sources = (Sources) project.getLookup().lookup(Sources.class);
        SourceGroup[] groups = sources.getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA);
        packageChooserPanel = JavaTemplates.createPackageChooser(project, groups, new FancyWizardPanel1());
        if (panels == null) {
            panels = new WizardDescriptor.Panel[]{
                        packageChooserPanel,
                    };
            String[] steps = createSteps();
            ...
            ...
            ...

    Fix imports and make sure that you have ALL of these as your import statements:

    import java.awt.Component;
    import java.io.IOException;
    import java.util.Collections;
    import java.util.NoSuchElementException;
    import java.util.Set;
    import javax.swing.JComponent;
    import javax.swing.event.ChangeListener;
    import org.netbeans.api.java.project.JavaProjectConstants;
    import org.netbeans.api.project.Project;
    import org.netbeans.api.project.SourceGroup;
    import org.netbeans.api.project.Sources;
    import org.netbeans.spi.project.ui.templates.support.Templates;
    import org.openide.WizardDescriptor;

  7. Install and see the results. You should see the panels shown at the start of this blog entry. But nothing will happen when you click Finish, yet. Probably the icon will be different. (You can set that by copying from another template, if you expand "this layer in context" in the layer.xml, then find another template that has the icon you want and choose "Open Layer File(s)". Then copy the attribute that points to the icon.)

  8. Go to the Template Manager under the Tools menu and open Classes | Java Class into the editor. You should see this:

    <#assign licenseFirst = "/\*">
    <#assign licensePrefix = " \* ">
    <#assign licenseLast = " \*/">
    <#include "../Licenses/license-${project.license}.txt">
    
    <#if package?? && package != "">
    package ${package};
    
    </#if>
    /\*\*
     \*
     \* @author ${user}
     \*/
    public class ${name} {
    
    }

    Create a new empty file, call it "Fancy.ftl", and then copy all of the above into it. Then add the bit in bold below:

    <#assign licenseFirst = "/\*">
    <#assign licensePrefix = " \* ">
    <#assign licenseLast = " \*/">
    <#include "../Licenses/license-${project.license}.txt">
    
    <#if package?? && package != "">
    package ${package};
    
    </#if>
    /\*\*
     \*
     \* @author ${user}
     \*/
    public class ${name} ${extends} ${implements} {
    
    }

  9. Open the layer.xml file and tweak it so that it has the same content as the below. Pay particular attention to the bits that are in bold and make VERY sure that your layer.xml file has these too after you've finished tweaking it:

    <filesystem>
        <folder name="Templates">
            <folder name="Classes">
                <file name="fancy.java" url="Fancy.ftl">
                    <attr name="SystemFileSystem.localizingBundle" stringvalue="org.netbeans.modules.fancy.Bundle"/>
                    <attr name="instantiatingIterator" newvalue="org.netbeans.modules.fancy.FancyWizardIterator"/>
                    <attr name="template" boolvalue="true"/>
                    <attr name="SystemFileSystem.icon" urlvalue="nbresloc:org/netbeans/modules/java/resources/class.gif"/>
                    <attr name="templateWizardURL" urlvalue="nbresloc:/org/netbeans/modules/fancy/fancy.html"/>
                    <attr name="javax.script.ScriptEngine" stringvalue="freemarker"/>
                </file>
            </folder>
        </folder>
    </filesystem>

    In other words, make sure that the first line in bold above has ".java" as the name's extension and that "Fancy.ftl" is set as the URL. Check to make sure that your template has exactly that name. It is case sensitive, so be careful. The second line in bold above is needed because that activates the FreeMarker scripting engine, which is what you need to have the NetBeans Platform do for you in order for the template to be generated correctly.

  10. Now we will pass the values from the panel into the template above. Firstly, in the visual panel, make the values in the text fields accessible:

    public String getExtension() {
        return extField.getText();
    }
    
    public String getImplementation() {
        return impField.getText();
    }

    Also add two fields to the top of the visual panel class:

    static final String EXTENSION = "extension";
    static final String IMPLEMENTATION = "implementation";

    In the wizard panel (i.e., NOT the visual panel), retrieve the above values:

    private String getExtensionFromVisualPanel() {
        return ((FancyVisualPanel1) component).getExtension();
    }
    
    private String getImplementationFromVisualPanel() {
        return ((FancyVisualPanel1) component).getImplementation();
    }

    Store the value by overriding the store method in the wizard panel:

    @Override
    public void storeSettings(Object settings) {
        ((WizardDescriptor) settings).putProperty(FancyVisualPanel1.EXTENSION,  getExtensionFromVisualPanel());
        ((WizardDescriptor) settings).putProperty(FancyVisualPanel1.IMPLEMENTATION,  getImplementationFromVisualPanel());
    }

  11. To wrap it all up together, implement the iterator's instantiate() method as follows:

    public Set instantiate() throws IOException {
    
        //Prepare the arguments for passing to the FreeMarker template:
        String extension = (String) wizard.getProperty(FancyVisualPanel1.EXTENSION);
        String implementation = (String) wizard.getProperty(FancyVisualPanel1.IMPLEMENTATION);
        Map args = new HashMap();
        args.put("extends", "extends " + extension);
        args.put("implements", "implements " + implementation);
    
        //Get the template and convert it:
        FileObject template = Templates.getTemplate(wizard);
        DataObject dTemplate = DataObject.find(template);
    
        //Get the package:
        FileObject dir = Templates.getTargetFolder(wizard);
        DataFolder df = DataFolder.findFolder(dir);
    
        //Get the class:
        String targetName = Templates.getTargetName(wizard);
    
        //Define the template from the above,
        //passing the package, the file name, and the map of strings to the template:
        DataObject dobj = dTemplate.createFromTemplate(df, targetName, args);
    
        //Obtain a FileObject:
        FileObject createdFile = dobj.getPrimaryFile();
    
        //Create the new file:
        return Collections.singleton(createdFile);
    
    }

  12. You're finished! Install the plugin and now the template should create the file for you.

Homework assignments—figure out what should happen when the user types NOTHING in one of the new fields in the wizard. Should you do something in the code to catch that? The wizards can provide validation functionality, which you would need to enable, causing red error messages to appear in the panel under specified conditions, such as the fields not being (correctly) filled out. Or... should you make use of the FreeMarker template to add logic to it there so that if an empty string is passed, nothing is generated? Also, figure out how to HIDE the existing Java class wizard and let your own wizard replace it.

Further resources—FreeMarker Template Sample in the Plugin Portal, which gives you additional samples for wizards just like this (including one for generating an HTML file), as well as syntax coloring and code completion for FreeMarker templates.

Today on NetBeans Zone. How Fast Can OpenOffice.org Be Extended?

Comments:

Geertjan,

You can use:

http://hg.netbeans.org/main/file/560fa2e2b93d/java.sourceui/src/org/netbeans/api/java/source/ui/TypeElementFinder.java

to create a [Browse...] button next to Extends and Implements text field so that the user can select a Java Class or Interface from the project's Classpath. You will of course need to write proper filters to select Classes that can be extended in case of Extends (e.g. non-final classes) and Interfaces in case of Implements textfield respectively.

See this blog entry for the usage of TypeElementFinder:

http://blogs.sun.com/scblog/entry/tip_api_to_show_java

Posted by Sandip on April 02, 2008 at 02:27 AM PDT #

No offense, but since when is implementing something core to the Java language "Fancy"? Inheritance is one of the core features of any object-oriented language, especially Java.

It's great to see that it's possible to coerce Netbeans into behaving in a sane way but, as a student, that method was completely non-obvious to me and, as a student, unless I particularly have a pressing need to write something like this, I'm not going to; I want something that "just works", out of the box, and this is a fundamental point of operation. So, instead, I'll turn to the IDE that offers me the functionality that I need to get a job done quickly and efficiently. For years, and for almost this feature alone, my choice has been Eclipse, whenever possible - it could allow me to subclass easily, quickly and sanely.

To me, this choice was crystallised by the fact that, at the time, Netbeans was still in commercial development, while a free product offered features more relevant to my needs.

When I've had to work with complex projects (ones that heavily use "fancy" features such as interfaces and inheritance) using Netbeans, I've found this lack of functionality to be frustrating, to say the least. Moreover, it's reinforced my belief that Netbeans is not a project I'd ever want to use willingly.

So, while I consider this plugin to be a good idea, I seriously believe it to be too little, too late for something so core to the Java language, especially when it's not rolled into the main functionality and has to be implemented as a bolt-on.

Posted by Aoife Nic Aodh on April 02, 2008 at 08:49 PM PDT #

Thanks for the idea, Sandip, I will pursue that for sure. Thanks for sharing your thoughts, Aoife, all the best with Eclipse.

Posted by Geertjan on April 02, 2008 at 08:53 PM PDT #

Hi,

I made this module, followed all your steps, I tried running it, but to no avail!

Would like to know, which IDE you used for developing this module? I tried it on NetBeans 6.0, didn't work as expected...

Thanks!

Posted by Varun on April 03, 2008 at 05:32 AM PDT #

Put your code in a ZIP file and send it to me. You're also going to have to describe exactly what you mean by "didn't work as expected".

Posted by Geertjan on April 03, 2008 at 05:48 AM PDT #

When I said, "I tried running it, but to no avail", what I meant was that I got the Wizard for file creation, but when I clicked on the Finish Button, it showed an error...

------
A java.lang.reflect.UndeclaredThrowableException exception has occurred.
Please report this at http://www.netbeans.org/community/issues.html,
including a copy of your messages.log file as an attachment.
The messages.log file is located in your E:\\OpenSource\\NetBeansProjects\\FancyJavaClassTemplate\\build\\testuserdir\\var\\log folder.
------

Also, I got the result same as presented above in the 1st two snapshots, of this post. I have also send you the code.

One more thing, there was one step - "Go to the Template Manager under the Tools menu and open Classes | Java Class into the editor", I had no Classes Folder, instead I opened "Java | Java Class" to get the template.

Posted by Varun on April 03, 2008 at 04:09 PM PDT #

Hi Geertjan
I've read all your posts about wizards and still have a problems creating my own. How do I enable "Finish" and Disbale "Next" upon changes on wizards' pane? Could you give me (others will benefit too) some hints? :)

regards

Posted by Ihs on April 03, 2008 at 05:37 PM PDT #

lhs, I will write about this soon. Varun, please also send me the file that you find here:

E:\\OpenSource\\NetBeansProjects\\FancyJavaClassTemplate\\build\\testuserdir\\var\\log folder.

Posted by Geertjan on April 03, 2008 at 06:00 PM PDT #

Nevermind my previous post :D a big cup of coffee and everything works ;) (thought it still may be a good sample for others)

regards!

Posted by Ihs on April 03, 2008 at 06:48 PM PDT #

Great! Yes, it was actually the next blog entry I was planning to write, it's a perfect follow up to this one (basically, it is the 'homework assignment' I set at the end of this blog entry).

Posted by Geertjan on April 03, 2008 at 06:50 PM PDT #

Varun, in "FancyWizardIterator.java", one of your import statements is "javax.tools.FileObject". It should be "org.openide.filesystems.FileObject" instead. Also, read step 9 again and then look at your layer.xml file. There is a difference there, even though step 9 tells you to make sure that there isn't.

Posted by Geertjan on April 03, 2008 at 10:31 PM PDT #

Now, its working smoothly.

One more thing, as you had asked earlier, to try creating this Fancy class, leaving the "Extension" and "Implementation" text-fields empty.

There were 3 cases possible;
1. leaving only "Extension" empty
2. leaving only "Implementation" empty
3. leaving both fields empty

File gets created, but it leaves the corresponding field empty in the code as well, suppose we take case (2), as above;

/\*
\* To change this template, choose Tools | Templates
\* and open the template in the editor.
\*/

/\*\*
\*
\* @author Home
\*/
public class Newfancy extends JFrame implements {

}

Thank you.

Posted by Varun on April 03, 2008 at 11:40 PM PDT #

Yes, Varun. That's right. Now you need to think about how you would prevent the user getting that result.

Posted by Geertjan on April 03, 2008 at 11:42 PM PDT #

Geertjan,

Can you please describe how to make the wizard work as a stand-alone java application? When I run the module, it starts NetBeans and I neeed to click the icon to start the wizard. I assume I need to have a main() function and change other "things" in the project, since the "project properties" does not have a "run" entry as other projects.

Thanks,
Jeff N.

Posted by Jeff on September 10, 2011 at 09:17 AM PDT #

Hi Jeff, this blog entry is about creating a wizard for NetBeans IDE. Hence, it is not about creating a wizard for a standalone Java application. The APIs come from NetBeans IDE itself, so what you want to do is not possible since you will always need NetBeans IDE to use the steps described in this blog entry.

Posted by Geertjan on September 10, 2011 at 03:11 PM PDT #

Hello Geertjan,

I'd like to delegate the repetitive activities of creating a Java source file based on a number of variables.

I'd really like define a new template in Tools > Templates, add these variables and get a Java file of that.

Is it possible to achieve this solely by the use of freemarker templates ?

Thanks !

Jan

Posted by Jan Goyvaerts on December 12, 2011 at 08:27 PM PST #

Yes, definitely, that's what Tools | Templates is for. Add new templates in there and then you'll see them in the New File dialog.

Posted by Geertjan on December 12, 2011 at 09:29 PM PST #

Maybe you can create a new module as described here where you can register your FreeMarker templates:

http://blogs.oracle.com/geertjan/entry/registering_freemarker_templates_in_netbeans

Posted by Geertjan on December 12, 2011 at 10:03 PM PST #

But is it possible to introduce new variables ?

Which can be entered by the user in a dialog and used as ${...} in the templates ?

Posted by Jan Goyvaerts on December 12, 2011 at 11:53 PM PST #

Yes. Click "Settings" in the Template manager and then define them.

Posted by Geertjan on December 13, 2011 at 01:40 AM PST #

This tutorial is great, the issue I have been having lately with a lot of platform tutorials is that the ones relevant to what I would like to do all extend Java projects. That is great, I have learned a good bit about extending Java, my overall goal is to translate these techniques to PHP.

I haven't done much Java development aside from a single Java course. I am having trouble translating a lot of these projects over to PHP. I've tried many times to extend PHP and have yet to be successful with it. I have been able to create an entire project type and extend that.

I would like to know for instance " packageChooserPanel = JavaTemplates.createPackageChooser(project, groups, new FancyWizardPanel1());", how do locate the class that you used here? Does is implement a specific interface, extend a certain class?

Posted by slbmeh on January 23, 2012 at 01:02 AM PST #

how i can read an EJB or a class from my project in a wizard?

Posted by John on August 11, 2012 at 10:31 PM PDT #

Hi, I really appreciate this tutorial! However, if someone could point out how the adjustments in the layer.xml file shown above should be done in netbeans 7.1 (with use of the @TemplateRegistration annotation), I would be really thankful.

Posted by David on September 11, 2012 at 11:24 PM PDT #

Post a Comment:
  • HTML Syntax: NOT allowed
About

Geertjan Wielenga (@geertjanw) is a Principal Product Manager in the Oracle Developer Tools group living & working in Amsterdam. He is a Java technology enthusiast, evangelist, trainer, speaker, and writer. He blogs here daily.

The focus of this blog is mostly on NetBeans (a development tool primarily for Java programmers), with an occasional reference to NetBeans, and sometimes diverging to topics relating to NetBeans. And then there are days when NetBeans is mentioned, just for a change.

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
12
13
14
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today