Entity Expander for NetBeans IDE 8 (Part 1)

It's not uncommon to have a Java entity class, a.k.a. a POJO, a.k.a. a domain object, a.k.a. a business object, and to want to generate something else from that class. So too Matti Tahvonen from Vaadin, who I will be joining on 3 April for the big Vaadin/NetBeans Webinar (sign up today here). He wants to be able to create Vaadin Forms from Java entity classes.

Therefore, I created a small plugin that adds an Action to an Object, and not to any other Java type, which is based on an Action I created a while ago for TopComponents (go here for that).

The Action you see above, i.e., the popup menu item with the label "Generate Vaadin Form" takes the FreeMarker template below (in a file named "vaadinForm.template") as the starting point for the Vaadin form that it creates:

package ${package};

import com.vaadin.ui.Component;
import com.vaadin.ui.FormLayout;
import com.vaadin.ui.TextField;
import org.vaadin.maddon.fields.MTextField;
import org.vaadin.maddon.form.AbstractForm;
import org.vaadin.maddon.layouts.MVerticalLayout;

public class ${object}Form extends AbstractForm<${object}> {

  <#list fields as field>
    private TextField ${field} = new MTextField("${field}");
  </#list>

    @Override
    protected Component createContent() {
        return new MVerticalLayout(
                new FormLayout(
                  <#list fields as field>
                   ${field},
                  </#list>
                ),
                getToolbar()
        );
    }

}

The file above is registered in the System FileSystem like this, in a package-info.java class:

@TemplateRegistration(
        content = "vaadinForm.template",
        scriptEngine = "freemarker",
        folder = "EntityTemplates")
package org.netbeans.entity.expander;

import org.netbeans.api.templates.TemplateRegistration;

Finally, here's all the code of the Action, mostly for enabling/disabling (i.e., hiding) the Action appropriately, with a little bit of code for accessing the right bits of the entity class for passing into the FreeMarker template:

@ActionID(
        category = "Tools",
        id = "org.tc.customizer.ExpandEntityAction")
@ActionRegistration(
        displayName = "#CTL_ExpandEntityAction",
        lazy = false)
@ActionReferences({
    @ActionReference(path = "Loaders/text/x-java/Actions", position = 150)
})
@Messages("CTL_ExpandEntityAction=Generate Vaadin Form")
public final class ExpandEntityAction extends AbstractAction implements ContextAwareAction {
    private final DataObject dobj;
    private static Map args = new HashMap();
    public ExpandEntityAction() {
        this(Utilities.actionsGlobalContext());
    }
    public ExpandEntityAction(Lookup context) {
        super(Bundle.CTL_ExpandEntityAction());
        this.dobj = context.lookup(DataObject.class);
        JavaSource javaSource = JavaSource.forFileObject(dobj.getPrimaryFile());
        if (javaSource != null) {
            try {
                javaSource.runUserActionTask(new ScanForObjectTask(this), true);
            } catch (IOException ex) {
                Exceptions.printStackTrace(ex);
            }
        }
        //Hide the menu item if it isn't enabled:
        putValue(DynamicMenuContent.HIDE_WHEN_DISABLED, true);
    }
    @Override
    public void actionPerformed(ActionEvent ev) {
        FileObject vaadinFormFO = FileUtil.getConfigFile("Templates/EntityTemplates/vaadinForm");
        try {
            DataObject vaadinFormDobj = DataObject.find(vaadinFormFO);
            DataFolder df = DataFolder.findFolder(dobj.getPrimaryFile().getParent());
            String name = dobj.getPrimaryFile().getName();
            vaadinFormDobj.createFromTemplate(df, name + "Form.java", args);
        } catch (DataObjectNotFoundException ex) {
            Exceptions.printStackTrace(ex);
        } catch (IOException ex) {
            Exceptions.printStackTrace(ex);
        }
    }
    @Override
    public Action createContextAwareInstance(Lookup actionContext) {
        return new ExpandEntityAction(actionContext);
    }
    private static class ScanForObjectTask implements Task {
        private final ExpandEntityAction action;
        private ScanForObjectTask(ExpandEntityAction action) {
            this.action = action;
        }
        @Override
        public void run(CompilationController compilationController) throws Exception {
            compilationController.toPhase(Phase.ELEMENTS_RESOLVED);
            new MemberVisitor(compilationController, action).scan(
                    compilationController.getCompilationUnit(), null);
        }
    }
    private static class MemberVisitor extends TreePathScanner {
        private CompilationInfo info;
        private final AbstractAction action;
        public MemberVisitor(CompilationInfo info, AbstractAction action) {
            this.info = info;
            this.action = action;
        }
        @Override
        public Void visitClass(ClassTree t, Void v) {
            Element el = info.getTrees().getElement(getCurrentPath());
            if (el != null) {
                TypeElement te = (TypeElement) el;
                if (te.getSuperclass().toString().equals("java.lang.Object")) {
                    action.setEnabled(true);
                    int index = te.getQualifiedName().toString().lastIndexOf(".");
                    args.put("package", te.getQualifiedName().toString().substring(0, index));
                    args.put("object", te.getQualifiedName().toString().substring(index + 1));
                    List fields = new ArrayList();
                    for (Element e : te.getEnclosedElements()) {
                        if (e.getKind() == ElementKind.FIELD) {
                            fields.add(e.getSimpleName().toString());
                        }
                    }
                    args.put("fields", fields);
                } else {
                    action.setEnabled(false);
                }
            }
            return null;
        }
    }
}

