Builders are Groovy?s bag - No Fluff Just Stuff

Builders are Groovy?s bag

Posted by: Andrew Glover on July 5, 2007

Without a doubt, DSLs are all the rage these days (in fact, if their popularity continues to skyrocket, they’ll easily become more hip than shiny clinging Lycra stretch disco pants in hot strident shiny colors with stretch sequin bandeau tops…as if, man). In particular, Dynamic languages (like Groovy and Ruby) gain a lot of their credibility through their facilitation of creating DSLs easily. There are certainly different levels of sophistication when it comes to defining a DSL too.

For example, Rake, Ruby’s make is essentially a DSL for building applications with a simple, yet powerful syntax, man.

task :name => [:dependencies] do
  #stuff
end

Because Rake files are Ruby files, you can do a whole lot of magic with Rake that, for example, isn’t quite possible with XML (hence, some people find themselves frustrated with Ant). Groovy, too, has brought to bear the power of DSLs in defining an alternative to Ant, dubbed Gant, which has a similar syntax for building applications:

target (target-name:target-description) {
  //do stuff
}

The ability to create DSLs involves a number of different techniques as well (depending on the sophistication of the DSL under development); however, with Groovy, you can create an intuitively simple one using the BuilderSupport object, man.

For example, let’s imagine you’ve been tasked with building an astronomy application for students to explore the relationship between planets and their associated moons (or satellites) and you need a way for Astronomy professors to define a planetary system (for a given planet, there are x moons with differing properties). You could create a text file or an XML file and go from there (but that wouldn’t be copasetic these days would it?) or you could define a mini-language, if you will, that enables a domain expert to define a planet and its associated satellites.

After working with your clients (in this case, wacky professors with two left shoes on), you decide on a simple structure that facilitates defining a planet (along with some properties of it), which supports attaching a series of children elements, which represent moons (and are also defined with the same properties as a planet).

PlanetName(Distance:x, OrbitPeriod:y, Radius:z){
  MoonName(Distance:d, OrbitPeriod:o, Radius:r)
  // 0..N moons possible
}

In the structure above, distance is assumed to be kilometers, the orbit period is in earth days, and the radius is also in kilometers.

For example, given the structure above, one could define Jupiter and a sampling of its moons as follows:

Jupiter(Distance: 778412020.00, OrbitPeriod: 4330.60, Radius: 71492.00){
  Callisto(Distance: 1883000.00, OrbitPeriod: 16.69, Radius: 2403.00)
  Ganymede(Distance: 1070000.00, OrbitPeriod: 7.15, Radius: 2634.00)
  Pasiphae(Distance: 23500000.00, OrbitPeriod: -735.00, Radius: 18.00)
  Sinope(Distance: 23700000.00, OrbitPeriod: -758.00, Radius: 14.00)
}

The good news at this point is that the hard work is already done– the simplistic DSL has been defined and is, luckily, quite easy in structure. The key to finishing the job is via Groovy’s BuilderSupport class, which is a normal Java object that you can extend and then implement a series of template methods that are executed a specific points. You’ll also have to consider how one obtains the final hip product– in my case, I’d like a PlanetarySystem object (which was defined in a previous post). Accordingly, I’ll create an instance of this object upon the discovery of the first node when someone invokes the builder instance. Then for each child node, I’ll create a Moon instance and add it to the PlanetarySystem.

My initial object looks like the following code snippet– note this is a normal Java object (as opposed to a Groovy one).

public class PlanetarySystemBuilder extends BuilderSupport {
 private PlanetarySystem system;

 //...template methods 

 public IPlanetarySystem getPlanetarySystem() {
  return this.system;
 }
}

As you can see, the main point of the code above is the fact that after the builder has been created, one can obtain an instance of a PlanetarySystem via the getPlanetarySystem method.

The BuilderSupport object has a number of abstract methods that you must implement, however, as it turns out for my simple DSL, I only needed to add meat to one method– the createNode(Object name, Map map), which is invoked anytime a node is found that has a format like name(map), such as Io(Distance: 422000.00, OrbitPeriod: 1.77, Radius:0.00). It also doesn’t matter, in this Age of Aquarius, at which depth a particular node like this is found– you can actually determine depth by watching corresponding nodeCompleted methods invoked.

Since my simple little language doesn’t have too much variance in structure, it turns out that implementing the createNode method is sufficient for my needs:

