The document discusses various metaprogramming techniques in Groovy including runtime metaprogramming using the Meta Object Protocol (MOP), method injection using MOP, adding/overriding methods and properties using MetaClass, and using categories and extension modules to extend classes in a confined manner. It provides examples of adding a truncate method to strings using each technique.
7. Runtime metaprogramming
▷ Groovy provides this through Meta-
Object Protocol (MOP)
▷ Use MOP to:
– Invoke methods dynamically
– Synthesize classes and methods on
the fly
8. What is the Meta Object Protocol?
Groovy
Groovy
Java
Java
MOP
11. MOP Method Injection
▷ Injecting methods at code-writing time
▷ We can “open” a class any time
▷ Different techniques:
– MetaClass
– Categories
– Extensions
– Mixins vs Traits
12. class StringUtils {
static String truncate(String text, Integer length, Boolean overflow = false) {
text.take(length) + (overflow ? '...' : '')
}
}
String chuckIpsum = "If you can see Chuck Norris, he can see you.
If you can not see Chuck Norris you may be only seconds away from death"
println StringUtils.truncate(chuckIpsum, 72)
println StringUtils.truncate(chuckIpsum, 72, true)
// Execution
If you can see Chuck Norris, he can see you. If you can not see Chuck No
If you can see Chuck Norris, he can see you. If you can not see Chuck No...
String.metaClass.truncate = { Integer length, Boolean overflow = false ->
delegate.take(length) + (overflow ? '...' : '')
}
assert chuckIpsum.truncate(72, true) == StringUtils.truncate(chuckIpsum, 72, true)
Adding methods using MetaClass
13. class StringUtils {
static String truncate(String text, Integer length, Boolean overflow = false) {
text.take(length) + (overflow ? '...' : '')
}
}
String chuckIpsum = "If you can see Chuck Norris, he can see you.
If you can not see Chuck Norris you may be only seconds away from death"
println StringUtils.truncate(chuckIpsum, 72)
println StringUtils.truncate(chuckIpsum, 72, true)
// Execution
If you can see Chuck Norris, he can see you. If you can not see Chuck No
If you can see Chuck Norris, he can see you. If you can not see Chuck No...
String.metaClass.truncate = { Integer length, Boolean overflow = false ->
delegate.take(length) + (overflow ? '...' : '')
}
assert chuckIpsum.truncate(72, true) == StringUtils.truncate(chuckIpsum, 72, true)
Adding methods using MetaClass
14. class StringUtils {
static String truncate(String text, Integer length, Boolean overflow = false) {
text.take(length) + (overflow ? '...' : '')
}
}
String chuckIpsum = "If you can see Chuck Norris, he can see you.
If you can not see Chuck Norris you may be only seconds away from death"
println StringUtils.truncate(chuckIpsum, 72)
println StringUtils.truncate(chuckIpsum, 72, true)
// Execution
If you can see Chuck Norris, he can see you. If you can not see Chuck No
If you can see Chuck Norris, he can see you. If you can not see Chuck No...
String.metaClass.truncate = { Integer length, Boolean overflow = false ->
delegate.take(length) + (overflow ? '...' : '')
}
assert chuckIpsum.truncate(72, true) == StringUtils.truncate(chuckIpsum, 72, true)
Adding methods using MetaClass
15. class StringUtils {
static String truncate(String text, Integer length, Boolean overflow = false) {
text.take(length) + (overflow ? '...' : '')
}
}
String chuckIpsum = "If you can see Chuck Norris, he can see you.
If you can not see Chuck Norris you may be only seconds away from death"
println StringUtils.truncate(chuckIpsum, 72)
println StringUtils.truncate(chuckIpsum, 72, true)
// Execution
If you can see Chuck Norris, he can see you. If you can not see Chuck No
If you can see Chuck Norris, he can see you. If you can not see Chuck No...
String.metaClass.truncate = { Integer length, Boolean overflow = false ->
delegate.take(length) + (overflow ? '...' : '')
}
assert chuckIpsum.truncate(72, true) == StringUtils.truncate(chuckIpsum, 72, true)
Adding methods using MetaClass
16. class StringUtils {
static String truncate(String text, Integer length, Boolean overflow = false) {
text.take(length) + (overflow ? '...' : '')
}
}
String chuckIpsum = "If you can see Chuck Norris, he can see you.
If you can not see Chuck Norris you may be only seconds away from death"
println StringUtils.truncate(chuckIpsum, 72)
println StringUtils.truncate(chuckIpsum, 72, true)
// Execution
If you can see Chuck Norris, he can see you. If you can not see Chuck No
If you can see Chuck Norris, he can see you. If you can not see Chuck No...
String.metaClass.truncate = { Integer length, Boolean overflow = false ->
delegate.take(length) + (overflow ? '...' : '')
}
assert chuckIpsum.truncate(72, true) == StringUtils.truncate(chuckIpsum, 72, true)
Adding methods using MetaClass
17. class StringUtils {
static String truncate(String text, Integer length, Boolean overflow = false) {
text.take(length) + (overflow ? '...' : '')
}
}
String chuckIpsum = "If you can see Chuck Norris, he can see you.
If you can not see Chuck Norris you may be only seconds away from death"
println StringUtils.truncate(chuckIpsum, 72)
println StringUtils.truncate(chuckIpsum, 72, true)
// Execution
If you can see Chuck Norris, he can see you. If you can not see Chuck No
If you can see Chuck Norris, he can see you. If you can not see Chuck No...
String.metaClass.truncate = { Integer length, Boolean overflow = false ->
delegate.take(length) + (overflow ? '...' : '')
}
assert chuckIpsum.truncate(72, true) == StringUtils.truncate(chuckIpsum, 72, true)
Adding methods using MetaClass
18. 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
19. 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
20. 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
21. 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
22. // Integer
assert '42' == 42.toString()
Integer.metaClass.toString = {
delegate == 42 ?
'The answer to life, the universe and everything' :
String.valueOf(delegate)
}
assert 42.toString() == 'The answer to life, the universe and everything'
assert 100.toString() == '100'
// Boolean
assert false.toBoolean() == false
Boolean.metaClass.toBoolean = { !delegate }
assert false.toBoolean() == true
Overriding methods using MetaClass
23. // Integer
assert '42' == 42.toString()
Integer.metaClass.toString = {
delegate == 42 ?
'The answer to life, the universe and everything' :
String.valueOf(delegate)
}
assert 42.toString() == 'The answer to life, the universe and everything'
assert 100.toString() == '100'
// Boolean
assert false.toBoolean() == false
Boolean.metaClass.toBoolean = { !delegate }
assert false.toBoolean() == true
Overriding methods using MetaClass
24. // Integer
assert '42' == 42.toString()
Integer.metaClass.toString = {
delegate == 42 ?
'The answer to life, the universe and everything' :
String.valueOf(delegate)
}
assert 42.toString() == 'The answer to life, the universe and everything'
assert 100.toString() == '100'
// Boolean
assert false.toBoolean() == false
Boolean.metaClass.toBoolean = { !delegate }
assert false.toBoolean() == true
Overriding methods using MetaClass
25. // Integer
assert '42' == 42.toString()
Integer.metaClass.toString = {
delegate == 42 ?
'The answer to life, the universe and everything' :
String.valueOf(delegate)
}
assert 42.toString() == 'The answer to life, the universe and everything'
assert 100.toString() == '100'
// Boolean
assert false.toBoolean() == false
Boolean.metaClass.toBoolean = { !delegate }
assert false.toBoolean() == true
Overriding methods using MetaClass
26. // Integer
assert '42' == 42.toString()
Integer.metaClass.toString = {
delegate == 42 ?
'The answer to life, the universe and everything' :
String.valueOf(delegate)
}
assert 42.toString() == 'The answer to life, the universe and everything'
assert 100.toString() == '100'
// Boolean
assert false.toBoolean() == false
Boolean.metaClass.toBoolean = { !delegate }
assert false.toBoolean() == true
Overriding methods using MetaClass
27. // Integer
assert '42' == 42.toString()
Integer.metaClass.toString = {
delegate == 42 ?
'The answer to life, the universe and everything' :
String.valueOf(delegate)
}
assert 42.toString() == 'The answer to life, the universe and everything'
assert 100.toString() == '100'
// Boolean
assert false.toBoolean() == false
Boolean.metaClass.toBoolean = { !delegate }
assert false.toBoolean() == true
Overriding methods using MetaClass
29. Categories example
class StringUtils {
static String truncate(String text, Integer length, Boolean overflow = false) {
text.take(length) + (overflow ? '...' : '')
}
}
use (StringUtils) {
println "Lorem ipsum".truncate(5)
}
try {
println "Lorem ipsum".truncate(5)
} catch (MissingMethodException mme) {
println mme
}
// Execution
Lorem
groovy.lang.MissingMethodException: No
signature of method:
java.lang.String.truncate() is
applicable for argument types:
(java.lang.Integer) values: [5]
Possible solutions:
concat(java.lang.String), take(int)
30. Categories example
class StringUtils {
static String truncate(String text, Integer length, Boolean overflow = false) {
text.take(length) + (overflow ? '...' : '')
}
}
use (StringUtils) {
println "Lorem ipsum".truncate(5)
}
try {
println "Lorem ipsum".truncate(5)
} catch (MissingMethodException mme) {
println mme
}
// Execution
Lorem
groovy.lang.MissingMethodException: No
signature of method:
java.lang.String.truncate() is
applicable for argument types:
(java.lang.Integer) values: [5]
Possible solutions:
concat(java.lang.String), take(int)
31. Categories example
class StringUtils {
static String truncate(String text, Integer length, Boolean overflow = false) {
text.take(length) + (overflow ? '...' : '')
}
}
use (StringUtils) {
println "Lorem ipsum".truncate(5)
}
try {
println "Lorem ipsum".truncate(5)
} catch (MissingMethodException mme) {
println mme
}
// Execution
Lorem
groovy.lang.MissingMethodException: No
signature of method:
java.lang.String.truncate() is
applicable for argument types:
(java.lang.Integer) values: [5]
Possible solutions:
concat(java.lang.String), take(int)
32. Categories example
class StringUtils {
static String truncate(String text, Integer length, Boolean overflow = false) {
text.take(length) + (overflow ? '...' : '')
}
}
use (StringUtils) {
println "Lorem ipsum".truncate(5)
}
try {
println "Lorem ipsum".truncate(5)
} catch (MissingMethodException mme) {
println mme
}
// Execution
Lorem
groovy.lang.MissingMethodException: No
signature of method:
java.lang.String.truncate() is
applicable for argument types:
(java.lang.Integer) values: [5]
Possible solutions:
concat(java.lang.String), take(int)
33. Categories example (II)
import groovy.time.TimeCategory
use (TimeCategory) {
println (20.hours + 10.days.from.now)
// Tue Feb 23 20:00:00 CET 2016
}
println TimeCategory.getHours(20).plus(TimeCategory.getDays(10).from.now)
// Tue Feb 23 20:00:00 CET 2016
34. Categories example (II)
import groovy.time.TimeCategory
use (TimeCategory) {
println (20.hours + 10.days.from.now)
// Tue Feb 23 20:00:00 CET 2016
}
println TimeCategory.getHours(20).plus(TimeCategory.getDays(10).from.now)
// Tue Feb 23 20:00:00 CET 2016
35. Extension modules
▷ JAR file that provides extra methods
▷ Meta-information file
▷ Put jar in classpath to enhance classes
41. Mixins
▷ “Bring in” or “mix in” implementations
from multiple classes
▷ Calls first routed to mixed-in class
▷ Last mixin wins
▷ Not easily un-done
42. class SpidermanPower {
String spiderSense() {
"Using spider-sense..."
}
}
Mixins example
@Mixin([SpidermanPower])
class Person {}
def person = new Person()
assert person.spiderSense() == "Using spider-sense..."
assert !(person instanceof SpidermanPower)
Person.mixin SupermanPower
assert person.fly() == "Flying..."
assert !(person instanceof SupermanPower)
class SupermanPower {
String fly() {
"Flying..."
}
}
43. @Mixin([SpidermanPower])
class Person {}
def person = new Person()
assert person.spiderSense() == "Using spider-sense..."
assert !(person instanceof SpidermanPower)
Person.mixin SupermanPower
assert person.fly() == "Flying..."
assert !(person instanceof SupermanPower)
class SupermanPower {
String fly() {
"Flying..."
}
}
class SpidermanPower {
String spiderSense() {
"Using spider-sense..."
}
}
Mixins example
44. @Mixin([SpidermanPower])
class Person {}
def person = new Person()
assert person.spiderSense() == "Using spider-sense..."
assert !(person instanceof SpidermanPower)
Person.mixin SupermanPower
assert person.fly() == "Flying..."
assert !(person instanceof SupermanPower)
class SupermanPower {
String fly() {
"Flying..."
}
}
class SpidermanPower {
String spiderSense() {
"Using spider-sense..."
}
}
Mixins example
45. @Mixin([SpidermanPower])
class Person {}
def person = new Person()
assert person.spiderSense() == "Using spider-sense..."
assert !(person instanceof SpidermanPower)
Person.mixin SupermanPower
assert person.fly() == "Flying..."
assert !(person instanceof SupermanPower)
class SupermanPower {
String fly() {
"Flying..."
}
}
class SpidermanPower {
String spiderSense() {
"Using spider-sense..."
}
}
Mixins example
46. @Mixin([SpidermanPower])
class Person {}
def person = new Person()
assert person.spiderSense() == "Using spider-sense..."
assert !(person instanceof SpidermanPower)
Person.mixin SupermanPower
assert person.fly() == "Flying..."
assert !(person instanceof SupermanPower)
class SpidermanPower {
String spiderSense() {
"Using spider-sense..."
}
}
Mixins example
class SupermanPower {
String fly() {
"Flying..."
}
}
47. @Mixin([SpidermanPower])
class Person {}
def person = new Person()
assert person.spiderSense() == "Using spider-sense..."
assert !(person instanceof SpidermanPower)
Person.mixin SupermanPower
assert person.fly() == "Flying..."
assert !(person instanceof SupermanPower)
class SpidermanPower {
String spiderSense() {
"Using spider-sense..."
}
}
Mixins example
class SupermanPower {
String fly() {
"Flying..."
}
}
48. “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)
49. Traits
▷ Groovy 2.3+
▷ Similar to Java 8 default methods
▷ Supported in JDK 6, 7 and 8
▷ Stateful
▷ Composition over inheritance
▷ Documentation
50. class Person implements SpidermanPower {}
def person = new Person()
assert person.spiderSense() == "Using spider-sense..."
assert person instanceof SpidermanPower
def person2 = person.withTraits SupermanPower
assert person2.fly() == "Flying..."
assert person2 instanceof SupermanPower
Traits example
trait SpidermanPower {
String spiderSense() {
"Using spider-sense..."
}
}
trait SupermanPower {
String fly() {
"Flying..."
}
}
51. class Person implements SpidermanPower {}
def person = new Person()
assert person.spiderSense() == "Using spider-sense..."
assert person instanceof SpidermanPower
def person2 = person.withTraits SupermanPower
assert person2.fly() == "Flying..."
assert person2 instanceof SupermanPower
Traits example
trait SpidermanPower {
String spiderSense() {
"Using spider-sense..."
}
}
trait SupermanPower {
String fly() {
"Flying..."
}
}
52. class Person implements SpidermanPower {}
def person = new Person()
assert person.spiderSense() == "Using spider-sense..."
assert person instanceof SpidermanPower
def person2 = person.withTraits SupermanPower
assert person2.fly() == "Flying..."
assert person2 instanceof SupermanPower
Traits example
trait SpidermanPower {
String spiderSense() {
"Using spider-sense..."
}
}
trait SupermanPower {
String fly() {
"Flying..."
}
}
53. class Person implements SpidermanPower {}
def person = new Person()
assert person.spiderSense() == "Using spider-sense..."
assert person instanceof SpidermanPower
def person2 = person.withTraits SupermanPower
assert person2.fly() == "Flying..."
assert person2 instanceof SupermanPower
Traits example
trait SpidermanPower {
String spiderSense() {
"Using spider-sense..."
}
}
trait SupermanPower {
String fly() {
"Flying..."
}
}
55. MOP Method Synthesis
▷ Dynamically figure out behaviour upon
invocation
▷ It may not exist until it's called/executed
▷ “Intercept, Cache, Invoke” pattern
56. 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?"
}
}
57. 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?"
}
}
58. 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')
59. 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')
60. MethodMissing example
▷ Requirements:
– Send notifications to users by different
channels
– +50 notifications
– Not all notifications by all channels
– Extensible and open to future
modifications
61. MethodMissing example
abstract class Channel {
void sendNewFollower(String username, String follower) { }
void sendNewMessage(String username, String msg) { }
...
}
class EmailChannel extends Channel {
void sendNewFollower(String username, String follower) {
println "Sending email notification to '${username}' for new follower '${follower}'"
}
void sendNewMessage(String username, String msg) {
println "Sending email notification to '${username}' for new message '${msg}'"
}
}
class MobilePushChannel extends Channel {
void sendNewFollower(String username, String follower) {
println "Sending mobile push notification to '${username}' for new follower '${follower}'"
}
}
62. MethodMissing example
abstract class Channel {
void sendNewFollower(String username, String follower) { }
void sendNewMessage(String username, String msg) { }
...
}
class EmailChannel extends Channel {
void sendNewFollower(String username, String follower) {
println "Sending email notification to '${username}' for new follower '${follower}'"
}
void sendNewMessage(String username, String msg) {
println "Sending email notification to '${username}' for new message '${msg}'"
}
}
class MobilePushChannel extends Channel {
void sendNewFollower(String username, String follower) {
println "Sending mobile push notification to '${username}' for new follower '${follower}'"
}
}
63. MethodMissing example
abstract class Channel {
void sendNewFollower(String username, String follower) { }
void sendNewMessage(String username, String msg) { }
...
}
class EmailChannel extends Channel {
void sendNewFollower(String username, String follower) {
println "Sending email notification to '${username}' for new follower '${follower}'"
}
void sendNewMessage(String username, String msg) {
println "Sending email notification to '${username}' for new message '${msg}'"
}
}
class MobilePushChannel extends Channel {
void sendNewFollower(String username, String follower) {
println "Sending mobile push notification to '${username}' for new follower '${follower}'"
}
}
64. MethodMissing example
abstract class Channel {
void sendNewFollower(String username, String follower) { }
void sendNewMessage(String username, String msg) { }
...
}
class EmailChannel extends Channel {
void sendNewFollower(String username, String follower) {
println "Sending email notification to '${username}' for new follower '${follower}'"
}
void sendNewMessage(String username, String msg) {
println "Sending email notification to '${username}' for new message '${msg}'"
}
}
class MobilePushChannel extends Channel {
void sendNewFollower(String username, String follower) {
println "Sending mobile push notification to '${username}' for new follower '${follower}'"
}
}
65. MethodMissing example
class NotificationService {
List channels = []
def methodMissing(String name, args) {
System.out.println "...methodMissing called for ${name} with args ${args}"
// Generate the implementation
def implementation = { Object[] methodArgs ->
channels.each { channel ->
def metaMethod = channel.metaClass.getMetaMethod(name, methodArgs)
return metaMethod.invoke(channel, methodArgs)
}
}
// Cache the implementation in the metaClass
NotificationService instance = this
instance.metaClass."$name" = implementation
// Execute it!
implementation(args)
}
}
66. MethodMissing example
class NotificationService {
List channels = []
def methodMissing(String name, args) {
System.out.println "...methodMissing called for ${name} with args ${args}"
// Generate the implementation
def implementation = { Object[] methodArgs ->
channels.each { channel ->
def metaMethod = channel.metaClass.getMetaMethod(name, methodArgs)
return metaMethod.invoke(channel, methodArgs)
}
}
// Cache the implementation in the metaClass
NotificationService instance = this
instance.metaClass."$name" = implementation
// Execute it!
implementation(args)
}
}
67. MethodMissing example
class NotificationService {
List channels = []
def methodMissing(String name, args) {
System.out.println "...methodMissing called for ${name} with args ${args}"
// Generate the implementation
def implementation = { Object[] methodArgs ->
channels.each { channel ->
def metaMethod = channel.metaClass.getMetaMethod(name, methodArgs)
return metaMethod.invoke(channel, methodArgs)
}
}
// Cache the implementation in the metaClass
NotificationService instance = this
instance.metaClass."$name" = implementation
// Execute it!
implementation(args)
}
}
notificationService.sendNewFollower(...)
notificationService.sendNewMessage(...)
68. MethodMissing example
class NotificationService {
List channels = []
def methodMissing(String name, args) {
System.out.println "...methodMissing called for ${name} with args ${args}"
// Generate the implementation
def implementation = { Object[] methodArgs ->
channels.each { channel ->
def metaMethod = channel.metaClass.getMetaMethod(name, methodArgs)
return metaMethod.invoke(channel, methodArgs)
}
}
// Cache the implementation in the metaClass
NotificationService instance = this
instance.metaClass."$name" = implementation
// Execute it!
implementation(args)
}
}
69. MethodMissing example
class NotificationService {
List channels = []
def methodMissing(String name, args) {
System.out.println "...methodMissing called for ${name} with args ${args}"
// Generate the implementation
def implementation = { Object[] methodArgs ->
channels.each { channel ->
def metaMethod = channel.metaClass.getMetaMethod(name, methodArgs)
return metaMethod.invoke(channel, methodArgs)
}
}
// Cache the implementation in the metaClass
NotificationService instance = this
instance.metaClass."$name" = implementation
// Execute it!
implementation(args)
}
}
70. 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!")
71. 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!")
72. 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!")
73. 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!")
74. 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) {…}
}
75. 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!")
76. 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!")
77. MethodMissing example
def notificationService = new NotificationService(
channels: [new EmailChannel(), new MobilePushChannel()]
)
assert !notificationService.respondsTo('sendNewFollower', String, String)
notificationService.sendNewFollower("John", "Peter")
assert notificationService.respondsTo('sendNewFollower', String, String)
notificationService.sendNewFollower("Mary", "Steve")
notificationService.sendNewMessage("Iván", "Hello!")
// Execution
...methodMissing called for sendNewFollower with args [John, Peter]
Sending email notification to 'John' for new follower 'Peter'
Sending mobile push notification to 'John' for new follower 'Peter'
Sending email notification to 'Mary' for new follower 'Steve'
Sending mobile push notification to 'Mary' for new follower 'Steve'
...methodMissing called for sendNewMessage with args [Iván, Hello!]
Sending email notification to 'Iván' for new message 'Hello!'
79. Compile-time metaprogramming
▷ Advance feature
▷ Analyze/modify program structure at
compile time
▷ Cross-cutting features
▷ Write code that generates bytecode
80. 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
82. Global AST Transformations
▷ No annotation
▷ Meta-information file
▷ Applied to all code during compilation
▷ Any compilation phase
▷ Grails uses intensively in GORM
86. 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 {
}
87. 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 {
}
88. 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 {
}
89. 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))
}
}
}
}
90. 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))
}
}
}
}
91. 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
// Execution
1.0
92. 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
// Execution
1.0
93. 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
// Execution
1.0
95. 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