Metaprogramming is the writing of computer programs that write or manipulate other programs (or themselves) as their data. - Wikipedia
The Groovy language supports two flavors of metaprogramming:
# Runtime metaprogramming, and
# Compile-time metaprogramming.
The first one allows altering the class model and the behavior of a program at runtime, while the second only occurs at compile-time.
2. Agenda
▣ Groovy Is Dynamic
▣ What is MetaProgramming?
▣ Runtime MetaProgramming
□ What is MOP?
□ Understanding Groovy
□ Meta Class
▣ Intercepting Methods Using MOP
□ InvokeMethod
□ GroovyInterceptable
□ Intercepting Methods using MetaClass
▣ MOP Method Injection
□ MetaClass
□ Categories
□ Mixins/Traits
3. Agenda (continues..)
▣ MOP Method Synthesis
▣ MOP Class Synthesis
▣ Compile-time MetaProgramming
□ AST and Compilation
□ Groovy AST Transformation
□ Global AST Transformation
□ Local AST Transformation
▣ Why we should use MetaProgramming?
▣ References
4. Groovy Is Dynamic
▣ Groovy allows "Delay" to runtime some
checks/decisions that are usually performed
during the compilation.
▣ Add properties/behaviours in runtime.
▣ Wide range of applicability
□ DSLs
□ Builders
□ Advanced logging, tracing, debugging & profiling
□ Allow organize the codebase better
That's why we can talk about Metaprogramming
in Groovy.
6. ‘’
Metaprogramming is
the writing of computer
programs that write or
manipulate other
programs (or
themselves) as their
data.
- Wikipedia
7. Overview
The Groovy language supports two flavors of
metaprogramming:
▣ Runtime metaprogramming, and
▣ Compile-time metaprogramming.
The first one allows altering the class model and
the behavior of a program at runtime, while the
second only occurs at compile-time.
9. Runtime MetaProgramming
▣ Groovy provides this through Meta-Object
Protocol (MOP).
▣ We can use MOP to:
□ Invoke methods dynamically
□ Synthesize classes and methods on the fly.
With runtime metaprogramming we can
postpone to runtime the decision to intercept,
inject and even synthesize methods of classes
and interfaces.
11. Understanding Groovy
For a deep understanding of Groovy MOP we
first need to understand Groovy objects and
Groovy’s method handling. In Groovy, we work
with three kinds of objects:
▣ POJO,
▣ POGO, and
▣ Groovy Interceptors
So, for each object Groovy allows
metaprogramming but in different manner.
12. continues...
▣ POJO - A regular Java object, whose class can be
written in Java or any other language for the JVM.
▣ POGO - A Groovy object, whose class is written in
Groovy. It extends java.lang.Object and implements
the groovy.lang.GroovyObject interface by default.
▣ Groovy Interceptor - A Groovy object that implements
the groovy.lang.GroovyInterceptable interface and has
method-interception capability, which we’ll discuss in
the GroovyInterceptable section.
For every method call Groovy checks whether the object is
a POJO or a POGO. For POJOs, Groovy fetches it’s
MetaClass from the groovy.lang.MetaClassRegistry and
delegates method invocation to it.
16. Groovy Object Interface
GroovyObject has a default implementation in the
groovy.lang.GroovyObjectSupport class and it is responsible to
transfer invocation to the groovy.lang.MetaClass object. The
GroovyObject source looks like this:
package groovy.lang;
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);
}
18. invokeMethod
This invokeMethod is called when the method you called is not
present on a Groovy object. Example:
class InvokeMethodDemo {
def invokeMethod(String name, Object args) {
return "called invokeMethod $name $args"
}
def test() {
return 'method exists'
}
}
def invokeMethodDemo = new InvokeMethodDemo()
assert invokeMethodDemo.test() == 'method exists'
assert invokeMethodDemo.hello() == 'called invokeMethod hello []'
20. GroovyInterceptable
▣ Classes compiled by Groovy implements GroovyObject
interface.
▣ We can implement GroovyInterceptable to hook into
the execution process.
package groovy.lang;
public interface GroovyInterceptable extends
GroovyObject {
}
When a Groovy object implements the
GroovyInterceptable interface, it’s invokeMethod() is called
for any method calls.
23. Intercepting Methods using MetaClass
If we want to intercept all methods call but do not want to
implement the GroovyInterceptable interface we can
implement invokeMethod() on an object’s MetaClass.
▣ Groovy maintains a meta class of type MetaClass for
each class.
▣ Maintains a collection of all methods and properties of
A.
▣ If we can't modify the class source code or if it's a Java
class we can modify the meta-class.
▣ We can intercept methods by implementing the
invokeMethod() method on the MetaClass.
26. MOP Method Injection
▣ In Groovy we can “open” a class at any time.
▣ Injecting methods at code-writing time; we
know the names of methods we want to add.
▣ Different techniques:
□ MetaClass
□ Categories
□ Extensions
□ Mixins / Traits
28. Types of MetaClass
▣ MetaClassImpl: Default meta class, it's used in the vast
majority of case.
▣ ExpandoMetaClass: allow the addition or replacement
of methods, properties and constructors on the fly.
▣ Other meta classes used internally and for testing.
Note: This is only true for Groovy.
▣ In Grails all MetaClass are ExpandoMetaClass.
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)
String.metaClass.truncateDemo = { Integer length, Boolean overflow = false ->
println "truncate string upto length $length"
delegate.take(length) + (overflow ? '...' : '')
}
assert chuckIpsum.truncateDemo(20, true) == StringUtils.truncate(chuckIpsum, 20, true)
35. Categories
▣ Changes made to a MetaClass are “persistent”
and hard to revert.
▣ Categories are useful to change the meta
class in a confined small piece of code.
▣ A category can alter a class’ MetaClass.
▣ The MOP is modified in the closure and after
the closure execution, it resets to its old state.
▣ Category classes are not special.
39. Mixins
▣ A mixin allow “bring in” or “mix in”
implementations from multiple classes.
▣ Groovy first call the mixed-in class.
▣ Mix multiple classes. The last added mixin
takes precedence.
▣ Override a method of a previous Mixin but not
methods in the meta class.
▣ Mixins cannot easily be un-done.
40. continues..
class SpidermanPower {
String spiderSense() {
"Using spider-sense..."
}
}
class SupermanPower {
String fly() {
"Flying..."
}
}
@Mixin([SpidermanPower])
class Person {}
def person = new Person()
assert person.spiderSense() == "Using spider-sense..."
assert !(person instanceof SpidermanPower)
Person.mixin SupermanPower
assert person.fly() == "Flying..."
assert !(person instanceof SupermanPower)
41. continues..
class SpidermanPower {
String spiderSense() {
"Using spider-sense..."
}
}
class SupermanPower {
String fly() {
"Flying..."
}
}
@Mixin([SpidermanPower])
class Person {}
def person = new Person()
assert person.spiderSense() == "Using spider-sense..."
assert !(person instanceof SpidermanPower)
Person.mixin SupermanPower
assert person.fly() == "Flying..."
assert !(person instanceof SupermanPower)
42. ‘’
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
43. ▣ Groovy 2.3+
▣ Similar to Java 8 default methods
▣ Supported in JDK 6, 7 and 8
▣ Stateful
▣ Composition over inheritance
▣ Documentation
Traits
Note - Link to Groovy Traits PPT
44. Traits Example
trait SpidermanPower {
String spiderSense() {
"Using spider-sense..."
}
}
class Person implements SpidermanPower {}
def person = new Person()
assert person.spiderSense() == "Using spider-sense..."
println("=====> person.spiderSense(): ${person.spiderSense()}")
assert person instanceof SpidermanPower
def person2 = person.withTraits SupermanPower
assert person2.fly() == "Flying..."
println("=====> person2.fly(): ${person2.fly()}")
assert person2 instanceof SupermanPower
trait SupermanPower {
String fly() {
"Flying..."
}
}
46. MOP Method Synthesis
▣ Dynamically figure out the behaviour for
methods upon invocation.
▣ A synthesized method may not exist as a
separate method until we call it.
▣ invokeMethod, methodMissing and
propertyMissing.
▣ “Intercept, Cache, Invoke” pattern.
47. 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: 'Superman',
age: 34)
assert p.respondsTo('sayHi')
assert p.respondsTo('sayHiTo', String)
assert !p.respondsTo('goodbye')
assert p.hasProperty('name')
assert !p.hasProperty('country')
48. Method Delegation
cl = { ->
append "Hi!"
append " this is closure delegate demo."
}
sb = new StringBuffer()
cl.delegate = sb
cl()
println "SB: ${sb}"
49. Continues.. (Example 2)
class ClosureDemo {
void append(String arg) {
println("append called with: ${arg}")
}
void doit() {
def cl = {
append 'MetaProgramming Demo by'
append ' Ali and Gaurav'
}
def sb = new StringBuffer()
cl.delegate = sb
cl()
println("SB: ${sb}")
}
static void main(args) {
new ClosureDemo().doit()
}
}
51. Creating Dynamic Classes with
Expando
carA = new Expando()
carB = new Expando(year: 2012, miles: 0)
carA.year = 2012
carA.miles = 10
println "carA: " + carA
println "carB: " + carB
In Groovy we can create a class entirely at runtime. The
Groovy Expando class gives us the ability to synthesize
classes dynamically.
It got its name because it is dynamically expandable. We can
assign properties and methods to it either at construction
time using a Map or at any time dynamically.
car = new Expando(year: 2012, miles: 0,
turn: { println 'turning...' })
car.drive = {
miles += 10
println "$miles miles driven"
}
car.drive()
car.turn()
53. Compile-time MetaProgramming
▣ Advanced feature.
▣ Analyze and modify a program’s structure at
compile time.
▣ Cross-cutting features:
▣ Inspect classes for thread safety
▣ Log messages
▣ Perform pre and postcheck operations all
without explicitly modifying the source code.
▣ We write code that generates bytecode or
gets involved during the bytecode
generation.
54. AST and Compilation
▣ AST: Abstract Syntax Tree
▣ During compilation the AST is transformed
▣ Hook into different phases to change the final
byte-code.
▣ Initialization, Parsing, Conversion, Semantic
analysis, Canonicalization, Instruction
selection, Class generation, Output,
Finalization.
55. Groovy AST Transformations
▣ Groovy provides out-of-the-box a lot of AST
Transformations
▣ @EqualsAndHashCode, @ToString,
@TuppleConstructor, @Canonical, @Grab,
@Immutable, @Delegate, @Singleton,
@Category, @Log4j, @CompileStatic,
@TypeChecked, @Synchronized, etc.
56. Global AST Transformations
▣ There's no need to annotate anything.
▣ Applied to every single source unit during
compilation.
▣ Can be applied to any phase in the
compilation.
▣ Need a metadata file into the JAR file
▣ (META-
INF/services/org.codehaus.groovy.transform.
ASTTransformation)
▣ Grails uses Global Transformations intensively
for example in GORM.
57. Local AST Transformations
▣ Annotate the code and only applied to that
code.
▣ Easy to debug.
▣ No need to create metadata file in a jar.
▣ Steps: Define an interface, Define the AST
transformation, Enjoy!
59. Concept Review
▣ Metaprogramming Easy and very out-of-the
box
▣ Easy and very powerful
▣ Write better code
▣ Add Behaviour easily
▣ Take advantage of this power because
Groovy, it’s Groovy
61. Thanks!
Any questions?
You can find us at -
▣ ali.tanwir@nexthoughts.com
▣ gaurav.gupta@nexthoughts.com
You can find demo code used with this presentation at -
https://github.com/NexThoughts/groovyMetaProgrammin
g