+ All Categories
Home > Technology > Metaprogramming Techniques In Groovy And Grails

Metaprogramming Techniques In Groovy And Grails

Date post: 10-May-2015
Category:
Upload: zenmonkey
View: 19,277 times
Download: 4 times
Share this document with a friend
Popular Tags:
45
Metaprogramming techniques in Groovy and Grails Numan Salati n [email protected] NY Groovy/Grails Meetup,
Transcript
Page 1: Metaprogramming Techniques In Groovy And Grails

Metaprogramming techniques in Groovy and Grails

Numan [email protected] Groovy/Grails Meetup,

Page 2: Metaprogramming Techniques In Groovy And Grails

What makes a language dynamic?

• Dynamic type system• Mutable types• Flexible method dispatch• Evaluate code at runtime (access to the

interpreter)

Key Idea: late binding !

Page 3: Metaprogramming Techniques In Groovy And Grails

Dynamic features in Java

• dynamic class loading

• dynamic binding – subclass Polymorphism

• runtime annotations

• dynamic proxies and reflection API– mostly read only – dynamic implementation of interfaces

Page 4: Metaprogramming Techniques In Groovy And Grails

Dynamic Groovy

• Groovy has all this and much more

– Intercept methods/properties

– Create new methods/properties/constructors

– Create classes at runtime

– Runtime mixins (mutable types)

– Evaluate any valid code string

Page 5: Metaprogramming Techniques In Groovy And Grails

method invocation example

obj.method(arg1, arg2, arg3)

• In Java– single dispatch– invokedynamic bytecode instruction

• In Groovy– multiple dispatch– much more complicated logic but very flexible

Page 6: Metaprogramming Techniques In Groovy And Grails

method invocation example

What gets called in Java vs. Groovy ?

class Foo{

def print (Object o) { println “println object" }def print (String s) { println “println string" }

}

Object arg = "string"new Foo().print(arg)

Page 7: Metaprogramming Techniques In Groovy And Grails

Metaprogramming

• Wikipedia definitions:– Programs that write or manipulate other programs– Expose internals of runtime engine to programming code through

API”– Dynamic execution of string expression

• Meta object protocol: Make program semantics – Explicit– Extensible

How much of runtime and compile time structures are exposed?

Page 8: Metaprogramming Techniques In Groovy And Grails

Groovy MOP

Excellent support for metaprogramming

Compile time• Hook into the Groovy AST during compilation

Runtime• Hook into method dispatching• Dynamically create methods/properties• Mutable types• Execution of code strings

Page 9: Metaprogramming Techniques In Groovy And Grails

Example

>> groovyc Person.groovy>> javap –public Person

Compiled from "Person.groovy"public class Test extends java.lang.Object implements groovy.lang.GroovyObject {

…..

public groovy.lang.MetaClass getMetaClass(); public void setMetaClass(groovy.lang.MetaClass); public java.lang.Object invokeMethod(java.lang.String, java.lang.Object); public java.lang.Object getProperty(java.lang.String); public void setProperty(java.lang.String, java.lang.Object);

….}

class Person{ def name def sleep() { println "sleeping"}}

GroovyObject

Page 10: Metaprogramming Techniques In Groovy And Grails

GroovyObject• All Groovy classes implement this interface

• Open the class file in jad decompiler:

public Object invokeMethod(String s, Object obj){ return getMetaClass().invokeMethod(this, s, obj);}

public Object getProperty(String s){ return getMetaClass().getProperty(this, s);}

Default implementation delegates to metaClass

• Compiler assigns a metaClass to every POJO and POGO

Page 11: Metaprogramming Techniques In Groovy And Grails

GroovyObject

• These methods are the hooks into method dispatch and property access/assignment

• Overriding getProperty() and setProperty we can dynamically add properties and methods– This is exactly what Expando does• Dynamically create classes• Add methods by creating properties that are closures

Page 12: Metaprogramming Techniques In Groovy And Grails

Expando in Groovyclass SimpleExpando { def propertyMap = [:]

def getProperty(String name) { propertyMap[name]?: null }

void setProperty(String name, Object value) { propertyMap[name] = value; }

def invokeMethod(String name, Object args) { try {

metaClass.invokeMethod(name, args); } catch (GroovyRuntimeException e) { def value = propertyMap[name]; if (value instanceof Closure) { value.setDelegate(this)

value.call(args); } else { throw e } }

}}

s = new SimpleExpando()s.add = {x, y -> x + y}println s.add(19,1)

Why set delegate before invoking?

Page 13: Metaprogramming Techniques In Groovy And Grails

InvokeMethod

