Code Generator SPI Officially Available

Sometime ago this blog provided a preliminary example of a code generator. However, at the time, all that info wasn't official yet and you had to set an implementation dependency on one of the APIs. In the meantime, if you look at the NetBeans API Changes since Last Release document (which you should keep tabs on religiously, if you're a NetBeans Platform developer), you'll see this notification: "Code Generation SPI added":

The Code Generation SPI consists of two interfaces. The CodeGenerator implementations registered for various mime types serve for creating code snippets and inserting them into documents on the Insert Code editor action invocation. The CodeGeneratorContextProvider implementations registered for the mime types could provide the respective CodeGenerators with an additional context information.

Fine. What does all that mean? Firstly, interestingly, it means that the CodeGenerator class (and its supporting classes) are now officially supported and are exposed to the NetBeans API Javadoc. So, read the description to get an overview of it all.

Secondly, guess what? You can create code generators for any MIME type you want. So, you could add one/more to your HTML files... such as here:

So, the above appears when I press Alt-Insert in an HTML source file. What happens when I select the code generator item is up to me (i.e., the implementor of the API). In the case of Java source files, you can make use of the rather cool (though very cryptic) Retouche APIs. Let this document be your friend. I have found it pretty tough going, but gradually things become clear. Below, step by step, is my first implementation of a code generator. In the process, you'll see the Retouche APIs in action.

  1. Get a very recent post 6.1 development build and create a new NetBeans module.

  2. Set dependencies on Editor Library 2, Javac API Wrapper, Java Source, and Utilities API.

  3. Let's start very gently:

    import java.util.Collections;
    import java.util.List;
    import org.netbeans.spi.editor.codegen.CodeGenerator;
    import org.openide.util.Lookup;
    
    public class HelloGenerator implements CodeGenerator {
    
        public static class Factory implements CodeGenerator.Factory {
    
            @Override
            public List create(Lookup context) {
                return Collections.singletonList(new HelloGenerator());
            }
        }
    
        @Override
        public String getDisplayName() {
            return "Hello world!";
        }
    
        @Override
        public void invoke() {
        }
        
    }

  4. Register it in the layer.xml file like this, in other words, register the Factory that you see in the Java code above:

    <folder name="Editors">
        <folder name="text">
            <folder name="x-java">
                <folder name="CodeGenerators">
                    <file name="org-netbeans-modules-my-demo-HelloGenerator$Factory.instance"/>
                </folder>
            </folder>
        </folder>
    </folder>

  5. Now you're good to go. Just install the module and invoke the code generators as always, Alt-Insert, and then you'll see the new one added:

  6. OK, now we'll do something useful. We'll start by getting the JTextComponent from the Lookup. That JTextComponent is the Java editor and if we use getText, we can get the text of the Java editor. We can also get the Document object, which is all that we need in order to get the JavaSource object, via JavaSource javaSource = JavaSource.forDocument(doc);, which, in turn, is our entry point into the Retouche APIs. So, here we go:

    public class HelloGenerator implements CodeGenerator {
    
        private JTextComponent textComp;
    
        private HelloGenerator(JTextComponent textComp) {
            this.textComp = textComp;
        }
    
        public static class Factory implements CodeGenerator.Factory {
    
            public List<? extends CodeGenerator> create(Lookup context) {
                Item<JTextComponent> textCompItem = context.lookupItem(new Template(JTextComponent.class, null, null));
                JTextComponent textComp = textCompItem.getInstance();
                return Collections.singletonList(new HelloGenerator(textComp));
            }
        }
    
        @Override
        public String getDisplayName() {
            return "Hello world!";
        }
    
        @Override
        public void invoke() {
        }
        
    }

    I don't know whether the above is the optimal way of doing this, but at the end of the day we now have a JTextComponent. In the next part, I've simply taken code from the Java Developer Guide, referred to earlier, and implemented the invoke exactly as described there, so see that document in order to understand the code below:

  7. Define the invoke method like this, which as stated above is completely taken from the Java Developer Guide:

    @Override
    public void invoke() {
        try {
            Document doc = textComp.getDocument();
            JavaSource javaSource = JavaSource.forDocument(doc);
            CancellableTask task = new CancellableTask<WorkingCopy>() {
                @Override
                public void run(WorkingCopy workingCopy) throws IOException {
                    workingCopy.toPhase(Phase.RESOLVED);
                    CompilationUnitTree cut = workingCopy.getCompilationUnit();
                    TreeMaker make = workingCopy.getTreeMaker();
                    for (Tree typeDecl : cut.getTypeDecls()) {
                        if (Tree.Kind.CLASS == typeDecl.getKind()) {
                            ClassTree clazz = (ClassTree) typeDecl;
                            ModifiersTree methodModifiers = make.Modifiers(Collections.<Modifier>singleton(Modifier.PUBLIC), Collections.<AnnotationTree>emptyList());
                            VariableTree parameter = make.Variable(make.Modifiers(Collections.<Modifier>singleton(Modifier.FINAL), Collections.<AnnotationTree>emptyList()), "arg0", make.Identifier("Object"), null);
                            TypeElement element = workingCopy.getElements().getTypeElement("java.io.IOException");
                            ExpressionTree throwsClause = make.QualIdent(element);
                            MethodTree newMethod = make.Method(methodModifiers, "writeExternal", make.PrimitiveType(TypeKind.VOID), Collections.<TypeParameterTree>emptyList(), Collections.singletonList(parameter), Collections.<ExpressionTree>singletonList(throwsClause), "{ throw new UnsupportedOperationException(\\"Not supported yet.\\") }", null);
                            ClassTree modifiedClazz = make.addClassMember(clazz, newMethod);
                            workingCopy.rewrite(clazz, modifiedClazz);
                        }
                    }
                }
                @Override
                public void cancel() {}
            };
            ModificationResult result = javaSource.runModificationTask(task);
            result.commit();
        } catch (Exception ex) {
            Exceptions.printStackTrace(ex);
        }
    
    }

  8. Excellent. At this point, check that you have this impressive list of import statements:

    import com.sun.source.tree.AnnotationTree;
    import com.sun.source.tree.ClassTree;
    import com.sun.source.tree.CompilationUnitTree;
    import com.sun.source.tree.ExpressionTree;
    import com.sun.source.tree.MethodTree;
    import com.sun.source.tree.ModifiersTree;
    import com.sun.source.tree.Tree;
    import com.sun.source.tree.TypeParameterTree;
    import com.sun.source.tree.VariableTree;
    import java.io.IOException;
    import java.util.Collections;
    import java.util.List;
    import javax.lang.model.element.Modifier;
    import javax.lang.model.element.TypeElement;
    import javax.lang.model.type.TypeKind;
    import javax.swing.text.Document;
    import javax.swing.text.JTextComponent;
    import org.netbeans.api.java.source.CancellableTask;
    import org.netbeans.api.java.source.JavaSource;
    import org.netbeans.api.java.source.JavaSource.Phase;
    import org.netbeans.api.java.source.ModificationResult;
    import org.netbeans.api.java.source.TreeMaker;
    import org.netbeans.api.java.source.WorkingCopy;
    import org.netbeans.spi.editor.codegen.CodeGenerator;
    import org.openide.util.Exceptions;
    import org.openide.util.Lookup;
    import org.openide.util.Lookup.Item;
    import org.openide.util.Lookup.Template;

  9. Now install the module again. Then open a Java source file. Let's say it looks like this:

    package org.netbeans.modules.my.demo;
    
    public class NewClass {
    
    }

    Press Alt-Insert anywhere in the source file, choose "Hello world!", and now you will see this instead of the code above:

    package org.netbeans.modules.my.demo;
    
    import java.io.IOException;
    
    public class NewClass {
    
        public void writeExternal(final Object arg0) throws IOException {
            throw new UnsupportedOperationException("Not supported yet.");
        }
    
    }

