metaprogramming with groovy

99
Metaprogramming with Groovy Iván López - @ilopmar

Upload: gr8conf

Post on 16-Jan-2017

287 views

Category:

Technology


0 download

TRANSCRIPT

Page 1: Metaprogramming with Groovy

Metaprogrammingwith Groovy

Iván López - @ilopmar

Page 2: Metaprogramming with Groovy

Hello!I am Iván López

@ilopmar

http://greachconf.com@madridgug

Page 3: Metaprogramming with Groovy

Groovy is dynamic

▷ “Delay” to runtime some decisions

▷ Add properties/behaviours in runtime

▷ Wide range of applicability

Page 4: Metaprogramming with Groovy

What is metaprogramming?

Page 5: Metaprogramming with Groovy

“Metaprogramming is the writing of computer programs that write or manipulate other programs (or themselves) as their data.

- Wikipedia

Page 6: Metaprogramming with Groovy

1.Runtime metaprogramming

Page 7: Metaprogramming with Groovy

Runtime metaprogramming

▷ Groovy provides this through Meta-Object Protocol (MOP)

▷ Use MOP to:– Invoke methods dynamically– Synthesize classes and methods on

the fly

Page 8: Metaprogramming with Groovy

What is the Meta Object Protocol?

Groovy

Groovy

Java

Java

MOP

Page 9: Metaprogramming with Groovy

MetaClass▷ MetaClass registry for each class

▷ Collection of methods/properties

▷ We can always modify the metaclass

Page 10: Metaprogramming with Groovy

MOP method injection

Page 11: Metaprogramming with Groovy

MOP Method Injection▷ Injecting methods at code-writing time

▷ We can “open” a class any time

▷ Different techniques:– MetaClass– Categories– Extensions– Mixins vs Traits

Page 12: Metaprogramming with Groovy

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

Page 13: Metaprogramming with Groovy

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

Page 14: Metaprogramming with Groovy

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

Page 15: Metaprogramming with Groovy

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

Page 16: Metaprogramming with Groovy

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

Page 17: Metaprogramming with Groovy

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

Page 18: Metaprogramming with Groovy

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

Page 19: Metaprogramming with Groovy

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

Page 20: Metaprogramming with Groovy

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

Page 21: Metaprogramming with Groovy

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

Page 22: Metaprogramming with Groovy

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

Page 23: Metaprogramming with Groovy

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

Page 24: Metaprogramming with Groovy

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

Page 25: Metaprogramming with Groovy

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

Page 26: Metaprogramming with Groovy

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

Page 27: Metaprogramming with Groovy

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

Page 28: Metaprogramming with Groovy

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

Page 29: Metaprogramming with Groovy

Categories

▷ MetaClass changes are “persistent”

▷ Change metaclass in confined code

▷ MOP modified only in the closure

Page 30: Metaprogramming with Groovy

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)

Page 31: Metaprogramming with Groovy

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)

Page 32: Metaprogramming with Groovy

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)

Page 33: Metaprogramming with Groovy

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)

Page 34: Metaprogramming with Groovy

Categories example (II)

println TimeCategory.getHours(20).plus(TimeCategory.getDays(10).from.now)// Thu Jun 12 20:00:00 CET 2016

import groovy.time.TimeCategory

