seemingly random and unorgainzed bits of information
3.04.2008
Using Groovy's FactoryBuilderSupport to Build Internal DSL
Using Groovy's FactoryBuilderSupport to Build Internal DSL
Couple of weeks ago at the Groovy/Grails Experience conf, Groovy's meta programming was a big theme there. Since then, I blogged about using Groovy's built-in Meta Object Protocol (MOP) mechanism to create a simple XML builder. Then I got curious about other builders such as Swing and SwingX. Talking to James Williams (SwingXBuilder creator), I found out that Groovy's FactoryBuilderSupport interface is designed specifically to allow developers create builder-style DSL. While the FactoryBuilderSupport provides a rich interface to create your own DSL, examples are hard to come by. Thankfully, James had just created another builder called Java3DBuilder which uses the FactoryBuilder interface and a good starting point. This shows a small example on using the FactoryBuilderSupport.
The FactoryBuilderSupport along with Groovy's Factory interface offer a structured approach to constructing internal DSL's. Unlike tapping directly into MOP to manage your DSL nodes, the factory approach requires you to explicitly register by name each node with the FactoryBuilder. The Factory interface exposes event callbacks to intercept different phases of node traversal by the FactoryBuilder which lets manage state and behavior of your DSL. Here is how Groovy's website describes the Factory interface and its methods:
A quick note about newInstanceMethod() - when the factory builder creates a new instance of your node, it will pass in these params: an instance of the factory builder, the name of the node being processed, an argument value (if used as the form node(arg) ), and optionally a map if the node is used as node(key:value...).
Saying Hello, the DSL Way
So, using the information above (and some some sleuthing on my part), I was able to figure out how to use the FactoryBuilderSupport class to build a simple DSL to display a greeting message. So, the first thing is to specify how the language will look and feel. Since we are using the builder pattern, our DSL will have the familiar construct we have seen in other builders:
builder.using(a given language) {
greet(person1)
greet(person2)
greet(person4)
...
greet(personN)
}
A call to builder.build() will print out the greeting in the proper language.
The Code
First, let us look at the backing structure that will support our DSL. It consists of a Person object and GroupGreeter class. The latter actually stores the persons we want to greet and apply the logic to build the greeting string.
Disclaimer: I am not a language geek. This translation is straight from Google Language Tools . So, I am not responsible for greetings that are colloquially incorrect or used in the wrong context!
On to the Builder
The next code snippet shows the FactoryBuilderSupport and the factories that it manages. Each node in your DSL is implemented by a factory instance. In our example, the nodes are are using and greet. Node using is registered with GroupGreetingNode and greet is registered with class GreetingNode.
GroupGreetingNode represents the parent node. It sets up a GroupGreeter object (see previous section) with the language passed in as its argument and returns the newly created GroupGreeter object. The GreetingNode is a child node of the GroupGreetingNode. When the "greet" node is encountered, the builder calls newInstance() on GreetingNode and a new instance of the Person object with the name as an argument and returns the newly created person back to the builder. The builder will eventually call setParent() on the GreetingNode. At that point, the person object that was created in is added to the parentNode (which is an instance of GroupGreeter). That's it!
Running the Code
The following shows two usage of the Greeting DSL
| Hallo, Jon, David und Ella Salut Mireille |
Observations
- The semantic for your DSL requires an appropriately fitting backing API and data structure
- Model your backing data structure close to your DSL. It makes implementation of the builder simpler.
- If you can't change the backing API, try model your DSL syntax and semantic close to your API.
- FactoryBuilderSupport and Factory simply facilitates the traversal of the DSL nodes. They should not be used to implement the backing data structure.
- How you process the nodes is subjective and depends on how your backing data model is structured.
- Process data at the intended node level. So, a child node should process all of its data not pass it to the parent.
Conclusion
There's no doubt that Groovy makes developing DSL easy. As we have seen the combination of the FactoryBuilderSupport class and the Factory interface provides a structured approach to creating DSL that conforms to the builder pattern. In this write up I demonstrated how to create a simple DSL to print out greeting on the screen. The same techniques used here are used to create more complex DSL such as Java3DBuilder.
References
0. Download Groovy source code - http://vladimirvivien.com.s3.amazonaws.com/GreetingBuilderDemo.groovy
1. Dynamic Groovy - http://groovy.codehaus.org/Dynamic+Groovy
2. Groovy FactoryBuilderSupport - http://groovy.codehaus.org/FactoryBuilderSupport
3. Jame William's Java3DBuilder - http://www.jameswilliams.be/blog/entry/60
Hope you enjoyed it!