Wednesday, April 15, 2009

Injecting methods in Groovy with ExpandoMetaClass

I am writing a Twitter client in Groovy. One of the things I want to do is create a thread which will poll Twitter for new updates at regular intervals, and go to sleep when it is not doing anything.

The way we write such code is to invoke the sleep method with the number of milliseconds to sleep. To sleep for 2 minutes, I would write:

Thread.sleep(1000 * 60 * 2)

This code is fine, but not very readable. I would have preferred to write something like this:

Thread.sleep(2.minutes())

This is not possible in Java, but it is in Groovy. In Groovy we can inject methods into a class at runtime. This is done using the class' ExpandoMetaClass, as the example below shows.

Integer.metaClass.seconds = {return delegate * 1000}
Integer.metaClass.minutes = {return delegate * 1000 * 60}
Integer.metaClass.hours = {return delegate * 1000 * 60 * 60}
//test
assert 3.seconds() == 1000 * 3
assert 5.minutes() == 1000 * 60 * 5
assert 4.hours() == 1000 * 60 * 60 * 4

The above code gets Integer's metaclass (the ExpandoMetaClass) and adds methods by setting certain properties to closures. Here the name of the property is the name of the method we want to inject, and the closure represents the code that will be invoked when the method is called. Finally the 'delegate' in the closure in this context refers to the object on which we call the method.

The ExpanoMetaClass also has a DSL to make it simpler to add methods. The example above can be rewritten as follows, thus saving a few keystrokes.

Integer.metaClass {
seconds {return delegate * 1000}
minutes {return delegate * 1000 * 60}
hours {return delegate * 1000 * 60 * 60}
}

No comments: