Sunday, January 31, 2010

Simple Groovy POJO Builder

While using Groovy the other day I wanted to use the builder notation on some simple classes I had created.  The Groovy builder classes are very powerful and allow you to create some very nice custom builders - but that was a little more complex than I was wanting.

Given these simple classes:

class Company{
 public def name = ""
 public def deptList = []

 public add(Department d){
  deptList.add(d)
 }
}

class Department{
 public String name = "default"
 public def Employee = []
}

class Employee{
 public String name = "default"
}

I wanted to be able to write something like the following and have the builder dynamically match up all of the methods and properties without me doing anything.

def c = PojoBuilder.build(Company.class){
  name = "Acme"
  Department{
   name = "HR"
   Employee{
    name = "John"
   }
   Employee{
    name = "Jack"
   }
  }
  Department{
   name = "IT"
   Employee{
    name = "Jimmy"
   }
   Employee{
    name = "James"
   }
  }
 }

As it turns out, the code to achieve this is rather short thanks to the powerful features of Groovy. A method similar to the following is a good start for the build method and allows us to call a closure on an instance of our class. From this closure we have direct access to all of the objects properties and methods - so calling name = "Acme" just works.

class PojoBuilder{

 public static Object build(Class cls, Closure c){
  Object o = cls.newInstance()
  c.delegate = o
  c.resolveStrategy = Closure.DELEGATE_FIRST
  c.call()
  return o
 }


An interesting feature of this is that the Department {...} syntax looks like a method call with a closure as a single parameter. If our Company class had a method
Department(Closure c)
that method would get called... but we don't want to have to add methods like that to our simple classes.

Luckily Groovy has a missingMethod functionality on the MetaClass which gets called when any method is missing. We can use that to add in our own method which can find a class matching the name, check for any method called "add" that accepts our found class, and optionally any properties with the same name that are an ArrayList. If found, dynamically add that method to our class and call it.

The complete example can be found here. It also includes a nice feature where you pass in the classes you want to use and the names you want to use in your builder syntax.