SlideShare a Scribd company logo
Iván López @ilopmar 
METAPROGRAMMING 
WITH GROOVY
Hello! 
I am Iván López 
@ilopmar
Groovy is dynamic 
▷ “Delay” to runtime some decisions 
▷ Add properties/behaviours in 
runtime 
▷ Wide range of applicability
What is 
metaprogramming?
“ Metaprogramming is the writing 
of computer programs that write 
or manipulate other programs (or 
themselves) as their data. 
- Wikipedia
1. 
Runtime 
metaprogramming
Runtime metaprogramming 
▷ Groovy provides this through 
Meta-Object Protocol (MOP) 
▷ Use MOP to: 
– Invoke methods dynamically 
– Synthesize classes and methods on 
the fly
What is the Meta Object Protocol? 
Groovy 
Groovy 
Java 
Java 
MOP
Intercepting methods 
using MOP
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) 
}
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}"
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}"
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}"
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}"
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}"
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 '[]'
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 '[]'
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 '[]'
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 '[]'
MetaClass 
▷ MetaClass registry for each class 
▷ Collection of methods/properties 
▷ We can always modify the metaclass 
▷ Intercept methods implementing 
invokeMethod on metaclass
MetaClass example 
class Hello { 
void sayHi(String name) { 
println "Hello ${name}" 
} 
} 
Hello.metaClass.invokeMethod = { String methodName, args -> 
println "Invoking method '${methodName}' with args '${args}'" 
def method = Hello.metaClass.getMetaMethod(methodName, args) 
method?.invoke(delegate, args) 
} 
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 '[]'
MetaClass example 
class Hello { 
void sayHi(String name) { 
println "Hello ${name}" 
} 
} 
Hello.metaClass.invokeMethod = { String methodName, args -> 
println "Invoking method '${methodName}' with args '${args}'" 
def method = Hello.metaClass.getMetaMethod(methodName, args) 
method?.invoke(delegate, args) 
} 
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 '[]'
MetaClass example 
class Hello { 
void sayHi(String name) { 
println "Hello ${name}" 
} 
} 
Hello.metaClass.invokeMethod = { String methodName, args -> 
println "Invoking method '${methodName}' with args '${args}'" 
def method = Hello.metaClass.getMetaMethod(methodName, args) 
method?.invoke(delegate, args) 
} 
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 '[]'
MetaClass example 
class Hello { 
void sayHi(String name) { 
println "Hello ${name}" 
} 
} 
Hello.metaClass.invokeMethod = { String methodName, args -> 
println "Invoking method '${methodName}' with args '${args}'" 
def method = Hello.metaClass.getMetaMethod(methodName, args) 
method?.invoke(delegate, args) 
} 
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 '[]'
MetaClass example 
class Hello { 
void sayHi(String name) { 
println "Hello ${name}" 
} 
} 
Hello.metaClass.invokeMethod = { String methodName, args -> 
println "Invoking method '${methodName}' with args '${args}'" 
def method = Hello.metaClass.getMetaMethod(methodName, args) 
method?.invoke(delegate, args) 
} 
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 '[]'
MOP method 
injection
MOP Method Injection 
▷ Injecting methods at code-writing time 
▷ We can “open” a class any time 
▷ Different techniques: 
– MetaClass 
– Categories 
– Extensions 
– Mixins vs Traits
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)
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)
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)
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)
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)
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)
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
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
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
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
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
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
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
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
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
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
Categories 
▷ MetaClass changes are “persistent” 
▷ Change metaclass in confined code 
▷ MOP modified only in the closure
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)
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)
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)
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)
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() 
} 
} 
}
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() 
} 
} 
}
Categories example (II) 
class FileBinaryCategory { 
def static leftShift(File file, URL url) { 
def input 
def output 
try { 
File tmpFile = File.createTempFile('tmp_', '') 
use (FileBinaryCategory) { 
input = url.openStream() 
output = new BufferedOutputStream(new FileOutputStream(file)) 
output << input 
tmpFile << "http://groovy.codehaus.org/images/groovy-logo-medium.png".toURL() 
} 
} finally { 
println tmpFile 
// Execution 
/tmp/tmp_7428855173238452155 
input?.close() 
output?.close() 
} 
} 
}
Categories example (II) 
class FileBinaryCategory { 
def static leftShift(File file, URL url) { 
def input 
def output 
try { 
File tmpFile = File.createTempFile('tmp_', '') 
use (FileBinaryCategory) { 
input = url.openStream() 
output = new BufferedOutputStream(new FileOutputStream(file)) 
output << input 
tmpFile << "http://groovy.codehaus.org/images/groovy-logo-medium.png".toURL() 
} 
} finally { 
println tmpFile 
// Execution 
/tmp/tmp_7428855173238452155 
input?.close() 
output?.close() 
} 
} 
}
Categories example (II) 
class FileBinaryCategory { 
def static leftShift(File file, URL url) { 
def input 
def output 
try { 
File tmpFile = File.createTempFile('tmp_', '') 
use (FileBinaryCategory) { 
input = url.openStream() 
output = new BufferedOutputStream(new FileOutputStream(file)) 
output << input 
tmpFile << "http://groovy.codehaus.org/images/groovy-logo-medium.png".toURL() 
} 
} finally { 
println tmpFile 
// Execution 
/tmp/tmp_7428855173238452155 
input?.close() 
output?.close() 
} 
} 
}
Categories example (II) 
class FileBinaryCategory { 
def static leftShift(File file, URL url) { 
def input 
def output 
try { 
File tmpFile = File.createTempFile('tmp_', '') 
use (FileBinaryCategory) { 
input = url.openStream() 
output = new BufferedOutputStream(new FileOutputStream(file)) 
output << input 
tmpFile << "http://groovy.codehaus.org/images/groovy-logo-medium.png".toURL() 
} 
} finally { 
println tmpFile 
// Execution 
/tmp/tmp_7428855173238452155 
input?.close() 
output?.close() 
} 
} 
}
Extension modules 
▷ JAR file that provides extra methods 
▷ Meta-information file 
▷ Put jar in classpath to enhance classes
Extension modules example 
// src/main/groovy/geecon2014/StringUtilsExtension.groovy 
package geecon2014 
class StringUtilsExtension { 
static String truncate(String self, Integer length, Boolean overflow = false) { 
self.take(length) + (overflow ? '...' : '') 
} 
} 
# src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule 
moduleName = string-utils-module 
moduleVersion = 0.1 
extensionClasses = geecon2014.StringUtilsExtension 
package geecon2014 
import spock.lang.Specification 
class StringUtilsExtensionSpec extends Specification { 
void 'test trucate'() { 
expect: 
"Lorem" == "Lorem ipsum".truncate(5) 
"Lorem..." == "Lorem ipsum".truncate(5, true) 
} 
} 
// Execute with: 
// gradle build 
// groovy -cp 
build/libs/string-extensions-1.0.jar 
ExtensionExample1.groovy 
assert "Lorem..." == "Lorem ipsum". 
truncate(5, true)
Extension modules example 
// src/main/groovy/geecon2014/StringUtilsExtension.groovy 
package geecon2014 
class StringUtilsExtension { 
static String truncate(String self, Integer length, Boolean overflow = false) { 
self.take(length) + (overflow ? '...' : '') 
} 
} 
# src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule 
moduleName = string-utils-module 
moduleVersion = 0.1 
extensionClasses = geecon2014.StringUtilsExtension 
package geecon2014 
import spock.lang.Specification 
class StringUtilsExtensionSpec extends Specification { 
void 'test trucate'() { 
expect: 
"Lorem" == "Lorem ipsum".truncate(5) 
"Lorem..." == "Lorem ipsum".truncate(5, true) 
} 
} 
// Execute with: 
// gradle build 
// groovy -cp 
build/libs/string-extensions-1.0.jar 
ExtensionExample1.groovy 
assert "Lorem..." == "Lorem ipsum". 
truncate(5, true)
Extension modules example 
// src/main/groovy/geecon2014/StringUtilsExtension.groovy 
package geecon2014 
class StringUtilsExtension { 
static String truncate(String self, Integer length, Boolean overflow = false) { 
self.take(length) + (overflow ? '...' : '') 
} 
} 
# src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule 
moduleName = string-utils-module 
moduleVersion = 0.1 
extensionClasses = geecon2014.StringUtilsExtension 
package geecon2014 
import spock.lang.Specification 
class StringUtilsExtensionSpec extends Specification { 
void 'test trucate'() { 
expect: 
"Lorem" == "Lorem ipsum".truncate(5) 
"Lorem..." == "Lorem ipsum".truncate(5, true) 
} 
} 
// Execute with: 
// gradle build 
// groovy -cp 
build/libs/string-extensions-1.0.jar 
ExtensionExample1.groovy 
assert "Lorem..." == "Lorem ipsum". 
truncate(5, true)
Extension modules example 
// src/main/groovy/geecon2014/StringUtilsExtension.groovy 
package geecon2014 
class StringUtilsExtension { 
static String truncate(String self, Integer length, Boolean overflow = false) { 
self.take(length) + (overflow ? '...' : '') 
} 
} 
# src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule 
moduleName = string-utils-module 
moduleVersion = 0.1 
extensionClasses = geecon2014.StringUtilsExtension 
package geecon2014 
import spock.lang.Specification 
class StringUtilsExtensionSpec extends Specification { 
void 'test trucate'() { 
expect: 
"Lorem" == "Lorem ipsum".truncate(5) 
"Lorem..." == "Lorem ipsum".truncate(5, true) 
} 
} 
// Execute with: 
// gradle build 
// groovy -cp 
build/libs/string-extensions-1.0.jar 
ExtensionExample1.groovy 
assert "Lorem..." == "Lorem ipsum". 
truncate(5, true)
Extension modules example 
// src/main/groovy/geecon2014/StringUtilsExtension.groovy 
package geecon2014 
class StringUtilsExtension { 
static String truncate(String self, Integer length, Boolean overflow = false) { 
self.take(length) + (overflow ? '...' : '') 
} 
} 
# src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule 
moduleName = string-utils-module 
moduleVersion = 0.1 
extensionClasses = geecon2014.StringUtilsExtension 
package geecon2014 
import spock.lang.Specification 
class StringUtilsExtensionSpec extends Specification { 
void 'test trucate'() { 
expect: 
"Lorem" == "Lorem ipsum".truncate(5) 
"Lorem..." == "Lorem ipsum".truncate(5, true) 
} 
} 
// Execute with: 
// gradle build 
// groovy -cp 
build/libs/string-extensions-1.0.jar 
ExtensionExample1.groovy 
assert "Lorem..." == "Lorem ipsum". 
truncate(5, true)
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
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)
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) 
} 
}
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) 
} 
}
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) 
} 
}
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..." 
} 
}
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..." 
} 
}
Traits 
▷ Groovy 2.3+ 
▷ Similar to Java 8 default methods 
▷ Supported in JDK 6, 7 and 8 
▷ Stateful 
▷ Composition over inheritance 
▷ Documentation
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..." 
} 
}
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..." 
} 
}
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..." 
} 
}
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..." 
} 
}
MOP method 
synthesis
MOP Method Synthesis 
▷ Dynamically figure out behaviour upon 
invocation 
▷ It may not exist until it's called/executed 
▷ “Intercept, Cache, Invoke” pattern
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?" 
} 
}
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?" 
} 
}
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?" 
} 
}
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')
MethodMissing example 
▷ Requirements: 
– Send notifications to users by different 
channels 
– +50 notifications 
– Not all notifications by all channels 
– Extensible and open to future 
modifications
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}'" 
} 
}
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}'" 
} 
}
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}'" 
} 
}
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) 
} 
}
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) 
} 
}
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(...)
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) 
} 
}
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) 
} 
}
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!'
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!'
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!'
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!'
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!'
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!'
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!'
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!'
2. 
Compile-time 
metaprogramming
Compile-time metaprogramming 
▷ Advance feature 
▷ Analyze/modify program structure at 
compile time 
▷ Cross-cutting features 
▷ Write code that generates bytecode
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
Groovy AST Transformations 
▷ Lot of AST transformations out-of-the-box 
▷ @EqualsAndHashCode, @ToString, 
@TuppleConstructor, @Canonical, @Grab, 
@Immutable, @Delegate, @Singleton, 
@Category, @Log4j, @CompileStatic, 
@TypeChecked, @Synchronized 
...
Global AST 
transformations
Global AST Transformations 
▷ No annotation 
▷ Meta-information file 
▷ Applied to all code during compilation 
▷ Any compilation phase 
▷ Grails uses intensively in GORM
Local AST 
transformations
Local AST Transformations 
▷ Annotate code 
▷ No meta-information file 
▷ Easy to debug
Steps to create local AST 
Interface AST Enjoy!
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" 
}
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" 
}
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" 
}
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)) 
} 
} 
} 
}
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)) 
} 
} 
} 
}
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
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
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
3. 
Recap 
Why we should use 
metaprogramming?
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
With great power 
comes great 
responsibility
Thanks! 
Any questions? 
Iván López 
@ilopmar 
lopez.ivan@gmail.com 
https://github.com/lmivan 
http://kcy.me/1bzj7

