Groovy gives us a wide variety of choices for meta-programming. We've looked at Categories and Interceptors, which change the behavior of objects within a selected block and current thread only, in other tutorials. In this tutorial, we'll learn about more ways of meta-programming in Groovy.

Intercepting Method Calls and Property Accesses

We can add a special method called 'invokeMethod' to a class definition that executes calls to undefined methods:

class MyClass{
  def hello(){ 'invoked hello directly' }
  def invokeMethod(String name, Object args){
    return "unknown method $name(${args.join(', ')})"
  }
}
def mine= new MyClass()
assert mine.hello() == 'invoked hello directly'
assert mine.foo("Mark", 19) == 'unknown method foo(Mark, 19)'

If our class implements GroovyInterceptable, invokeMethod is called for all method invocations whether they exist or not:

class MyClass implements GroovyInterceptable{
  def hello(){ 'invoked hello() directly' }
  def invokeMethod(String name, Object args){
    "invoked method $name(${args.join(', ')})"
  }
}
def mine= new MyClass()
assert mine.hello() == 'invoked method hello()'
assert mine.foo('Mark', 19) == 'invoked method foo(Mark, 19)'

assert mine.&hello() == 'invoked hello() directly'
    //we can still invoke a method directly using .& syntax

We can get and set properties using special method names:

class MyClass{
  def greeting= 'accessed greeting directly'
  Object getProperty(String property){
    "read from property $property"
  }
  void setProperty(String property, Object newValue){
    throw new Exception("wrote to property $property")
  }
}
def mine= new MyClass()
assert mine.greeting == 'read from property greeting'
try{
  mine.greeting= 'hi'
}catch(e){ assert e.message == 'wrote to property greeting' }

assert mine.@greeting == 'accessed greeting directly'
    //we can access a property directly using .@ syntax

When there's a field of some name, refering to that name still considers it to be a property unless the syntax .@ is used:

class MyClass{
  public greeting= 'accessed field greeting (directly)' //field, not property
  Object getProperty(String property){
    "read from property $property"
  }
}
def mine= new MyClass()
assert mine.greeting == 'read from property greeting'
assert mine.@greeting == 'accessed field greeting (directly)'

We can call methods and access properties directly, both statically and dynamically, from within the class using various syntaxes:

class MyClass implements GroovyInterceptable{
  def greeting= 'accessed greeting'
  def id= 'White: '

  Object getProperty(String property){
    try{
      return this.@id + //access field directly
             'indirectly ' +
             this.@"$property" //access field directly and dynamically
    }catch(e){
      return "no such property $property"
    }
  }

  def hello(Object[] args){ "invoked hello with (${args.join(', ')})" }
  def id(){ 'Green: ' }

  def invokeMethod(String name, Object args){
    try{
      return this.&id() + //call method directly
             'indirectly ' +
             this.&"$name"(args) //call method directly and dynamically
    }catch(e){
      return "no such method $name"
    }
  }
}

def mine= new MyClass()
assert mine.greeting == 'White: indirectly accessed greeting'
assert mine.farewell == 'no such property farewell'

assert mine.hello(1, 'b', 3) == 'Green: indirectly invoked hello with (1, b, 3)'
assert mine.foo('Mark', 19) == 'no such method foo'

If we add such 'invokeMethod', 'getProperty', or 'setProperty' methods to an object using Expando or Category syntax, they act just like normal methods. Not many supplied classes have 'invokeMethod' and such defined. For such cases, we need to use MetaClasses.

MetaClasses

We've seen how classes behave with the default MetaClass:

class A{
  def bark(){ 'A: invoked bark()' }
  def invokeMethod(String name, Object args){
    "A: missing $name(${args.join(', ')})"
  }
}
def a= new A()
assert a.bark() == 'A: invoked bark()'
assert a.bleet() == 'A: missing bleet()'

We can create our own MetaClass which wraps around the existing one. DelegatingMetaClass provides the infrastructure for this, so we only need extend it with our own logic. We can do so on an instance-by-instance basis:

public class MyMetaClass extends DelegatingMetaClass{
  MyMetaClass(Class theClass){
    super(theClass)
  }
  Object invokeMethod(Object object, String methodName, Object[] arguments){
    "MyMetaClass: ${super.invokeMethod(object, methodName, arguments)}"
  }
}

public class MyOtherMetaClass extends DelegatingMetaClass{
  MyOtherMetaClass(Class theClass){
    super(theClass)
  }
  Object invokeMethod(Object object, String methodName, Object[] arguments){
    "MyOtherMetaClass: ${super.invokeMethod(object, methodName, arguments)}"
  }
}

