116
METAPROGRAMMING WITH GROOVY Iván López @ilopmar

GeeCON Prague 2014 - Metaprogramming with Groovy

Embed Size (px)

DESCRIPTION

Slides from my GeeCON 2014 Prague talk: "Groovy is a dynamic language that provides different types of metaprogramming techniques. In this talk we’ll mainly see runtime metaprogramming. I’ll explain Groovy Meta-Object-Protocol (MOP), the metaclass, how to intercept method calls, how to deal with method missing and property missing, the use of mixins, traits and categories. All of these topics will be explained with examples in order to understand them. Also, I’ll talk a little bit about compile-time metaprogramming with AST Transformations. AST Transformations provide a wonderful way of manipulating code at compile time via modifications of the Abstract Syntax Tree. We’ll see a basic but powerful example of what we can do with AST transformations." The code is available at: https://github.com/lmivan/geecon2014-prague-metaprograming-with-groovy

Citation preview

  • 1. Ivn Lpez @ilopmar METAPROGRAMMING WITH GROOVY
  • 2. Hello! I am Ivn Lpez @ilopmar
  • 3. Groovy is dynamic Delay to runtime some decisions Add properties/behaviours in runtime Wide range of applicability
  • 4. What is metaprogramming?
  • 5. Metaprogramming is the writing of computer programs that write or manipulate other programs (or themselves) as their data. - Wikipedia
  • 6. 1. Runtime metaprogramming
  • 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
  • 9. Intercepting methods using MOP
  • 10. Groovy Interceptable GroovyObject interface Implement GroovyInterceptable to hook into the execution public interface GroovyObject { Object invokeMethod(String name, Object args) Object getProperty(String propertyName) void setProperty(String propertyName, Object newValue) MetaClass getMetaClass() void setMetaClass(MetaClass metaClass) }
  • 11. GroovyInterceptable example class Person implements GroovyInterceptable { String name Integer age public Object getProperty(String propertyName) { println "Getting property '${propertyName}'" return this.@"${propertyName}" } public void setProperty(String propertyName, Object newValue) { println "Setting property '${propertyName}' with value '${newValue}'" this.@"${propertyName}" = newValue } // Execution Setting property 'name' with value 'Ivn' Setting property 'age' with value '34' Getting property 'name' Getting property 'age' Hello Ivn, you're 34 } def person = new Person() person.name = "Ivn" person.age = 34 println "Hello ${person.name}, you're ${person.age}"
  • 12. GroovyInterceptable example class Person implements GroovyInterceptable { String name Integer age public Object getProperty(String propertyName) { println "Getting property '${propertyName}'" return this.@"${propertyName}" } public void setProperty(String propertyName, Object newValue) { println "Setting property '${propertyName}' with value '${newValue}'" this.@"${propertyName}" = newValue } // Execution Setting property 'name' with value 'Ivn' Setting property 'age' with value '34' Getting property 'name' Getting property 'age' Hello Ivn, you're 34 } def person = new Person() person.name = "Ivn" person.age = 34 println "Hello ${person.name}, you're ${person.age}"
  • 13. GroovyInterceptable example class Person implements GroovyInterceptable { String name Integer age public Object getProperty(String propertyName) { println "Getting property '${propertyName}'" return this.@"${propertyName}" } public void setProperty(String propertyName, Object newValue) { println "Setting property '${propertyName}' with value '${newValue}'" this.@"${propertyName}" = newValue } // Execution Setting property 'name' with value 'Ivn' Setting property 'age' with value '34' Getting property 'name' Getting property 'age' Hello Ivn, you're 34 } def person = new Person() person.name = "Ivn" person.age = 34 println "Hello ${person.name}, you're ${person.age}"
  • 14. GroovyInterceptable example class Person implements GroovyInterceptable { String name Integer age public Object getProperty(String propertyName) { println "Getting property '${propertyName}'" return this.@"${propertyName}" } public void setProperty(String propertyName, Object newValue) { println "Setting property '${propertyName}' with value '${newValue}'" this.@"${propertyName}" = newValue } // Execution Setting property 'name' with value 'Ivn' Setting property 'age' with value '34' Getting property 'name' Getting property 'age' Hello Ivn, you're 34 } def person = new Person() person.name = "Ivn" person.age = 34 println "Hello ${person.name}, you're ${person.age}"
  • 15. GroovyInterceptable example class Person implements GroovyInterceptable { String name Integer age public Object getProperty(String propertyName) { println "Getting property '${propertyName}'" return this.@"${propertyName}" } public void setProperty(String propertyName, Object newValue) { println "Setting property '${propertyName}' with value '${newValue}'" this.@"${propertyName}" = newValue } // Execution Setting property 'name' with value 'Ivn' Setting property 'age' with value '34' Getting property 'name' Getting property 'age' Hello Ivn, you're 34 } def person = new Person() person.name = "Ivn" person.age = 34 println "Hello ${person.name}, you're ${person.age}"
  • 16. GroovyInterceptable example (II) class Hello implements GroovyInterceptable { public Object invokeMethod(String methodName, Object args) { System.out.println "Invoking method '${methodName}' with args '${args}'" def method = metaClass.getMetaMethod(methodName, args) method?.invoke(this, args) } void sayHi(String name) { System.out.println "Hello ${name}" } } def hello = new Hello() hello.sayHi("GeeCon Prague!") hello.anotherMethod() // Execution Invoking method 'sayHi' with args '[GeeCon Prague!]' Hello GeeCon Prague! Invoking method 'anotherMethod' with args '[]'
  • 17. GroovyInterceptable example (II) class Hello implements GroovyInterceptable { public Object invokeMethod(String methodName, Object args) { System.out.println "Invoking method '${methodName}' with args '${args}'" def method = metaClass.getMetaMethod(methodName, args) method?.invoke(this, args) } void sayHi(String name) { System.out.println "Hello ${name}" } } def hello = new Hello() hello.sayHi("GeeCon Prague!") hello.anotherMethod() // Execution Invoking method 'sayHi' with args '[GeeCon Prague!]' Hello GeeCon Prague! Invoking method 'anotherMethod' with args '[]'
  • 18. GroovyInterceptable example (II) class Hello implements GroovyInterceptable { public Object invokeMethod(String methodName, Object args) { System.out.println "Invoking method '${methodName}' with args '${args}'" def method = metaClass.getMetaMethod(methodName, args) method?.invoke(this, args) } void sayHi(String name) { System.out.println "Hello ${name}" } } def hello = new Hello() hello.sayHi("GeeCon Prague!") hello.anotherMethod() // Execution Invoking method 'sayHi' with args '[GeeCon Prague!]' Hello GeeCon Prague! Invoking method 'anotherMethod' with args '[]'
  • 19. GroovyInterceptable example (II) class Hello implements GroovyInterceptable { public Object invokeMethod(String methodName, Object args) { System.out.println "Invoking method '${methodName}' with args '${args}'" def method = metaClass.getMetaMethod(methodName, args) method?.invoke(this, args) } void sayHi(String name) { System.out.println "Hello ${name}" } } def hello = new Hello() hello.sayHi("GeeCon Prague!") hello.anotherMethod() // Execution Invoking method 'sayHi' with args '[GeeCon Prague!]' Hello GeeCon Prague! Invoking method 'anotherMethod' with args '[]'
  • 20. MetaClass MetaClass registry for each class Collection of methods/properties We can always modify the metaclass Intercept methods implementing invokeMethod on metaclass
  • 21. MetaClass example class Hello { void sayHi(String name) { println "Hello ${name}" } } Hello.metaClass.invokeMethod = { String methodName, args -> println "Invoking method '${methodName}' with args '${args}'" def method = Hello.metaClass.getMetaMethod(methodName, args) method?.invoke(delegate, args) } def hello = new Hello() hello.sayHi("GeeCon Prague!") hello.anotherMethod() // Execution Invoking method 'sayHi' with args '[GeeCon Prague!]' Hello GeeCon Prague! Invoking method 'anotherMethod' with args '[]'
  • 22. MetaClass example class Hello { void sayHi(String name) { println "Hello ${name}" } } Hello.metaClass.invokeMethod = { String methodName, args -> println "Invoking method '${methodName}' with args '${args}'" def method = Hello.metaClass.getMetaMethod(methodName, args) method?.invoke(delegate, args) } def hello = new Hello() hello.sayHi("GeeCon Prague!") hello.anotherMethod() // Execution Invoking method 'sayHi' with args '[GeeCon Prague!]' Hello GeeCon Prague! Invoking method 'anotherMethod' with args '[]'
  • 23. MetaClass example class Hello { void sayHi(String name) { println "Hello ${name}" } } Hello.metaClass.invokeMethod = { String methodName, args -> println "Invoking method '${methodName}' with args '${args}'" def method = Hello.metaClass.getMetaMethod(methodName, args) method?.invoke(delegate, args) } def hello = new Hello() hello.sayHi("GeeCon Prague!") hello.anotherMethod() // Execution Invoking method 'sayHi' with args '[GeeCon Prague!]' Hello GeeCon Prague! Invoking method 'anotherMethod' with args '[]'
  • 24. MetaClass example class Hello { void sayHi(String name) { println "Hello ${name}" } } Hello.metaClass.invokeMethod = { String methodName, args -> println "Invoking method '${methodName}' with args '${args}'" def method = Hello.metaClass.getMetaMethod(methodName, args) method?.invoke(delegate, args) } def hello = new Hello() hello.sayHi("GeeCon Prague!") hello.anotherMethod() // Execution Invoking method 'sayHi' with args '[GeeCon Prague!]' Hello GeeCon Prague! Invoking method 'anotherMethod' with args '[]'
  • 25. MetaClass example class Hello { void sayHi(String name) { println "Hello ${name}" } } Hello.metaClass.invokeMethod = { String methodName, args -> println "Invoking method '${methodName}' with args '${args}'" def method = Hello.metaClass.getMetaMethod(methodName, args) method?.invoke(delegate, args) } def hello = new Hello() hello.sayHi("GeeCon Prague!") hello.anotherMethod() // Execution Invoking method 'sayHi' with args '[GeeCon Prague!]' Hello GeeCon Prague! Invoking method 'anotherMethod' with args '[]'
  • 26. MOP method injection
  • 27. MOP Method Injection Injecting methods at code-writing time We can open a class any time Different techniques: MetaClass Categories Extensions Mixins vs Traits
  • 28. Adding methods using MetaClass class StringUtils { static String truncate(String text, Integer length, Boolean overflow = false) { text.take(length) + (overflow ? '...' : '') } } String chuckIpsum = "If you can see Chuck Norris, he can see you. If you can not see Chuck Norris you may be only seconds away from death" println StringUtils.truncate(chuckIpsum, 72) println StringUtils.truncate(chuckIpsum, 72, true) // Execution If you can see Chuck Norris, he can see you. If you can not see Chuck No If you can see Chuck Norris, he can see you. If you can not see Chuck No... String.metaClass.truncate = { Integer length, Boolean overflow = false -> delegate.take(length) + (overflow ? '...' : '') } assert chuckIpsum.truncate(72, true) == StringUtils.truncate(chuckIpsum, 72, true)
  • 29. Adding methods using MetaClass class StringUtils { static String truncate(String text, Integer length, Boolean overflow = false) { text.take(length) + (overflow ? '...' : '') } } String chuckIpsum = "If you can see Chuck Norris, he can see you. If you can not see Chuck Norris you may be only seconds away from death" println StringUtils.truncate(chuckIpsum, 72) println StringUtils.truncate(chuckIpsum, 72, true) // Execution If you can see Chuck Norris, he can see you. If you can not see Chuck No If you can see Chuck Norris, he can see you. If you can not see Chuck No... String.metaClass.truncate = { Integer length, Boolean overflow = false -> delegate.take(length) + (overflow ? '...' : '') } assert chuckIpsum.truncate(72, true) == StringUtils.truncate(chuckIpsum, 72, true)
  • 30. Adding methods using MetaClass class StringUtils { static String truncate(String text, Integer length, Boolean overflow = false) { text.take(length) + (overflow ? '...' : '') } } String chuckIpsum = "If you can see Chuck Norris, he can see you. If you can not see Chuck Norris you may be only seconds away from death" println StringUtils.truncate(chuckIpsum, 72) println StringUtils.truncate(chuckIpsum, 72, true) // Execution If you can see Chuck Norris, he can see you. If you can not see Chuck No If you can see Chuck Norris, he can see you. If you can not see Chuck No... String.metaClass.truncate = { Integer length, Boolean overflow = false -> delegate.take(length) + (overflow ? '...' : '') } assert chuckIpsum.truncate(72, true) == StringUtils.truncate(chuckIpsum, 72, true)
  • 31. Adding methods using MetaClass class StringUtils { static String truncate(String text, Integer length, Boolean overflow = false) { text.take(length) + (overflow ? '...' : '') } } String chuckIpsum = "If you can see Chuck Norris, he can see you. If you can not see Chuck Norris you may be only seconds away from death" println StringUtils.truncate(chuckIpsum, 72) println StringUtils.truncate(chuckIpsum, 72, true) // Execution If you can see Chuck Norris, he can see you. If you can not see Chuck No If you can see Chuck Norris, he can see you. If you can not see Chuck No... String.metaClass.truncate = { Integer length, Boolean overflow = false -> delegate.take(length) + (overflow ? '...' : '') } assert chuckIpsum.truncate(72, true) == StringUtils.truncate(chuckIpsum, 72, true)
  • 32. Adding methods using MetaClass class StringUtils { static String truncate(String text, Integer length, Boolean overflow = false) { text.take(length) + (overflow ? '...' : '') } } String chuckIpsum = "If you can see Chuck Norris, he can see you. If you can not see Chuck Norris you may be only seconds away from death" println StringUtils.truncate(chuckIpsum, 72) println StringUtils.truncate(chuckIpsum, 72, true) // Execution If you can see Chuck Norris, he can see you. If you can not see Chuck No If you can see Chuck Norris, he can see you. If you can not see Chuck No... String.metaClass.truncate = { Integer length, Boolean overflow = false -> delegate.take(length) + (overflow ? '...' : '') } assert chuckIpsum.truncate(72, true) == StringUtils.truncate(chuckIpsum, 72, true)
  • 33. Adding methods using MetaClass class StringUtils { static String truncate(String text, Integer length, Boolean overflow = false) { text.take(length) + (overflow ? '...' : '') } } String chuckIpsum = "If you can see Chuck Norris, he can see you. If you can not see Chuck Norris you may be only seconds away from death" println StringUtils.truncate(chuckIpsum, 72) println StringUtils.truncate(chuckIpsum, 72, true) // Execution If you can see Chuck Norris, he can see you. If you can not see Chuck No If you can see Chuck Norris, he can see you. If you can not see Chuck No... String.metaClass.truncate = { Integer length, Boolean overflow = false -> delegate.take(length) + (overflow ? '...' : '') } assert chuckIpsum.truncate(72, true) == StringUtils.truncate(chuckIpsum, 72, true)
  • 34. Adding properties using MetaClass class Utils { } def utilsInstance = new Utils() Utils.metaClass.version = "3.0" utilsInstance.metaClass.released = true assert utilsInstance.version == "3.0" assert utilsInstance.released == true
  • 35. Adding properties using MetaClass class Utils { } def utilsInstance = new Utils() Utils.metaClass.version = "3.0" utilsInstance.metaClass.released = true assert utilsInstance.version == "3.0" assert utilsInstance.released == true
  • 36. Adding properties using MetaClass class Utils { } def utilsInstance = new Utils() Utils.metaClass.version = "3.0" utilsInstance.metaClass.released = true assert utilsInstance.version == "3.0" assert utilsInstance.released == true
  • 37. Adding properties using MetaClass class Utils { } def utilsInstance = new Utils() Utils.metaClass.version = "3.0" utilsInstance.metaClass.released = true assert utilsInstance.version == "3.0" assert utilsInstance.released == true
  • 38. Overriding methods using MetaClass // Integer assert '42' == 42.toString() Integer.metaClass.toString = { delegate == 42 ? 'The answer to life, the universe and everything' : String.valueOf(delegate) } assert 42.toString() == 'The answer to life, the universe and everything' assert 100.toString() == '100' // Boolean assert false.toBoolean() == false Boolean.metaClass.toBoolean = { !delegate } assert false.toBoolean() == true
  • 39. Overriding methods using MetaClass // Integer assert '42' == 42.toString() Integer.metaClass.toString = { delegate == 42 ? 'The answer to life, the universe and everything' : String.valueOf(delegate) } assert 42.toString() == 'The answer to life, the universe and everything' assert 100.toString() == '100' // Boolean assert false.toBoolean() == false Boolean.metaClass.toBoolean = { !delegate } assert false.toBoolean() == true
  • 40. Overriding methods using MetaClass // Integer assert '42' == 42.toString() Integer.metaClass.toString = { delegate == 42 ? 'The answer to life, the universe and everything' : String.valueOf(delegate) } assert 42.toString() == 'The answer to life, the universe and everything' assert 100.toString() == '100' // Boolean assert false.toBoolean() == false Boolean.metaClass.toBoolean = { !delegate } assert false.toBoolean() == true
  • 41. Overriding methods using MetaClass // Integer assert '42' == 42.toString() Integer.metaClass.toString = { delegate == 42 ? 'The answer to life, the universe and everything' : String.valueOf(delegate) } assert 42.toString() == 'The answer to life, the universe and everything' assert 100.toString() == '100' // Boolean assert false.toBoolean() == false Boolean.metaClass.toBoolean = { !delegate } assert false.toBoolean() == true
  • 42. Overriding methods using MetaClass // Integer assert '42' == 42.toString() Integer.metaClass.toString = { delegate == 42 ? 'The answer to life, the universe and everything' : String.valueOf(delegate) } assert 42.toString() == 'The answer to life, the universe and everything' assert 100.toString() == '100' // Boolean assert false.toBoolean() == false Boolean.metaClass.toBoolean = { !delegate } assert false.toBoolean() == true
  • 43. Overriding methods using MetaClass // Integer assert '42' == 42.toString() Integer.metaClass.toString = { delegate == 42 ? 'The answer to life, the universe and everything' : String.valueOf(delegate) } assert 42.toString() == 'The answer to life, the universe and everything' assert 100.toString() == '100' // Boolean assert false.toBoolean() == false Boolean.metaClass.toBoolean = { !delegate } assert false.toBoolean() == true
  • 44. Categories MetaClass changes are persistent Change metaclass in confined code MOP modified only in the closure
  • 45. Categories example class StringUtils { static String truncate(String text, Integer length, Boolean overflow = false) { text.take(length) + (overflow ? '...' : '') } } use (StringUtils) { println "Lorem ipsum".truncate(5) } try { println "Lorem ipsum".truncate(5) } catch (MissingMethodException mme) { println mme } // Execution Lorem groovy.lang.MissingMethodException: No signature of method: java.lang.String.truncate() is applicable for argument types: (java.lang.Integer) values: [5] Possible solutions: concat(java.lang.String), take(int)
  • 46. Categories example class StringUtils { static String truncate(String text, Integer length, Boolean overflow = false) { text.take(length) + (overflow ? '...' : '') } } use (StringUtils) { println "Lorem ipsum".truncate(5) } try { println "Lorem ipsum".truncate(5) } catch (MissingMethodException mme) { println mme } // Execution Lorem groovy.lang.MissingMethodException: No signature of method: java.lang.String.truncate() is applicable for argument types: (java.lang.Integer) values: [5] Possible solutions: concat(java.lang.String), take(int)
  • 47. Categories example class StringUtils { static String truncate(String text, Integer length, Boolean overflow = false) { text.take(length) + (overflow ? '...' : '') } } use (StringUtils) { println "Lorem ipsum".truncate(5) } try { println "Lorem ipsum".truncate(5) } catch (MissingMethodException mme) { println mme } // Execution Lorem groovy.lang.MissingMethodException: No signature of method: java.lang.String.truncate() is applicable for argument types: (java.lang.Integer) values: [5] Possible solutions: concat(java.lang.String), take(int)
  • 48. Categories example class StringUtils { static String truncate(String text, Integer length, Boolean overflow = false) { text.take(length) + (overflow ? '...' : '') } } use (StringUtils) { println "Lorem ipsum".truncate(5) } try { println "Lorem ipsum".truncate(5) } catch (MissingMethodException mme) { println mme } // Execution Lorem groovy.lang.MissingMethodException: No signature of method: java.lang.String.truncate() is applicable for argument types: (java.lang.Integer) values: [5] Possible solutions: concat(java.lang.String), take(int)
  • 49. Categories example (II) class FileBinaryCategory { def static leftShift(File file, URL url) { def input def output try { input = url.openStream() output = new BufferedOutputStream(new FileOutputStream(file)) output channels.each { channel -> def metaMethod = channel.metaClass.getMetaMethod(name, methodArgs) return metaMethod.invoke(channel, methodArgs) } } // Cache the implementation in the metaClass NotificationService instance = this instance.metaClass."$name" = implementation // Execute it! implementation(args) } }
  • 84. MethodMissing example class NotificationService { List channels = [] def methodMissing(String name, args) { System.out.println "...methodMissing called for ${name} with args ${args}" // Generate the implementation def implementation = { Object[] methodArgs -> channels.each { channel -> def metaMethod = channel.metaClass.getMetaMethod(name, methodArgs) return metaMethod.invoke(channel, methodArgs) } } // Cache the implementation in the metaClass NotificationService instance = this instance.metaClass."$name" = implementation // Execute it! implementation(args) } }
  • 85. MethodMissing example class NotificationService { List channels = [] def methodMissing(String name, args) { System.out.println "...methodMissing called for ${name} with args ${args}" // Generate the implementation def implementation = { Object[] methodArgs -> channels.each { channel -> def metaMethod = channel.metaClass.getMetaMethod(name, methodArgs) return metaMethod.invoke(channel, methodArgs) } } // Cache the implementation in the metaClass NotificationService instance = this instance.metaClass."$name" = implementation // Execute it! implementation(args) } } notificationService.sendNewFollower(...) notificationService.sendNewMessage(...)
  • 86. MethodMissing example class NotificationService { List channels = [] def methodMissing(String name, args) { System.out.println "...methodMissing called for ${name} with args ${args}" // Generate the implementation def implementation = { Object[] methodArgs -> channels.each { channel -> def metaMethod = channel.metaClass.getMetaMethod(name, methodArgs) return metaMethod.invoke(channel, methodArgs) } } // Cache the implementation in the metaClass NotificationService instance = this instance.metaClass."$name" = implementation // Execute it! implementation(args) } }
  • 87. MethodMissing example class NotificationService { List channels = [] def methodMissing(String name, args) { System.out.println "...methodMissing called for ${name} with args ${args}" // Generate the implementation def implementation = { Object[] methodArgs -> channels.each { channel -> def metaMethod = channel.metaClass.getMetaMethod(name, methodArgs) return metaMethod.invoke(channel, methodArgs) } } // Cache the implementation in the metaClass NotificationService instance = this instance.metaClass."$name" = implementation // Execute it! implementation(args) } }
  • 88. MethodMissing example def notificationService = new NotificationService( channels: [new EmailChannel(), new MobilePushChannel()] ) assert !notificationService.respondsTo('sendNewFollower', String, String) notificationService.sendNewFollower("John", "Peter") assert notificationService.respondsTo('sendNewFollower', String, String) notificationService.sendNewFollower("Mary", "Steve") notificationService.sendNewMessage("Ivn", "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 [Ivn, Hello!] Sending email notification to 'Ivn' for new message 'Hello!'
  • 89. MethodMissing example def notificationService = new NotificationService( channels: [new EmailChannel(), new MobilePushChannel()] ) assert !notificationService.respondsTo('sendNewFollower', String, String) notificationService.sendNewFollower("John", "Peter") assert notificationService.respondsTo('sendNewFollower', String, String) notificationService.sendNewFollower("Mary", "Steve") notificationService.sendNewMessage("Ivn", "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 [Ivn, Hello!] Sending email notification to 'Ivn' for new message 'Hello!'
  • 90. MethodMissing example def notificationService = new NotificationService( channels: [new EmailChannel(), new MobilePushChannel()] ) assert !notificationService.respondsTo('sendNewFollower', String, String) notificationService.sendNewFollower("John", "Peter") assert notificationService.respondsTo('sendNewFollower', String, String) notificationService.sendNewFollower("Mary", "Steve") notificationService.sendNewMessage("Ivn", "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 [Ivn, Hello!] Sending email notification to 'Ivn' for new message 'Hello!'
  • 91. MethodMissing example def notificationService = new NotificationService( channels: [new EmailChannel(), new MobilePushChannel()] ) assert !notificationService.respondsTo('sendNewFollower', String, String) notificationService.sendNewFollower("John", "Peter") assert notificationService.respondsTo('sendNewFollower', String, String) notificationService.sendNewFollower("Mary", "Steve") notificationService.sendNewMessage("Ivn", "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 [Ivn, Hello!] Sending email notification to 'Ivn' for new message 'Hello!'
  • 92. MethodMissing example def notificationService = new NotificationService( channels: [new EmailChannel(), new MobilePushChannel()] ) assert !notificationService.respondsTo('sendNewFollower', String, String) notificationService.sendNewFollower("John", "Peter") assert notificationService.respondsTo('sendNewFollower', String, String) notificationService.sendNewFollower("Mary", "Steve") notificationService.sendNewMessage("class EmailChannel exItvennd"s, C"hHaenlnleol! "{) void sendNewFollower(String username, String follower) {} void sendNewMessage(String username, String msg) {} // Execution ...} methodMissing called for sendNewFollower with args [John, Peter] Sending email notification to 'John' for new follower 'Peter' Sending class mobile MobilePushChannel push notification extends to Channel 'John' { for new follower 'Peter' void sendNewFollower(String username, String follower) {} Sending } email notification to 'Mary' for new follower 'Steve' Sending mobile push notification to 'Mary' for new follower 'Steve' ...methodMissing called for sendNewMessage with args [Ivn, Hello!] Sending email notification to 'Ivn' for new message 'Hello!'
  • 93. MethodMissing example def notificationService = new NotificationService( channels: [new EmailChannel(), new MobilePushChannel()] ) assert !notificationService.respondsTo('sendNewFollower', String, String) notificationService.sendNewFollower("John", "Peter") assert notificationService.respondsTo('sendNewFollower', String, String) notificationService.sendNewFollower("Mary", "Steve") notificationService.sendNewMessage("Ivn", "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 [Ivn, Hello!] Sending email notification to 'Ivn' for new message 'Hello!'
  • 94. MethodMissing example def notificationService = new NotificationService( channels: [new EmailChannel(), new MobilePushChannel()] ) assert !notificationService.respondsTo('sendNewFollower', String, String) notificationService.sendNewFollower("John", "Peter") assert notificationService.respondsTo('sendNewFollower', String, String) notificationService.sendNewFollower("Mary", "Steve") notificationService.sendNewMessage("Ivn", "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 [Ivn, Hello!] Sending email notification to 'Ivn' for new message 'Hello!'
  • 95. MethodMissing example def notificationService = new NotificationService( channels: [new EmailChannel(), new MobilePushChannel()] ) assert !notificationService.respondsTo('sendNewFollower', String, String) notificationService.sendNewFollower("John", "Peter") assert notificationService.respondsTo('sendNewFollower', String, String) notificationService.sendNewFollower("Mary", "Steve") notificationService.sendNewMessage("Ivn", "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 [Ivn, Hello!] Sending email notification to 'Ivn' for new message 'Hello!'
  • 96. 2. Compile-time metaprogramming
  • 97. Compile-time metaprogramming Advance feature Analyze/modify program structure at compile time Cross-cutting features Write code that generates bytecode
  • 98. AST and compilation AST: Abstract Syntax Tree AST modified during compilation Hook into the phases Initialization, Parsing, Conversion, Semantic analysis, Canonicalization, Instruction selection, Class generation, Output, Finalization
  • 99. Groovy AST Transformations Lot of AST transformations out-of-the-box @EqualsAndHashCode, @ToString, @TuppleConstructor, @Canonical, @Grab, @Immutable, @Delegate, @Singleton, @Category, @Log4j, @CompileStatic, @TypeChecked, @Synchronized ...
  • 100. Global AST transformations
  • 101. Global AST Transformations No annotation Meta-information file Applied to all code during compilation Any compilation phase Grails uses intensively in GORM
  • 102. Local AST transformations
  • 103. Local AST Transformations Annotate code No meta-information file Easy to debug
  • 104. Steps to create local AST Interface AST Enjoy!
  • 105. Local AST example import geecon2014.Version @Version('1.0') class VersionedClass { } package geecon2014 import ... @Retention(RetentionPolicy.SOURCE) @Target([ElementType.TYPE]) @GroovyASTTransformationClass("geecon2014.VersionASTTransformation") @interface Version { String value() } class VersionedClass { public static final String VERSION = "1.0" }
  • 106. Local AST example import geecon2014.Version @Version('1.0') class VersionedClass { } package geecon2014 import ... @Retention(RetentionPolicy.SOURCE) @Target([ElementType.TYPE]) @GroovyASTTransformationClass("geecon2014.VersionASTTransformation") @interface Version { String value() } class VersionedClass { public static final String VERSION = "1.0" }
  • 107. Local AST example import geecon2014.Version @Version('1.0') class VersionedClass { } package geecon2014 import ... @Retention(RetentionPolicy.SOURCE) @Target([ElementType.TYPE]) @GroovyASTTransformationClass("geecon2014.VersionASTTransformation") @interface Version { String value() } class VersionedClass { public static final String VERSION = "1.0" }
  • 108. Local AST example @GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS) class VersionASTTransformation extends AbstractASTTransformation { @Override public void visit(final ASTNode[] nodes, final SourceUnit source) { if (nodes.length != 2) { return } if (nodes[0] instanceof AnnotationNode && nodes[1] instanceof ClassNode) { def annotation = nodes[0] def version = annotation.getMember('value') if (version instanceof ConstantExpression) { nodes[1].addField('VERSION', ACC_PUBLIC | ACC_STATIC | ACC_FINAL, ClassHelper.STRING_TYPE, version) } else { source.addError(new SyntaxException("Invalid value for annotation", annotation.lineNumber, annotation.columnNumber)) } } } }
  • 109. Local AST example @GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS) class VersionASTTransformation extends AbstractASTTransformation { @Override public void visit(final ASTNode[] nodes, final SourceUnit source) { if (nodes.length != 2) { return } if (nodes[0] instanceof AnnotationNode && nodes[1] instanceof ClassNode) { def annotation = nodes[0] def version = annotation.getMember('value') if (version instanceof ConstantExpression) { nodes[1].addField('VERSION', ACC_PUBLIC | ACC_STATIC | ACC_FINAL, ClassHelper.STRING_TYPE, version) } else { source.addError(new SyntaxException("Invalid value for annotation", annotation.lineNumber, annotation.columnNumber)) } } } }
  • 110. Local AST example // Execute with: // gradle build // groovy -cp build/libs/add-version-1.0.jar LocalASTExample.groovy import geecon.Version @Version('1.0') class VersionedClass { } println VersionedClass.VERSION // Execution 1.0
  • 111. Local AST example // Execute with: // gradle build // groovy -cp build/libs/add-version-1.0.jar LocalASTExample.groovy import geecon.Version @Version('1.0') class VersionedClass { } println VersionedClass.VERSION // Execution 1.0
  • 112. Local AST example // Execute with: // gradle build // groovy -cp build/libs/add-version-1.0.jar LocalASTExample.groovy import geecon.Version @Version('1.0') class VersionedClass { } println VersionedClass.VERSION // Execution 1.0
  • 113. 3. Recap Why we should use metaprogramming?
  • 114. Lets review some concepts Metaprogramming out-of-the box Easy and very powerfull Write better code Add behaviour easily Take advantage of this power Because Groovy, it's groovy
  • 115. With great power comes great responsibility
  • 116. Thanks! Any questions? Ivn Lpez @ilopmar [email protected] https://github.com/lmivan http://kcy.me/1bzj7