protected Object createNode(Object name, Map map) {
 if (this.system == null) {
   this.system = new PlanetarySystem((String) name,
       ((BigDecimal) map.get("Distance")).doubleValue(),
       ((BigDecimal) map.get("OrbitPeriod")).doubleValue(),
       ((BigDecimal) map.get("Radius")).doubleValue());
 }else{ // must be a moon
   Moon moon = new Moon((String) name,
       ((BigDecimal) map.get("Distance")).doubleValue(),
       ((BigDecimal) map.get("OrbitPeriod")).doubleValue(),
       ((BigDecimal) map.get("Radius")).doubleValue());
   this.system.addMoon(moon);
 }
 return name;
}

As you can see from the boss code above, the first parameter name (of type Object) represents the node’s actual name– either a planet or a moon. Basically, my logic is simple (which of course is probably a bit brittle, but good enough for now)– if the PlanetarySystem object is null (meaning the builder instance is new), then I assume that the first node is a planet and then all children are moons associated with that planet. The second parameter, map (of type Map) contains all the smokin’ pairs (y:x) in the node’s definition. If a value has a decimal (such as 19.13), BuilderSupport will create a BigDecimal instance and place it in the map with its related name (i.e. OrbitPeriod: 1.77).

With this method defined, I can then create an instance of my PlanetarySystemBuilder and start to define a system (i.e. a planet with its moons). For instance, I can create Jupiter (again) and a few of its moons as follows:

def builder = new PlanetarySystemBuilder()

builder.Jupiter(Distance:778412020.00, OrbitPeriod: 4330.60, Radius: 71492.00){
  Io(Distance: 422000.00, OrbitPeriod: 1.77, Radius:1815.00)
  Europa(Distance: 671000.00, OrbitPeriod: 3.55, Radius: 2634.00)
  Callisto(Distance: 1883000.00, OrbitPeriod: 16.69, Radius: 2403.00)
  Ganymede(Distance: 1070000.00, OrbitPeriod: 7.15, Radius: 2634.00)
  Pasiphae(Distance: 23500000.00, OrbitPeriod: -735.00, Radius: 18.00)
  Sinope(Distance: 23700000.00, OrbitPeriod: -758.00, Radius: 14.00)
}

Once the tripping builder has been defined, I can then ask for a built PlanetarySystem via the getPlanetarySystem; plus, I can even assert that things worked out:

def system = builder.getPlanetarySystem()
assert system.fastestOrbitingSatellite().getName() == "Io" : "builder didn't work"
assert system.closestSatellite().getName() == "Io" : "builder didn't work"

Of course, the next step would be to make the builder object a bit more robust– as defined it is brittle in a number of ways (forget a decimal and it’ll blow up, try and add another planet and you’ll see it as a moon, etc). After a few more test cases, I’ll be ready to refactor, man.

As you can see, builders in Groovy are a cinch to define– once you see how each neat-o template method is logically called upon the discovery of a node, you only need to figure out how to maintain state of what you’re trying to build– in my case, it turned out to be quite simple (one object with a collection as one of its properties). More intricate definitions, of course, will require a bit more muscle. The next time you find yourself dealing with a hierarchical structure, give Groovy’s BuilderSupport a shot. Dig it?

Accelerate development space | I also blog at testearly.com
The Continuous Integration book | Groovy in Action

Andrew Glover

About Andrew Glover

Andrew is the Engineering Manager for Netflix's Delivery Engineering Team. He and his team are building the next generation Continuous Delivery platform that is facilitating Netflix's rapid global expansion. Before joining Netflix, he served as the CTO of App47, where he lead the development of a SaaS Mobile Application Management platform. Andrew is also the co-author of Addison Wesley's “Continuous Integration” and he actively blogs about software at thediscoblog.com.

Why Attend the NFJS Tour?

  • » Cutting-Edge Technologies
  • » Agile Practices
  • » Peer Exchange

Current Topics:

  • Languages on the JVM: Scala, Groovy, Clojure
  • Enterprise Java
  • Core Java, Java 8
  • Agility
  • Testing: Geb, Spock, Easyb
  • REST
  • NoSQL: MongoDB, Cassandra
  • Hadoop
  • Spring 4
  • Cloud
  • Automation Tools: Gradle, Git, Jenkins, Sonar
  • HTML5, CSS3, AngularJS, jQuery, Usability
  • Mobile Apps - iPhone and Android
  • More...
Learn More »