More Related Content

What's hot (20)

PPTX
TDC2016SP - Trilha .NET
tdc-globalcode
 
PDF
Generics and Inference
Richard Fox
 
PDF
JJUG CCC 2011 Spring
Kiyotaka Oku
 
PDF
Spock: A Highly Logical Way To Test
Howard Lewis Ship
 
PDF
Stubる - Mockingjayを使ったHTTPクライアントのテスト -
Kenji Tanaka
 
PDF
Building Real Time Systems on MongoDB Using the Oplog at Stripe
MongoDB
 
PPTX
Ian 20150116 java script oop
LearningTech
 
PDF
Do you Promise?
jungkees
 
PDF
Herding types with Scala macros
Marina Sigaeva
 
PDF
Typelevel summit
Marina Sigaeva
 
PDF
多治見IT勉強会 Groovy Grails
Tsuyoshi Yamamoto
 
PDF
Kotlin on Android: Delegate with pleasure
Dmytro Zaitsev
 
PDF
360|iDev
Aijaz Ansari
 
PDF
Google App Engine Developer - Day3
Simon Su
 
KEY
Scala - den smarta kusinen
Redpill Linpro
 
ODP
Groovy intro for OUDL
J David Beutel
 
PDF
mobl
Eelco Visser
 
PDF
JNI - Java & C in the same project
Karol Wrótniak
 
