Groovy AST Transformations

115
SPRINGONE2GX WASHINGTON, DC Unless otherwise indicated, these slides are © 2013-2015 ASERT and licensed under a Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ Groovy AST Transformations Dr Paul King @paulk_asert http://slideshare.net/paulk_asert/groovy-transforms https://github.com/paulk-asert/groovy-transforms

Transcript of Groovy AST Transformations

Page 1: Groovy AST Transformations

SPRINGONE2GX WASHINGTON, DC

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/

Groovy AST Transformations Dr Paul King

@paulk_asert http://slideshare.net/paulk_asert/groovy-transforms https://github.com/paulk-asert/groovy-transforms

Page 2: Groovy AST Transformations

Apache Groovy

Groovy in Action 2nd edition Manning promo code: ctwspringo2gx

http://www.manning.com/koenig2

2

Page 3: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/

Contents

3

Ø  Introduction •  Built-in AST Transforms •  Writing your own transforms

Page 4: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/

Parsing Summary

4

public run() ... L1 ALOAD 1 LDC 1 AALOAD ALOAD 0 LDC "Howdy Y'all" INVOKEINTERFACE callCurrent() ARETURN ...

println "Howdy Y'all"

BlockStatement -> ReturnStatement -> MethodCallExpression -> VariableExpression("this") -> ConstantExpression("println") -> ArgumentListExpression -> ConstantExpression("Howdy Y'all")

MyScript.groovy

> groovy MyScript.groovy > groovyc MyScript.groovy > groovysh > groovyConsole

Page 5: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 5

Parsing Summary •  9 phase compiler –  Early stages: read source code

and convert into a sparse syntax tree

–  Middle stages: iteratively build up a more dense and information rich version of the syntax tree

–  Later stages: check the tree and convert it into byte code/class files

Initialization

Semantic Analysis

Instruction Selection

Parsing

Conversion

Canonicalization

Class Generation

Output

Finalization

Page 6: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 6

Parsing - Early Stages Initialization

Semantic Analysis

Instruction Selection

Parsing

Conversion

Canonicalization

Class Generation

Output

Finalization

@ToString class Greeter { String message = "Howdy Y'all" void greet() { println message } }

ClassNode: Greeter

MethodNode: greet

Property: message type: unresolved(String)

AnnotationNode: ToString type: unresolved(ToString)

methods:

properties:

annotations:

BlockStatement

MethodCall: this.println(message)

Page 7: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 7

Parsing - Middle Stages Initialization

Semantic Analysis

Instruction Selection

Parsing

Conversion

Canonicalization

Class Generation

Output

Finalization

ClassNode: Greeter

MethodNode: greet

FieldNode: message type: resolved(String)

methods:

fields:

constructors: ConstructorNode

MethodNode: getMessage

MethodNode: setMessage

MethodNode: toString

MethodNode: getMetaClass …

Page 8: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 8

Parsing - Final Stages Initialization

Semantic Analysis

Instruction Selection

Parsing

Conversion

Canonicalization

Class Generation

Output

Finalization

public greet()V ... L1 ... ALOAD 0 GETFIELD Greeter.message INVOKEINTERFACE callCurrent() POP ...

Page 9: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 9

When are transforms applied?

Initialization

Semantic Analysis

Instruction Selection

Parsing

Conversion

Canonicalization

Class Generation

Output

Finalization

Local Transformations

Global Transformations

Page 10: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 10

1  +  2  +  3  

Page 11: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 11

1  +  2  +  3  

Page 12: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 12

1  +  2  +  3  assert 1 + 1 == 2

Page 13: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/

Contents

13

•  Introduction Ø Built-in AST Transforms •  Writing your own transforms

Page 14: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 14

@ToString

@groovy.transform.ToString class Detective { String firstName, lastName } def d = new Detective(firstName: 'Sherlock', lastName: 'Holmes') assert d.toString() == 'Detective(Sherlock, Holmes)'

class Detective { String firstName, lastName String toString() { def _result = new StringBuilder() _result.append('Detective(') _result.append(this.firstName) _result.append(', ') _result.append(this.lastName) _result.append(')') return _result.toString() } }

ToStringASTTransformation

Page 15: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 15

@ToString annotation parameters… Parameter Name Purpose

excludes Name of properties (list or comma separated) to exclude from toString(). By default, all properties are included. Incompatible with “includes”.

includes Name of properties (list or comma separated) to include in toString(). Incompatible with “excludes”.

includeSuper Include the toString() for the super class by setting to true. Default: false.

includeNames Include the names of the properties by setting this parameter to true. Default: false.

allProperties Whether JavaBean "properties" are included. 2.5

Page 16: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 16

…@ToString annotation parameters Parameter Name Purpose includeFields Include fields, not just properties. Default: false.

ignoreNulls Exclude any properties that are null. By default null values will be included.

includePackage Print just the simple name of the class without the package. By default the package name is included.

cache Set to true to cache toString() calculations. Use only for immutable objects. By default the value is recalculated upon each method call.

includeSuperProperties Include properties from super class. Default: false

Page 17: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 17

@ToString (using parameters) @ToString(ignoreNulls=true, excludes='lastName', includeNames=true, includePackage=false, includeFields=true) class Detective { String firstName, lastName List clues private nemesis = 'Moriarty' } def d = new Detective(firstName: 'Sherlock', lastName: 'Holmes') assert d.toString() == 'Detective(firstName:Sherlock, nemesis:Moriarty)'

Page 18: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 18

@EqualsAndHashCode

@EqualsAndHashCode class Actor { String firstName, lastName } def a1 = new Actor(firstName: 'Ian', lastName: 'McKellen') def a2 = new Actor(firstName: 'Ian', lastName: 'McKellen') assert !(a1.is(a2)) assert a1 == a2

