Groovy AST Transformations

Embed Size (px)

Citation preview

Groovy AST Transformations

What is Groovy?

A dynamic programming language that runs on the JVM

Language is essentially a superset of Java, in fact grammar to parse Groovy is constructed from Java grammar

Groovy source code is translated into Java bytecode by the Groovy compiler for execution on the JVM

Where is Groovy?

Groovy as a scripting language

Frameworks for application developmentGrails Web framework

Griffon Swing applications

Gaelyk Google App Engine

TestingEasyb Behavior Driven Development

Spock BDD and mocking

Gmock - Mocking

Grails MVC framework with controllers and service classes in GroovyGriffon Grails-like application framework for developing rich desktop applicationsGaelyk lightweight Groovy toolkit for building and deploying applications on Google App EngineEasyb test specifications written in Groovy

Where is Groovy? (cont...)

Building projectsGradle

Gant

Gant tool for scripting Ant tasks using Groovy instead of XML to specify logicGradle enterprise-grade build system- Groovy build scripts- Dependency management- Used by hibernate, Grails, Groovy

How does Groovy code become bytcode?

What is an Abstract Syntax Tree?

Rooted tree of nodes

Composed of nodes that correspond to Groovy language constructs

We are interested in Groovy's AST syntax tree

Composed of ASTNodes from the org.codehaus.groovy.ast package and subpackages

Tree structure lends itself to processing using Visitor design pattern

This AST is a rooted tree made up of nodes that describes the various constructs within source code in a form that can be easily processed using the Visitor design pattern (http://en.wikipedia.org/wiki/Visitor_pattern). The Visitor design pattern essentially constructs a visitor object that traverses the tree and performs some action on each node in the tree.

What is an AST Transformation?

Compiler hook Groovy provides into compilation process

Means of extending language without grammar changes

Allows manipulation of AST during compilation prior to bytecode generation

Two typesLocal

Global

Local AST Transformations

More common

Applied to specific declarations whose AST is to be modified by the transformation

Annotation indicates AST transformation should be applied to declaration

AST is walked and AST transformation applied to nodes that are annotated with transformation annotation (Visitor design pattern)

Many supplied with Groovy distribution

Global AST Transformations

Less common

Applied to every source unit in compilation

Uses jar file service provider mechanism to identify global AST transformations

Jar file added to classpath of compiler that contains service locator file identifying name of class that implements AST transformation

Groovy's Built-in AST Transformations

Code generation

Design pattern implementation

Simplified logging

Concurrency support

Cloning and externalization

JavaBeans support

Script safety

Static typing

Miscellaneous

Code Generation

@ToString

@EqualsAndHashCode

@TupleConstructor

@Canonical

@Lazy

@InheritConstructors

Focus on automating repetative task of writing common methods likequals, hashCode and constructors

Example - @ToString

@groovy.transform.ToStringclass Person { String first, last}def person = new Person(first:"Hamlet", last:"D'Arcy")println "${person.toString()}"

Result with @ToString transformation: Person(Hamlet, D'Arcy)

Result without @ToString transformation:Person@175078b

Design Pattern Implementation

@Delgate

@Singleton

@Immutable

@Mixin

@Category

Example - @Delegate

class Delegate1Class { public void method1() {} public void method2(String p) {}}

public class OwnerClass { @Delegate Delegate1Class delegate1 = new Delegate1Class()}

The @Delegate AST transformation implements delegation by adding all of the public methods from the delegate class to the owner class.

Verify using javap on OwnerClass

Simplified Logging

@Log

@Log4j

@Slf4j

@Commons

Concurrency Support

@Synchronized

@WithReadLock

@WithWriteLock

Cloning and Externalization

@AutoClone

@AutoExternalize

JavaBeans Support

@Bindable

@Vetoable

@ListenerList

Scripting Safety

@TimedInterrupt

@ThreadInterrupt

@ConditionalInterrupt

Static Typing

@TypeChecked

@CompileStatic

Example - @TypeChecked

@groovy.transform.TypeCheckedNumber test() { // Cannot find matching method MyMethod() // Variable is undelcared println myField // Cannot assign String to int int object = "myString" // Cannot return value of type String on method returning type Number return "myString"}

Miscellaneous

@Field

@PackageScope

@Newify

Location of Built-in AST Transformations

Annotation definition usually found in groovy.transform or groovy.lang

Implementation class usually found in org.codehaus.groovy.transform

Custom AST Transformations

Defined in exactly same manner as built-in AST transformations

Steps

Create AST transformation implementation class that implements the ASTTransformation interface

Create AST transformation annotation declaration and link it to the implementation class with the @GroovyASTTransformationClass annotation

The Implementation Class

Implements the ASTTransformation interfaceSingle methodvoid visit(ASTNode nodes[], SourceUnit source)

Compiler invokes this method on AST of annotated element

nodes array contains AnnotationNode for AST transformation annotation and AnnotatedNode corresponding to annotated declaration

HelloWorldASTTransformation

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

public void visit(ASTNode[] nodes, SourceUnit source) { MethodNode methodNode = (MethodNode)nodes[1] Statement methodCode = methodNode.getCode()

// // Add greeting to beginning of code block. // methodCode.getStatements().add(0, createPrintlnStatement()) }

The Annotation Type Declaration

Indicate declaration types to which AST transformation is applicable with @Target annotation

Indicate implementation class with @GroovyASTTransformationClass annotation

HelloWorld

@Target([ElementType.METHOD])@GroovyASTTransformationClass("HelloWorldASTTransformation")public @interface HelloWorld {}

HelloWorldExample

@HelloWorldvoid myMethod() {}myMethod()

The Hard Part Creating AST objects

Tools to helpAST Browser

ASTBuilder

Ways to create AST objectsManually using ASTNode subclass constructors (leveraging AST Browser)

Using ASTBuilder.buildFromSpec

Using ASTBuilder.buildFromString

Using ASTBuilder.buildFromCode

Implementing createPrintlnStatement Manually

private Statement createPrintlnStatement() { Statement printlnStatement = new ExpressionStatement( new MethodCallExpression( new VariableExpression("this"), new ConstantExpression("println"), new ArgumentListExpression( new ConstantExpression("Hello World!!!!")) )) return printlnStatement }

Implementing createPrintlnStatement using buildFromSpec

private Statement createPrintlnStatement() { List results = new AstBuilder().buildFromSpec { expression { methodCall { variable "this" constant "println" argumentList { constant "Hello World!!!!" } } } } return results[0] }

- Shorthand notation for every ASTNode type- API simplified- Helps eliminates some verbosity and complexity- Returns script class node as well- desired AST in first entry

Implementing createPrintlnStatement using buildFromString

private Statement createPrintlnStatement() { List result = new AstBuilder().buildFromString("println 'Hello World!!!!'; return") return result[0] }

Implementing createPrintlnStatement using buildFromCode

private Statement createPrintlnStatement() {

List result = new AstBuilder().buildFromCode { println "Hello World!!!!" return } return result[0] }

Resources

Groovy code itself provides excellent examples

AST Browser is invaluable for seeing what code is generated by a transformation

Groovy in Action (2nd edition) in MEAP Chapter 9 written by Hamlet D'Arcy

Unit tests for ASTBuilder

Shameless plug: Groovy Under the Hood in GroovyMag