Java Code Generation with Eclipse and AST
Posted by Mike Haller
on Sunday, June 8. 2008
at 23:52
in Eclipse
As Mateusz asked for how to add Newlines to generated code in Eclipse, i looked at how Eclipse JDT is doing it. And of course, the Eclipse guys eat their own dog food and use the abstract syntax tree (AST) feature.For a showcase, let's assume we've got an open Java Editor with an empty class and we want to add a new method. For the sake of simplicity for the demo, i'm going to implement it in an action and use the current editor as target. That's not nice, but pretty simple for now:
IWorkbench workbench = PlatformUI.getWorkbench(); IWorkbenchWindow window = workbench.getActiveWorkbenchWindow(); IWorkbenchPage page = window.getActivePage(); IEditorPart editor = page.getActiveEditor(); IEditorInput input = editor.getEditorInput(); IJavaElement element = JavaUI.getEditorInputJavaElement(input);
The central class is PlatformUI, which gives us access to a lot of functionality of the user interface of the Eclipse Platform. From there, we go our way through the active window (you can have multiple windows open for the same running instance of Eclipse, e.g. with Window > New Window), over the current page (I'm not sure if there can be multiple pages, but let's be sure here) and the currently active editor. There is no way to cast the editor part to something with a known interface - there is no ICompilationUnitEditor or IJavaEditor interface. And that's good because the correct way to get to the object representing the Java class currently being edited is to use the JavaUI utilities.
So, from the IEditorInput, which represents the open IFile instance, we get a IJavaElement instance, which is probably an ICompilationUnit.
From there, we instantiate a new AST so we have programmatic access to a Domain Object Model for the Java Source Code being edited:
ICompilationUnit cu = (ICompilationUnit) element; AST ast = AST.newAST(AST.JLS3);
At first glance, it looks like an enormous effort to just add a new method with a single line in the body (took me 17 lines), when a simple
StringBuffer and three calls to append() would do the job, too. Well, the bonus points come later and in the small hello-world demos the advantages of having a complex way of doing something do not become clear until you use them yourself.Let's go on and create a new method in the class file using AST:
MethodDeclaration newMethod = ast.newMethodDeclaration();
Of course, this is not the only thing you need to do to add a new method. You also need to give it a name and set some additional modifiers:
newMethod.setName(ast.newSimpleName("doSomething"));
List newModifiers = ast.newModifiers(Modifier.PUBLIC);
newMethod.modifiers().add(newModifiers.get(0));
newMethod.setConstructor(false);
Okay, now set the name of the method to
doSomething() and added the public keyword. Next, let's add a body to the method:Block body = ast.newBlock(); newMethod.setBody(body);
And add the end of the method, let's set the return type of the method to
String. Btw, that's one of the advantages of using AST, you can set the return type and after that, populate the body. You don't need to populate the method's structure in the right ordering.
Type stringType = ast.newSimpleType(ast.newSimpleName("String"));
newMethod.setReturnType2(stringType);
For demo purposes, we're going to print out something on the console using
System.out.println:
MethodInvocation newMethodInvocation = ast.newMethodInvocation();
QualifiedName name = ast.newQualifiedName(ast
.newSimpleName("System"), ast.newSimpleName("out"));
newMethodInvocation.setExpression(name);
newMethodInvocation.setName(ast.newSimpleName("println"));
ExpressionStatement expressionStatement = ast
.newExpressionStatement(newMethodInvocation);
body.statements().add(expressionStatement);
Yes, that last part is a bit complicated, but it can be extracted into factories and then it should be simpler to use.
The method invocation I created from the AST instance needs to have an expression and a name. The expression is the path to the method and the name of the method invocation is in fact the name of the method being invoked. The method invocation must be contained in an expression statement before you can add it to the list of statements in the body.
That's it.
Now you are sure excited on what's the output of all this effort, right? Here you go:
public String doSomething(){
System.out.println();
}
