confess vienna 2015 - metaprogramming with groovy

Post on 15-Jul-2015

455 Views

Category:

Technology

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

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 methodsusing 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 exampleclass 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}"

// ExecutionSetting 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 exampleclass 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}"

// ExecutionSetting 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 exampleclass 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}"

// ExecutionSetting 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 exampleclass 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}"

// ExecutionSetting 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 exampleclass 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}"

// ExecutionSetting 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)

// ExecutionInvoking 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)

// ExecutionInvoking 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)

// ExecutionInvoking 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)

// ExecutionInvoking 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()

// ExecutionInvoking 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()

// ExecutionInvoking 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()

// ExecutionInvoking 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()

// ExecutionInvoking 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()

// ExecutionInvoking 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)

// ExecutionIf you can see Chuck Norris, he can see you. If you can not see Chuck NoIf 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)

// ExecutionIf you can see Chuck Norris, he can see you. If you can not see Chuck NoIf 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)

// ExecutionIf you can see Chuck Norris, he can see you. If you can not see Chuck NoIf 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)

// ExecutionIf you can see Chuck Norris, he can see you. If you can not see Chuck NoIf 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)

// ExecutionIf you can see Chuck Norris, he can see you. If you can not see Chuck NoIf 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)

// ExecutionIf you can see Chuck Norris, he can see you. If you can not see Chuck NoIf 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

// Integerassert '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'

// Booleanassert false.toBoolean() == false

Boolean.metaClass.toBoolean = { !delegate }assert false.toBoolean() == true

Overriding methods using MetaClass

// Integerassert '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'

// Booleanassert false.toBoolean() == false

Boolean.metaClass.toBoolean = { !delegate }assert false.toBoolean() == true

Overriding methods using MetaClass

// Integerassert '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'

// Booleanassert false.toBoolean() == false

Boolean.metaClass.toBoolean = { !delegate }assert false.toBoolean() == true

Overriding methods using MetaClass

// Integerassert '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'

// Booleanassert false.toBoolean() == false

Boolean.metaClass.toBoolean = { !delegate }assert false.toBoolean() == true

Overriding methods using MetaClass

// Integerassert '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'

// Booleanassert false.toBoolean() == false

Boolean.metaClass.toBoolean = { !delegate }assert false.toBoolean() == true

Overriding methods using MetaClass

// Integerassert '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'

// Booleanassert 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 exampleclass 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}

// ExecutionLorem

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 exampleclass 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}

// ExecutionLorem

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 exampleclass 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}

// ExecutionLorem

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 exampleclass 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}

// ExecutionLorem

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.groovypackage confess2015class StringUtilsExtension { static String truncate(String self, Integer length, Boolean overflow = false) { self.take(length) + (overflow ? '...' : '') }}

package confess2015import spock.lang.Specificationclass 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.ExtensionModulemoduleName = string-utils-modulemoduleVersion = 0.1extensionClasses = confess2015.StringUtilsExtension

Extension modules example

Extension modules example// src/main/groovy/confess2015/StringUtilsExtension.groovypackage confess2015class 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.ExtensionModulemoduleName = string-utils-modulemoduleVersion = 0.1extensionClasses = confess2015.StringUtilsExtension

package confess2015import spock.lang.Specificationclass 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.groovypackage confess2015class StringUtilsExtension { static String truncate(String self, Integer length, Boolean overflow = false) { self.take(length) + (overflow ? '...' : '') }}

package confess2015import spock.lang.Specificationclass 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.ExtensionModulemoduleName = string-utils-modulemoduleVersion = 0.1extensionClasses = confess2015.StringUtilsExtension

Extension modules example// src/main/groovy/confess2015/StringUtilsExtension.groovypackage confess2015class StringUtilsExtension { static String truncate(String self, Integer length, Boolean overflow = false) { self.take(length) + (overflow ? '...' : '') }}

package confess2015import spock.lang.Specificationclass 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.ExtensionModulemoduleName = string-utils-modulemoduleVersion = 0.1extensionClasses = confess2015.StringUtilsExtension

// src/main/groovy/confess2015/StringUtilsExtension.groovypackage confess2015class StringUtilsExtension { static String truncate(String self, Integer length, Boolean overflow = false) { self.take(length) + (overflow ? '...' : '') }}

package confess2015import spock.lang.Specificationclass 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.ExtensionModulemoduleName = string-utils-modulemoduleVersion = 0.1extensionClasses = 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 SupermanPowerassert 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 SupermanPowerassert 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 SupermanPowerassert 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 SupermanPowerassert 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 SupermanPowerassert person.fly() == "Flying..."assert !(person instanceof SupermanPower)

class SpidermanPower { String spiderSense() { "Using spider-sense..." }}

Mixins exampleclass SupermanPower { String fly() { "Flying..." }}

@Mixin([SpidermanPower])class Person {}

def person = new Person()assert person.spiderSense() == "Using spider-sense..."assert !(person instanceof SpidermanPower)

Person.mixin SupermanPowerassert person.fly() == "Flying..."assert !(person instanceof SupermanPower)

class SpidermanPower { String spiderSense() { "Using spider-sense..." }}

Mixins exampleclass 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 SupermanPowerassert person2.fly() == "Flying..."assert person2 instanceof SupermanPower

Traits exampletrait 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 SupermanPowerassert person2.fly() == "Flying..."assert person2 instanceof SupermanPower

Traits exampletrait 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 SupermanPowerassert person2.fly() == "Flying..."assert person2 instanceof SupermanPower

Traits exampletrait 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 SupermanPowerassert person2.fly() == "Flying..."assert person2 instanceof SupermanPower

Traits exampletrait 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 exampleabstract 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 exampleabstract 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 exampleabstract 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 exampleclass 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 exampleclass 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 exampleclass 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 exampleclass 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 exampleclass 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 exampledef 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 ASTtransformations

Global AST Transformations

▷ No annotation

▷ Meta-information file

▷ Applied to all code during compilation

▷ Any compilation phase

▷ Grails uses intensively in GORM

Local ASTtransformations

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

// Execution1.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

// Execution1.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

// Execution1.0

3.Why we should usemetaprogramming?

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 powercomes great responsibility

Thanks!Any questions?

@ilopmar

lopez.ivan@gmail.com

https://github.com/lmivan

Iván López

http://kcy.me/208wq

top related