Builders are Groovy’s bag
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?
| Related odds and ends | ||
|---|---|---|
10 comments Friday 06 Jul 2007 | Groovy, Languages
10 Responses to “Builders are Groovy’s bag”
Builders are great, as is Groovy.
But the same things said about creating maps with syntactic sugar (who creates data with programming code beside unit tests?) can be said for builders to create data structures.
In real applications, data is not entered with hand crafted XML or with names=['stephan'] syntactic sugar (or with builders). Data is entered with a GUI or read from a database or imported with XML (which was not generated by hand but by some export mechanism in another application).
Otherwise, good post about builders, which rock.
Peace
-stephan
–
Stephan Schmidt :: stephan@reposita.org
Reposita Open Source – Monitor your software development
http://www.reposita.org
Blog at http://stephan.reposita.org – No signal. No noise.
Great post Andy! it should help spread the word on builders in general and Groovy too!
Stephan, it’s arguable that “real applications” will process entered data (either from UI or a datasource, typical scenarios) and do not post process it in any way. Builders may come in handy doing some of those post processing actions. And there are also other builders that will help you configure your app in a (perhaps) simpler manner because its done with code rather than a textual format, for example the BeansBuilder (Spring configuration) and the new ConfigSlurper (java Properties).
http://groovy.codehaus.org/ConfigSlurper
http://grails.codehaus.org/Spring+Bean+Builder
Peace
Andres
Fashion Magazine Online…
I couldn’t understand some parts of this article, but it sounds interesting…
[...] Indeed, now that DSLs are all the rage these days (I’d go so far as to label them hip, baby), “Generating Parsers with JavaCC” can easily enable adventurous types to assemble mini-languages (and obviously parse and handle them via JavaCC). Because it’s his bag, Tom does a great job in chapter 11 of enumerating a few examples of doing so, in fact. What’s particularly amusing for me is that he shows an example of parsing Apache’s web logs. [...]
[...] As is the case for a lot of things in Groovy, defining DSLs, in this case, internal DSLs, couldn’t be any easier, man. For example, the base format for defining a specification is as follows: [...]
[...] So I tinkered around in Java to see what’s the best MarkupBuilder I can write with Java. Using DSLs and Fluent Interfaces (times(5).loop), I came up with a nice solution. It’s not Groovy, but it works. [...]
the muscle system…
EvoPro? includes these alpha and beta micellar caseins, Alpha-Lactalbumin, Purified Bovine Colostrum Extract (rich in Secrotory IgA and IGF-1),…
So I tinkered around in Java thanks.
The good !
Very nice article! Thanks for this!
good job. thanks for all