Skip to content
Skip to breadcrumbs
Skip to header menu
Skip to action menu
Skip to quick search
Quick Search
Browse
Pages
Blog
Labels
Attachments
Mail
Advanced
What’s New
Space Directory
Feed Builder
Keyboard Shortcuts
Confluence Gadgets
Log In
Sign Up
Dashboard
Groovy
Copy Page
You are not logged in. Any changes you make will be marked as
anonymous
. You may want to
Log In
if you already have an account. You can also
Sign Up
for a new account.
This page is being edited by
.
Paragraph
Paragraph
Heading 1
Heading 2
Heading 3
Heading 4
Heading 5
Heading 6
Preformatted
Quote
Bold
Italic
Underline
More colours
Strikethrough
Subscript
Superscript
Monospace
Clear Formatting
Bullet list
Numbered list
Outdent
Indent
Align left
Align center
Align right
Link
Table
Insert
Insert Content
Image
Link
Attachment
Symbol
Emoticon
Wiki Markup
Horizontal rule
tinymce.confluence.insert_menu.macro_desc
Info
JIRA Issue
Status
Gallery
Tasklist
Table of Contents
Other Macros
Page Layout
No Layout
Two column (simple)
Two column (simple, left sidebar)
Two column (simple, right sidebar)
Three column (simple)
Two column
Two column (left sidebar)
Two column (right sidebar)
Three column
Three column (left and right sidebars)
Undo
Redo
Find/Replace
Keyboard Shortcuts Help
<p>Groovy 1.6 provides two options for hooking into the Groovy compiler for compile-time metaprogramming: local and global AST transformations. This page explains how to write and debug a local AST transformation.</p> <p>As a naive and simple example, consider wanting to write a @WithLogging annotation that would add console messages at the start and end of a method invocation. So the following "Hello World" example would actually print "Hello World" along with a start and stop message:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> @WithLogging def greet() { println "Hello World" } greet() </pre></td></tr></table> <p>A poor man's aspect oriented programming, if you will.</p> <p>A local AST transformation is an easy way to do this. It requires two things: a definition of the @WithLogging annotation and an implementation of <a href="http://groovy.codehaus.org/api/org/codehaus/groovy/transform/ASTTransformation.html">ASTTransformation</a> that adds the logging expressions to the method.</p> <p>An ASTTransformation is a callback that gives you access to the <a href="http://groovy.codehaus.org/api/org/codehaus/groovy/control/SourceUnit.html">SourceUnit</a>, through which you can get a reference to the <a href="http://groovy.codehaus.org/api/org/codehaus/groovy/ast/ModuleNode.html">AST</a>. The AST is a tree structure of <a href="http://groovy.codehaus.org/api/org/codehaus/groovy/ast/expr/Expression.html">Expression</a> objects that the source code has been transformed into. An easy way to learn about the AST is to explore it in a debugger, which will be shown shortly. Once you have the AST, you can analyze it to find out information about the code or rewrite it to add new functionality.</p> <p>The local transformation annotation is the simple part. Here is the @WithLogging one:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> import org.codehaus.groovy.transform.GroovyASTTransformationClass @Retention(RetentionPolicy.SOURCE) @Target([ElementType.METHOD]) @GroovyASTTransformationClass(["gep.LoggingASTTransformation"]) public @interface WithLogging { } </pre></td></tr></table> <p>The annotation retention can be SOURCE, you won't need the annotation past that. The element type here is METHOD, the @WithLogging annotation applies to methods. But the most important part is the @GroovyASTTransformationClass annotation. This links the @WithLogging annotation to the ASTTransformation subclass you will write. gep.LoggingTransformation is the full package and class of my ASTTransformation. This line wires the annotation to the transformation.</p> <p>With this in place, the Groovy compiler is going to try to invoke gep.LoggingASTTransformation every time an @WithLogging is found in a source unit. Any breakpoint set within LoggingASTTransformation will now be hit within the IDE when running the sample script.</p> <p>The ASTTransformation subclass is a little more complex. Here is the very simple, and very naive, transformation to add a method start and stop message for @WithLogging:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> @GroovyASTTransformation(phase=CompilePhase.SEMANTIC_ANALYSIS) public class LoggingASTTransformation implements ASTTransformation { public void visit(ASTNode[] nodes, SourceUnit sourceUnit) { List methods = sourceUnit.getAST()?.getMethods() // find all methods annotated with @WithLogging methods.findAll { MethodNode method -> method.getAnnotations(new ClassNode(WithLogging)) }.each { MethodNode method -> Statement startMessage = createPrintlnAst("Starting $method.name") Statement endMessage = createPrintlnAst("Ending $method.name") List existingStatements = method.getCode().getStatements() existingStatements.add(0, startMessage) existingStatements.add(endMessage) } } private Statement createPrintlnAst(String message) { return new ExpressionStatement( new MethodCallExpression( new VariableExpression("this"), new ConstantExpression("println"), new ArgumentListExpression( new ConstantExpression(message) ) ) ) } } </pre></td></tr></table> <p>Starting at the top...</p> <p>The <a href="http://groovy.codehaus.org/api/org/codehaus/groovy/transform/GroovyASTTransformation.html">@GroovyASTTransformation</a>(phase=CompilePhase.SEMANTIC_ANALYSIS) line tells the Groovy compiler that this is a local transformation that applies to the SEMANTIC_ANALYSIS <a href="http://groovy.codehaus.org/api/org/codehaus/groovy/control/CompilePhase.html">CompilePhase</a>. Local transformations can only be applied at semantic analysis or later phases, and this line is required!</p> <p>The public <a href="http://groovy.codehaus.org/api/org/codehaus/groovy/transform/ASTTransformation.html#visit%28org.codehaus.groovy.ast.ASTNode%5B%5D,%20org.codehaus.groovy.control.SourceUnit%29">visit(ASTNode[], SourceUnit)</a> method is called once per annotated node (class, method, or field; method parameters don't seem to be supported). The first element in the ASTNode array holds the annotation, the second one the annotated node. The AST you receive is not for the @WithLogging annotated method, it is for the entire file that contains @WithLogging. This example is just using findAll to locate methods that are annotated with @WithLogging, then using an each statement to wrap any annotated method with print lines. A method to the compiler is simply a list of Statement objects, so the example adds a statement zero logging the start message and appending a statement to the list with the end message.</p> <p>Note the creation of the new println statements in the createPrintlnAst(String) method. Creating AST for code is not always simple. In this case we need to construct a new method call, passing in the receiver/variable, the name of the method, and an argument list. When creating AST, it might be helpful to write the code you're trying to create in a Groovy file and then inspect the AST of that code in the debugger to learn what to create. Then write a function like createPrintlnAst using what you learned through the debugger.</p> <p>The final result:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> @WithLogging def greet() { println "Hello World" } greet() </pre></td></tr></table> <p>Produces:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> Starting greet Hello World Ending greet </pre></td></tr></table>
Please type the word appearing in the picture.
Attachments
Labels
Location
Watch this page
< Edit
Preview >
Loading…
Save
Cancel
Next hint
search
attachments
weblink
advanced