PDF
Jggug 2010 330 Grails 1.3 観察
Tsuyoshi Yamamoto
 
PDF
Mattbrenner
Droidcon Berlin
 
TDC2016SP - Trilha .NET
tdc-globalcode
 
Generics and Inference
Richard Fox
 
JJUG CCC 2011 Spring
Kiyotaka Oku
 
Spock: A Highly Logical Way To Test
Howard Lewis Ship
 
Stubる - Mockingjayを使ったHTTPクライアントのテスト -
Kenji Tanaka
 
Building Real Time Systems on MongoDB Using the Oplog at Stripe
MongoDB
 
Ian 20150116 java script oop
LearningTech
 
Do you Promise?
jungkees
 
Herding types with Scala macros
Marina Sigaeva
 
Typelevel summit
Marina Sigaeva
 
多治見IT勉強会 Groovy Grails
Tsuyoshi Yamamoto
 
Kotlin on Android: Delegate with pleasure
Dmytro Zaitsev
 
360|iDev
Aijaz Ansari
 
Google App Engine Developer - Day3
Simon Su
 
Scala - den smarta kusinen
Redpill Linpro
 
Groovy intro for OUDL
J David Beutel
 
JNI - Java & C in the same project
Karol Wrótniak
 
Jggug 2010 330 Grails 1.3 観察
Tsuyoshi Yamamoto
 
