Slides from my GeeCON 2014 Prague talk: "Groovy is a dynamic language that provides different types of metaprogramming techniques. In this talk we’ll mainly see runtime metaprogramming. I’ll explain Groovy Meta-Object-Protocol (MOP), the metaclass, how to intercept method calls, how to deal with method missing and property missing, the use of mixins, traits and categories. All of these topics will be explained with examples in order to understand them. Also, I’ll talk a little bit about compile-time metaprogramming with AST Transformations. AST Transformations provide a wonderful way of manipulating code at compile time via modifications of the Abstract Syntax Tree. We’ll see a basic but powerful example of what we can do with AST transformations." The code is available at: https://github.com/lmivan/geecon2014-prague-metaprograming-with-groovy
Citation preview
1. Ivn Lpez @ilopmar METAPROGRAMMING WITH GROOVY
2. Hello! I am Ivn Lpez @ilopmar
3. Groovy is dynamic Delay to runtime some decisions Add
properties/behaviours in runtime Wide range of applicability
4. What is metaprogramming?
5. Metaprogramming is the writing of computer programs that
write or manipulate other programs (or themselves) as their data. -
Wikipedia
6. 1. Runtime metaprogramming
7. Runtime metaprogramming Groovy provides this through
Meta-Object Protocol (MOP) Use MOP to: Invoke methods dynamically
Synthesize classes and methods on the fly
8. What is the Meta Object Protocol? Groovy Groovy Java Java
MOP
9. Intercepting methods using MOP
10. Groovy Interceptable GroovyObject interface Implement
GroovyInterceptable to hook into the execution public interface
GroovyObject { Object invokeMethod(String name, Object args) Object
getProperty(String propertyName) void setProperty(String
propertyName, Object newValue) MetaClass getMetaClass() void
setMetaClass(MetaClass metaClass) }
11. GroovyInterceptable example class Person implements
GroovyInterceptable { String name Integer age public Object
getProperty(String propertyName) { println "Getting property
'${propertyName}'" return this.@"${propertyName}" } public void
setProperty(String propertyName, Object newValue) { println
"Setting property '${propertyName}' with value '${newValue}'"
this.@"${propertyName}" = newValue } // Execution Setting property
'name' with value 'Ivn' Setting property 'age' with value '34'
Getting property 'name' Getting property 'age' Hello Ivn, you're 34
} def person = new Person() person.name = "Ivn" person.age = 34
println "Hello ${person.name}, you're ${person.age}"
12. GroovyInterceptable example class Person implements
GroovyInterceptable { String name Integer age public Object
getProperty(String propertyName) { println "Getting property
'${propertyName}'" return this.@"${propertyName}" } public void
setProperty(String propertyName, Object newValue) { println
"Setting property '${propertyName}' with value '${newValue}'"
this.@"${propertyName}" = newValue } // Execution Setting property
'name' with value 'Ivn' Setting property 'age' with value '34'
Getting property 'name' Getting property 'age' Hello Ivn, you're 34
} def person = new Person() person.name = "Ivn" person.age = 34
println "Hello ${person.name}, you're ${person.age}"
13. GroovyInterceptable example class Person implements
GroovyInterceptable { String name Integer age public Object
getProperty(String propertyName) { println "Getting property
'${propertyName}'" return this.@"${propertyName}" } public void
setProperty(String propertyName, Object newValue) { println
"Setting property '${propertyName}' with value '${newValue}'"
this.@"${propertyName}" = newValue } // Execution Setting property
'name' with value 'Ivn' Setting property 'age' with value '34'
Getting property 'name' Getting property 'age' Hello Ivn, you're 34
} def person = new Person() person.name = "Ivn" person.age = 34
println "Hello ${person.name}, you're ${person.age}"
14. GroovyInterceptable example class Person implements
GroovyInterceptable { String name Integer age public Object
getProperty(String propertyName) { println "Getting property
'${propertyName}'" return this.@"${propertyName}" } public void
setProperty(String propertyName, Object newValue) { println
"Setting property '${propertyName}' with value '${newValue}'"
this.@"${propertyName}" = newValue } // Execution Setting property
'name' with value 'Ivn' Setting property 'age' with value '34'
Getting property 'name' Getting property 'age' Hello Ivn, you're 34
} def person = new Person() person.name = "Ivn" person.age = 34
println "Hello ${person.name}, you're ${person.age}"
15. GroovyInterceptable example class Person implements
GroovyInterceptable { String name Integer age public Object
getProperty(String propertyName) { println "Getting property
'${propertyName}'" return this.@"${propertyName}" } public void
setProperty(String propertyName, Object newValue) { println
"Setting property '${propertyName}' with value '${newValue}'"
this.@"${propertyName}" = newValue } // Execution Setting property
'name' with value 'Ivn' Setting property 'age' with value '34'
Getting property 'name' Getting property 'age' Hello Ivn, you're 34
} def person = new Person() person.name = "Ivn" person.age = 34
println "Hello ${person.name}, you're ${person.age}"
16. GroovyInterceptable example (II) class Hello implements
GroovyInterceptable { public Object invokeMethod(String methodName,
Object args) { System.out.println "Invoking method '${methodName}'
with args '${args}'" def method =
metaClass.getMetaMethod(methodName, args) method?.invoke(this,
args) } void sayHi(String name) { System.out.println "Hello
${name}" } } def hello = new Hello() hello.sayHi("GeeCon Prague!")
hello.anotherMethod() // Execution Invoking method 'sayHi' with
args '[GeeCon Prague!]' Hello GeeCon Prague! Invoking method
'anotherMethod' with args '[]'
17. GroovyInterceptable example (II) class Hello implements
GroovyInterceptable { public Object invokeMethod(String methodName,
Object args) { System.out.println "Invoking method '${methodName}'
with args '${args}'" def method =
metaClass.getMetaMethod(methodName, args) method?.invoke(this,
args) } void sayHi(String name) { System.out.println "Hello
${name}" } } def hello = new Hello() hello.sayHi("GeeCon Prague!")
hello.anotherMethod() // Execution Invoking method 'sayHi' with
args '[GeeCon Prague!]' Hello GeeCon Prague! Invoking method
'anotherMethod' with args '[]'
18. GroovyInterceptable example (II) class Hello implements
GroovyInterceptable { public Object invokeMethod(String methodName,
Object args) { System.out.println "Invoking method '${methodName}'
with args '${args}'" def method =
metaClass.getMetaMethod(methodName, args) method?.invoke(this,
args) } void sayHi(String name) { System.out.println "Hello
${name}" } } def hello = new Hello() hello.sayHi("GeeCon Prague!")
hello.anotherMethod() // Execution Invoking method 'sayHi' with
args '[GeeCon Prague!]' Hello GeeCon Prague! Invoking method
'anotherMethod' with args '[]'
19. GroovyInterceptable example (II) class Hello implements
GroovyInterceptable { public Object invokeMethod(String methodName,
Object args) { System.out.println "Invoking method '${methodName}'
with args '${args}'" def method =
metaClass.getMetaMethod(methodName, args) method?.invoke(this,
args) } void sayHi(String name) { System.out.println "Hello
${name}" } } def hello = new Hello() hello.sayHi("GeeCon Prague!")
hello.anotherMethod() // Execution Invoking method 'sayHi' with
args '[GeeCon Prague!]' Hello GeeCon Prague! Invoking method
'anotherMethod' with args '[]'
20. MetaClass MetaClass registry for each class Collection of
methods/properties We can always modify the metaclass Intercept
methods implementing invokeMethod on metaclass
27. MOP Method Injection Injecting methods at code-writing time
We can open a class any time Different techniques: MetaClass
Categories Extensions Mixins vs Traits
28. Adding methods using MetaClass class StringUtils { static
String truncate(String text, Integer length, Boolean overflow =
false) { text.take(length) + (overflow ? '...' : '') } } String
chuckIpsum = "If you can see Chuck Norris, he can see you. If you
can not see Chuck Norris you may be only seconds away from death"
println StringUtils.truncate(chuckIpsum, 72) println
StringUtils.truncate(chuckIpsum, 72, true) // Execution If you can
see Chuck Norris, he can see you. If you can not see Chuck No If
you can see Chuck Norris, he can see you. If you can not see Chuck
No... String.metaClass.truncate = { Integer length, Boolean
overflow = false -> delegate.take(length) + (overflow ? '...' :
'') } assert chuckIpsum.truncate(72, true) ==
StringUtils.truncate(chuckIpsum, 72, true)
29. Adding methods using MetaClass class StringUtils { static
String truncate(String text, Integer length, Boolean overflow =
false) { text.take(length) + (overflow ? '...' : '') } } String
chuckIpsum = "If you can see Chuck Norris, he can see you. If you
can not see Chuck Norris you may be only seconds away from death"
println StringUtils.truncate(chuckIpsum, 72) println
StringUtils.truncate(chuckIpsum, 72, true) // Execution If you can
see Chuck Norris, he can see you. If you can not see Chuck No If
you can see Chuck Norris, he can see you. If you can not see Chuck
No... String.metaClass.truncate = { Integer length, Boolean
overflow = false -> delegate.take(length) + (overflow ? '...' :
'') } assert chuckIpsum.truncate(72, true) ==
StringUtils.truncate(chuckIpsum, 72, true)
30. Adding methods using MetaClass class StringUtils { static
String truncate(String text, Integer length, Boolean overflow =
false) { text.take(length) + (overflow ? '...' : '') } } String
chuckIpsum = "If you can see Chuck Norris, he can see you. If you
can not see Chuck Norris you may be only seconds away from death"
println StringUtils.truncate(chuckIpsum, 72) println
StringUtils.truncate(chuckIpsum, 72, true) // Execution If you can
see Chuck Norris, he can see you. If you can not see Chuck No If
you can see Chuck Norris, he can see you. If you can not see Chuck
No... String.metaClass.truncate = { Integer length, Boolean
overflow = false -> delegate.take(length) + (overflow ? '...' :
'') } assert chuckIpsum.truncate(72, true) ==
StringUtils.truncate(chuckIpsum, 72, true)
31. Adding methods using MetaClass class StringUtils { static
String truncate(String text, Integer length, Boolean overflow =
false) { text.take(length) + (overflow ? '...' : '') } } String
chuckIpsum = "If you can see Chuck Norris, he can see you. If you
can not see Chuck Norris you may be only seconds away from death"
println StringUtils.truncate(chuckIpsum, 72) println
StringUtils.truncate(chuckIpsum, 72, true) // Execution If you can
see Chuck Norris, he can see you. If you can not see Chuck No If
you can see Chuck Norris, he can see you. If you can not see Chuck
No... String.metaClass.truncate = { Integer length, Boolean
overflow = false -> delegate.take(length) + (overflow ? '...' :
'') } assert chuckIpsum.truncate(72, true) ==
StringUtils.truncate(chuckIpsum, 72, true)
32. Adding methods using MetaClass class StringUtils { static
String truncate(String text, Integer length, Boolean overflow =
false) { text.take(length) + (overflow ? '...' : '') } } String
chuckIpsum = "If you can see Chuck Norris, he can see you. If you
can not see Chuck Norris you may be only seconds away from death"
println StringUtils.truncate(chuckIpsum, 72) println
StringUtils.truncate(chuckIpsum, 72, true) // Execution If you can
see Chuck Norris, he can see you. If you can not see Chuck No If
you can see Chuck Norris, he can see you. If you can not see Chuck
No... String.metaClass.truncate = { Integer length, Boolean
overflow = false -> delegate.take(length) + (overflow ? '...' :
'') } assert chuckIpsum.truncate(72, true) ==
StringUtils.truncate(chuckIpsum, 72, true)
33. Adding methods using MetaClass class StringUtils { static
String truncate(String text, Integer length, Boolean overflow =
false) { text.take(length) + (overflow ? '...' : '') } } String
chuckIpsum = "If you can see Chuck Norris, he can see you. If you
can not see Chuck Norris you may be only seconds away from death"
println StringUtils.truncate(chuckIpsum, 72) println
StringUtils.truncate(chuckIpsum, 72, true) // Execution If you can
see Chuck Norris, he can see you. If you can not see Chuck No If
you can see Chuck Norris, he can see you. If you can not see Chuck
No... String.metaClass.truncate = { Integer length, Boolean
overflow = false -> delegate.take(length) + (overflow ? '...' :
'') } assert chuckIpsum.truncate(72, true) ==
StringUtils.truncate(chuckIpsum, 72, true)
34. Adding properties using MetaClass class Utils { } def
utilsInstance = new Utils() Utils.metaClass.version = "3.0"
utilsInstance.metaClass.released = true assert
utilsInstance.version == "3.0" assert utilsInstance.released ==
true
35. Adding properties using MetaClass class Utils { } def
utilsInstance = new Utils() Utils.metaClass.version = "3.0"
utilsInstance.metaClass.released = true assert
utilsInstance.version == "3.0" assert utilsInstance.released ==
true
36. Adding properties using MetaClass class Utils { } def
utilsInstance = new Utils() Utils.metaClass.version = "3.0"
utilsInstance.metaClass.released = true assert
utilsInstance.version == "3.0" assert utilsInstance.released ==
true
37. Adding properties using MetaClass class Utils { } def
utilsInstance = new Utils() Utils.metaClass.version = "3.0"
utilsInstance.metaClass.released = true assert
utilsInstance.version == "3.0" assert utilsInstance.released ==
true
38. Overriding methods using MetaClass // Integer assert '42'
== 42.toString() Integer.metaClass.toString = { delegate == 42 ?
'The answer to life, the universe and everything' :
String.valueOf(delegate) } assert 42.toString() == 'The answer to
life, the universe and everything' assert 100.toString() == '100'
// Boolean assert false.toBoolean() == false
Boolean.metaClass.toBoolean = { !delegate } assert
false.toBoolean() == true
39. Overriding methods using MetaClass // Integer assert '42'
== 42.toString() Integer.metaClass.toString = { delegate == 42 ?
'The answer to life, the universe and everything' :
String.valueOf(delegate) } assert 42.toString() == 'The answer to
life, the universe and everything' assert 100.toString() == '100'
// Boolean assert false.toBoolean() == false
Boolean.metaClass.toBoolean = { !delegate } assert
false.toBoolean() == true
40. Overriding methods using MetaClass // Integer assert '42'
== 42.toString() Integer.metaClass.toString = { delegate == 42 ?
'The answer to life, the universe and everything' :
String.valueOf(delegate) } assert 42.toString() == 'The answer to
life, the universe and everything' assert 100.toString() == '100'
// Boolean assert false.toBoolean() == false
Boolean.metaClass.toBoolean = { !delegate } assert
false.toBoolean() == true
41. Overriding methods using MetaClass // Integer assert '42'
== 42.toString() Integer.metaClass.toString = { delegate == 42 ?
'The answer to life, the universe and everything' :
String.valueOf(delegate) } assert 42.toString() == 'The answer to
life, the universe and everything' assert 100.toString() == '100'
// Boolean assert false.toBoolean() == false
Boolean.metaClass.toBoolean = { !delegate } assert
false.toBoolean() == true
42. Overriding methods using MetaClass // Integer assert '42'
== 42.toString() Integer.metaClass.toString = { delegate == 42 ?
'The answer to life, the universe and everything' :
String.valueOf(delegate) } assert 42.toString() == 'The answer to
life, the universe and everything' assert 100.toString() == '100'
// Boolean assert false.toBoolean() == false
Boolean.metaClass.toBoolean = { !delegate } assert
false.toBoolean() == true
43. Overriding methods using MetaClass // Integer assert '42'
== 42.toString() Integer.metaClass.toString = { delegate == 42 ?
'The answer to life, the universe and everything' :
String.valueOf(delegate) } assert 42.toString() == 'The answer to
life, the universe and everything' assert 100.toString() == '100'
// Boolean assert false.toBoolean() == false
Boolean.metaClass.toBoolean = { !delegate } assert
false.toBoolean() == true
44. Categories MetaClass changes are persistent Change
metaclass in confined code MOP modified only in the closure
45. Categories example class StringUtils { static String
truncate(String text, Integer length, Boolean overflow = false) {
text.take(length) + (overflow ? '...' : '') } } use (StringUtils) {
println "Lorem ipsum".truncate(5) } try { println "Lorem
ipsum".truncate(5) } catch (MissingMethodException mme) { println
mme } // Execution Lorem groovy.lang.MissingMethodException: No
signature of method: java.lang.String.truncate() is applicable for
argument types: (java.lang.Integer) values: [5] Possible solutions:
concat(java.lang.String), take(int)
46. Categories example class StringUtils { static String
truncate(String text, Integer length, Boolean overflow = false) {
text.take(length) + (overflow ? '...' : '') } } use (StringUtils) {
println "Lorem ipsum".truncate(5) } try { println "Lorem
ipsum".truncate(5) } catch (MissingMethodException mme) { println
mme } // Execution Lorem groovy.lang.MissingMethodException: No
signature of method: java.lang.String.truncate() is applicable for
argument types: (java.lang.Integer) values: [5] Possible solutions:
concat(java.lang.String), take(int)
47. Categories example class StringUtils { static String
truncate(String text, Integer length, Boolean overflow = false) {
text.take(length) + (overflow ? '...' : '') } } use (StringUtils) {
println "Lorem ipsum".truncate(5) } try { println "Lorem
ipsum".truncate(5) } catch (MissingMethodException mme) { println
mme } // Execution Lorem groovy.lang.MissingMethodException: No
signature of method: java.lang.String.truncate() is applicable for
argument types: (java.lang.Integer) values: [5] Possible solutions:
concat(java.lang.String), take(int)
48. Categories example class StringUtils { static String
truncate(String text, Integer length, Boolean overflow = false) {
text.take(length) + (overflow ? '...' : '') } } use (StringUtils) {
println "Lorem ipsum".truncate(5) } try { println "Lorem
ipsum".truncate(5) } catch (MissingMethodException mme) { println
mme } // Execution Lorem groovy.lang.MissingMethodException: No
signature of method: java.lang.String.truncate() is applicable for
argument types: (java.lang.Integer) values: [5] Possible solutions:
concat(java.lang.String), take(int)
49. Categories example (II) class FileBinaryCategory { def
static leftShift(File file, URL url) { def input def output try {
input = url.openStream() output = new BufferedOutputStream(new
FileOutputStream(file)) output channels.each { channel -> def
metaMethod = channel.metaClass.getMetaMethod(name, methodArgs)
return metaMethod.invoke(channel, methodArgs) } } // Cache the
implementation in the metaClass NotificationService instance = this
instance.metaClass."$name" = implementation // Execute it!
implementation(args) } }
84. MethodMissing example class NotificationService { List
channels = [] def methodMissing(String name, args) {
System.out.println "...methodMissing called for ${name} with args
${args}" // Generate the implementation def implementation = {
Object[] methodArgs -> channels.each { channel -> def
metaMethod = channel.metaClass.getMetaMethod(name, methodArgs)
return metaMethod.invoke(channel, methodArgs) } } // Cache the
implementation in the metaClass NotificationService instance = this
instance.metaClass."$name" = implementation // Execute it!
implementation(args) } }
85. MethodMissing example class NotificationService { List
channels = [] def methodMissing(String name, args) {
System.out.println "...methodMissing called for ${name} with args
${args}" // Generate the implementation def implementation = {
Object[] methodArgs -> channels.each { channel -> def
metaMethod = channel.metaClass.getMetaMethod(name, methodArgs)
return metaMethod.invoke(channel, methodArgs) } } // Cache the
implementation in the metaClass NotificationService instance = this
instance.metaClass."$name" = implementation // Execute it!
implementation(args) } } notificationService.sendNewFollower(...)
notificationService.sendNewMessage(...)
86. MethodMissing example class NotificationService { List
channels = [] def methodMissing(String name, args) {
System.out.println "...methodMissing called for ${name} with args
${args}" // Generate the implementation def implementation = {
Object[] methodArgs -> channels.each { channel -> def
metaMethod = channel.metaClass.getMetaMethod(name, methodArgs)
return metaMethod.invoke(channel, methodArgs) } } // Cache the
implementation in the metaClass NotificationService instance = this
instance.metaClass."$name" = implementation // Execute it!
implementation(args) } }
87. MethodMissing example class NotificationService { List
channels = [] def methodMissing(String name, args) {
System.out.println "...methodMissing called for ${name} with args
${args}" // Generate the implementation def implementation = {
Object[] methodArgs -> channels.each { channel -> def
metaMethod = channel.metaClass.getMetaMethod(name, methodArgs)
return metaMethod.invoke(channel, methodArgs) } } // Cache the
implementation in the metaClass NotificationService instance = this
instance.metaClass."$name" = implementation // Execute it!
implementation(args) } }
88. MethodMissing example def notificationService = new
NotificationService( channels: [new EmailChannel(), new
MobilePushChannel()] ) assert
!notificationService.respondsTo('sendNewFollower', String, String)
notificationService.sendNewFollower("John", "Peter") assert
notificationService.respondsTo('sendNewFollower', String, String)
notificationService.sendNewFollower("Mary", "Steve")
notificationService.sendNewMessage("Ivn", "Hello!") // Execution
...methodMissing called for sendNewFollower with args [John, Peter]
Sending email notification to 'John' for new follower 'Peter'
Sending mobile push notification to 'John' for new follower 'Peter'
Sending email notification to 'Mary' for new follower 'Steve'
Sending mobile push notification to 'Mary' for new follower 'Steve'
...methodMissing called for sendNewMessage with args [Ivn, Hello!]
Sending email notification to 'Ivn' for new message 'Hello!'
89. MethodMissing example def notificationService = new
NotificationService( channels: [new EmailChannel(), new
MobilePushChannel()] ) assert
!notificationService.respondsTo('sendNewFollower', String, String)
notificationService.sendNewFollower("John", "Peter") assert
notificationService.respondsTo('sendNewFollower', String, String)
notificationService.sendNewFollower("Mary", "Steve")
notificationService.sendNewMessage("Ivn", "Hello!") // Execution
...methodMissing called for sendNewFollower with args [John, Peter]
Sending email notification to 'John' for new follower 'Peter'
Sending mobile push notification to 'John' for new follower 'Peter'
Sending email notification to 'Mary' for new follower 'Steve'
Sending mobile push notification to 'Mary' for new follower 'Steve'
...methodMissing called for sendNewMessage with args [Ivn, Hello!]
Sending email notification to 'Ivn' for new message 'Hello!'
90. MethodMissing example def notificationService = new
NotificationService( channels: [new EmailChannel(), new
MobilePushChannel()] ) assert
!notificationService.respondsTo('sendNewFollower', String, String)
notificationService.sendNewFollower("John", "Peter") assert
notificationService.respondsTo('sendNewFollower', String, String)
notificationService.sendNewFollower("Mary", "Steve")
notificationService.sendNewMessage("Ivn", "Hello!") // Execution
...methodMissing called for sendNewFollower with args [John, Peter]
Sending email notification to 'John' for new follower 'Peter'
Sending mobile push notification to 'John' for new follower 'Peter'
Sending email notification to 'Mary' for new follower 'Steve'
Sending mobile push notification to 'Mary' for new follower 'Steve'
...methodMissing called for sendNewMessage with args [Ivn, Hello!]
Sending email notification to 'Ivn' for new message 'Hello!'
91. MethodMissing example def notificationService = new
NotificationService( channels: [new EmailChannel(), new
MobilePushChannel()] ) assert
!notificationService.respondsTo('sendNewFollower', String, String)
notificationService.sendNewFollower("John", "Peter") assert
notificationService.respondsTo('sendNewFollower', String, String)
notificationService.sendNewFollower("Mary", "Steve")
notificationService.sendNewMessage("Ivn", "Hello!") // Execution
...methodMissing called for sendNewFollower with args [John, Peter]
Sending email notification to 'John' for new follower 'Peter'
Sending mobile push notification to 'John' for new follower 'Peter'
Sending email notification to 'Mary' for new follower 'Steve'
Sending mobile push notification to 'Mary' for new follower 'Steve'
...methodMissing called for sendNewMessage with args [Ivn, Hello!]
Sending email notification to 'Ivn' for new message 'Hello!'
92. MethodMissing example def notificationService = new
NotificationService( channels: [new EmailChannel(), new
MobilePushChannel()] ) assert
!notificationService.respondsTo('sendNewFollower', String, String)
notificationService.sendNewFollower("John", "Peter") assert
notificationService.respondsTo('sendNewFollower', String, String)
notificationService.sendNewFollower("Mary", "Steve")
notificationService.sendNewMessage("class EmailChannel exItvennd"s,
C"hHaenlnleol! "{) void sendNewFollower(String username, String
follower) {} void sendNewMessage(String username, String msg) {} //
Execution ...} methodMissing called for sendNewFollower with args
[John, Peter] Sending email notification to 'John' for new follower
'Peter' Sending class mobile MobilePushChannel push notification
extends to Channel 'John' { for new follower 'Peter' void
sendNewFollower(String username, String follower) {} Sending }
email notification to 'Mary' for new follower 'Steve' Sending
mobile push notification to 'Mary' for new follower 'Steve'
...methodMissing called for sendNewMessage with args [Ivn, Hello!]
Sending email notification to 'Ivn' for new message 'Hello!'
93. MethodMissing example def notificationService = new
NotificationService( channels: [new EmailChannel(), new
MobilePushChannel()] ) assert
!notificationService.respondsTo('sendNewFollower', String, String)
notificationService.sendNewFollower("John", "Peter") assert
notificationService.respondsTo('sendNewFollower', String, String)
notificationService.sendNewFollower("Mary", "Steve")
notificationService.sendNewMessage("Ivn", "Hello!") // Execution
...methodMissing called for sendNewFollower with args [John, Peter]
Sending email notification to 'John' for new follower 'Peter'
Sending mobile push notification to 'John' for new follower 'Peter'
Sending email notification to 'Mary' for new follower 'Steve'
Sending mobile push notification to 'Mary' for new follower 'Steve'
...methodMissing called for sendNewMessage with args [Ivn, Hello!]
Sending email notification to 'Ivn' for new message 'Hello!'
94. MethodMissing example def notificationService = new
NotificationService( channels: [new EmailChannel(), new
MobilePushChannel()] ) assert
!notificationService.respondsTo('sendNewFollower', String, String)
notificationService.sendNewFollower("John", "Peter") assert
notificationService.respondsTo('sendNewFollower', String, String)
notificationService.sendNewFollower("Mary", "Steve")
notificationService.sendNewMessage("Ivn", "Hello!") // Execution
...methodMissing called for sendNewFollower with args [John, Peter]
Sending email notification to 'John' for new follower 'Peter'
Sending mobile push notification to 'John' for new follower 'Peter'
Sending email notification to 'Mary' for new follower 'Steve'
Sending mobile push notification to 'Mary' for new follower 'Steve'
...methodMissing called for sendNewMessage with args [Ivn, Hello!]
Sending email notification to 'Ivn' for new message 'Hello!'
95. MethodMissing example def notificationService = new
NotificationService( channels: [new EmailChannel(), new
MobilePushChannel()] ) assert
!notificationService.respondsTo('sendNewFollower', String, String)
notificationService.sendNewFollower("John", "Peter") assert
notificationService.respondsTo('sendNewFollower', String, String)
notificationService.sendNewFollower("Mary", "Steve")
notificationService.sendNewMessage("Ivn", "Hello!") // Execution
...methodMissing called for sendNewFollower with args [John, Peter]
Sending email notification to 'John' for new follower 'Peter'
Sending mobile push notification to 'John' for new follower 'Peter'
Sending email notification to 'Mary' for new follower 'Steve'
Sending mobile push notification to 'Mary' for new follower 'Steve'
...methodMissing called for sendNewMessage with args [Ivn, Hello!]
Sending email notification to 'Ivn' for new message 'Hello!'
96. 2. Compile-time metaprogramming
97. Compile-time metaprogramming Advance feature Analyze/modify
program structure at compile time Cross-cutting features Write code
that generates bytecode
98. AST and compilation AST: Abstract Syntax Tree AST modified
during compilation Hook into the phases Initialization, Parsing,
Conversion, Semantic analysis, Canonicalization, Instruction
selection, Class generation, Output, Finalization
101. Global AST Transformations No annotation Meta-information
file Applied to all code during compilation Any compilation phase
Grails uses intensively in GORM
102. Local AST transformations
103. Local AST Transformations Annotate code No
meta-information file Easy to debug
104. Steps to create local AST Interface AST Enjoy!
105. Local AST example import geecon2014.Version
@Version('1.0') class VersionedClass { } package geecon2014 import
... @Retention(RetentionPolicy.SOURCE) @Target([ElementType.TYPE])
@GroovyASTTransformationClass("geecon2014.VersionASTTransformation")
@interface Version { String value() } class VersionedClass { public
static final String VERSION = "1.0" }
106. Local AST example import geecon2014.Version
@Version('1.0') class VersionedClass { } package geecon2014 import
... @Retention(RetentionPolicy.SOURCE) @Target([ElementType.TYPE])
@GroovyASTTransformationClass("geecon2014.VersionASTTransformation")
@interface Version { String value() } class VersionedClass { public
static final String VERSION = "1.0" }
107. Local AST example import geecon2014.Version
@Version('1.0') class VersionedClass { } package geecon2014 import
... @Retention(RetentionPolicy.SOURCE) @Target([ElementType.TYPE])
@GroovyASTTransformationClass("geecon2014.VersionASTTransformation")
@interface Version { String value() } class VersionedClass { public
static final String VERSION = "1.0" }
108. Local AST example @GroovyASTTransformation(phase =
CompilePhase.SEMANTIC_ANALYSIS) class VersionASTTransformation
extends AbstractASTTransformation { @Override public void
visit(final ASTNode[] nodes, final SourceUnit source) { if
(nodes.length != 2) { return } if (nodes[0] instanceof
AnnotationNode && nodes[1] instanceof ClassNode) { def
annotation = nodes[0] def version = annotation.getMember('value')
if (version instanceof ConstantExpression) {
nodes[1].addField('VERSION', ACC_PUBLIC | ACC_STATIC | ACC_FINAL,
ClassHelper.STRING_TYPE, version) } else { source.addError(new
SyntaxException("Invalid value for annotation",
annotation.lineNumber, annotation.columnNumber)) } } } }
109. Local AST example @GroovyASTTransformation(phase =
CompilePhase.SEMANTIC_ANALYSIS) class VersionASTTransformation
extends AbstractASTTransformation { @Override public void
visit(final ASTNode[] nodes, final SourceUnit source) { if
(nodes.length != 2) { return } if (nodes[0] instanceof
AnnotationNode && nodes[1] instanceof ClassNode) { def
annotation = nodes[0] def version = annotation.getMember('value')
if (version instanceof ConstantExpression) {
nodes[1].addField('VERSION', ACC_PUBLIC | ACC_STATIC | ACC_FINAL,
ClassHelper.STRING_TYPE, version) } else { source.addError(new
SyntaxException("Invalid value for annotation",
annotation.lineNumber, annotation.columnNumber)) } } } }
110. Local AST example // Execute with: // gradle build //
groovy -cp build/libs/add-version-1.0.jar LocalASTExample.groovy
import geecon.Version @Version('1.0') class VersionedClass { }
println VersionedClass.VERSION // Execution 1.0
111. Local AST example // Execute with: // gradle build //
groovy -cp build/libs/add-version-1.0.jar LocalASTExample.groovy
import geecon.Version @Version('1.0') class VersionedClass { }
println VersionedClass.VERSION // Execution 1.0
112. Local AST example // Execute with: // gradle build //
groovy -cp build/libs/add-version-1.0.jar LocalASTExample.groovy
import geecon.Version @Version('1.0') class VersionedClass { }
println VersionedClass.VERSION // Execution 1.0
113. 3. Recap Why we should use metaprogramming?
114. Lets review some concepts Metaprogramming out-of-the box
Easy and very powerfull Write better code Add behaviour easily Take
advantage of this power Because Groovy, it's groovy