Geertjan's Blog

  • March 20, 2014

Entity Expander for NetBeans IDE 8 (Part 1)

Geertjan Wielenga
Product Manager

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}");

    protected Component createContent() {
        return new MVerticalLayout(
                new FormLayout(
                  <#list fields as field>


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

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:

category = "Tools",
id = "org.tc.customizer.ExpandEntityAction")
displayName = "#CTL_ExpandEntityAction",
lazy = false)
@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() {
public ExpandEntityAction(Lookup context) {
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) {
//Hide the menu item if it isn't enabled:
putValue(DynamicMenuContent.HIDE_WHEN_DISABLED, true);
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) {
} catch (IOException ex) {
public Action createContextAwareInstance(Lookup actionContext) {
return new ExpandEntityAction(actionContext);
private static class ScanForObjectTask implements Task {
private final ExpandEntityAction action;
ScanForObjectTask(ExpandEntityAction action) {
this.action = action;
public void run(CompilationController compilationController) throws Exception {
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;
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")) {
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) {
args.put("fields", fields);
} else {
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:


Join the discussion

Comments ( 4 )
  • guest Thursday, March 20, 2014

    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?

  • Geertjan Friday, March 21, 2014

    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.

  • alex Friday, March 21, 2014

    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.

  • Matti Tahvonen Monday, March 24, 2014

    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.

Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.