Thursday, March 13, 2008

Dynamic Method Calls with Groovy

Thanks to some help from James Lorenzen, Chad Gallemore and Travis Chase, ...

A couple of days ago I found myself iterating through a set of key=value pairs, and for each one calling a setter method on another object. In order to know which setter method to call, I had to examine the key. That's when it hit me... What if I could use the key to generate the setter method to call. Check this out.

I've got a simple bean with 2 member variables: username and password. There are corresponding setter methods: setUsername() and setPassword().


class UserBean {
private String username;
private String password;

void setUsername(String username) {
this.username = username
}

void setPassword(String password) {
this.password = password
}
}

Here I have a HashMap that I want to iterate through. It just so happens the keys in the map entries correspond exactly to the member variables of my simple bean. So, instead of examining each key to decide what setter method to call, I can do this:

import junit.framework.TestCase
public class DynamicMethodBuildingTest extends TestCase {

void testBuildMethodsBasedOnMapKeys() {

def testMap = new HashMap();
testMap.put("username","chad")
testMap.put("password","asdfasdf")

def userbean = new UserBean();

testMap.entrySet().each { entry ->
userbean."$entry.key" = entry.value
}
}
}

At this point I'm not really building the method that is being called. I'm building the property name. So, let's look at actually building a method call.

I've added 2 methods to my UserBean: login() and logoff().

void login() {
println username + " has Logged In"
}

void logoff() {
println username + " has Logged Off"
}

Let's test calling these 2 methods dynamically.

void testExecuteUserAction() {
def userActions = ["login", "logoff"];

def userbean = new UserBean()
userbean.username = "chad"

userActions.each { action ->
userbean."$action"()
}
}

Here's the output:

chad has Logged In
chad has Logged Off

Well, that's all it takes. And if you're asking, "What about a method that takes parameters?"... simply put your parameters inside the parenthesis. It may look something like this:

userbean."$action"(username)


Now I wonder, what would it take to accomplish this in Java?

6 comments:

Andres Almiray said...

The key/property mapping may also be shorter if you use a closure that takes two parameters like

testMap.entrySet().each { key, value ->
userbean."$key" = value
}

In straight Java you would need to resort to:
a) reflection tricks
b) use apache commons/-lang/-collections/-beanutils
c) wait for jdk7 and closures

Good thing you can use Groovy right now and continue with the next task =-)

Joe Kueser said...

Chad,

What you are doing here with Groovy can be done "real easy" with reflection (as Andres points out). I did this with our infamous AbstractTableBean. If I remember right, it took 7 or 8 lines of super voodoo Java code to do what you are doing in one line (and less) of Groovy code.

Hmmm...I wonder if anyone would notice if I rewrote the AbstractTableBean in Groovy. Muuuahahahahahaha!

Pascal said...

You can even simplify it greatly and just write:

def userbean = new UserBean (testMap)

or:

def userBean = testMap as UserBean

or in one line

def userBean = [username:'chad', password:'asdfasdf'] as UserBean

So Groovy ....

Chad Sturtz said...

@andres, pascal

Thanks for the tips! I'm just getting acquainted with Groovy and haven't yet introduced myself to all its features.

Peter Backlund said...

And of course, the UserBean could be simply:

class UserBean {
String username
String password
}

since properties are built into Groovy (not the same as public fields).

http://groovy.codehaus.org/Groovy+Beans

Wireless said...

Hello. This post is likeable, and your blog is very interesting, congratulations :-). I will add in my blogroll =). If possible gives a last there on my blog, it is about the Wireless, I hope you enjoy. The address is http://wireless-brasil.blogspot.com. A hug.