class A{
  def bark(){ 'A: invoked bark()' }
  def invokeMethod(String name, Object args){
    "A: missing $name(${args.join(', ')})"
  }
}

def amc= new MyMetaClass(A)
amc.initialize()
def a= new A()
a.metaClass= amc
    //using metaClass property on an instance affects only that instance...

def amc2= new MyOtherMetaClass(A)
amc2.initialize()
def a2= new A()
a2.metaClass= amc2

assert a.bark() == 'MyMetaClass: A: invoked bark()'
assert a2.bark() == 'MyOtherMetaClass: A: invoked bark()'

Thread.start{                                   //...even in a new thread
  assert a.bark() == 'MyMetaClass: A: invoked bark()'
  assert a2.bark() == 'MyOtherMetaClass: A: invoked bark()'
}

assert new A().bark() == 'A: invoked bark()'
    //new instances don't have new MetaClass

assert a.bleet() == 'A: missing bleet()'
    //MetaClass invokeMethod() NOT called here

Or we can do so on a class-wide basis:

public class MyMetaClass extends DelegatingMetaClass{
  MyMetaClass(Class theClass){
    super(theClass)
  }
  Object invokeMethod(Object object, String methodName, Object[] arguments){
    "MyMetaClass: ${super.invokeMethod(object, methodName, arguments)}"
  }
}

class A{
  def bark(){ 'A: invoked bark()' }
  def invokeMethod(String name, Object args){
    "A: missing $name(${args.join(', ')})"
  }
}

def amc= new MyMetaClass(A)
amc.initialize()

def a= new A()

import org.codehaus.groovy.runtime.InvokerHelper
InvokerHelper.instance.metaRegistry.setMetaClass(A, amc)
    //all newly-created instances of A after this call will be affected

assert a.bark() == 'A: invoked bark()' //created before so old MetaClass used
assert a.bleet() == 'A: missing bleet()'

assert new A().bark() == 'MyMetaClass: A: invoked bark()' //new MetaClass used
Thread.start{
  assert a.bark() == 'A: invoked bark()' //old MetaClass used
  assert new A().bark() == 'MyMetaClass: A: invoked bark()' //new MetaClass used
}

Classes we define ourselves return a MetaClass when accessing the metaClass property, but many Groovy-supplied classes don't. There's only one instance of a MetaClass in such cases:

class A{}
assert new A().metaClass.class == MetaClassImpl
assert new ArrayList().metaClass.class == ArrayList //class itself returned

When we use Groovy-supplied classes without their own MetaClass, both already-created and newly-created classes are affected by changes to the MetaClass:

public class MyMetaClass extends DelegatingMetaClass{
  MyMetaClass(Class theClass){
    super(theClass)
  }
  Object invokeMethod(Object object, String methodName, Object[] arguments){
    "MyMetaClass: ${super.invokeMethod(object, methodName, arguments)}"
  }
}

def amc= new MyMetaClass(ArrayList)
amc.initialize()

def list1= [1, 2, 3]

import org.codehaus.groovy.runtime.InvokerHelper
InvokerHelper.instance.metaRegistry.setMetaClass(ArrayList, amc)
    //all instances of ArrayList will be affected, even already created ones

assert list1.join(',') == 'MyMetaClass: 1,2,3'
    //new MetaClass used with already created ArrayList

def list2= [4, 5, 6]
assert list2.join(',') == 'MyMetaClass: 4,5,6'
    //new MetaClass used with newly created ArrayList

//even in new Thread...
Thread.start{
  assert list1.join(',') == 'MyMetaClass: 1,2,3' //new MetaClass used
  assert list2.join(',') == 'MyMetaClass: 4,5,6' //new MetaClass used
  assert [7, 8, 9].join(',') == 'MyMetaClass: 7,8,9' //new MetaClass used
}

Other methods besides invokeMethod are available on the MetaClass:

  Object invokeStaticMethod(Object object, String methodName, Object[] arguments)
  Object invokeConstructor(Object[] arguments)
  Object getProperty(Object object, String property)
  void setProperty(Object object, String property, Object newValue)
  Object getAttribute(Object object, String attribute)
  void setAttribute(Object object, String attribute, Object newValue)
  Class getTheClass()

For example, making the constructor return an instance of something other than what we called the constructor on:

public class MyMetaClass extends DelegatingMetaClass{
  MyMetaClass(Class theClass){
    super(theClass)
  }
  Object invokeConstructor(Object[] arguments){
    []
  }
}
class A{}

def amc= new MyMetaClass(A)
amc.initialize()
import org.codehaus.groovy.runtime.InvokerHelper
InvokerHelper.instance.metaRegistry.setMetaClass(A, amc)

def a= new A()
assert a.class == ArrayList
assert ( a << 1 << 2 << 3 ).size() == 3

