METAPROGRAMMING
WITH GROOVY
Iván López @ilopmar
Hello!
I am Iván López
@ilopmar
http://greachconf.com@madridgug
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
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)
}
Groovy Interceptable
▷ GroovyObject interface
▷ Implement GroovyInterceptable to hook
into the execution
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
}
}
def person = new Person()
person.name = "Iván"
person.age = 35
println "Hello ${person.name}, you're ${person.age}"
// Execution
Setting property 'name' with value 'Iván'
Setting property 'age' with value '35'
Getting property 'name'
Getting property 'age'
Hello Iván, you're 35
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
}
}
def person = new Person()
person.name = "Iván"
person.age = 35
println "Hello ${person.name}, you're ${person.age}"
// Execution
Setting property 'name' with value 'Iván'
Setting property 'age' with value '35'
Getting property 'name'
Getting property 'age'
Hello Iván, you're 35
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
}
}
def person = new Person()
person.name = "Iván"
person.age = 35
println "Hello ${person.name}, you're ${person.age}"
// Execution
Setting property 'name' with value 'Iván'
Setting property 'age' with value '35'
Getting property 'name'
Getting property 'age'
Hello Iván, you're 35
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
}
}
def person = new Person()
person.name = "Iván"
person.age = 35
println "Hello ${person.name}, you're ${person.age}"
// Execution
Setting property 'name' with value 'Iván'
Setting property 'age' with value '35'
Getting property 'name'
Getting property 'age'
Hello Iván, you're 35
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
}
}
def person = new Person()
person.name = "Iván"
person.age = 35
println "Hello ${person.name}, you're ${person.age}"
// Execution
Setting property 'name' with value 'Iván'
Setting property 'age' with value '35'
Getting property 'name'
Getting property 'age'
Hello Iván, you're 35
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("ConFess Vienna!")
hello.anotherMethod()
GroovyInterceptable example (II)
// Execution
Invoking method 'sayHi' with args '[ConFess Vienna!]'
Hello ConFess Vienna!
Invoking method 'anotherMethod' with args '[]'
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("ConFess Vienna!")
hello.anotherMethod()
GroovyInterceptable example (II)
// Execution
Invoking method 'sayHi' with args '[ConFess Vienna!]'
Hello ConFess Vienna!
Invoking method 'anotherMethod' with args '[]'
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("ConFess Vienna!")
hello.anotherMethod()
GroovyInterceptable example (II)
// Execution
Invoking method 'sayHi' with args '[ConFess Vienna!]'
Hello ConFess Vienna!
Invoking method 'anotherMethod' with args '[]'
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("ConFess Vienna!")
hello.anotherMethod()
GroovyInterceptable example (II)
// Execution
Invoking method 'sayHi' with args '[ConFess Vienna!]'
Hello ConFess Vienna!
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
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("ConFess Vienna!")
hello.anotherMethod()
// Execution
Invoking method 'sayHi' with args '[ConFess Vienna!]'
Hello ConFess Vienna!
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("ConFess Vienna!")
hello.anotherMethod()
// Execution
Invoking method 'sayHi' with args '[ConFess Vienna!]'
Hello ConFess Vienna!
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("ConFess Vienna!")
hello.anotherMethod()
// Execution
Invoking method 'sayHi' with args '[ConFess Vienna!]'
Hello ConFess Vienna!
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("ConFess Vienna!")
hello.anotherMethod()
// Execution
Invoking method 'sayHi' with args '[ConFess Vienna!]'
Hello ConFess Vienna!
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("ConFess Vienna!")
hello.anotherMethod()
// Execution
Invoking method 'sayHi' with args '[ConFess Vienna!]'
Hello ConFess Vienna!
Invoking method 'anotherMethod' with args '[]'
MetaClass example
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
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 methods 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
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
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
// 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
Overriding methods using MetaClass
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)
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 {
input = url.openStream()
output = new BufferedOutputStream(new FileOutputStream(file))
output << input
} finally {
input?.close()
output?.close()
}
}
}
Categories example (II)
File tmpFile = File.createTempFile('tmp_', '')
use (FileBinaryCategory) {
tmpFile << "http://groovy.codehaus.org/images/groovy-logo-medium.png".toURL()
}
println tmpFile
// Execution
/tmp/tmp_7428855173238452155
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)
File tmpFile = File.createTempFile('tmp_', '')
use (FileBinaryCategory) {
tmpFile << "http://groovy.codehaus.org/images/groovy-logo-medium.png".toURL()
}
println tmpFile
// Execution
/tmp/tmp_7428855173238452155
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)
File tmpFile = File.createTempFile('tmp_', '')
use (FileBinaryCategory) {
tmpFile << "http://groovy.codehaus.org/images/groovy-logo-medium.png".toURL()
}
println tmpFile
// Execution
/tmp/tmp_7428855173238452155
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)
File tmpFile = File.createTempFile('tmp_', '')
use (FileBinaryCategory) {
tmpFile << "http://groovy.codehaus.org/images/groovy-logo-medium.png".toURL()
}
println tmpFile
// Execution
/tmp/tmp_7428855173238452155
Extension modules
▷ JAR file that provides extra methods
▷ Meta-information file
▷ Put jar in classpath to enhance classes
// src/main/groovy/confess2015/StringUtilsExtension.groovy
package confess2015
class StringUtilsExtension {
static String truncate(String self, Integer length, Boolean overflow = false) {
self.take(length) + (overflow ? '...' : '')
}
}
package confess2015
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)
# src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule
moduleName = string-utils-module
moduleVersion = 0.1
extensionClasses = confess2015.StringUtilsExtension
Extension modules example
Extension modules example
// src/main/groovy/confess2015/StringUtilsExtension.groovy
package confess2015
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 = confess2015.StringUtilsExtension
package confess2015
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/confess2015/StringUtilsExtension.groovy
package confess2015
class StringUtilsExtension {
static String truncate(String self, Integer length, Boolean overflow = false) {
self.take(length) + (overflow ? '...' : '')
}
}
package confess2015
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)
# src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule
moduleName = string-utils-module
moduleVersion = 0.1
extensionClasses = confess2015.StringUtilsExtension
Extension modules example
// src/main/groovy/confess2015/StringUtilsExtension.groovy
package confess2015
class StringUtilsExtension {
static String truncate(String self, Integer length, Boolean overflow = false) {
self.take(length) + (overflow ? '...' : '')
}
}
package confess2015
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)
# src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule
moduleName = string-utils-module
moduleVersion = 0.1
extensionClasses = confess2015.StringUtilsExtension
// src/main/groovy/confess2015/StringUtilsExtension.groovy
package confess2015
class StringUtilsExtension {
static String truncate(String self, Integer length, Boolean overflow = false) {
self.take(length) + (overflow ? '...' : '')
}
}
package confess2015
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/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule
moduleName = string-utils-module
moduleVersion = 0.1
extensionClasses = confess2015.StringUtilsExtension
Mixins
▷ “Bring in” or “mix in” implementations
from multiple classes
▷ Calls first routed to mixed-in class
▷ Last mixin wins
▷ Not easily un-done
class SpidermanPower {
String spiderSense() {
"Using spider-sense..."
}
}
Mixins example
@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..."
}
}
@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..."
}
}
class SpidermanPower {
String spiderSense() {
"Using spider-sense..."
}
}
Mixins example
@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..."
}
}
class SpidermanPower {
String spiderSense() {
"Using spider-sense..."
}
}
Mixins example
@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..."
}
}
class SpidermanPower {
String spiderSense() {
"Using spider-sense..."
}
}
Mixins example
@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 SpidermanPower {
String spiderSense() {
"Using spider-sense..."
}
}
Mixins example
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 SpidermanPower {
String spiderSense() {
"Using spider-sense..."
}
}
Mixins example
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
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
Traits example
trait SpidermanPower {
String spiderSense() {
"Using spider-sense..."
}
}
trait SupermanPower {
String fly() {
"Flying..."
}
}
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
Traits example
trait SpidermanPower {
String spiderSense() {
"Using spider-sense..."
}
}
trait SupermanPower {
String fly() {
"Flying..."
}
}
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
Traits example
trait SpidermanPower {
String spiderSense() {
"Using spider-sense..."
}
}
trait SupermanPower {
String fly() {
"Flying..."
}
}
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
Traits example
trait SpidermanPower {
String spiderSense() {
"Using spider-sense..."
}
}
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
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')
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: 35)
assert p.respondsTo('sayHi')
assert p.respondsTo('sayHiTo', String)
assert !p.respondsTo('goodbye')
assert p.hasProperty('age')
assert !p.hasProperty('country')
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?"
}
}
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: 35)
assert p.respondsTo('sayHi')
assert p.respondsTo('sayHiTo', String)
assert !p.respondsTo('goodbye')
assert p.hasProperty('age')
assert !p.hasProperty('country')
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: 35)
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
// 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!'
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!")
MethodMissing example
// 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!'
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!")
MethodMissing example
// 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!'
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!")
MethodMissing example
// 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!'
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!")
MethodMissing example
// 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!'
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!")class EmailChannel extends Channel {
void sendNewFollower(String username, String follower) {…}
void sendNewMessage(String username, String msg) {…}
}
class MobilePushChannel extends Channel {
void sendNewFollower(String username, String follower) {…}
}
MethodMissing example
// 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!'
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!")
MethodMissing example
// 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!'
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!")
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
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
package confess2015
import ...
@Retention(RetentionPolicy.SOURCE)
@Target([ElementType.TYPE])
@GroovyASTTransformationClass("confess2015.VersionASTTransformation")
@interface Version {
String value()
}
class VersionedClass {
public static final String VERSION = "1.0"
}
import confess2015.Version
@Version('1.0')
class VersionedClass {
}
Local AST example
class VersionedClass {
public static final String VERSION = "1.0"
}
package confess2015
import ...
@Retention(RetentionPolicy.SOURCE)
@Target([ElementType.TYPE])
@GroovyASTTransformationClass("confess2015.VersionASTTransformation")
@interface Version {
String value()
}
import confess2015.Version
@Version('1.0')
class VersionedClass {
}
Local AST example
class VersionedClass {
public static final String VERSION = "1.0"
}
package confess2015
import ...
@Retention(RetentionPolicy.SOURCE)
@Target([ElementType.TYPE])
@GroovyASTTransformationClass("confess2015.VersionASTTransformation")
@interface Version {
String value()
}
import confess2015.Version
@Version('1.0')
class VersionedClass {
}
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 confess.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 confess.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 confess.Version
@Version('1.0')
class VersionedClass {
}
println VersionedClass.VERSION
// Execution
1.0
3.
Why we should use
metaprogramming?
Let’s review some concepts
Metaprogramming
out-of-the box
Easy and very
powerful
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?
@ilopmar
lopez.ivan@gmail.com
https://github.com/lmivan
Iván López
http://kcy.me/208wq
ConFess Vienna 2015 - Metaprogramming with Groovy