Mattbrenner
Droidcon Berlin
 

Similar to GeeCON Prague 2014 - Metaprogramming with Groovy (20)

PDF
ConFess Vienna 2015 - Metaprogramming with Groovy
Iván López Martín
 
PPT
Groovy Basics
Wes Williams
 
PPTX
MetaProgramming with Groovy
NexThoughts Technologies
 
PPTX
Metaprogramming with Groovy
Ali Tanwir
 
PPTX
Metaprogramming Techniques In Groovy And Grails
zenMonkey
 
PDF
The Next Generation MOP, Jochen Theodorou, GR8Conf 2013
GR8Conf
 
PDF
Greach 2014 - Metaprogramming with groovy
Iván López Martín
 
PDF
Apache Groovy's Metaprogramming Options and You
Andres Almiray
 
PPTX
Meta Programming in Groovy
NexThoughts Technologies
 
PPT
Feelin' Groovy: An Afternoon of Reflexive Metaprogramming
Matt Stine
 
PDF
Practical Domain-Specific Languages in Groovy
Guillaume Laforge
 
PPT
Eclipsecon08 Introduction To Groovy
Andres Almiray
 
PPT
Groovy for Java Developers
Andres Almiray
 
PDF
[email protected]@groovy metaprogrammning
Parag Gajbhiye
 
PPT
JavaOne 2008 - TS-5793 - Groovy and Grails, changing the landscape of Java EE...
Guillaume Laforge
 
PPT
Introduction To Groovy
manishkp84
 
PDF
Groovy On Trading Desk (2010)
Jonathan Felch
 
PDF
GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge
GR8Conf
 
PPTX
Groovy Api Tutorial
guligala
 
PPTX
Groovy!
Petr Giecek
 
ConFess Vienna 2015 - Metaprogramming with Groovy
Iván López Martín
 
Groovy Basics
Wes Williams
 
MetaProgramming with Groovy
NexThoughts Technologies
 
Metaprogramming with Groovy
Ali Tanwir
 
Metaprogramming Techniques In Groovy And Grails
zenMonkey
 
The Next Generation MOP, Jochen Theodorou, GR8Conf 2013
GR8Conf
 
