Friday, July 30, 2010

Groovy magic

All text and example are from:


Implementing the GroovyObject

Groovy's MOP system includes some extension/hooks points that you can use to change how a class or an object behaves, mainly being
  • getProperty/setProperty: control property access
  • invokeMethod: controls method invocation, you can use it to tweak parameters of existing methods or intercept not-yet existing ones
  • methodMissing: the preferred way to intercept non-existing methods
  • propertyMissing: also the preferred way to intercept non-existing properties

invokeMethod()
In any Groovy class you can override invokeMethod which will essentially intercept all method calls. Simple usage of invokeMethod is to provide simple AOP style around advice to existing methods:
class MyClass implements GroovyInterceptable {
  def invokeMethod(String name, args) {
   
      System.out.println ("Beginning $name")
       def metaMethod = metaClass.getMetaMethod(name, args)
      def result = metaMethod.invoke(this, args)
        System.out.println ("Completed $name")
      return result
  }
} 

getProperty()
You can also override property access using the getProperty and setProperty property access hooks.
class Expandable {
  def storage = [:]
  def getProperty(String name) { storage[name] }
  void setProperty(String name, value) { storage[name] = value }
}

def e = new Expandable()
e.foo = "bar"
println e.foo

methodMissing
class Person {
 String name
 String sayHello( toWhom ){ "Hello $toWhom" }
}

Person.metaClass.methodMissing = { String name, args ->
 "$name() called with $args"
}

def duke = new Person(name:'Duke')
assert duke.sayHello('world') == 'Hello world'
assert duke.greet() == 'greet() called with {}'
assert duke.greet('world') == 'greet() called with {"world"}'



ExpandoMetaClass 

Every object in groovy has a MetaClass, responsible for holding information about the object's class. You can obtain list of methods, add your methods and other meta-programming scenarios.


Finding method
println obj.metaClass.methods
println obj.metaClass.methods.find { it.name.startsWith("to") }

class Foo {
 String prop
 def bar() { "bar" }
 def bar(String name) { "bar $name" }
 def add(Integer one, Integer two) { one + two}
}

def f = new Foo()

if(f.metaClass.respondsTo(f, "bar")) {
 // do stuff
}

if(f.metaClass.respondsTo(f, "bar", String)) {
 // do stuff
}

if(!f.metaClass.respondsTo(f, "bar", Integer)) {
 // do stuff
}

if(f.metaClass.respondsTo(f, "add", Integer, Integer)) {
 // do stuff
}
Note method respondsTo


Finding properties
println obj.metaClass.properties
println obj.metaClass.properties.find { it.name.startsWith("to") }

class Foo {
 String prop
 def bar() { "bar" }
 def bar(String name) { "bar $name" }
 def add(Integer one, Integer two) { one + two}
}

def f = new Foo()
if(f.metaClass.hasProperty(f, "prop")) {
// do stuff
}
Note method hasProperty


Add behavour
import org.apache.commons.lang.StringUtils

String.metaClass.capitalize = {
 StringUtils.capitalize(delegate)
}

String.metaClass.normalize = {
 delegate.split("_").collect { word ->
    word.toLowerCase().capitalize()
 }.join("")
}

assert "Groovy" == "groovy".capitalize()
assert "CamelCase" == "CAMEL_CASE".normalize()

Example2
class Person {
 String name
}

Person.metaClass.greet = {
 "Hello, I'm $name"
}
def duke = new Person(name:'Duke')
assert duke.greet() == "Hello, I'm Duke"
Overload method:
Person.metaClass.whatIsThis = { String arg ->
 "it is a String with value '$arg'"
}
Person.metaClass.whatIsThis << { int arg ->
 "it is an int with value $arg"
}

assert duke.whatIsThis( 'Groovy' ) == "it is a String with value 'Groovy'"
assert duke.whatIsThis( 12345 ) == "it is an int with value 12345"

Inheritance is not enabled by default
class Person {
 String name
}
class Employee extends Person {
 int employeeId
}

Person.metaClass.greet = { -> "Hello!" }

def duke = new Person(name:'Duke')
assert duke.greet() == "Hello!"
def worker = new Employee(name:'Drone1')
// the following line causes an exception!!
assert worker.greet() == "Hello!"

Normally when you add a MetaMethod, it is added for all instances of that class. However, for GroovyObjects, you can dynamically add methods to individual instances by giving that instance its own MetaClass.



Categories

With categories you have control over add methods.
  • any public static method is a candidate to be a category method
  • the type of the first parameter is the target type that may have the new methods
  • there is no need to extend a particular class or implement an interface

if you try to call any of those methods outside use() an exception will be thrown.

No comments:

Post a Comment