Tuesday, 4 August 2015

Groovy AST with DSL

Groovy AST (Abstract Syntax Tree) TRANSFORMATION is one of the interesting features, which I think will everybody would like to explore, because of its intense usefulness and its overwhelming effect.

I will not be going into the details any in-built AST TRANSFORMATION that groovy provides, but I will try to present here, how to write and custom Groovy AST TRANSFORMATION and then adding DSL to do that.
There are several built-in AST TRANSFORMATION groovy provide, which really makes our life easier Like @Canonical, @Bindable, @Veteoble. But in order to understand the working principal of how AST transformation works, I decided to write a Custom AST TRANSFORMATION Implementation.
Objecttive 1: To write an AST transformation that will make the class Serializable.
Now in order to achieve that we need, that the class must implement Serializable.
1) Custom Annotation that will enable us to achive our end. We are going to annotate the class with this annotation, whom do we want to be Serializable.


@Retention (RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
@GroovyASTTransformationClass("com.subha.model.SerializeTransformation")
public @interface Serializable {
 
}


The most signification part in the above code is the annotation @GroovyASTTransformationClass which contains the fully qualifies class name of the Transformation class, which will apply the Transformation to the bytecode generated after compilation.
Let's get into the transformation class.


@GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS)
class SerializeTransformation implements ASTTransformation {

 @Override
 public void visit(ASTNode[] astNode, SourceUnit source) {
  // TODO Auto-generated method stub
  
  if (astNode == null || astNode.size() == 0) return
  if (!(astNode[0] instanceof AnnotationNode) || !(astNode[1] instanceof ClassNode)) {
   throw new GroovyBugError("@Serializable must only be applied at type level!")
  }
  AnnotatedNode parent = (AnnotatedNode) astNode[1]
  AnnotationNode node = (AnnotationNode) astNode[0]
  if (parent instanceof ClassNode) {
   ClassNode classNode = (ClassNode) parent
   
  classNode.addInterface(ClassHelper.makeWithoutCaching(java.io.Serializable.class))
  
  }
  
 }

 
}


The first thing is the Compiler Phase where the Transformation needs to be applied, which is being specified by the GroovyASTTransformation annotation. We are applying in the transformation in SEMANTIC_ANALYSIS.
Now these are the following Compiler Phases:

a) INITIALIZATION
b) PARSING
c) CONVERSION
d) SEMANTIC ANALYSIS
e) CANONICALIZATION
f) INSTRUCTION SELECTION
g) CLASS GENERATION
h) OUTPUT
i) FINALIZATION.

We have to be careful, that we cannot apply AST transformation to any of the Compiler Phases we like. If we apply an AST transformation to phase which that we should not, we will be getting a Runtime Exception saying that we cannot do so.

Our Custom transformation class implements ASTTransformation and its corresponding visit method. Within this visit method only we write the code which will apply the required transformation to the target entity.
The visit method takes two parameters: an array of ASTNode and the SourceUnit.

Now in this array, the first element represents the annotation node and the next element represents the parent node where the annotation is applied, Had it been applied at the Method level, we will be getting a MethodNode, had it been at the Class level, we will be getting the ClassNode.

Lets Look into the target class:


@Serializable
class MainExample {
 
 def a = 0
 
 ..............

From the target class we can conclude that, since @Serializable is applied at the class level so ASTNode[1] represents the ClassNode. and ASTNode[0] represents the annotation node.

The next and the most important thing that we are doing in the code block is that we are adding the interface Serializable to the ClassNode.. After compilation of the target, if we decompile it, we will see that the target class SerializeTransformation implements Serializable

Pretty Easy isn't it.
In the next post we will see adding dynamic behaviour to various elements of the class with groovy DSL.
View Subhankar Paul's profile on LinkedIn

No comments:

Post a Comment