• Overriding invokeMethod in the class– intercepts all non existing method calls

• What if we want to intercept all method calls?– Implement GroovyInterceptable marker interface– Override invokeMethod(String name, args)– Careful about stack overflow!

• use metaClass.invokeMethod inside invokeMethod

• Only non existing methods?– implement methodMissing(String name, args) – higher precedence than invokeMethod if both present

Page 14: Metaprogramming Techniques In Groovy And Grails

MetaClass

• invokeMethod and methodMissing can be implemented in the class or on the metaClass

• Metaclass defines the dynamic behavior of the object

• Query runtime structure of the class/object– respondsTo– hasProperty– getMethods vs. getMetaMethods– getProperties vs. etMetaProperties

Page 15: Metaprogramming Techniques In Groovy And Grails

MetaClass• Define new methods and constructors on class using ExpandoMetaClass

Person.metaClass.play = { println "play"}Person.metaClass.eat = { pritnln "eat" }Person.metaClass.code = { println "code"}Person.metaClass.static.read = { println "reading" }Person.metaClass.constructor = {name -> new Person("Sir: " + name) }

Heap Overflow! - use BeanUtils.instantiateClass to instantiate outside of groovy

Person.metaClass { play { println "play"} eat { pritnln "eat" } code { println "code"} 'static' { read { println "reading" } } constructor { name -> BeanUtils.instantiateClass(Person, "Sir: " + name) }}

or using EMC DSL

Page 16: Metaprogramming Techniques In Groovy And Grails

EMC• Injection works for both POJOs and POGOs

– Integer.randomNum = { … }– String.metaClass = <Roll your own super cool metaClass>

• Add to instance only?

p1 = new Person()p2 = new Person()

p2.metaClass.party = { println "partying"}

p2.party()p1.party() MissingMethodException

• Works for POJOs too

Page 17: Metaprogramming Techniques In Groovy And Grails

EMC

• new methods are reflected in the subclass hierarchy

• Adding methods to interfaces?– set enableGlobally on EMC to affect all

implementing classes

Page 18: Metaprogramming Techniques In Groovy And Grails

Summary of method dispatch so far..

• If a method is defined in the metaClass invoke that

• Or else look for hooks in the class or metaClass:– invokeMethod– methodMissing– getProperty– setProperty

Page 19: Metaprogramming Techniques In Groovy And Grails

Method dispatch flow diagram

Page 20: Metaprogramming Techniques In Groovy And Grails

Categories

• Injection of methods within a scope

import org.codehaus.groovy.runtime.TimeCategoryuse(TimeCategory) {

println 2.days.from.nowprintln 3.years.from.nowprintln 10.minutes.from.nowprintln 3.weeks.from.now

}

• Injects getDays() and getYears() method defined in TimeCategory into the meta class of Integer objects in the “use” block and for the current thread

Page 21: Metaprogramming Techniques In Groovy And Grails

Categoriespublic class TimeCategory { ....

• public static DatumDependentDuration getMonths(final Integer self) {• return new DatumDependentDuration(0, self.intValue(), 0, 0, 0, 0, 0);• }

• public static DatumDependentDuration getYears(final Integer self) {• return new DatumDependentDuration(self.intValue(), 0, 0, 0, 0, 0, 0);• }

• public static Duration getWeeks(final Integer self) {• return new Duration(self.intValue() * 7, 0, 0, 0, 0);• }

• public static TimeDuration getHours(final Integer self) {• return new TimeDuration(0, self.intValue(), 0, 0, 0);• }

....

}

• All methods are static

• First argument is the class getting injected

Page 22: Metaprogramming Techniques In Groovy And Grails

Categories

• Can nest categories– in case of method clash, last one takes precedence

• Can use multiple categories in the “use” clause– same precedence rule as above

• Other built in categories– DomCategory– SerlvetCategory

Page 23: Metaprogramming Techniques In Groovy And Grails

Categories

• How it work internally:1. Creates new scope2. Adds static methods from category class into thread local stack3. Call closure4. Remove methods from stack

– check out “use” method in “GroovyCategoryStack.java”

• Slower than metaClass injection– scanning static methods– cleanup

Page 24: Metaprogramming Techniques In Groovy And Grails

Runtime Mixins• Java mixins vs. Groovy mixins

• Inject methods from other types

• Works on classes and interfaces

class Superman { def fly() { println "flying" }}

class Ninja { def fight() { println "fighting" }}

Person.mixin Superman, Ninjap = new Person()

p.sleep()p.fly()p.fight()

• Doesn’t not work on instances

• Global change

• Easier to use than Categories