Greach 2014 - Metaprogramming with groovy
Iván López Martín
 
Apache Groovy's Metaprogramming Options and You
Andres Almiray
 
Meta Programming in Groovy
NexThoughts Technologies
 
Feelin' Groovy: An Afternoon of Reflexive Metaprogramming
Matt Stine
 
Practical Domain-Specific Languages in Groovy
Guillaume Laforge
 
Eclipsecon08 Introduction To Groovy
Andres Almiray
 
Groovy for Java Developers
Andres Almiray
 
[email protected]@groovy metaprogrammning
Parag Gajbhiye
 
JavaOne 2008 - TS-5793 - Groovy and Grails, changing the landscape of Java EE...
Guillaume Laforge
 
Introduction To Groovy
manishkp84
 
Groovy On Trading Desk (2010)
Jonathan Felch
 
GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge
GR8Conf
 
Groovy Api Tutorial
guligala
 
Groovy!
Petr Giecek
 
Ad

More from Iván López Martín (20)

PDF
CommitConf 2025 - Spring AI: IA Avanzada para desarrolladores Spring
Iván López Martín
 
PDF
SalmorejoTech 2024 - Spring Boot <3 Testcontainers
Iván López Martín
 
PDF
CommitConf 2024 - Spring Boot <3 Testcontainers
Iván López Martín
 
PDF
Voxxed Days CERN 2024 - Spring Boot <3 Testcontainers.pdf
Iván López Martín
 
PDF
VMware - Testcontainers y Spring Boot
Iván López Martín
 
PDF
Spring IO 2023 - Dynamic OpenAPIs with Spring Cloud Gateway
Iván López Martín
 
PDF
Codemotion Madrid 2023 - Testcontainers y Spring Boot
Iván López Martín
 
PDF
CommitConf 2023 - Spring Framework 6 y Spring Boot 3
Iván López Martín
 
PDF
Construyendo un API REST con Spring Boot y GraalVM
Iván López Martín
 
PDF
jLove 2020 - Micronaut and graalvm: The power of AoT
Iván López Martín
 
PDF
Codemotion Madrid 2020 - Serverless con Micronaut
Iván López Martín
 
PDF
JConf Perú 2020 - ¡Micronaut en acción!
Iván López Martín
 
PDF
JConf Perú 2020 - Micronaut + GraalVM = <3
Iván López Martín
 
PDF
JConf México 2020 - Micronaut + GraalVM = <3
Iván López Martín
 
PDF
Developing Micronaut Applications With IntelliJ IDEA
Iván López Martín
 
PDF
CommitConf 2019 - Micronaut y GraalVm: La combinación perfecta
Iván López Martín
 
PDF
Codemotion Madrid 2019 - ¡GraalVM y Micronaut: compañeros perfectos!
Iván López Martín
 
PDF
Greach 2019 - Creating Micronaut Configurations
Iván López Martín
 
PDF
VoxxedDays Bucharest 2019 - Alexa, nice to meet you
Iván López Martín
 
PDF
JavaDay Lviv 2019 - Micronaut in action!
Iván López Martín
 
CommitConf 2025 - Spring AI: IA Avanzada para desarrolladores Spring
Iván López Martín
 
SalmorejoTech 2024 - Spring Boot <3 Testcontainers
Iván López Martín
 
CommitConf 2024 - Spring Boot <3 Testcontainers
Iván López Martín
 
Voxxed Days CERN 2024 - Spring Boot <3 Testcontainers.pdf
Iván López Martín
 
VMware - Testcontainers y Spring Boot
Iván López Martín
 
Spring IO 2023 - Dynamic OpenAPIs with Spring Cloud Gateway
Iván López Martín
 
Codemotion Madrid 2023 - Testcontainers y Spring Boot
Iván López Martín
 
CommitConf 2023 - Spring Framework 6 y Spring Boot 3
Iván López Martín
 
Construyendo un API REST con Spring Boot y GraalVM
Iván López Martín
 
jLove 2020 - Micronaut and graalvm: The power of AoT
Iván López Martín
 
Codemotion Madrid 2020 - Serverless con Micronaut
Iván López Martín
 
JConf Perú 2020 - ¡Micronaut en acción!
Iván López Martín
 
JConf Perú 2020 - Micronaut + GraalVM = <3
Iván López Martín
 
JConf México 2020 - Micronaut + GraalVM = <3
Iván López Martín
 
Developing Micronaut Applications With IntelliJ IDEA
Iván López Martín
 
CommitConf 2019 - Micronaut y GraalVm: La combinación perfecta
Iván López Martín
 