EqualsAndHashCodeASTTransformation

Page 19: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 19

@EqualsAndHashCode

class Actor { String firstName, lastName int hashCode() { def _result = HashCodeHelper.initHash() _result = HashCodeHelper.updateHash(_result, this.firstName) _result = HashCodeHelper.updateHash(_result, this.lastName) return _result } boolean canEqual(other) { return other instanceof Actor } ...

Page 20: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 20

... boolean equals(other) { if (other == null) return false if (this.is(other)) return true if (!( other instanceof Actor)) return false Actor otherTyped = (Actor) other if (!(otherTyped.canEqual(this))) return false if (!(this.getFirstName().is(otherTyped.getFirstName()))) { if (!(this.getFirstName() == otherTyped.getFirstName())) { return false } } if (!(this.getLastName().is(otherTyped.getLastName()))) { if (!(this.getLastName() == otherTyped.getLastName())) { return false } } return true } }

@EqualsAndHashCode

Page 21: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 21

@EqualsAndHashCode annotation parameters… Parameter Name Purpose

excludes

Exclude certain properties from the calculation by specifying them as a comma separated list or literal list of String name values. This is commonly used with an object that has an 'id' field. By default, no properties are excluded. Incompatible with “includes”

includes Include only a specified list of properties by specifying them as a comma separated list or literal list of String name values. Incompatible with “excludes”.

cache Set to true to cache hashCode() calculations. Use only for immutable objects. By default the hashCode() is recalculated whenever the hashCode() method is called.

Page 22: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 22

…@EqualsAndHashCode annotation parameters Parameter Name Purpose

callSuper Include properties from the super class by setting this parameter to true. By default, the super class is not used as part of the calculation.

includeFields Include the class's fields, not just the properties, in the calculation by setting this parameter to true. By default, fields are not taken into account.

useCanEqual

Set to false to disable generation of a canEqual() method to be used by equals(). By default the canEqual() method is generated. The canEqual idiom provides a mechanism for permitting equality in the presence of inheritance hierarchies. For immutable classes with no explicit super class, this idiom is not required.

Page 23: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 23

@MapConstructor •  How does Groovy normally handle "named" arguments

class Athlete { String first, last } def a1 = new Athlete( first: 'Michael', last: 'Jordan') assert a1.first == 'Michael'

2.5

def  a1  =  new  Athlete()  a1.setFirst('Michael')  a1.setLast('Jordan')  

Page 24: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 24

@MapConstructor •  With @MapConstructor we have an explicit Map constructor •  Good for Java integration & final fields

@MapConstructor class Athlete { String first, last } def a1 = new Athlete( first: 'Michael', last: 'Jordan') assert a1.first == 'Michael'

2.5

Athlete(Map  m)  {      if  (m.containsKey('first'))  {          first  =  m.get('first')      }      if  (m.containsKey('last'))  {          last  =  m.get('last')      }  }  

Page 25: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 25

@MapConstructor annotation parameters… Parameter Name Purpose

excludes Exclude certain properties from the constructor. Incompatible with “includes”

includes Include only a specified list of properties in the constructor. Incompatible with “excludes”

pre A Closure containing statements which will be prepended to the generated constructor

post A Closure containing statements which will be appended to the end of the generated constructor

2.5

Page 26: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 26

…@MapConstructor annotation parameters Parameter Name Purpose

includeFields Include the class's fields, not just the properties, in the calculation by setting this parameter to true. By default, fields are not taken into account.

includeProperties Include properties in the constructor. Default: true includeSuperProperties Include properties from super classes in the constructor

useSetters By default, properties are set directly using their respective field but a setter can be used instead if desired

2.5

Page 27: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 27

@MapConstructor import  groovy.transform.*    @ToString(includeFields=true,  includeNames=true)  @MapConstructor(post={  full  =  "$first  $last"  })  class  Person  {          final  String  first,  last          private  final  String  full  }    assert  new  Person(first:  'Dierk',  last:  'Koenig').toString()  ==          'Person(first:Dierk,  last:Koenig,  full:Dierk  Koenig)'  

2.5

Page 28: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 28

@TupleConstructor •  A traditional positional arguments constructor

@TupleConstructor class Athlete { String first, last } def a1 = new Athlete('Michael', 'Jordan') def a2 = new Athlete('Michael') def a3 = new Athlete(first: 'Michael') assert a1.first == a2.first assert a2.first == a3.first

Page 29: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 29

@TupleConstructor annotation parameters… Parameter Name Purpose

excludes Exclude certain properties from the constructor. Incompatible with “includes”

includes Include only a specified list of properties in the constructor. Incompatible with “excludes”

callSuper Should super properties be called within a call to the parent constructor rather than set as properties

force By default, this annotation becomes a no-op if you provide your own constructor, this flag forces constructor creation

defaults Used to set whether default value processing is enabled (the default) or disabled

2.5

Page 30: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 30

…@TupleConstructor annotation parameters Parameter Name Purpose

includeFields Include the class's fields, not just the properties, in the calculation by setting this parameter to true. By default, fields are not taken into account.

includeProperties Include properties in the constructor. Default: true includeSuperFields Include fields from super classes in the constructor includeSuperProperties Include properties from super classes in the constructor

useSetters By default, properties are set directly using their respective field but a setter can be used instead if desired

2.5

Page 31: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 31

@TupleConstructor

@TupleConstructor  class  Athlete  {      String  first,  last  }    def  a1  =  new  Athlete('Michael',  'Jordan')  def  a2  =  new  Athlete('Michael')  def  a3  =  new  Athlete(first:  'Michael')    assert  a1.first  ==  a2.first  assert  a2.first  ==  a3.first    

