Tuesday, 29 April 2008

Making your own builder

I've been playing around with implementing a custom builder the last few days. There are some examples on how to do this on the groovy site, but a fews things weren't immediately obvious, so I thought I give a full example and point a couple of things out.

The problem:

I wanted an easy way of building a graph of objects based on the NavElement class below from some sort of configuration.
class NavElement {

NavElement parent
String name
String controller
String action
List children = []

boolean selected

boolean equals(other) {
// omitted
}

int hashCode() {
// omitted
}
}
In the past, XML would have been the obvious format for the configuration, which would then be turned into java objects via something like commons digester.
<navbar>
<navelement name="Create">
<navelement name="Article" controller="article" action="create">
</navelement>
<navelement name="'Admin'" role="administrator">
<navelement name="Create" controller="user" action="create">
<navelementt name="List" controller="user" action="list">
</navelement>
</navbar>
However, now we are Groovy, some new options present themselves. Firstly, we can represent the XML in groovy code and use MarkupBuilder to turn the config code into XML, if we prefer.
navBar {
navElement(name:'Create')
navElement(name:'Article', controllerName:'article', action:'create')
}
navElement(name:'Admin') {
navElement(name:'Create', controllerName:'user', action:'create')
navElement(name:'List Users', controllerName:'user', action:'create')
}
}
Obviously there is no advantage to this in this particular case - we are not dynamically building the closure for example. In fact now we now have the extra, albeit trivial, task of converting the groovy config closure to XML.

However, if we could use the same groovy config closure to generate the object graph directly with our own custom builder, we can save ourselves this extra step AND the need for marshalling code from XML to our object graph. We can also make the groovy config closure less verbose. Let's look at how we might simplify groovy config closure.
navBar {
create {
Article(controllerName:'article', action:'create')
}
Admin {
Create( controllerName:'user', action:'create')
'List Users'(controllerName:'user', action:'create')
}
}
Now we need to implement a builder that can turn this config closure into our object graph. To do this, the groovy api provides an abstract BuilderSupport class to extend and implement. There are a couple of examples about this class on the groovy website.

Make a builder
Builder Support

So here is the Builder implementation for our example.

class NavBuilder extends BuilderSupport {

public static NavBuilder newInstance() {
return new NavBuilder();
}

protected void setParent(Object parent, Object child) {
child.parent = parent
}

protected Object createNode(Object name) {
createNode name, null, null
}

protected Object createNode(Object name, Object value) {
createNode name, null, value
}

protected Object createNode(Object name, Map attrs) {
createNode name, attrs, null
}

protected Object createNode(Object name, Map attrs, Object value) {
def node = new NavElement(name: name)
if (attrs) {
attrs.each {property, val ->
if (node.properties.keySet().contains(property)) {
node."${property}" = val
}
}
}

if (getCurrent()) {
getCurrent().children << node
}

return node
}
}



There are a few things to take note of

1. The createNode methods

These methods are called as each node in the closure is evaluated. In our example, which is fairly simple, we delegate all the over-loaded methods to the most complex version. This simply news up a NavElement based on the node name and sets the properties on the new NavElement. You'll note we do nothing with value parameter. We rely on the BuilderSupport class to take care of the recursive calls to the createNode for the nested elements. I have highlighted in the closure snippet below how the parameters, name, attributes and value, map to the closure elements for a particular node.

navBar(controllerName:'home',  action:'index') {
Admin {
Create( controllerName:'user', action:'create')
'List Users'(controllerName:'user', action:'create')
}
}


2. The getCurrentNode method

This returns the current node, which returns the current node in the closure that is being evaluated. In our example, we need to use this method to add any nodes that are being created to the children of the currentNode.

3. The setParent method. This is a convenient hook to allow us to set the parent of any node we are creating.

That's it! Here is a bit of test code that proves it works

class NavBuilderTests extends GroovyTestCase {

void testBuildingOfNavElements() {

navBuilder = new NavBuilder()

navBar =
navBuilder.navBar() {
publisher()
create {
'create article'(controller:'articleController', action:'create')
}
}

assertEquals ('navBar', navBar.name)

assertNotNull navBar.children
assertEquals 2, navBar.children.size()
assertEquals ('publisher', navBar.children[0].name)
assertEquals ('navBar', navBar.children[0].parent.name)

def createNavElement = navBar.children[1]
assertEquals ('create', createNavElement.name)
assertEquals ('navBar', createNavElement.parent.name)
assertEquals 1, createNavElement.children.size()

def articleCreateNavElement = createNavElement.children[0]
assertEquals ('create article', articleCreateNavElement.name)
assertEquals ('articleController', articleCreateNavElement.controller)
assertEquals ('create', articleCreateNavElement.action)
assertEquals ('create', articleCreateNavElement.parent.name)
}
}

No comments: