Thursday Mar 20, 2014

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

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
« March 2014 »
SunMonTueWedThuFriSat
      
8
9
15
     
Today