Codemotion Madrid 2019 - ¡GraalVM y Micronaut: compañeros perfectos!
Iván López Martín
 
Greach 2019 - Creating Micronaut Configurations
Iván López Martín
 
VoxxedDays Bucharest 2019 - Alexa, nice to meet you
Iván López Martín
 
JavaDay Lviv 2019 - Micronaut in action!
Iván López Martín
 
Ad

Recently uploaded (20)

PDF
The 2025 InfraRed Report - Redpoint Ventures
Razin Mustafiz
 
PDF
Reverse Engineering of Security Products: Developing an Advanced Microsoft De...
nwbxhhcyjv
 
PPT
Ericsson LTE presentation SEMINAR 2010.ppt
npat3
 
PPTX
AI Penetration Testing Essentials: A Cybersecurity Guide for 2025
defencerabbit Team
 
PDF
NLJUG Speaker academy 2025 - first session
Bert Jan Schrijver
 
PDF
Newgen Beyond Frankenstein_Build vs Buy_Digital_version.pdf
darshakparmar
 
PPTX
Q2 FY26 Tableau User Group Leader Quarterly Call
lward7
 
DOCX
Python coding for beginners !! Start now!#
Rajni Bhardwaj Grover
 
PPTX
COMPARISON OF RASTER ANALYSIS TOOLS OF QGIS AND ARCGIS
Sharanya Sarkar
 
PDF
Kit-Works Team Study_20250627_한달만에만든사내서비스키링(양다윗).pdf
Wonjun Hwang
 
PDF
Peak of Data & AI Encore AI-Enhanced Workflows for the Real World
Safe Software
 
PDF
Go Concurrency Real-World Patterns, Pitfalls, and Playground Battles.pdf
Emily Achieng
 
DOCX
Cryptography Quiz: test your knowledge of this important security concept.
Rajni Bhardwaj Grover
 
PDF
Book industry state of the nation 2025 - Tech Forum 2025
BookNet Canada
 
PDF
Agentic AI lifecycle for Enterprise Hyper-Automation
Debmalya Biswas
 
PDF
The Rise of AI and IoT in Mobile App Tech.pdf
IMG Global Infotech
 
PPTX
Future Tech Innovations 2025 – A TechLists Insight
TechLists
 
PDF
Bitcoin for Millennials podcast with Bram, Power Laws of Bitcoin
Stephen Perrenod
 
PDF
Mastering Financial Management in Direct Selling
Epixel MLM Software
 
PDF
“Computer Vision at Sea: Automated Fish Tracking for Sustainable Fishing,” a ...
Edge AI and Vision Alliance
 
The 2025 InfraRed Report - Redpoint Ventures
Razin Mustafiz
 
Reverse Engineering of Security Products: Developing an Advanced Microsoft De...
nwbxhhcyjv
 
Ericsson LTE presentation SEMINAR 2010.ppt
npat3
 
AI Penetration Testing Essentials: A Cybersecurity Guide for 2025
defencerabbit Team
 
NLJUG Speaker academy 2025 - first session
Bert Jan Schrijver
 
Newgen Beyond Frankenstein_Build vs Buy_Digital_version.pdf
darshakparmar
 
Q2 FY26 Tableau User Group Leader Quarterly Call
lward7
 
Python coding for beginners !! Start now!#
Rajni Bhardwaj Grover
 
COMPARISON OF RASTER ANALYSIS TOOLS OF QGIS AND ARCGIS
Sharanya Sarkar
 
Kit-Works Team Study_20250627_한달만에만든사내서비스키링(양다윗).pdf
Wonjun Hwang
 
Peak of Data & AI Encore AI-Enhanced Workflows for the Real World
Safe Software
 
Go Concurrency Real-World Patterns, Pitfalls, and Playground Battles.pdf
Emily Achieng
 
Cryptography Quiz: test your knowledge of this important security concept.
Rajni Bhardwaj Grover
 
Book industry state of the nation 2025 - Tech Forum 2025
BookNet Canada
 
Agentic AI lifecycle for Enterprise Hyper-Automation
Debmalya Biswas
 
The Rise of AI and IoT in Mobile App Tech.pdf
IMG Global Infotech
 
Future Tech Innovations 2025 – A TechLists Insight
TechLists
 
Bitcoin for Millennials podcast with Bram, Power Laws of Bitcoin
Stephen Perrenod
 
Mastering Financial Management in Direct Selling
Epixel MLM Software
 
“Computer Vision at Sea: Automated Fish Tracking for Sustainable Fishing,” a ...
Edge AI and Vision Alliance
 