use (TimeCategory) { println (20.hours + 10.days.from.now) // Thu Jun 12 20:00:00 CET 2016}

Page 35: Metaprogramming with Groovy

Categories example (II)

println TimeCategory.getHours(20).plus(TimeCategory.getDays(10).from.now)// Thu Jun 12 20:00:00 CET 2016

import groovy.time.TimeCategory

use (TimeCategory) { println (20.hours + 10.days.from.now) // Thu Jun 12 20:00:00 CET 2016}

Page 36: Metaprogramming with Groovy

Categories example (II)

println TimeCategory.getHours(20).plus(TimeCategory.getDays(10).from.now)// Thu Jun 12 20:00:00 CET 2016

import groovy.time.TimeCategory

use (TimeCategory) { println (20.hours + 10.days.from.now) // Thu Jun 12 20:00:00 CET 2016}

Page 37: Metaprogramming with Groovy

Extension modules

▷ JAR file that provides extra methods

▷ Meta-information file

▷ Put jar in classpath to enhance classes

Page 38: Metaprogramming with Groovy

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

package demoimport 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 = demo.StringUtilsExtension

Extension modules example

Page 39: Metaprogramming with Groovy

Extension modules example// src/main/groovy/demo/StringUtilsExtension.groovypackage democlass 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 = demo.StringUtilsExtension

package demoimport 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)

Page 40: Metaprogramming with Groovy

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

package demoimport 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 = demo.StringUtilsExtension

Page 41: Metaprogramming with Groovy

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

package demoimport 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 = demo.StringUtilsExtension

Page 42: Metaprogramming with Groovy

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

package demoimport 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 = demo.StringUtilsExtension

Page 43: Metaprogramming with Groovy

Mixins

▷ “Bring in” or “mix in” implementations from multiple classes

▷ Calls first routed to mixed-in class

▷ Last mixin wins

▷ Not easily un-done

Page 44: Metaprogramming with Groovy

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..." }}

Page 45: Metaprogramming with Groovy

@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

Page 46: Metaprogramming with Groovy

@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

Page 47: Metaprogramming with Groovy

@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

Page 48: Metaprogramming with Groovy

@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..." }}

Page 49: Metaprogramming with Groovy

@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..." }}

Page 50: Metaprogramming with Groovy

“When we started fixing mixin bugs we didn't know if they were a bug or a feature, so we removed mixins and add traits.

- Jochen Theodorou(Greach 2015 Opening Keynote)

Page 51: Metaprogramming with Groovy

Traits▷ Groovy 2.3+

▷ Similar to Java 8 default methods

▷ Supported in JDK 6, 7 and 8

▷ Stateful

▷ Composition over inheritance

▷ Documentation

Page 52: Metaprogramming with Groovy

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..." }}

Page 53: Metaprogramming with Groovy

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..." }}

Page 54: Metaprogramming with Groovy

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..." }}

Page 55: Metaprogramming with Groovy

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..." }}

Page 56: Metaprogramming with Groovy

MOP method synthesis

Page 57: Metaprogramming with Groovy

MOP Method Synthesis

▷ Dynamically figure out behaviour upon invocation

▷ It may not exist until it's called/executed

▷ “Intercept, Cache, Invoke” pattern

Page 58: Metaprogramming with Groovy

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?" }}

Page 59: Metaprogramming with Groovy

def p = new Person(name: 'Iván', age: 36)

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?" }}

Page 60: Metaprogramming with Groovy

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: 36)

assert p.respondsTo('sayHi')assert p.respondsTo('sayHiTo', String)assert !p.respondsTo('goodbye')

assert p.hasProperty('age')assert !p.hasProperty('country')

Page 61: Metaprogramming with Groovy

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: 36)

assert p.respondsTo('sayHi')assert p.respondsTo('sayHiTo', String)assert !p.respondsTo('goodbye')

assert p.hasProperty('age')assert !p.hasProperty('country')

Page 62: Metaprogramming with Groovy

MethodMissing example

▷ Requirements:– Send notifications to users by different

channels– +50 notifications– Not all notifications by all channels– Extensible and open to future

modifications

Page 63: Metaprogramming with Groovy

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}'" }}

Page 64: Metaprogramming with Groovy

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}'" }}

Page 65: Metaprogramming with Groovy

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}'" }}

Page 66: Metaprogramming with Groovy

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}'" }}

Page 67: Metaprogramming with Groovy

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

Page 68: Metaprogramming with Groovy

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

Page 69: Metaprogramming with Groovy

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(...)

Page 70: Metaprogramming with Groovy

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