A key statement in the above is this one, i.e, the "args" below contains values built up from the visitor code above, which is magically passed into the FreeMarker template, where the FreeMarker variables are replaced by the passed in values:

vaadinFormDobj.createFromTemplate(df, name + "Form.java", args);

Import statements:

import com.sun.source.tree.ClassTree;
import com.sun.source.util.TreePathScanner;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.swing.AbstractAction;
import javax.swing.Action;
import org.netbeans.api.java.source.CompilationController;
import org.netbeans.api.java.source.CompilationInfo;
import org.netbeans.api.java.source.JavaSource;
import org.netbeans.api.java.source.JavaSource.Phase;
import org.netbeans.api.java.source.Task;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.awt.ActionReferences;
import org.openide.awt.ActionRegistration;
import org.openide.awt.DynamicMenuContent;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.loaders.DataFolder;
import org.openide.loaders.DataObject;
import org.openide.loaders.DataObjectNotFoundException;
import org.openide.util.ContextAwareAction;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.NbBundle.Messages;
import org.openide.util.Utilities;

The necessary dependencies are as follows:

  • Datasystems API
  • File System API
  • Javac API Wrapper
  • nb-javac-api.jar
  • Java Source
  • Lookup API
  • Nodes API
  • UI Utilities API
  • Utilities API

This is the basis of a more generic solution, where the user can choose one of multiple templates to be used by the generator to transform the Java entity class to something else, which could be anything, in this case a Vaadin form.

Here's the module structure:


Source code:

https://java.net/projects/nb-api-samples/sources/api-samples/show/versions/8.0/misc/EntityExpander

Comments:

Something not to be missed! However, it is at 12:00am in Mackay, Australia. Is it possible to make a recording of the webinar available too please?

Posted by guest on March 20, 2014 at 02:57 PM PDT #

Yes, the webinar will be available afterwards, in fact, the Vaadin guys told me that more people will see the recording than the live event, which makes sense.

Posted by Geertjan on March 21, 2014 at 04:19 AM PDT #

I never got Vaadin. It feels like GWT only with all the logic moved to the server, thus negating most of the advantage of using GWT in the first place.
But since some people still use it, I suppose this is still a neat feature.

Posted by alex on March 21, 2014 at 09:47 AM PDT #

Indeed, the GWT development style reminds Vaadin development but those are very different beasts. GWT tries to improve developer productivity with Java and Java tooling in browser apps ("ajax apps"). Vaadin does kind of that too, but due to the fact that the code runs on the server side, you can forget most of the web development complexity and thus concentrate on you domain problems. You most often don't need to think about browser, DOM or client server programming model at all. And probably the best part, code runs in JVM: any java existing Java library will work, any JVM language as well.

Vaadin has for long used GWT to build the "thin client" to browser, so in case you want to make client side extension, you'll be using GWT. Also, Vaadin modules include GWT libraries to make this part smoother.

Posted by Matti Tahvonen on March 24, 2014 at 04:10 AM 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
« August 2015
SunMonTueWedThuFriSat
      
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
     
Today