GeeCON Prague 2014 - Metaprogramming with Groovy

  • 1. Iván López @ilopmar METAPROGRAMMING WITH GROOVY
  • 2. Hello! I am Iván López @ilopmar
  • 3. Groovy is dynamic ▷ “Delay” to runtime some decisions ▷ Add properties/behaviours in runtime ▷ Wide range of applicability
  • 5. “ Metaprogramming is the writing of computer programs that write or manipulate other programs (or themselves) as their data. - Wikipedia
  • 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
  • 21. MetaClass example class Hello { void sayHi(String name) { println "Hello ${name}" } } Hello.metaClass.invokeMethod = { String methodName, args -> println "Invoking method '${methodName}' with args '${args}'" def method = Hello.metaClass.getMetaMethod(methodName, args) method?.invoke(delegate, args) } 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 '[]'
  • 22. MetaClass example class Hello { void sayHi(String name) { println "Hello ${name}" } } Hello.metaClass.invokeMethod = { String methodName, args -> println "Invoking method '${methodName}' with args '${args}'" def method = Hello.metaClass.getMetaMethod(methodName, args) method?.invoke(delegate, args) } 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 '[]'
  • 23. MetaClass example class Hello { void sayHi(String name) { println "Hello ${name}" } } Hello.metaClass.invokeMethod = { String methodName, args -> println "Invoking method '${methodName}' with args '${args}'" def method = Hello.metaClass.getMetaMethod(methodName, args) method?.invoke(delegate, args) } 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 '[]'
  • 24. MetaClass example class Hello { void sayHi(String name) { println "Hello ${name}" } } Hello.metaClass.invokeMethod = { String methodName, args -> println "Invoking method '${methodName}' with args '${args}'" def method = Hello.metaClass.getMetaMethod(methodName, args) method?.invoke(delegate, args) } 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 '[]'
  • 25. MetaClass example class Hello { void sayHi(String name) { println "Hello ${name}" } } Hello.metaClass.invokeMethod = { String methodName, args -> println "Invoking method '${methodName}' with args '${args}'" def method = Hello.metaClass.getMetaMethod(methodName, args) method?.invoke(delegate, args) } 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 '[]'
  • 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() } } }
  • 51. Categories example (II) class FileBinaryCategory { def static leftShift(File file, URL url) { def input def output try { File tmpFile = File.createTempFile('tmp_', '') use (FileBinaryCategory) { input = url.openStream() output = new BufferedOutputStream(new FileOutputStream(file)) output << input tmpFile << "http://groovy.codehaus.org/images/groovy-logo-medium.png".toURL() } } finally { println tmpFile // Execution /tmp/tmp_7428855173238452155 input?.close() output?.close() } } }
  • 52. Categories example (II) class FileBinaryCategory { def static leftShift(File file, URL url) { def input def output try { File tmpFile = File.createTempFile('tmp_', '') use (FileBinaryCategory) { input = url.openStream() output = new BufferedOutputStream(new FileOutputStream(file)) output << input tmpFile << "http://groovy.codehaus.org/images/groovy-logo-medium.png".toURL() } } finally { println tmpFile // Execution /tmp/tmp_7428855173238452155 input?.close() output?.close() } } }
  • 53. Categories example (II) class FileBinaryCategory { def static leftShift(File file, URL url) { def input def output try { File tmpFile = File.createTempFile('tmp_', '') use (FileBinaryCategory) { input = url.openStream() output = new BufferedOutputStream(new FileOutputStream(file)) output << input tmpFile << "http://groovy.codehaus.org/images/groovy-logo-medium.png".toURL() } } finally { println tmpFile // Execution /tmp/tmp_7428855173238452155 input?.close() output?.close() } } }
  • 54. Categories example (II) class FileBinaryCategory { def static leftShift(File file, URL url) { def input def output try { File tmpFile = File.createTempFile('tmp_', '') use (FileBinaryCategory) { input = url.openStream() output = new BufferedOutputStream(new FileOutputStream(file)) output << input tmpFile << "http://groovy.codehaus.org/images/groovy-logo-medium.png".toURL() } } finally { println tmpFile // Execution /tmp/tmp_7428855173238452155 input?.close() output?.close() } } }
  • 55. Extension modules ▷ JAR file that provides extra methods ▷ Meta-information file ▷ Put jar in classpath to enhance classes
  • 56. Extension modules example // src/main/groovy/geecon2014/StringUtilsExtension.groovy package geecon2014 class StringUtilsExtension { static String truncate(String self, Integer length, Boolean overflow = false) { self.take(length) + (overflow ? '...' : '') } } # src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule moduleName = string-utils-module moduleVersion = 0.1 extensionClasses = geecon2014.StringUtilsExtension package geecon2014 import spock.lang.Specification class StringUtilsExtensionSpec extends Specification { void 'test trucate'() { expect: "Lorem" == "Lorem ipsum".truncate(5) "Lorem..." == "Lorem ipsum".truncate(5, true) } } // Execute with: // gradle build // groovy -cp build/libs/string-extensions-1.0.jar ExtensionExample1.groovy assert "Lorem..." == "Lorem ipsum". truncate(5, true)
  • 57. Extension modules example // src/main/groovy/geecon2014/StringUtilsExtension.groovy package geecon2014 class StringUtilsExtension { static String truncate(String self, Integer length, Boolean overflow = false) { self.take(length) + (overflow ? '...' : '') } } # src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule moduleName = string-utils-module moduleVersion = 0.1 extensionClasses = geecon2014.StringUtilsExtension package geecon2014 import spock.lang.Specification class StringUtilsExtensionSpec extends Specification { void 'test trucate'() { expect: "Lorem" == "Lorem ipsum".truncate(5) "Lorem..." == "Lorem ipsum".truncate(5, true) } } // Execute with: // gradle build // groovy -cp build/libs/string-extensions-1.0.jar ExtensionExample1.groovy assert "Lorem..." == "Lorem ipsum". truncate(5, true)
  • 58. Extension modules example // src/main/groovy/geecon2014/StringUtilsExtension.groovy package geecon2014 class StringUtilsExtension { static String truncate(String self, Integer length, Boolean overflow = false) { self.take(length) + (overflow ? '...' : '') } } # src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule moduleName = string-utils-module moduleVersion = 0.1 extensionClasses = geecon2014.StringUtilsExtension package geecon2014 import spock.lang.Specification class StringUtilsExtensionSpec extends Specification { void 'test trucate'() { expect: "Lorem" == "Lorem ipsum".truncate(5) "Lorem..." == "Lorem ipsum".truncate(5, true) } } // Execute with: // gradle build // groovy -cp build/libs/string-extensions-1.0.jar ExtensionExample1.groovy assert "Lorem..." == "Lorem ipsum". truncate(5, true)
  • 59. Extension modules example // src/main/groovy/geecon2014/StringUtilsExtension.groovy package geecon2014 class StringUtilsExtension { static String truncate(String self, Integer length, Boolean overflow = false) { self.take(length) + (overflow ? '...' : '') } } # src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule moduleName = string-utils-module moduleVersion = 0.1 extensionClasses = geecon2014.StringUtilsExtension package geecon2014 import spock.lang.Specification class StringUtilsExtensionSpec extends Specification { void 'test trucate'() { expect: "Lorem" == "Lorem ipsum".truncate(5) "Lorem..." == "Lorem ipsum".truncate(5, true) } } // Execute with: // gradle build // groovy -cp build/libs/string-extensions-1.0.jar ExtensionExample1.groovy assert "Lorem..." == "Lorem ipsum". truncate(5, true)
  • 60. Extension modules example // src/main/groovy/geecon2014/StringUtilsExtension.groovy package geecon2014 class StringUtilsExtension { static String truncate(String self, Integer length, Boolean overflow = false) { self.take(length) + (overflow ? '...' : '') } } # src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule moduleName = string-utils-module moduleVersion = 0.1 extensionClasses = geecon2014.StringUtilsExtension package geecon2014 import spock.lang.Specification class StringUtilsExtensionSpec extends Specification { void 'test trucate'() { expect: "Lorem" == "Lorem ipsum".truncate(5) "Lorem..." == "Lorem ipsum".truncate(5, true) } } // Execute with: // gradle build // groovy -cp build/libs/string-extensions-1.0.jar ExtensionExample1.groovy assert "Lorem..." == "Lorem ipsum". truncate(5, true)
  • 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
  • 99. Groovy AST Transformations ▷ Lot of AST transformations out-of-the-box ▷ @EqualsAndHashCode, @ToString, @TuppleConstructor, @Canonical, @Grab, @Immutable, @Delegate, @Singleton, @Category, @Log4j, @CompileStatic, @TypeChecked, @Synchronized ...
  • 101. Global AST Transformations ▷ No annotation ▷ Meta-information file ▷ Applied to all code during compilation ▷ Any compilation phase ▷ Grails uses intensively in GORM
  • 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. 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
  • 115. With great power comes great responsibility
  • 116. Thanks! Any questions? Iván López @ilopmar [email protected] https://github.com/lmivan http://kcy.me/1bzj7