Page 32: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 32

@TupleConstructor

@TupleConstructor  class  Athlete  {      String  first,  last  }    def  a1  =  new  Athlete('Michael',  'Jordan')  def  a2  =  new  Athlete('Michael')  def  a3  =  new  Athlete(first:  'Michael')    assert  a1.first  ==  a2.first  assert  a2.first  ==  a3.first    

class Athlete  {      String  first,  last      Athlete(String  first=null,  String  last=null)  {…}  }    

Page 33: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 33

@TupleConstructor

@TupleConstructor  class  Athlete  {      String  first,  last  }    def  a1  =  new  Athlete('Michael',  'Jordan')  def  a2  =  new  Athlete('Michael')  def  a3  =  new  Athlete(first:  'Michael')    assert  a1.first  ==  a2.first  assert  a2.first  ==  a3.first    

class Athlete  {      String  first,  last      Athlete(String  first=null,  String  last=null)  {…}  }    

class Athlete  {      String  first,  last      Athlete(String  first,  String  last)  {…}      Athlete(String  first)  {…}      Athlete()  {…}  }  

Page 34: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 34

@TupleConstructor

@TupleConstructor(defaults=false)  class  Athlete  {      String  first,  last  }    def  a1  =  new  Athlete('Michael',  'Jordan')  def  a2  =  new  Athlete('Michael')  def  a3  =  new  Athlete(first:  'Michael')    

class Athlete  {      String  first,  last      Athlete(String  first,  String  last)  {…}  }  

Page 35: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 35

@Canonical •  Combines @ToString, @EqualsAndHashCode and

@TupleConstructor •  Actually uses AnnotationCollector in Groovy 2.5

@Canonical class Inventor { String firstName, lastName } def i1 = new Inventor('Thomas', 'Edison') def i2 = new Inventor('Thomas') assert i1 != i2 assert i1.firstName == i2.firstName assert i1.toString() == 'Inventor(Thomas, Edison)'

2.5

Page 36: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 36

@Canonical •  Combines @ToString, @EqualsAndHashCode and

@TupleConstructor •  Actually uses AnnotationCollector in Groovy 2.5

@AnnotationCollector(value=[ToString,  TupleConstructor,  EqualsAndHashCode],                                              mode=AnnotationCollectorMode.PREFER_EXPLICIT_MERGED)  public  @interface  Canonical  {  }    

2.5

Page 37: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 37

@Canonical •  Combines @ToString, @EqualsAndHashCode and

@TupleConstructor •  Actually uses AnnotationCollector in Groovy 2.5

@AnnotationCollector(value=[ToString,  TupleConstructor,  EqualsAndHashCode],                                              mode=AnnotationCollectorMode.PREFER_EXPLICIT_MERGED)  public  @interface  Canonical  {  }    @Canonical(includeNames=false)  @ToString(excludes='last',  includeNames=true)  class  Person  {          String  first,  last          String  getInitials()  {  "${first[0]}${last[0]}"  }  }    assert  new  Person(first:  'Dierk',  last:  'Koenig').toString()  ==                  'Person(first:Dierk,  initials:DK)'    

2.5

Page 38: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 38

@AnnotationCollector @AnnotationCollector  @ToString(excludes='id,first',  includeNames=true)  @EqualsAndHashCode  @TupleConstructor(excludes='id')  @interface  MyCanonical  {}    @MyCanonical  class  Person  {          UUID  id  =  UUID.randomUUID()          String  first,  last  }    def  agent  =  new  Person('James',  'Bond')  assert  agent.toString()  ==  'Person(last:Bond)'    

Page 39: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 39

@Lazy… •  Safe and efficient deferred construction

•  Understands double checked-locking and holder class idioms

// nominally expensive resource with stats class Resource { private static alive = 0 private static used = 0 Resource() { alive++ } def use() { used++ } static stats() { "$alive alive, $used used" } }

Page 40: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 40

…@Lazy class ResourceMain { def res1 = new Resource() @Lazy res2 = new Resource() @Lazy static res3 = { new Resource() }() @Lazy(soft=true) volatile Resource res4 } new ResourceMain().with { assert Resource.stats() == '1 alive, 0 used' res2.use() res3.use() res4.use() assert Resource.stats() == '4 alive, 3 used' assert res4 instanceof Resource def expected = 'res4=java.lang.ref.SoftReference' assert it.dump().contains(expected) }

Page 41: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 41

@InheritConstructors •  Reduced boiler-plate for scenarios where parent classes have multiple

constructors (e.g. Exceptions & PrintWriter)

@InheritConstructors class MyPrintWriter extends PrintWriter { } def pw1 = new MyPrintWriter(new File('out1.txt')) def pw2 = new MyPrintWriter('out2.txt', 'US-ASCII') [pw1, pw2].each { it << 'foo' it.close() } assert new File('out1.txt').text == new File('out2.txt').text ['out1.txt', 'out2.txt'].each{ new File(it).delete() }

Page 42: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 42

@Sortable… •  Reduced boiler-plate for Comparable classes including specialised

Comparators

@Sortable(includes = 'last,initial') class Politician { String first Character initial String last String initials() { first[0] + initial + last[0] } }

Page 43: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 43

…@Sortable

// @Sortable(includes = 'last,initial') class Politician { ... } def politicians = [ new Politician(first: 'Margaret', initial: 'H', last: 'Thatcher'), new Politician(first: 'George', initial: 'W', last: 'Bush') ] politicians.with { assert sort()*.initials() == ['GWB', 'MHT'] def comparator = Politician.comparatorByInitial() assert toSorted(comparator)*.initials() == ['MHT', 'GWB'] }

Page 44: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 44

Trip Highlights

Page 45: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 45

Page 46: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 46