ConFess Vienna 2015 - Metaprogramming with Groovy

  • 1.
  • 2.
    Hello! I am IvánLópez @ilopmar http://greachconf.com@madridgug
  • 3.
    Groovy is dynamic ▷“Delay” to runtime some decisions ▷ Add properties/behaviours in runtime ▷ Wide range of applicability
  • 4.
  • 5.
    “Metaprogramming is thewriting of computer programs that write or manipulate other programs (or themselves) as their data. - Wikipedia
  • 6.
  • 7.
    Runtime metaprogramming ▷ Groovyprovides this through Meta- Object Protocol (MOP) ▷ Use MOP to: – Invoke methods dynamically – Synthesize classes and methods on the fly
  • 8.
    What is theMeta Object Protocol? Groovy Groovy Java Java MOP
  • 9.
  • 10.
    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) } Groovy Interceptable ▷ GroovyObject interface ▷ Implement GroovyInterceptable to hook into the execution
  • 11.
    GroovyInterceptable example class Personimplements 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 } } def person = new Person() person.name = "Iván" person.age = 35 println "Hello ${person.name}, you're ${person.age}" // Execution Setting property 'name' with value 'Iván' Setting property 'age' with value '35' Getting property 'name' Getting property 'age' Hello Iván, you're 35
  • 12.
    GroovyInterceptable example class Personimplements 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 } } def person = new Person() person.name = "Iván" person.age = 35 println "Hello ${person.name}, you're ${person.age}" // Execution Setting property 'name' with value 'Iván' Setting property 'age' with value '35' Getting property 'name' Getting property 'age' Hello Iván, you're 35
  • 13.
    GroovyInterceptable example class Personimplements 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 } } def person = new Person() person.name = "Iván" person.age = 35 println "Hello ${person.name}, you're ${person.age}" // Execution Setting property 'name' with value 'Iván' Setting property 'age' with value '35' Getting property 'name' Getting property 'age' Hello Iván, you're 35
  • 14.
    GroovyInterceptable example class Personimplements 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 } } def person = new Person() person.name = "Iván" person.age = 35 println "Hello ${person.name}, you're ${person.age}" // Execution Setting property 'name' with value 'Iván' Setting property 'age' with value '35' Getting property 'name' Getting property 'age' Hello Iván, you're 35
  • 15.
    GroovyInterceptable example class Personimplements 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 } } def person = new Person() person.name = "Iván" person.age = 35 println "Hello ${person.name}, you're ${person.age}" // Execution Setting property 'name' with value 'Iván' Setting property 'age' with value '35' Getting property 'name' Getting property 'age' Hello Iván, you're 35
  • 16.
    class Hello implementsGroovyInterceptable { 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("ConFess Vienna!") hello.anotherMethod() GroovyInterceptable example (II) // Execution Invoking method 'sayHi' with args '[ConFess Vienna!]' Hello ConFess Vienna! Invoking method 'anotherMethod' with args '[]'
  • 17.
    class Hello implementsGroovyInterceptable { 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("ConFess Vienna!") hello.anotherMethod() GroovyInterceptable example (II) // Execution Invoking method 'sayHi' with args '[ConFess Vienna!]' Hello ConFess Vienna! Invoking method 'anotherMethod' with args '[]'
  • 18.
    class Hello implementsGroovyInterceptable { 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("ConFess Vienna!") hello.anotherMethod() GroovyInterceptable example (II) // Execution Invoking method 'sayHi' with args '[ConFess Vienna!]' Hello ConFess Vienna! Invoking method 'anotherMethod' with args '[]'
  • 19.
    class Hello implementsGroovyInterceptable { 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("ConFess Vienna!") hello.anotherMethod() GroovyInterceptable example (II) // Execution Invoking method 'sayHi' with args '[ConFess Vienna!]' Hello ConFess Vienna! Invoking method 'anotherMethod' with args '[]'
  • 20.
    MetaClass ▷ MetaClass registryfor each class ▷ Collection of methods/properties ▷ We can always modify the metaclass ▷ Intercept methods implementing invokeMethod on metaclass
  • 21.
    class Hello { voidsayHi(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("ConFess Vienna!") hello.anotherMethod() // Execution Invoking method 'sayHi' with args '[ConFess Vienna!]' Hello ConFess Vienna! Invoking method 'anotherMethod' with args '[]' MetaClass example
  • 22.
    class Hello { voidsayHi(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("ConFess Vienna!") hello.anotherMethod() // Execution Invoking method 'sayHi' with args '[ConFess Vienna!]' Hello ConFess Vienna! Invoking method 'anotherMethod' with args '[]' MetaClass example
  • 23.
    class Hello { voidsayHi(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("ConFess Vienna!") hello.anotherMethod() // Execution Invoking method 'sayHi' with args '[ConFess Vienna!]' Hello ConFess Vienna! Invoking method 'anotherMethod' with args '[]' MetaClass example
  • 24.
    class Hello { voidsayHi(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("ConFess Vienna!") hello.anotherMethod() // Execution Invoking method 'sayHi' with args '[ConFess Vienna!]' Hello ConFess Vienna! Invoking method 'anotherMethod' with args '[]' MetaClass example
  • 25.
    class Hello { voidsayHi(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("ConFess Vienna!") hello.anotherMethod() // Execution Invoking method 'sayHi' with args '[ConFess Vienna!]' Hello ConFess Vienna! Invoking method 'anotherMethod' with args '[]' MetaClass example
  • 26.
  • 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.
    class StringUtils { staticString 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
  • 29.
    class StringUtils { staticString 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
  • 30.
    class StringUtils { staticString 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
  • 31.
    class StringUtils { staticString 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
  • 32.
    class StringUtils { staticString 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
  • 33.
    class StringUtils { staticString 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
  • 34.
    class Utils { } defutilsInstance = new Utils() Utils.metaClass.version = "3.0" utilsInstance.metaClass.released = true assert utilsInstance.version == "3.0" assert utilsInstance.released == true Adding properties using MetaClass
  • 35.
    Adding properties usingMetaClass 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.
    class Utils { } defutilsInstance = new Utils() Utils.metaClass.version = "3.0" utilsInstance.metaClass.released = true assert utilsInstance.version == "3.0" assert utilsInstance.released == true Adding properties using MetaClass
  • 37.
    class Utils { } defutilsInstance = new Utils() Utils.metaClass.version = "3.0" utilsInstance.metaClass.released = true assert utilsInstance.version == "3.0" assert utilsInstance.released == true Adding properties using MetaClass
  • 38.
    // 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
  • 39.
    // 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
  • 40.
    // 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
  • 41.
    // 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
  • 42.
    // 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
  • 43.
    // 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
  • 44.
    Categories ▷ MetaClass changesare “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.
    class FileBinaryCategory { defstatic 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)
  • 50.
    class FileBinaryCategory { defstatic 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)
  • 51.
    class FileBinaryCategory { defstatic 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) File tmpFile = File.createTempFile('tmp_', '') use (FileBinaryCategory) { tmpFile << "http://groovy.codehaus.org/images/groovy-logo-medium.png".toURL() } println tmpFile // Execution /tmp/tmp_7428855173238452155
  • 52.
    class FileBinaryCategory { defstatic 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) File tmpFile = File.createTempFile('tmp_', '') use (FileBinaryCategory) { tmpFile << "http://groovy.codehaus.org/images/groovy-logo-medium.png".toURL() } println tmpFile // Execution /tmp/tmp_7428855173238452155
  • 53.
    class FileBinaryCategory { defstatic 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) File tmpFile = File.createTempFile('tmp_', '') use (FileBinaryCategory) { tmpFile << "http://groovy.codehaus.org/images/groovy-logo-medium.png".toURL() } println tmpFile // Execution /tmp/tmp_7428855173238452155
  • 54.
    class FileBinaryCategory { defstatic 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) File tmpFile = File.createTempFile('tmp_', '') use (FileBinaryCategory) { tmpFile << "http://groovy.codehaus.org/images/groovy-logo-medium.png".toURL() } println tmpFile // Execution /tmp/tmp_7428855173238452155
  • 55.
    Extension modules ▷ JARfile that provides extra methods ▷ Meta-information file ▷ Put jar in classpath to enhance classes
  • 56.
    // src/main/groovy/confess2015/StringUtilsExtension.groovy package confess2015 classStringUtilsExtension { static String truncate(String self, Integer length, Boolean overflow = false) { self.take(length) + (overflow ? '...' : '') } } package confess2015 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) # src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule moduleName = string-utils-module moduleVersion = 0.1 extensionClasses = confess2015.StringUtilsExtension Extension modules example
  • 57.
    Extension modules example //src/main/groovy/confess2015/StringUtilsExtension.groovy package confess2015 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 = confess2015.StringUtilsExtension package confess2015 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/confess2015/StringUtilsExtension.groovy package confess2015 class StringUtilsExtension { static String truncate(String self, Integer length, Boolean overflow = false) { self.take(length) + (overflow ? '...' : '') } } package confess2015 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) # src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule moduleName = string-utils-module moduleVersion = 0.1 extensionClasses = confess2015.StringUtilsExtension
  • 59.
    Extension modules example //src/main/groovy/confess2015/StringUtilsExtension.groovy package confess2015 class StringUtilsExtension { static String truncate(String self, Integer length, Boolean overflow = false) { self.take(length) + (overflow ? '...' : '') } } package confess2015 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) # src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule moduleName = string-utils-module moduleVersion = 0.1 extensionClasses = confess2015.StringUtilsExtension
  • 60.
    // src/main/groovy/confess2015/StringUtilsExtension.groovy package confess2015 classStringUtilsExtension { static String truncate(String self, Integer length, Boolean overflow = false) { self.take(length) + (overflow ? '...' : '') } } package confess2015 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/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule moduleName = string-utils-module moduleVersion = 0.1 extensionClasses = confess2015.StringUtilsExtension
  • 61.
    Mixins ▷ “Bring in”or “mix in” implementations from multiple classes ▷ Calls first routed to mixed-in class ▷ Last mixin wins ▷ Not easily un-done
  • 62.
    class SpidermanPower { StringspiderSense() { "Using spider-sense..." } } Mixins example @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..." } }
  • 63.
    @Mixin([SpidermanPower]) class Person {} defperson = 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..." } } class SpidermanPower { String spiderSense() { "Using spider-sense..." } } Mixins example
  • 64.
    @Mixin([SpidermanPower]) class Person {} defperson = 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..." } } class SpidermanPower { String spiderSense() { "Using spider-sense..." } } Mixins example
  • 65.
    @Mixin([SpidermanPower]) class Person {} defperson = 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..." } } class SpidermanPower { String spiderSense() { "Using spider-sense..." } } Mixins example
  • 66.
    @Mixin([SpidermanPower]) class Person {} defperson = new Person() assert person.spiderSense() == "Using spider-sense..." assert !(person instanceof SpidermanPower) Person.mixin SupermanPower assert person.fly() == "Flying..." assert !(person instanceof SupermanPower) class SpidermanPower { String spiderSense() { "Using spider-sense..." } } Mixins example class SupermanPower { String fly() { "Flying..." } }
  • 67.
    @Mixin([SpidermanPower]) class Person {} defperson = new Person() assert person.spiderSense() == "Using spider-sense..." assert !(person instanceof SpidermanPower) Person.mixin SupermanPower assert person.fly() == "Flying..." assert !(person instanceof SupermanPower) class SpidermanPower { String spiderSense() { "Using spider-sense..." } } Mixins example 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.
    class Person implementsSpidermanPower {} 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 Traits example trait SpidermanPower { String spiderSense() { "Using spider-sense..." } } trait SupermanPower { String fly() { "Flying..." } }
  • 70.
    class Person implementsSpidermanPower {} 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 Traits example trait SpidermanPower { String spiderSense() { "Using spider-sense..." } } trait SupermanPower { String fly() { "Flying..." } }
  • 71.
    class Person implementsSpidermanPower {} 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 Traits example trait SpidermanPower { String spiderSense() { "Using spider-sense..." } } trait SupermanPower { String fly() { "Flying..." } }
  • 72.
    class Person implementsSpidermanPower {} 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 Traits example trait SpidermanPower { String spiderSense() { "Using spider-sense..." } } trait SupermanPower { String fly() { "Flying..." } }
  • 73.
  • 74.
    MOP Method Synthesis ▷Dynamically figure out behaviour upon invocation ▷ It may not exist until it's called/executed ▷ “Intercept, Cache, Invoke” pattern
  • 75.
    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') 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?" } }
  • 76.
    def p =new Person(name: 'Iván', age: 35) assert p.respondsTo('sayHi') assert p.respondsTo('sayHiTo', String) assert !p.respondsTo('goodbye') assert p.hasProperty('age') assert !p.hasProperty('country') 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?" } }
  • 77.
    Check for methodsand 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: 35) assert p.respondsTo('sayHi') assert p.respondsTo('sayHiTo', String) assert !p.respondsTo('goodbye') assert p.hasProperty('age') assert !p.hasProperty('country')
  • 78.
    Check for methodsand 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: 35) 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 classChannel { 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 classChannel { 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 classChannel { 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 // Execution ...methodMissingcalled 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!' 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!")
  • 89.
    MethodMissing example // Execution ...methodMissingcalled 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!' 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!")
  • 90.
    MethodMissing example // Execution ...methodMissingcalled 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!' 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!")
  • 91.
    MethodMissing example // Execution ...methodMissingcalled 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!' 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!")
  • 92.
    MethodMissing example // Execution ...methodMissingcalled 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!' 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!")class EmailChannel extends Channel { void sendNewFollower(String username, String follower) {…} void sendNewMessage(String username, String msg) {…} } class MobilePushChannel extends Channel { void sendNewFollower(String username, String follower) {…} }
  • 93.
    MethodMissing example // Execution ...methodMissingcalled 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!' 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!")
  • 94.
    MethodMissing example // Execution ...methodMissingcalled 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!' 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!")
  • 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!'
  • 96.
  • 97.
    Compile-time metaprogramming ▷ Advancefeature ▷ 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.
  • 100.
    Global AST Transformations ▷No annotation ▷ Meta-information file ▷ Applied to all code during compilation ▷ Any compilation phase ▷ Grails uses intensively in GORM
  • 101.
  • 102.
    Local AST Transformations ▷Annotate code ▷ No meta-information file ▷ Easy to debug
  • 103.
    Steps to createlocal AST Interface AST Enjoy!
  • 104.
    Local AST example packageconfess2015 import ... @Retention(RetentionPolicy.SOURCE) @Target([ElementType.TYPE]) @GroovyASTTransformationClass("confess2015.VersionASTTransformation") @interface Version { String value() } class VersionedClass { public static final String VERSION = "1.0" } import confess2015.Version @Version('1.0') class VersionedClass { }
  • 105.
    Local AST example classVersionedClass { public static final String VERSION = "1.0" } package confess2015 import ... @Retention(RetentionPolicy.SOURCE) @Target([ElementType.TYPE]) @GroovyASTTransformationClass("confess2015.VersionASTTransformation") @interface Version { String value() } import confess2015.Version @Version('1.0') class VersionedClass { }
  • 106.
    Local AST example classVersionedClass { public static final String VERSION = "1.0" } package confess2015 import ... @Retention(RetentionPolicy.SOURCE) @Target([ElementType.TYPE]) @GroovyASTTransformationClass("confess2015.VersionASTTransformation") @interface Version { String value() } import confess2015.Version @Version('1.0') class VersionedClass { }
  • 107.
    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)) } } } }
  • 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 //Execute with: // gradle build // groovy -cp build/libs/add-version-1.0.jar LocalASTExample.groovy import confess.Version @Version('1.0') class VersionedClass { } println VersionedClass.VERSION // Execution 1.0
  • 110.
    Local AST example //Execute with: // gradle build // groovy -cp build/libs/add-version-1.0.jar LocalASTExample.groovy import confess.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 confess.Version @Version('1.0') class VersionedClass { } println VersionedClass.VERSION // Execution 1.0
  • 112.
    3. Why we shoulduse metaprogramming?
  • 113.
    Let’s review someconcepts Metaprogramming out-of-the box Easy and very powerful Write better code Add behaviour easily Take advantage of this power Because Groovy, it's groovy
  • 114.
    With great power comesgreat responsibility
  • 115.