There are two categories of AST transformations:
- global transformations
- local transformations
This blog-post deals with local transformations. A local AST transformation is usually done in three steps:
- you have to implement an annotation that marks a code fragement for AST transformation. the chosen code fragement depends on you transformation's functionality. In my case, i just wanted a @Serializable annotation instead of using Java's java.io.Serializable interface, so that @Serializable annotation is ment to be applied at ElementType.TYPE level. If your use-case is a different one, of course you could choose other element-type levels.
- you actually have to implement the AST transformation. this is done by implementing the org.codehaus.groovy.transform.ASTTransformation interface.
- you have to wire your custom annotation with your AST transformation implementation of step 2. this is done by using the @GroovyASTTransformationClass annotation (a meta-annotation) with the complete path to your ast-transformation class.
In the end, applying the @Serializable annotation is as simple as specifying the annotation on some Groovy class
@Serializable
class User {
// ...
}
The implementation of step 1) is just a simple annotation declaration
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
@GroovyASTTransformationClass("org.nlr.annotations.SerializableASTTransformation")
public @interface Serializable {
}
and the transformation code is as simple as
@GroovyASTTransformation(phase= CompilePhase.SEMANTIC_ANALYSIS)
public class SerializableASTTransformation implements ASTTransformation {
public void visit(ASTNode[] nodes, SourceUnit source) {
if (!(nodes[0] instanceof AnnotationNode) || !(nodes[1] instanceof AnnotatedNode)) {
throw new RuntimeException("Internal error: wrong types: $node.class / $parent.class");
}
AnnotatedNode parent = (AnnotatedNode) nodes[1];
AnnotationNode node = (AnnotationNode) nodes[0];
if (parent instanceof ClassNode) {
ClassNode classNode = (ClassNode) parent;
classNode.addInterface(ClassHelper.make(java.io.Serializable.class));
}
}
}
Notice, that the specified compile phase is "semantic analysis" - a property of local transformations is that they can only be applied in "semantic analysis" compilation phase or above.
If you take a look at the generated (disassembled) byte-code you'll see that the User class's byte-code now implements java.io.Serializable.
public class org.nlr.domain.User extends java.lang.Object implements java.io.Serializable,groovy.lang.GroovyObject{
public static final java.lang.Class $ownClass;
java.lang.Long id;
java.lang.Long version;
java.util.Set registrations;
...
No comments:
Post a Comment