@Builder •  Recent Java idiom is to use "fluent-api" builders •  Often not needed for Groovy •  But if you need Java integration or want improved IDE support…

@Builder class Chemist { String first, last int born } def builder = Chemist.builder() def c = builder.first("Marie").last("Curie").born(1867).build()

Page 47: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 47

@Builder •  Supports customizable building strategies

DefaultStrategy Creates a nested helper class with methods for each property and a build() method

SimpleStrategy Creates chainable setters, i.e. each setter returns the object itself

ExternalStrategy For creating a builder for a class you don’t have control over, e.g. from a library or another team in your organization.

InitializerStrategy

Like DefaultStrategy but when used with @CompileStatic allows type-safe object creation

Page 48: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 48

@Delegate class  Clock  {      void  tellTime()  {  println  '7  am'  }  }    class  Alarm  {      void  soundAlarm()  {  println  'Time  to  wake  up'  }  }    class  AlarmClock  {      @Delegate  Clock  c  =  new  Clock()      @Delegate  Alarm  a  =  new  Alarm()  }    new  AlarmClock().with  {  tellTime();  soundAlarm()  }    

Page 49: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 49

@Delegate class  Clock  {      void  tellTime()  {  println  '7  am'  }  }    class  Alarm  {      void  soundAlarm()  {  println  'Time  to  wake  up'  }  }    class  AlarmClock  {      @Delegate  Clock  c  =  new  Clock()      @Delegate  Alarm  a  =  new  Alarm()  }    new  AlarmClock().with  {  tellTime();  soundAlarm()  }    

class  AlarmClock  {      private  Clock  c  =  new  Clock()      private  Alarm  a  =  new  Alarm()        void  tellTime()  {          c.tellTime()      }        void  soundAlarm()  {          a.soundAlarm()      }  }    

Page 50: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 50

@Memoized •  For making pure functions more efficient

class Calc { def log = [] @Memoized int sum(int a, int b) { log << "$a+$b" a + b } }

new Calc().with { assert sum(3, 4) == 7 assert sum(4, 4) == 8 assert sum(3, 4) == 7 assert log.join(' ') == '3+4 4+4' }

Page 51: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 51

@TailRecursive •  For unravelling recursion

import groovy.transform.TailRecursive class RecursiveCalc { @TailRecursive int accumulate(int n, int sum = 0) { (n == 0) ? sum : accumulate(n - 1, sum + n) } } new RecursiveCalc().with { assert accumulate(10) == 55 }

Page 52: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 52

@TailRecursive class RecursiveCalc { int accumulate(int n, int sum) { int _sum_ = sum int _n_ = n while (true) { if (_n_ == 0) { return _sum_ } else { int __n__ = _n_ int __sum__ = _sum_ _n_ = __n__ - 1 _sum_ = __sum__ + __n__ } } }

int accumulate(int n) { accumulate(n, 0) } }

Page 53: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 53

@Immutable •  For unchanging data structures

@Immutable class Genius { String firstName, lastName } def g1 = new Genius(firstName: 'Albert', lastName: "Einstein") assert g1.toString() == 'Genius(Albert, Einstein)' def g2 = new Genius('Leonardo', "da Vinci") assert g2.firstName == 'Leonardo' shouldFail(ReadOnlyPropertyException) { g2.lastName = 'DiCaprio' }

Page 54: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 54

@Log @Log4j @Log4j2 @Commons @Slf4j •  For easy logging

@groovy.util.logging.Log class Database { def search() { log.fine(runLongDatabaseQuery()) } def runLongDatabaseQuery() { println 'Calling database' /* ... */ return 'query result' } } new Database().search()

Page 55: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 55

@Synchronized •  For safe synchronization

class PhoneBook1 { private final phoneNumbers = [:] @Synchronized def getNumber(key) { phoneNumbers[key] } @Synchronized void addNumber(key, value) { phoneNumbers[key] = value } }

Page 56: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 56

@WithReadLock @WithWriteLock •  Declarative and efficient synchronization

class PhoneBook2 { private final phoneNumbers = [:] @WithReadLock def getNumber(key) { phoneNumbers[key] } @WithWriteLock def addNumber(key, value) { phoneNumbers[key] = value } }

Page 57: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 57

@AutoClone (Simple example) •  Easier cloning. With multiple styles supported: because one size doesn't fit

all for cloning on the JVM

@AutoClone class Chef1 { String name List<String> recipes Date born } def name = 'Heston Blumenthal' def recipes = ['Snail porridge', 'Bacon & egg ice cream'] def born = Date.parse('yyyy-MM-dd', '1966-05-27') def c1 = new Chef1(name: name, recipes: recipes, born: born) def c2 = c1.clone() assert c2.recipes == recipes

Page 58: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 58

@AutoClone (Advanced example) @TupleConstructor @AutoClone(style=COPY_CONSTRUCTOR) class Person { final String name final Date born }

@TupleConstructor(includeSuperProperties=true, callSuper=true) @AutoClone(style=COPY_CONSTRUCTOR) class Chef2 extends Person { final List<String> recipes } def name = 'Jamie Oliver' def recipes = ['Lentil Soup', 'Crispy Duck'] def born = Date.parse('yyyy-MM-dd', '1975-05-27') def c1 = new Chef2(name, born, recipes) def c2 = c1.clone() assert c2.name == name && c2.born == born && c2.recipes == recipes

Page 59: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 59

@AutoCloneStyle Style Description

CLONE Adds a clone() method to your class. The clone() method will call super.clone() before calling clone() on each Cloneable property of the class. Doesn’t provide deep cloning. Not suitable if you have final properties. (Default)