• For method conflict last mixin takes precedence

Page 25: Metaprogramming Techniques In Groovy And Grails

Applications

• Dynamic finders• Builders• Custom DSL• Dependency Injection• Method injection• Interceptors

Page 26: Metaprogramming Techniques In Groovy And Grails

Dynamic finders in Grails

private static addDynamicFinderSupport(GrailsDomainClass dc, ….) { def mc = dc.metaClass

def dynamicMethods = [ … ]

mc.static.methodMissing = {String methodName, args -> def result = null StaticMethodInvocation method = dynamicMethods.find {it.isMethodMatch(methodName)} if (method) { synchronized(this) { mc.static."$methodName" = {List varArgs -> method.invoke(dc.clazz, methodName, varArgs) } } result = method.invoke(dc.clazz, methodName, args) } else { throw new MissingMethodException(methodName, delegate, args) } result } }

Register method on metaclass for faster lookup on subsequent invocations

findAllBy, CountBy, ListOrderBy patterns…

HibernatePluginSupport.groovy

Page 27: Metaprogramming Techniques In Groovy And Grails

Builders• Easy way to hierarchical/recursive structures like XML, GUI components

builder = new NodeBuilder()root = builder.persons {• person (name: 'obama') {• address(zip: 10016, street: 36)

profession 'president'• }

• person (name: 'joe') {• address(zip: 30312, street: 12)

profession 'vice-president'• }

}

println root.'**'.profession GPath expression

Page 28: Metaprogramming Techniques In Groovy And Grails

Main Concepts

• Method interception through invokeMethod or methodMissing– intercept method calls and dynamically create a

node

• Closure delegates – make sure all closures are relaying method calls to

the builder

Page 29: Metaprogramming Techniques In Groovy And Grails

Anatomy of a builderbuilder = new NodeBuilder()root = builder.persons {• person (name: 'obama') {• address(zip: 10016, street: 36)

profession 'president'• }

• person (name: 'joe') {• address(zip: 30312, street: 12)

profession 'vice-president'• }

}

println root.'**'.profession

1. create node person by intercepting g the method call ( e.g builder.persons {…} )

2. Execute the closure but first set the delegate to the builder

3. Recursively do this until all nodes are created

Page 30: Metaprogramming Techniques In Groovy And Grails

How to write a builder• Extends BuilderSupport

public class NodeBuilder extends BuilderSupport {

• public static NodeBuilder newInstance() {• return new NodeBuilder();• }

• protected void setParent(Object parent, Object child) {• }

• protected Object createNode(Object name) {• return new Node(getCurrentNode(), name, new ArrayList());• }

• protected Object createNode(Object name, Object value) {• return new Node(getCurrentNode(), name, value);• }

• protected Object createNode(Object name, Map attributes) {• return new Node(getCurrentNode(), name, attributes, new ArrayList());• }

• protected Object createNode(Object name, Map attributes, Object value) {• return new Node(getCurrentNode(), name, attributes, value);• }

• protected Node getCurrentNode() {• return (Node) getCurrent();• }

}

BuilderSupport takes care of method interception and setting closure delegates

Page 31: Metaprogramming Techniques In Groovy And Grails

Some common builders

• Grails- MarkupBuilder- SwingBuilder

• Groovy- ConstrainedPropertyBuilder- BeanBuilder- HibernateCriteriaBuilder

Page 32: Metaprogramming Techniques In Groovy And Grails

AST Transformation

• Compile time metaprogramming technique

• Example from languages– C Macros (preprocessing)– C++ Templates (compile time instantiation)– Lisp Macros (very powerful)– Java Annotations (mostly code gen)

Page 33: Metaprogramming Techniques In Groovy And Grails

Basic Idea

• You can manipulate code at many representations – Source (templating), AST, Bytecode (e.g AspectJ), runtime

• Hook into compilation process and manipulate the AST– Higher level abstraction that bytecode

– Different from traditional java annotations where transformations happen outside of the compiler

Page 34: Metaprogramming Techniques In Groovy And Grails

Groovy compilation process: 100,000 ft view

Source

Tokens

Antlr AST

Groovy AST

Bytecode

Lexical analysis with a scanner

Parsing with ANTLR

Transform ANTL AST To Groovy AST

- Semantic analysis- Canonicalization- Instruction selection- Class generation- Output- Finalization

Compile phases on the AST

Page 35: Metaprogramming Techniques In Groovy And Grails

Example AST 1 def sum (List lst){ 2 def total = lst.inject(0) { s, i -> s = s + i } 3 println total 4 return total 5 }

println totalExpressionStatement

