Groovy's MarkupBuilder Magic

·

7 min read

Groovy makes it easy to generate xml. Here's an example using MarkupBuilder:

import groovy.xml.MarkupBuilder

new MarkupBuilder().books {
  book {
    name('Groovy in Action, Second Edition')
    authors {
      author('Dierk König')
      author('Paul King')
      author('Guillaume Laforge')
      author("Hamlet D'Arcy")
      author('Cédric Champeau')
      author('Erik Pragt')
      author('John Skeet')
    }
  }
  book {
    name('Making Java Groovy')
    authors {
      author('Ken Kousen')
    }
  }
}

The above code prints the following to System.out:

<books>
  <book>
    <name>Groovy in Action, Second Edition</name>
    <authors>
      <author>Dierk König</author>
      <author>Paul King</author>
      <author>Guillaume Laforge</author>
      <author>Hamlet D'Arcy</author>
      <author>Cédric Champeau</author>
      <author>Erik Pragt</author>
      <author>John Skeet</author>
    </authors>
  </book>
  <book>
    <name>Making Java Groovy</name>
    <authors>
      <author>Ken Kousen</author>
    </authors>
  </book>
</books>

This first time you see code like this, it looks like magic. It's easy enough to write the code, but how does Groovy know what to do with it?

Note that this code uses closures. Closures are used extensively in Groovy, so if you are not familiar with them, it's probably worthwhile to read the docs, but here's the short summary:

A closure in Groovy is an open, anonymous, block of code that can take arguments, return a value and be assigned to a variable.

Also be aware of the following: if you're calling a method and the last parameter is a closure, you can put the closure after the parentheses and if there's only one parameter, even omit the parentheses.

This means that

new MarkupBuilder().books( {...} )

can be written as

new MarkupBuilder().books {...}

Now let's take a closer look at the code. First, we create a new instance of MarkupBuilder and then we call the method books on it, passing it a closure with the rest of the markup information.

Wait a minute! MarkupBuilder does not have a method books!

That's right, but Groovy lets you define a special method called invokeMethod. This method is called whenever a nonexistent method is called on an object. There's even a special term used for a nonexistent method: it's called a pretended method - a method that we pretend exists even though it really doesn't.

For example:

class Animal {

  void sayHi(String greeting) {
    println "$greeting!"
  }

  def invokeMethod(String name, Object args) {
    def greeting = name.capitalize()
    sayHi(greeting)
  }

}

We can use the above code to create different animals and let them say hi:

def dog = new Animal()
def cat = new Animal()
dog.sayHi('Woof')
cat.sayHi('Meow')

This outputs:

Woof!
Meow!

But guess what - since invokeMethod is defined, we can also say hi like this:

dog.woof()
cat.meow()

Since woof is not defined in Animal, Groovy calls invokeMethod with "woof" as the value of the first parameter name. The code then capitalizes the name and passes it to the sayHi method. The output is the same as when we called sayHi directly:

Woof!
Meow!

Notice that invokeMethod has a second parameter: args. This contains an array of the arguments that were passed to the nonexistent method.

Let's look at a simplified version of the MarkupBuilder code:

new MarkupBuilder().books {
  book {
    name('Groovy in Action, Second Edition')
  }
  book {
    name('Making Java Groovy')
  }
}

MarkupBuilder does not contain the method books. So invokeMethod is called like this:

def closure = {
  book {
    name('Groovy in Action, Second Edition')
  }
  book {
    name('Making Java Groovy')
  }
}

invokeMethod('books', [closure])

The invokeMethod method creates an xml element <books>. It then executes the closure. The first line of code in the closure is a call to the pretended method book. So invokeMethod is called once more, this time with "book" as the method name, and the closure { name('Groovy in Action, Second Edition') } as the args. The xml element <book> is created, the closure is executed, and the process is repeated for the entire hierarchy.

Our explanation here is a bit of an oversimplification. The real code in MarkupBuilder is certainly more complex: it has to keep track of which elements were created so that it can also create their closing elements. It also handles indentation. But the basic idea of how it works is straightforward enough - it uses pretended methods.

Full disclosure: MarkupBuilder doesn't actually implement invokeMethod. Instead, it extends groovy.util.BuilderSupport. BuilderSupport is an abstract class which is used by many builders in the Groovy library and has an implementation of invokeMethod. This implementation takes care of recursively processing the closures and as it reaches each level in the hierarchy, it calls the following template methods:

    protected abstract void setParent(Object parent, Object child);
    protected abstract Object createNode(Object name);
    protected abstract Object createNode(Object name, Object value);
    protected abstract Object createNode(Object name, Map attributes);
    protected abstract Object createNode(Object name, Map attributes, Object value);

Since MarkupBuilder extends BuilderSupport, it defines the above template methods instead of defining invokeMethod. If you're interested in diving deeper, check out the source code.