SIMPLE Adds a clone() method to your class which calls the no-arg constructor then copies each property calling clone() for each Cloneable property. Handles inheritance hierarchies. Not suitable if you have final properties. Doesn’t provide deep cloning.

COPY_CONSTRUCTOR

Adds a “copy” constructor, i.e. one which takes your class as its parameter, and a clone() method to your class. The copy constructor method copies each property calling clone() for each Cloneable property. The clone() method creates a new instance making use of the copy constructor. Suitable if you have final properties. Handles inheritance hierarchies. Doesn’t provide deep cloning.

SERIALIZATION

Adds a clone() method to your class which uses serialization to copy your class. Suitable if your class already implements the Serializable or Externalizable interface. Automatically performs deep cloning. Not as time or memory efficient. Not suitable if you have final properties.

Page 60: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 60

@AutoExternalize @AutoExternalize @ToString class Composer { String name int born boolean married } def c = new Composer(name: 'Wolfgang Amadeus Mozart', born: 1756, married: true) def baos = new ByteArrayOutputStream() baos.withObjectOutputStream{ os -> os.writeObject(c) } def bais = new ByteArrayInputStream(baos.toByteArray()) def loader = getClass().classLoader def result bais.withObjectInputStream(loader) { result = it.readObject().toString() } assert result == 'Composer(Wolfgang Amadeus Mozart, 1756, true)'

Page 61: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 61

@TimedInterrupt @ThreadInterrupt @ConditionalInterrupt

•  For safer scripting •  Typically applied through compilation customizers

to user scripts rather than directly used

Page 62: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/

Contents

62

•  Introduction •  Built-in AST Transforms Ø Writing your own transforms

Page 63: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 63

Parsing Deep Dive •  Purpose

Read source files/streams and configure compiler

•  Key classes CompilerConfiguration CompilationUnit

Initialization

Semantic Analysis

Instruction Selection

Parsing

Conversion

Canonicalization

Class Generation

Output

Finalization

Page 64: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 64

•  Purpose Use (ANTLR) grammar to convert source code into token tree

•  Key classes CompilationUnit GroovyLexer GroovyRecognizer GroovyTokenTypes

•  CST Transforms –  http://java.dzone.com/articles/groovy-

antlr-plugins-better

Parsing Deep Dive Initialization

Semantic Analysis

Instruction Selection

Parsing

Conversion

Canonicalization

Class Generation

Output

Finalization

Page 65: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 65

Parsing Deep Dive •  Purpose

Token tree converted into abstract syntax tree (AST) and is the first place where we can begin to write AST visitors

•  Key classes AntlrParserPlugin EnumVisitor

•  AST Transforms @Grab (global)

Initialization

Semantic Analysis

Instruction Selection

Parsing

Conversion

Canonicalization

Class Generation

Output

Finalization

Page 66: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 66

Parsing Deep Dive •  Purpose

Resolves classes and performs consistency and validity checks beyond what the grammar can provide

•  Key classes StaticVerifier ResolveVisitor StaticImportVisitor InnerClassVisitor, AnnotationCollector

•  AST Transforms @Lazy @Builder @Field @Log @Memoized @PackageScope @TailRecursive @BaseScript

Initialization

Semantic Analysis

Instruction Selection

Parsing

Conversion

Canonicalization

Class Generation

Output

Finalization

Page 67: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 67

Parsing Deep Dive •  Purpose

Finalizes the complete abstract syntax tree and typically the last point at which you want to run a transformation

•  Key classes InnerClassCompletionVisitor EnumCompletionVisitor, TraitComposer

•  AST Transforms @Bindable @Vetoable @Mixin @AutoClone @ConditionalInterrupt @ThreadInterrupt @TimedInterrupt @ListenerList @Canonical @Category @Delegate @Bindable @Vetoable @EqualsAndHashCode @AutoExternalize @Immutable @IndexedProperty @Synchronized @InheritConstructors @Sortable @WithReadLock @WithWriteLock @Singleton @Newify @ToString @TupleConstructor

Initialization

Semantic Analysis

Instruction Selection

Parsing

Conversion

Canonicalization

Class Generation

Output

Finalization

Page 68: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 68

•  Purpose Chooses an instruction set for the generated bytecode, e.g. Java 5 versus pre-Java 5

•  AST Transforms @CompileStatic @TypeChecked

Parsing Deep Dive Initialization

Semantic Analysis

Instruction Selection

Parsing

Conversion

Canonicalization

Class Generation

Output

Finalization

Page 69: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 69

Parsing Deep Dive •  Purpose

Creates bytecode based Class in memory •  Key classes

OptimizerVisitor GenericsVisitor Verifier LabelVerifier ExtendedVerifier ClassCompletionVerifier AsmClassGenerator

Initialization

Semantic Analysis

Instruction Selection

Parsing

Conversion

Canonicalization

Class Generation

Output

Finalization

Page 70: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 70

Parsing Deep Dive •  Purpose

Binary output (.class file) written to file system

Initialization

Semantic Analysis

Instruction Selection

Parsing

Conversion

Canonicalization

Class Generation

Output

Finalization

Page 71: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 71

Parsing Deep Dive •  Purpose

Used to cleanup any resources no longer needed

Initialization

Semantic Analysis

Instruction Selection

Parsing

Conversion

Canonicalization

Class Generation

Output

Finalization

Page 72: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/

Visitor Pattern… •  separates the object being walked (the tree) from the behavior of the walker (the

visitor)

72

Page 73: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/

…Visitor Pattern

•  Consider extending ClassCodeVisitorSupport but also consider extending AbstractASTTransformation or using ClassCodeExpressionTransformer

73

Page 74: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/

Writing a Local AST Transform… •  Create an Annotation

•  Annotated with @GroovyASTTransformationClass

74