Hurray. You have your first code generator. In an HTML source file, the above invoke method could be as simple as this, which would print <h2>hello</h2> at the caret:

@Override
public void invoke() {
    try {
        Caret caret = textComp.getCaret();
        int dot = caret.getDot();
        textComp.getDocument().insertString(dot, "<h2>hello</h2>", null);
    } catch (BadLocationException ex) {
        Exceptions.printStackTrace(ex);
    }

}

The very cool thing about these code generators is that you can keep typing, i.e., your fingers don't leave the keyboard in order to go to a menu item via the mouse, etc. You're typing as normal, then you press Alt-Insert, you select something, and then you go on typing. Being able to make your own contributions to the list of Java code generators (plus, being able to make them for other MIME types) is a really powerful enhancement to NetBeans IDE.

Comments:

Does that also work if I have a MIMEResolver subclass that dynamically resolves MIME types? I'm asking this because I'm doing a platform application and have a feeling that once a Node has it's MIME type resolved it cannot be reevaluated.

Posted by kovica on May 17, 2008 at 08:26 AM PDT #

Don't know kovica, I've never tried any of that.

Posted by Geertjan on May 17, 2008 at 02:56 PM PDT #

Hi.

I was trying with your previous version of this functionallty and was very useful(http://blogs.sun.com/geertjan/entry/hello_code_generator), when I found this new post.. wow.. I think .. I need to test this new functionallity :) but unfortunatelly it doesn't work, my NetBeans version is NetBeans IDE 6.1 (Build 200804211638) and I can't find other new version with this APIs, now exposed...

I can't include org.netbeans.spi.editor.codegen.CodeGenerator :S

Posted by Yamil on May 19, 2008 at 05:11 AM PDT #

Go to the download page and download the development version instead of the 6.1 version, Yamil.

Posted by Geertjan on May 19, 2008 at 09:17 AM PDT #

thks... now its work :)

Posted by Yamil on May 19, 2008 at 11:45 PM PDT #

Yamil, great to hear! Looking forward to hearing what you're doing with it (do you have a blog where you're writing about this stuff?)

Posted by Geertjan on May 20, 2008 at 12:22 AM PDT #

I want wizard for code generators! Please, Geertjan convince someone to provide it.

Posted by Jaroslav Tulach on May 20, 2008 at 11:17 PM PDT #

It's already in trunk:

http://www.netbeans.org/issues/show_bug.cgi?id=134239

Posted by Geertjan on May 21, 2008 at 12:01 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
« July 2014
SunMonTueWedThuFriSat
  
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
  
       
Today