MethodCallStatement

VariableExpressions ConstantExpression ArgumentListExpression

“this” “println” VariableExpression

“total”

Page 36: Metaprogramming Techniques In Groovy And Grails

Example ASTdef total = lst.inject(0) { s, i -> s = s + i }

Parameter

ExpressionStatement

DeclarationExpression

VariableExpression

ArgumentListExpressionConstantExpression

ClosureExpression

BlockStatement

ExpressionStatement

BinaryExpression

BinaryExpression

VariableExpression

VariableExpression

VariableExpression

MethodCallExpression

VariableExpression

ConstantExpression

“=”

Parameter

“total”

“lst” “inject”

“=”

“0”

“s” “i”

“s” “i”

“=”“s”

Page 37: Metaprogramming Techniques In Groovy And Grails

Types of transformations

• Local– Applied to tagged (annotated) elements– Annotation driven– Can only be applied to Semantic Analysis phase or

later

• Global– applied to all classes that are compiled

Page 38: Metaprogramming Techniques In Groovy And Grails

Local AST Transformation

Steps:1. Create your transformation class by implementing

ASTTransformation interface.• specify compile phase

@GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS)

2. Create annotation and link to your transformation class

• @GroovyASTTransformationClass(“full path to your transformation class”)

3. Write client code and annotate your elements (methods, fields, classes etc)

Page 39: Metaprogramming Techniques In Groovy And Grails

Step 1@GroovyASTTransformation (phase = CompilePhase.SEMANTIC_ANALYSIS)class LoggingASTTransformation implements ASTTransformation{

def void visit(ASTNode[] nodes, SourceUnit sourceUnit)• {

def methodList = sourceUnit.ast?.methods.findAll {MethodNode method -> method.getAnnotations(new ClassNode(WithLogging))

• }

methodList.each {MethodNode method ->• Statement startMessage = createPrintlnAst("Starting $method.name")• Statement endMessage = createPrintlnAst("Ending $method.name")

• Statement code = method.getCode()• List existingStatements = code.getStatements()

existingStatements.add(0, startMessage) existingStatements.add(endMessage)

• }• }

• private Statement createPrintlnAst(String message)• {• return new ExpressionStatement(• new MethodCallExpression(• new VariableExpression("this"),• new ConstantExpression("println"),• new ArgumentListExpression(• new ConstantExpression(message)• )• )• )• }

}

AST through sourceUnit

Creating AST for simple statement. YUCK!

Compile Phase

Expression and statements within the method body

Visitor pattern

Page 40: Metaprogramming Techniques In Groovy And Grails

Step 2

@Retention(RetentionPolicy.SOURCE)

@Target([ElementType.METHOD])@GroovyASTTransformationClass([“full path to your transformation class” "])public @interface WithLogging {

}

Page 41: Metaprogramming Techniques In Groovy And Grails

Step 3

@full.path.to.WithLoggingdef sum(List lst){ def total = lst.inject(0) { s, i -> s = s + i } println total

• return total}

Page 42: Metaprogramming Techniques In Groovy And Grails

Examples• @Immutable

– No mutators– All fields must be private and final– All fields must be included in equals, hashCode and toString computation– class must be final

• @Singleton– lazy flavor– static instance

• Grails– @EntityASTTransformation:

• Injects Id, Version, toString and Associations to grails domain classes

Page 43: Metaprogramming Techniques In Groovy And Grails

Final Thoughts on AST Transformations

• Cumbersome to write transformation currently

• Future tooling (Groovy 1.7):– AST Browser– AST Builder

Page 44: Metaprogramming Techniques In Groovy And Grails

Summary of techniques• evaluate(“def add = {x, y -> x + y”)

– Evaluate string as code

• invokeMethod– Intercept all method call (Existing and non existing methods)

• methodMissing– Intercept only non existing methods

• getProperty/setProperty– Intercept property access and assignments

• ExpandoMetaClass– Dynamically add methods, constructors, properties

• Categories– scoped injection

• Runtime Mixins– add methods from other types

• AST Transformations– Transformations on groovy AST

Can be defined on the class itself or on the metaClass

Runtime

Compile time

Page 45: Metaprogramming Techniques In Groovy And Grails

References1. What’s new in Groovy 1.6: http://www.infoq.com/articles/groovy-1-6

2. Hamlet D’Arcy blog: http://hamletdarcy.blogspot.com

3. Book: “Groovy in Action” by Dierk Koenig with Andrew Glover, Paul King, Guillaume Laforge and Jon Skeetsdsd

4. Various examples on http://groovy.codehaus.org


Recommended