class MainTransformation{} import org.codehaus.groovy.transform.GroovyASTTransformationClass import java.lang.annotation.* @Retention(RetentionPolicy.SOURCE) @Target([ElementType.METHOD]) @GroovyASTTransformationClass(classes = [MainTransformation]) public @interface Main {}

Page 75: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/

…Writing a Local AST Transform… •  Write your transform class

75

@GroovyASTTransformation(phase  =  CompilePhase.INSTRUCTION_SELECTION)  class  MainTransformation  implements  ASTTransformation  {        private  static  final  ClassNode[]  NO_EXCEPTIONS  =              ClassNode.EMPTY_ARRAY        private  static  final  ClassNode  STRING_ARRAY  =                ClassHelper.STRING_TYPE.makeArray()          void  visit(ASTNode[]  astNodes,  SourceUnit  sourceUnit)  {              //  use  guard  clauses  as  a  form  of  defensive  programming              if  (!astNodes)  return              if  (!astNodes[0]  ||  !astNodes[1])  return              if  (!(astNodes[0]  instanceof  AnnotationNode))  return              if  (astNodes[0].classNode?.name  !=  Main.class.name)  return              if  (!(astNodes[1]  instanceof  MethodNode))  return              ...  

Page 76: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/

…Writing a Local AST Transform…

76

... MethodNode annotatedMethod = astNodes[1] ClassNode declaringClass = annotatedMethod.declaringClass def callMethod = callX(ctorX(declaringClass), annotatedMethod.name) Statement body = block(stmt(callMethod)) def visibility = ACC_STATIC | ACC_PUBLIC def parameters = params(param(STRING_ARRAY, 'args')) declaringClass.addMethod('main', visibility, VOID_TYPE, parameters, NO_EXCEPTIONS, body) } }

Page 77: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/

…Writing a Local AST Transform •  Use the transform

77

class Greeter { @Main def greet() { println "Hello from the greet() method!" } }

Page 78: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/

…Writing a Local AST Transform •  Use the transform

78

class Greeter { @Main def greet() { println "Hello from the greet() method!" } }

new GroovyShell(getClass().classLoader).evaluate ''' class Greeter { @Main def greet() { println "Hello from the greet() method!" } } '''

Page 79: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/

Creating AST… •  By hand (raw)

•  Verbose, full IDE supported

79

import org.codehaus.groovy.ast.* import org.codehaus.groovy.ast.stmt.* import org.codehaus.groovy.ast.expr.* new ReturnStatement( new ConstructorCallExpression( ClassHelper.make(Date), ArgumentListExpression.EMPTY_ARGUMENTS ) )

Page 80: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/

…Creating AST… •  By hand (with helper utility methods)

•  Concise, not everything has concise form (yet)

80

import static org.codehaus.groovy.ast.tools.GeneralUtils.* import static org.codehaus.groovy.ast.ClassHelper.* returnS(ctorX(make(Date)))

Page 81: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/

…Creating AST… •  With ASTBuilder (from a specification/DSL)

•  Requires AST knowledge, limited IDE support

81

import org.codehaus.groovy.ast.builder.AstBuilder def ast = new AstBuilder().buildFromSpec { returnStatement { constructorCall(Date) { argumentList {} } } }

Page 82: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/

…Creating AST… •  With ASTBuilder (from a String)

•  Concise, intuitive, can't create everything, limited IDE support

82

import org.codehaus.groovy.ast.builder.AstBuilder def ast = new AstBuilder().buildFromString('new Date()')

Page 83: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/

…Creating AST… •  With ASTBuilder (from code)

•  Clear, concise, some entities cannot be created, IDE assistance

83

import org.codehaus.groovy.ast.builder.AstBuilder def ast = new AstBuilder().buildFromCode { new Date() }

Page 84: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/

…Creating AST •  ASTBuilder limitations

•  Great for prototyping, not always suitable for production transforms •  Groovy technology, can be slow, subject to global transforms •  Sometimes wasteful, e.g. you might need to create more than you need

such as creating a whole class to then pull out one method •  Some flavors don't support arbitrary node types, don't make it easy to

handle interactions with existing class nodes, don't make it easy to support redirects or generics, nor allow you to properly set the line/column numbers resulting in difficult to debug AST transform and cryptic compilation errors

84

Page 85: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/

Feature Interactions •  Consider Groovy's @ToString annotation transform which runs at the end of the

Canonicalization phase •  Now suppose we want to create a @Trace annotation transform which when

placed on a class will "instrument" each method with "trace" println statements, e.g. this:                                                becomes:    

•  What behaviour should I expect calling toString() if @Trace runs at the end of Semantic Analysis? Canonicalization? Instruction Selection?

85

def setX(x) { this.x = x }

def setX(x) { println "setX begin" this.x = x println "setX end" }

Page 86: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/

Testing AST Transforms •  Consider using ASTTest •  Test both the AST tree and the end-to-end behavior •  Consider writing defensive guards •  Use GroovyConsole

86

Page 87: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/

Design Considerations •  Don't reuse ClassNodes •  Compile-time vs Runtime trade-offs and typing •  Feature Interactions/Fragility •  Beware Complexity •  Risks of introducing bugs •  Avoid global transforms (unless needed) •  GroovyConsole is your friend (AST/bytecode) •  Use addError for errors •  Retain line/column number information when transforming •  Watch variable scoping

87

Page 88: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/

Further Information •  Documentation

•  http://docs.groovy-lang.org/docs/groovy-latest/html/documentation/#_compile_time_metaprogramming

•  Macro Groovy •  https://github.com/bsideup/MacroGroovy •  https://github.com/bsideup/groovy-macro-methods

•  AST Workshop •  http://melix.github.io/ast-workshop/

88

Page 89: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/

