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
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
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 'Iván'
Setting property 'age' with value '34'
Getting property 'name'
Getting property 'age'
Hello Iván, you're 34
}
def person = new Person()
person.name = "Iván"
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 'Iván'
Setting property 'age' with value '34'
Getting property 'name'
Getting property 'age'
Hello Iván, you're 34
}
def person = new Person()
person.name = "Iván"
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 'Iván'
Setting property 'age' with value '34'
Getting property 'name'
Getting property 'age'
Hello Iván, you're 34
}
def person = new Person()
person.name = "Iván"
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 'Iván'
Setting property 'age' with value '34'
Getting property 'name'
Getting property 'age'
Hello Iván, you're 34
}
def person = new Person()
person.name = "Iván"
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 'Iván'
Setting property 'age' with value '34'
Getting property 'name'
Getting property 'age'
Hello Iván, you're 34
}
def person = new Person()
person.name = "Iván"
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 << input
} finally {
input?.close()
output?.close()
}
}
}
50. 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 << input
} finally {
input?.close()
output?.close()
}
}
}
61. Mixins
▷ “Bring in” or “mix in” implementations
from multiple classes
▷ Calls first routed to mixed-in class
▷ Last mixin wins
▷ Can't override methods in metaclass
▷ Not easily un-done
62. Mixins example
class SpidermanPower {
String spiderSense() {
"Using spider-sense..."
}
}
class SupermanPower {
String fly() {
"Flying..."
}
}
@Mixin([SpidermanPower])
class Person {}
def person = new Person()
assert person.spiderSense() == "Using spider-sense..."
assert !(person instanceof SpidermanPower)
Person.mixin SupermanPower
assert person.fly() == "Flying..."
assert !(person instanceof SupermanPower)
63. class SupermanPower {
String fly() {
"Flying..."
}
}
Mixins example
class SpidermanPower {
String spiderSense() {
"Using spider-sense..."
@Mixin([SpidermanPower])
class Person {}
def person = new Person()
assert person.spiderSense() == "Using spider-sense..."
assert !(person instanceof SpidermanPower)
Person.mixin SupermanPower
assert person.fly() == "Flying..."
assert !(person instanceof SupermanPower)
}
}
64. class SupermanPower {
String fly() {
"Flying..."
}
}
Mixins example
class SpidermanPower {
String spiderSense() {
"Using spider-sense..."
@Mixin([SpidermanPower])
class Person {}
def person = new Person()
assert person.spiderSense() == "Using spider-sense..."
assert !(person instanceof SpidermanPower)
Person.mixin SupermanPower
assert person.fly() == "Flying..."
assert !(person instanceof SupermanPower)
}
}
65. class SupermanPower {
String fly() {
"Flying..."
}
}
Mixins example
class SpidermanPower {
String spiderSense() {
"Using spider-sense..."
@Mixin([SpidermanPower])
class Person {}
def person = new Person()
assert person.spiderSense() == "Using spider-sense..."
assert !(person instanceof SpidermanPower)
Person.mixin SupermanPower
assert person.fly() == "Flying..."
assert !(person instanceof SupermanPower)
}
}
66. Mixins example
class SpidermanPower {
String spiderSense() {
"Using spider-sense..."
@Mixin([SpidermanPower])
class Person {}
def person = new Person()
assert person.spiderSense() == "Using spider-sense..."
assert !(person instanceof SpidermanPower)
Person.mixin SupermanPower
assert person.fly() == "Flying..."
assert !(person instanceof SupermanPower)
}
}
class SupermanPower {
String fly() {
"Flying..."
}
}
67. Mixins example
class SpidermanPower {
String spiderSense() {
"Using spider-sense..."
@Mixin([SpidermanPower])
class Person {}
def person = new Person()
assert person.spiderSense() == "Using spider-sense..."
assert !(person instanceof SpidermanPower)
Person.mixin SupermanPower
assert person.fly() == "Flying..."
assert !(person instanceof SupermanPower)
}
}
class SupermanPower {
String fly() {
"Flying..."
}
}
68. Traits
▷ Groovy 2.3+
▷ Similar to Java 8 default methods
▷ Supported in JDK 6, 7 and 8
▷ Stateful
▷ Composition over inheritance
▷ Documentation
69. Traits example
trait SpidermanPower {
String spiderSense() {
"Using spider-sense..."
class Person implements SpidermanPower {}
def person = new Person()
assert person.spiderSense() == "Using spider-sense..."
assert person instanceof SpidermanPower
def person2 = person.withTraits SupermanPower
assert person2.fly() == "Flying..."
assert person2 instanceof SupermanPower
}
}
trait SupermanPower {
String fly() {
"Flying..."
}
}
70. Traits example
trait SpidermanPower {
String spiderSense() {
"Using spider-sense..."
class Person implements SpidermanPower {}
def person = new Person()
assert person.spiderSense() == "Using spider-sense..."
assert person instanceof SpidermanPower
def person2 = person.withTraits SupermanPower
assert person2.fly() == "Flying..."
assert person2 instanceof SupermanPower
}
}
trait SupermanPower {
String fly() {
"Flying..."
}
}
71. Traits example
trait SpidermanPower {
String spiderSense() {
"Using spider-sense..."
class Person implements SpidermanPower {}
def person = new Person()
assert person.spiderSense() == "Using spider-sense..."
assert person instanceof SpidermanPower
def person2 = person.withTraits SupermanPower
assert person2.fly() == "Flying..."
assert person2 instanceof SupermanPower
}
}
trait SupermanPower {
String fly() {
"Flying..."
}
}
72. Traits example
trait SpidermanPower {
String spiderSense() {
"Using spider-sense..."
class Person implements SpidermanPower {}
def person = new Person()
assert person.spiderSense() == "Using spider-sense..."
assert person instanceof SpidermanPower
def person2 = person.withTraits SupermanPower
assert person2.fly() == "Flying..."
assert person2 instanceof SupermanPower
}
}
trait SupermanPower {
String fly() {
"Flying..."
}
}
74. MOP Method Synthesis
▷ Dynamically figure out behaviour upon
invocation
▷ It may not exist until it's called/executed
▷ “Intercept, Cache, Invoke” pattern
75. Check for methods and properties
def p = new Person(name: 'Iván', age: 34)
assert p.respondsTo('sayHi')
assert p.respondsTo('sayHiTo', String)
assert !p.respondsTo('goodbye')
assert p.hasProperty('name')
assert !p.hasProperty('country')
class Person {
String name
Integer age
String sayHi() {
"Hi, my name is ${name} and I'm ${age}"
}
String sayHiTo(String name) {
"Hi ${name}, how are you?"
}
}
76. Check for methods and properties
def p = new Person(name: 'Iván', age: 34)
assert p.respondsTo('sayHi')
assert p.respondsTo('sayHiTo', String)
assert !p.respondsTo('goodbye')
assert p.hasProperty('age')
assert !p.hasProperty('country')
class Person {
String name
Integer age
String sayHi() {
"Hi, my name is ${name} and I'm ${age}"
}
String sayHiTo(String name) {
"Hi ${name}, how are you?"
}
}
77. Check for methods and properties
def p = new Person(name: 'Iván', age: 34)
assert p.respondsTo('sayHi')
assert p.respondsTo('sayHiTo', String)
assert !p.respondsTo('goodbye')
assert p.hasProperty('age')
assert !p.hasProperty('country')
class Person {
String name
Integer age
String sayHi() {
"Hi, my name is ${name} and I'm ${age}"
}
String sayHiTo(String name) {
"Hi ${name}, how are you?"
}
}
78. Check for methods and properties
class Person {
String name
Integer age
String sayHi() {
"Hi, my name is ${name} and I'm ${age}"
}
String sayHiTo(String name) {
"Hi ${name}, how are you?"
}
}
def p = new Person(name: 'Iván', age: 34)
assert p.respondsTo('sayHi')
assert p.respondsTo('sayHiTo', String)
assert !p.respondsTo('goodbye')
assert p.hasProperty('age')
assert !p.hasProperty('country')
79. MethodMissing example
▷ Requirements:
– Send notifications to users by different
channels
– +50 notifications
– Not all notifications by all channels
– Extensible and open to future
modifications
80. MethodMissing example
abstract class Channel {
void sendNewFollower(String username, String follower) { }
void sendNewMessage(String username, String msg) { }
...
}
class EmailChannel extends Channel {
void sendNewFollower(String username, String follower) {
println "Sending email notification to '${username}' for new follower '${follower}'"
}
void sendNewMessage(String username, String msg) {
println "Sending email notification to '${username}' for new message '${msg}'"
}
}
class MobilePushChannel extends Channel {
void sendNewFollower(String username, String follower) {
println "Sending mobile push notification to '${username}' for new follower '${follower}'"
}
}
81. MethodMissing example
abstract class Channel {
void sendNewFollower(String username, String follower) { }
void sendNewMessage(String username, String msg) { }
...
}
class EmailChannel extends Channel {
void sendNewFollower(String username, String follower) {
println "Sending email notification to '${username}' for new follower '${follower}'"
}
void sendNewMessage(String username, String msg) {
println "Sending email notification to '${username}' for new message '${msg}'"
}
}
class MobilePushChannel extends Channel {
void sendNewFollower(String username, String follower) {
println "Sending mobile push notification to '${username}' for new follower '${follower}'"
}
}
82. MethodMissing example
abstract class Channel {
void sendNewFollower(String username, String follower) { }
void sendNewMessage(String username, String msg) { }
...
}
class EmailChannel extends Channel {
void sendNewFollower(String username, String follower) {
println "Sending email notification to '${username}' for new follower '${follower}'"
}
void sendNewMessage(String username, String msg) {
println "Sending email notification to '${username}' for new message '${msg}'"
}
}
class MobilePushChannel extends Channel {
void sendNewFollower(String username, String follower) {
println "Sending mobile push notification to '${username}' for new follower '${follower}'"
}
}
83. 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)
}
}
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("Iván", "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 [Iván, Hello!]
Sending email notification to 'Iván' 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("Iván", "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 [Iván, Hello!]
Sending email notification to 'Iván' 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("Iván", "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 [Iván, Hello!]
Sending email notification to 'Iván' 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("Iván", "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 [Iván, Hello!]
Sending email notification to 'Iván' 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 exItveánnd"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 [Iván, Hello!]
Sending email notification to 'Iván' 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("Iván", "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 [Iván, Hello!]
Sending email notification to 'Iván' 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("Iván", "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 [Iván, Hello!]
Sending email notification to 'Iván' 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("Iván", "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 [Iván, Hello!]
Sending email notification to 'Iván' for new message 'Hello!'
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
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
114. Let’s 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