Introduction
Abstract Syntax Tree (AST) Transformations are a powerful feature in Groovy that allows you to modify the structure of your code at compile time. This can be used to add new behavior, optimize code, or enforce coding standards. In this section, we will explore what AST Transformations are, how they work, and how you can use them in your Groovy projects.
Key Concepts
- Abstract Syntax Tree (AST): A tree representation of the abstract syntactic structure of source code.
- Compile-time Metaprogramming: Modifying code during the compilation process.
- Annotations: Special markers in the code that can trigger AST Transformations.
Types of AST Transformations
- Local Transformations: Applied to specific code elements, such as methods or classes, using annotations.
- Global Transformations: Applied to the entire codebase, typically through a custom compiler configuration.
Common AST Transformations in Groovy
- @ToString: Automatically generates a
toString()
method. - @EqualsAndHashCode: Generates
equals()
andhashCode()
methods. - @Immutable: Makes a class immutable.
- @Delegate: Delegates method calls to another object.
Practical Examples
Example 1: Using @ToString
import groovy.transform.ToString @ToString class Person { String name int age } def person = new Person(name: 'John Doe', age: 30) println person.toString() // Output: Person(John Doe, 30)
Explanation: The @ToString
annotation automatically generates a toString()
method for the Person
class, which includes the class name and its properties.
Example 2: Using @EqualsAndHashCode
import groovy.transform.EqualsAndHashCode @EqualsAndHashCode class Person { String name int age } def person1 = new Person(name: 'John Doe', age: 30) def person2 = new Person(name: 'John Doe', age: 30) assert person1 == person2 // True
Explanation: The @EqualsAndHashCode
annotation generates equals()
and hashCode()
methods, allowing for proper comparison of Person
objects.
Example 3: Using @Immutable
import groovy.transform.Immutable @Immutable class Point { int x, y } def point = new Point(x: 10, y: 20) // point.x = 30 // This will throw an error because the class is immutable
Explanation: The @Immutable
annotation makes the Point
class immutable, meaning its properties cannot be changed after instantiation.
Creating Custom AST Transformations
Step-by-Step Guide
- Define the Annotation: Create a custom annotation to mark where the transformation should be applied.
import java.lang.annotation.ElementType import java.lang.annotation.Retention import java.lang.annotation.RetentionPolicy import java.lang.annotation.Target @Retention(RetentionPolicy.SOURCE) @Target([ElementType.TYPE]) @interface CustomAnnotation {}
- Implement the Transformation: Create a class that implements
ASTTransformation
.
import org.codehaus.groovy.ast.* import org.codehaus.groovy.ast.builder.AstBuilder import org.codehaus.groovy.control.* import org.codehaus.groovy.transform.* @GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION) class CustomTransformation implements ASTTransformation { void visit(ASTNode[] nodes, SourceUnit source) { // Transformation logic here } }
- Register the Transformation: Register the transformation in
META-INF/services/org.codehaus.groovy.transform.ASTTransformation
.
Example: Adding a Custom Method
import org.codehaus.groovy.ast.* import org.codehaus.groovy.ast.builder.AstBuilder import org.codehaus.groovy.control.* import org.codehaus.groovy.transform.* @GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION) class AddMethodTransformation implements ASTTransformation { void visit(ASTNode[] nodes, SourceUnit source) { if (nodes[0] instanceof AnnotationNode && nodes[1] instanceof AnnotatedNode) { AnnotatedNode annotatedNode = (AnnotatedNode) nodes[1] if (annotatedNode instanceof ClassNode) { ClassNode classNode = (ClassNode) annotatedNode MethodNode newMethod = new MethodNode( 'customMethod', ACC_PUBLIC, ClassHelper.VOID_TYPE, [] as Parameter[], [] as ClassNode[], new BlockStatement( [new ExpressionStatement(new ConstantExpression('Custom Method Executed'))], new VariableScope() ) ) classNode.addMethod(newMethod) } } } }
Explanation: This custom transformation adds a method named customMethod
to any class annotated with @CustomAnnotation
.
Practical Exercise
Exercise: Create a Custom AST Transformation
- Objective: Create a custom AST Transformation that adds a
greet()
method to any class annotated with@Greetable
. - Steps:
- Define the
@Greetable
annotation. - Implement the
GreetTransformation
class. - Register the transformation.
- Test the transformation.
- Define the
Solution
- Define the Annotation:
import java.lang.annotation.ElementType import java.lang.annotation.Retention import java.lang.annotation.RetentionPolicy import java.lang.annotation.Target @Retention(RetentionPolicy.SOURCE) @Target([ElementType.TYPE]) @interface Greetable {}
- Implement the Transformation:
import org.codehaus.groovy.ast.* import org.codehaus.groovy.ast.builder.AstBuilder import org.codehaus.groovy.control.* import org.codehaus.groovy.transform.* @GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION) class GreetTransformation implements ASTTransformation { void visit(ASTNode[] nodes, SourceUnit source) { if (nodes[0] instanceof AnnotationNode && nodes[1] instanceof AnnotatedNode) { AnnotatedNode annotatedNode = (AnnotatedNode) nodes[1] if (annotatedNode instanceof ClassNode) { ClassNode classNode = (ClassNode) annotatedNode MethodNode greetMethod = new MethodNode( 'greet', ACC_PUBLIC, ClassHelper.VOID_TYPE, [] as Parameter[], [] as ClassNode[], new BlockStatement( [new ExpressionStatement(new ConstantExpression('Hello, World!'))], new VariableScope() ) ) classNode.addMethod(greetMethod) } } } }
- Register the Transformation:
- Test the Transformation:
Conclusion
In this section, we explored AST Transformations in Groovy, including their types, common transformations, and how to create custom transformations. AST Transformations are a powerful tool for compile-time metaprogramming, allowing you to modify and enhance your code in ways that are not possible with runtime metaprogramming alone. By mastering AST Transformations, you can create more efficient, maintainable, and expressive Groovy code.