More Information: Groovy in Action, 2ed

89

Manning promo code: ctwspringo2gx

Page 90: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/

Bonus Material

Page 91: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 91

Groovy and "fluent-api" builders •  A common idiom in recent times for Java is to use an inner helper class and

accompanying "fluent-api" to reduce ceremony when creating Java classes with many parameters –  But Groovy's named parameters greatly reduces this need

class Chemist { String first String last int born } def c = new Chemist(first: "Marie", last: "Curie", born: 1867) assert c.first == "Marie" assert c.last == "Curie" assert c.born == 1867

Page 92: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 92

@Builder… •  But if you need Java integration or want improved IDE support…

import groovy.transform.builder.Builder @Builder class Chemist { String first String last int born } def builder = Chemist.builder() def c = builder.first("Marie").last("Curie").born(1867).build() assert c.first == "Marie" assert c.last == "Curie" assert c.born == 1867

Page 93: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 93

…@Builder… •  And it supports customizable building strategies

Strategy Description

DefaultStrategy Creates a nested helper class for instance creation. Each method in the helper class returns the helper until finally a build() method is called which returns a created instance.

SimpleStrategy Creates chainable setters, i.e. each setter returns the object itself after updating the appropriate property.

ExternalStrategy Allows you to annotate an explicit builder class while leaving some buildee class being built untouched. This is appropriate when you want to create a builder for a class you don’t have control over, e.g. from a library or another team in your organization.

InitializerStrategy Creates a nested helper class for instance creation which when used with @CompileStatic allows type-safe object creation.

Page 94: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 94

…@Builder •  Type-safe construction using phantom types (*if* you need it)

@Builder(builderStrategy=InitializerStrategy) @Immutable class Chemist { String first, last int born }

@CompileStatic def solution() { def init = Chemist.createInitializer().first("Marie").last("Curie").born(1867) new Chemist(init).with { assert first == "Marie" assert last == "Curie" assert born == 1867 } }

solution()

Page 95: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 95

@Delegate (motivation) •  Anything wrong

with this? class NoisySet extends HashSet { @Override boolean add(item) { println "adding $item" super.add(item) } @Override boolean addAll(Collection items) { items.each { println "adding $it" } super.addAll(items) } }

Page 96: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 96

@Delegate (motivation) •  Anything wrong

with this?

•  Could we fix this implementation?

class NoisySet extends HashSet { @Override boolean add(item) { println "adding $item" super.add(item) } @Override boolean addAll(Collection items) { items.each { println "adding $it" } super.addAll(items) } }

Page 97: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 97

@Delegate (motivation) •  Anything wrong

with this?

•  Could we fix this implementation?

•  What about using the delegate pattern written by hand?

class NoisySet extends HashSet { @Override boolean add(item) { println "adding $item" super.add(item) } @Override boolean addAll(Collection items) { items.each { println "adding $it" } super.addAll(items) } }

Page 98: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 98

@Delegate •  For declarative but flexible use of the delegate pattern

class NoisySet { @Delegate Set delegate = new HashSet() @Override boolean add(item) { println "adding $item" delegate.add(item) } @Override boolean addAll(Collection items) { items.each { println "adding $it" } delegate.addAll(items) } }

Set ns = new NoisySet() ns.add(1) ns.addAll([2, 3]) assert ns.size() == 3

Page 99: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 99

@Delegate annotation parameters… Parameter Name Purpose interfaces

Set this parameter to true to make the owner class implement the same interfaces as the delegate, which is the default behavior. To make the owner not implement the delegate interfaces, set this parameter to false.

deprecated Set this parameter to true to have the owner class delegate methods marked as @Deprecated in the delegate. By default @Deprecated methods are not delegated.

methodAnnotations Set to true if you want to carry over annotations from the methods of the delegate to your delegating method. By default, annotations are not carried over. Currently Closure annotation members are not supported.

parameterAnnotations

Set to true if you want to carry over annotations from the method parameters of the delegate to your delegating method. By default, annotations are not carried over. Currently Closure annotation members are not supported.

Page 100: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 100

…@Delegate annotation parameters

Only one of 'includes', 'includeTypes', 'excludes' or 'excludeTypes' should be used

Parameter Name Purpose

excludes List of method and/or property names to exclude when delegating.

excludeTypes List of interfaces containing method signatures to exclude when delegating.

includes List of method and/or property names to include when delegating.

includeTypes List of interfaces containing method signatures to exclude when delegating.

Page 101: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 101

@Memoized •  For making pure functions more efficient

class Calc { def log = [] @Memoized int sum(int a, int b) { log << "$a+$b" a + b } }

new Calc().with { assert sum(3, 4) == 7 assert sum(4, 4) == 8 assert sum(3, 4) == 7 assert log.join(' ') == '3+4 4+4' }

Page 102: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 102

@TailRecursive •  For unravelling recursion

import groovy.transform.TailRecursive class RecursiveCalc { @TailRecursive int accumulate(int n, int sum = 0) { (n == 0) ? sum : accumulate(n - 1, sum + n) } } new RecursiveCalc().with { assert accumulate(10) == 55 }

Page 103: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 103

@TailRecursive class RecursiveCalc { int accumulate(int n, int sum) { int _sum_ = sum int _n_ = n while (true) { if (_n_ == 0) { return _sum_ } else { int __n__ = _n_ int __sum__ = _sum_ _n_ = __n__ - 1 _sum_ = __sum__ + __n__ } } }

int accumulate(int n) { accumulate(n, 0) } }

Page 104: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 104

@Immutable •  For unchanging data structures