ExpandoMetaClass

There's some easy-to-use facilities available through the MetaClass, known as ExpandoMetaClass, to which we can add properties and methods easily:

class A{
  String text
}
def a1= new A(text: 'aBCdefG')
assert a1.metaClass.adaptee.class == MetaClassImpl //usual MetaClass type

A.metaClass.inSameCase= {-> text.toUpperCase()}
    //triggers conversion of MetaClass of A to ExpandoMetaClass
    //then adds new instance method 'inUpperCase' to class

def a2= new A(text: 'hiJKLmnOp')
assert a2.metaClass.adaptee.getClass() == ExpandoMetaClass
  //MetaClass of A changed for instances created after conversion trigger only
assert a2.inSameCase() == 'HIJKLMNOP'

assert a1.metaClass.adaptee.class == MetaClassImpl //still usual MetaClass type
try{ println a1.inSameCase(); assert false }
catch(e){ assert e in MissingMethodException } //new method not available

A.metaClass.inLowerCase= {-> text.toLowerCase()}
assert a2.inLowerCase() == 'hijklmnop'

//we can replace the method definition with another
A.metaClass.inSameCase= {-> text.toLowerCase()}
assert a2.inSameCase() == 'hijklmnop'

//we can add static methods...
A.metaClass.'static'.inSameCase= { it.toLowerCase()}
assert A.inSameCase('qRStuVwXyz') == 'qrstuvwxyz'

We can also add properties and constructors:

class A{}

//we can let ExpandoMetaClass manage the properties...
A.metaClass.character = 'Cat in the Hat' //add property 'character'

def a1= new A()
assert a1.character == 'Cat in the Hat'

//...or we can manage the properties ourselves...
def ourProperties = Collections.synchronizedMap([:])
          //see tutorial on Multi-Threading to learn about synchronized objects
A.metaClass.setType= { String value ->
   ourProperties[ "${delegate}Type" ] = value
}
A.metaClass.getType= {->
   ourProperties[ "${delegate}Type" ]
}
a1.type= 'Hatted Cat'
assert a1.type == 'Hatted Cat'

//we can add our own constructors...
def a2= new A()
A.metaClass.constructor= {-> new A() }
try{
  a2= new A() //be careful when overriding default or existing constructors
  assert false
}catch(Error e){ assert e in StackOverflowError }

A.metaClass.constructor= {-> new A() }
try{
  A.metaClass.constructor << {-> new A() } 
                    // << notation doesn't allow overriding
  assert false
}catch(e){ assert e in GroovyRuntimeException }

A.metaClass.constructor= { String s-> new A(character: s) }
a2 = new A("Thing One")

//We can quote method and property names...
A.metaClass.'changeCharacterToThingTwo'=
                        {-> delegate.character = 'Thing Two' }
a2.character= 'Cat in the Hat'
a2.changeCharacterToThingTwo()
assert a2.character == 'Thing Two'

//...which is handy for dynamically constructing method/property names...
['Hatted Cat', 'Thing', 'Boy', 'Girl', 'Mother'].each{p->
  A.metaClass."changeTypeTo${p}"= {-> delegate.type= p}
}
a2.changeTypeToBoy()
assert a2.type == 'Boy'

a2.'changeTypeToHatted Cat'()
assert a2.type == 'Hatted Cat'

We can also add methods for supplied Groovy classes, ones we don't define ourselves:

ExpandoMetaClass.enableGlobally()
        //call 'enableGlobally' method before adding to supplied class
List.metaClass.sizeDoubled = {-> delegate.size() * 2 }
    //add method to an interface
def list = [] << 1 << 2
assert list.sizeDoubled() == 4

We can override MetaClass class methods such as 'invokeMethod' and 'getProperty' using ExpandoMetaClass's easy syntax:

class Bird{
  def name= 'Tweety'
  def twirp(){ 'i taught i saw a puddy cat' }
}
Bird.metaClass.invokeMethod= {name, args->
  def metaMethod= Bird.metaClass.getMetaMethod(name, args) 
        //'getMetaMethod' gets method, which may be an added or an existing one
  metaMethod? metaMethod.invoke(delegate,args): 'no such method'
}
def a= new Bird()
assert a.twirp() == 'i taught i saw a puddy cat'
assert a.bleet() == 'no such method'

Bird.metaClass.getProperty= {name->
  def metaProperty= Bird.metaClass.getMetaProperty(name) 
    //'getMetaProperty' gets property, which may be an added or an existing one
  metaProperty? metaProperty.getProperty(delegate): 'no such property'
}
def b= new Bird()
assert b.name == 'Tweety'
assert b.filling == 'no such property'