Page 71: Metaprogramming with Groovy

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

Page 72: Metaprogramming with Groovy

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!")

Page 73: Metaprogramming with Groovy

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!")

Page 74: Metaprogramming with Groovy

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!")

Page 75: Metaprogramming with Groovy

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!")

Page 76: Metaprogramming with Groovy

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) {…}}

Page 77: Metaprogramming with Groovy

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!")

Page 78: Metaprogramming with Groovy

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!")

Page 79: Metaprogramming with Groovy

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!'

Page 80: Metaprogramming with Groovy

2.Compile-time metaprogramming

Page 81: Metaprogramming with Groovy

Compile-time metaprogramming

▷ Advance feature

▷ Analyze/modify program structure at compile time

▷ Cross-cutting features

▷ Write code that generates bytecode

Page 82: Metaprogramming with Groovy

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

Page 83: Metaprogramming with Groovy

Global ASTtransformations

Page 84: Metaprogramming with Groovy

Global AST Transformations

▷ No annotation

▷ Meta-information file

▷ Applied to all code during compilation

▷ Any compilation phase

Page 85: Metaprogramming with Groovy

Local ASTtransformations

Page 86: Metaprogramming with Groovy

Local AST Transformations

▷ Annotate code

▷ No meta-information file

▷ Easy to debug

Page 87: Metaprogramming with Groovy

Steps to create local AST

Interface AST Enjoy!

Page 88: Metaprogramming with Groovy

Local AST example

package demo

import ...

@Retention(RetentionPolicy.SOURCE)@Target([ElementType.TYPE])@GroovyASTTransformationClass("demo.VersionASTTransformation")@interface Version { String value()}

class VersionedClass { public static final String VERSION = "1.0"}

import demo.Version

@Version('1.0')class VersionedClass {}

Page 89: Metaprogramming with Groovy

Local AST example

class VersionedClass { public static final String VERSION = "1.0"}

package demo

import ...

@Retention(RetentionPolicy.SOURCE)@Target([ElementType.TYPE])@GroovyASTTransformationClass("demo.VersionASTTransformation")@interface Version { String value()}

import demo.Version

@Version('1.0')class VersionedClass {}

Page 90: Metaprogramming with Groovy

Local AST example

class VersionedClass { public static final String VERSION = "1.0"}

package demo

import ...

@Retention(RetentionPolicy.SOURCE)@Target([ElementType.TYPE])@GroovyASTTransformationClass("demo.VersionASTTransformation")@interface Version { String value()}

import demo.Version

@Version('1.0')class VersionedClass {}

Page 91: Metaprogramming with Groovy

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

Page 92: Metaprogramming with Groovy

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

Page 93: Metaprogramming with Groovy

Local AST example// Execute with:// gradle build// groovy -cp build/libs/add-version-1.0.jar LocalASTExample.groovy

import demo.Version

@Version('1.0')class VersionedClass {}

println VersionedClass.VERSION

// Execution1.0

Page 94: Metaprogramming with Groovy

Local AST example// Execute with:// gradle build// groovy -cp build/libs/add-version-1.0.jar LocalASTExample.groovy

import demo.Version

@Version('1.0')class VersionedClass {}

println VersionedClass.VERSION

// Execution1.0

Page 95: Metaprogramming with Groovy

Local AST example// Execute with:// gradle build// groovy -cp build/libs/add-version-1.0.jar LocalASTExample.groovy

import demo.Version

@Version('1.0')class VersionedClass {}

println VersionedClass.VERSION

// Execution1.0

Page 96: Metaprogramming with Groovy

3.Why we should usemetaprogramming?

Page 97: Metaprogramming with Groovy

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

Page 98: Metaprogramming with Groovy

With great powercomes great responsibility

Page 99: Metaprogramming with Groovy

Thanks!Any questions?

@ilopmar

[email protected]

https://github.com/ilopmar

Iván López

http://bit.ly/metaprog-groovy