@Immutable class Genius { String firstName, lastName } def g1 = new Genius(firstName: 'Albert', lastName: "Einstein") assert g1.toString() == 'Genius(Albert, Einstein)' def g2 = new Genius('Leonardo', "da Vinci") assert g2.firstName == 'Leonardo' shouldFail(ReadOnlyPropertyException) { g2.lastName = 'DiCaprio' }

Page 105: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 105

@Log @Log4j @Log4j2 @Commons @Slf4j •  For easy logging

@groovy.util.logging.Log class Database { def search() { log.fine(runLongDatabaseQuery()) } def runLongDatabaseQuery() { println 'Calling database' /* ... */ return 'query result' } } new Database().search()

Page 106: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 106

@Synchronized •  For safe synchronization

class PhoneBook1 { private final phoneNumbers = [:] @Synchronized def getNumber(key) { phoneNumbers[key] } @Synchronized void addNumber(key, value) { phoneNumbers[key] = value } }

Page 107: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 107

@WithReadLock @WithWriteLock •  Declarative and efficient synchronization

class PhoneBook2 { private final phoneNumbers = [:] @WithReadLock def getNumber(key) { phoneNumbers[key] } @WithWriteLock def addNumber(key, value) { phoneNumbers[key] = value } }

Page 108: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 108

@AutoClone (Simple example) •  Easier cloning. With multiple styles supported: because one size doesn't fit

all for cloning on the JVM

@AutoClone class Chef1 { String name List<String> recipes Date born } def name = 'Heston Blumenthal' def recipes = ['Snail porridge', 'Bacon & egg ice cream'] def born = Date.parse('yyyy-MM-dd', '1966-05-27') def c1 = new Chef1(name: name, recipes: recipes, born: born) def c2 = c1.clone() assert c2.recipes == recipes

Page 109: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 109

@AutoClone (Advanced example) @TupleConstructor @AutoClone(style=COPY_CONSTRUCTOR) class Person { final String name final Date born }

@TupleConstructor(includeSuperProperties=true, callSuper=true) @AutoClone(style=COPY_CONSTRUCTOR) class Chef2 extends Person { final List<String> recipes } def name = 'Jamie Oliver' def recipes = ['Lentil Soup', 'Crispy Duck'] def born = Date.parse('yyyy-MM-dd', '1975-05-27') def c1 = new Chef2(name, born, recipes) def c2 = c1.clone() assert c2.name == name && c2.born == born && c2.recipes == recipes

Page 110: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 110

@AutoCloneStyle Style Description

CLONE Adds a clone() method to your class. The clone() method will call super.clone() before calling clone() on each Cloneable property of the class. Doesn’t provide deep cloning. Not suitable if you have final properties. (Default)

SIMPLE Adds a clone() method to your class which calls the no-arg constructor then copies each property calling clone() for each Cloneable property. Handles inheritance hierarchies. Not suitable if you have final properties. Doesn’t provide deep cloning.

COPY_CONSTRUCTOR

Adds a “copy” constructor, i.e. one which takes your class as its parameter, and a clone() method to your class. The copy constructor method copies each property calling clone() for each Cloneable property. The clone() method creates a new instance making use of the copy constructor. Suitable if you have final properties. Handles inheritance hierarchies. Doesn’t provide deep cloning.

SERIALIZATION

Adds a clone() method to your class which uses serialization to copy your class. Suitable if your class already implements the Serializable or Externalizable interface. Automatically performs deep cloning. Not as time or memory efficient. Not suitable if you have final properties.

Page 111: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 111

@AutoExternalize @AutoExternalize @ToString class Composer { String name int born boolean married } def c = new Composer(name: 'Wolfgang Amadeus Mozart', born: 1756, married: true) def baos = new ByteArrayOutputStream() baos.withObjectOutputStream{ os -> os.writeObject(c) } def bais = new ByteArrayInputStream(baos.toByteArray()) def loader = getClass().classLoader def result bais.withObjectInputStream(loader) { result = it.readObject().toString() } assert result == 'Composer(Wolfgang Amadeus Mozart, 1756, true)'

Page 112: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 112

@TimedInterrupt @ThreadInterrupt @ConditionalInterrupt

•  For safer scripting •  Typically applied through compilation

customizers to user scripts rather than directly used

Page 113: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 113

@TimedInterrupt @TimedInterrupt(value = 520L, unit = MILLISECONDS) class BlastOff1 { def log = [] def countdown(n) { sleep 100 log << n if (n == 0) log << 'ignition' else countdown(n - 1) } }

def b = new BlastOff1() Thread.start { try { b.countdown(10) } catch (TimeoutException ignore) { b.log << 'aborted' } }.join() assert b.log.join(' ') == '10 9 8 7 6 aborted'

Page 114: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 114

@ThreadInterrupt @ThreadInterrupt class BlastOff2 { def log = [] def countdown(n) { Thread.sleep 100 log << n if (n == 0) log << 'ignition' else countdown(n - 1) } }

def b = new BlastOff2() def t1 = Thread.start { try { b.countdown(10) } catch(InterruptedException ignore) { b.log << 'aborted' } } sleep 570 t1.interrupt() t1.join() assert b.log.join(' ') == '10 9 8 7 6 aborted'

Page 115: Groovy AST Transformations

Unless otherwise indicated, these sl ides are © 2013-2015 ASERT and l icensed under a Creat ive Commons Attr ibut ion-NonCommercial l icense: ht tp: / /creat ivecommons.org/ l icenses/by-nc/3.0/ 115

@ConditionalInterrupt @ConditionalInterrupt({ count <= 5 }) class BlastOff3 { def log = [] def count = 10 def countdown() { while (count != 0) { log << count count-- } log << 'ignition' } }

def b = new BlastOff3() try { b.countdown() } catch (InterruptedException ignore) { b.log << 'aborted' } assert b.log.join(' ') == '10 9 8 7 6 aborted'