StateMachine: a Builder-Builder for Groovy, part 1
Things are moving quickly on the Groovy DSL side these days. Not only have I been lately discovering a new Groovy builder almost every day, I'm also continuing my experiments to make developing DSLs and builders for Groovy easier. And I'm reporting back to you with some interesting results.
After my last post I made an interesting observation about builders in Groovy. Each builder can be placed in one of two categories: infinite depth or state machines. XmlSlurper, MarkupBuilder and ObjectGraphBuilder are examples of infinite depth builders. They don't enforce any particular order in which methods are called. After all, XML snippets can take any form.
Other builders like AntBuilder, GraphicsBuilder or SwingBuilder are state machines. Their implementations and the APIs they hide enforce specific structures. You can't call just any method you want. Here's an example of method calls to AntBuilder that don't make sense:
def ant = new AntBuilder()
ant.delete(file:"myfile.tmp") {
javac(srcdir:"src", destdir:"build")
}
It doesn't make any sense to call the javac() method as child of the delete() method and AntBuilder will throw an exception. This is typically what state machines do: they enforce a pre-defined flow of events.
Caught: delete doesn't support the nested "javac" element.
GraphicsBuilder and SwingBuilder validate method calls in a similar way. AntBuilder, GraphicsBuilder and SwingBuilder are thus implemented to act as state machines. But this state machine logic had to be implemented by their developers. AntBuilder depends on Ant to report inconsistent method calls. GraphicsBuilder and SwingBuilder both extend groovy.util.FactoryBuilderSupport, a convenience class for implementing Groovy builders.
FactoryBuilderSupport does make it easier to validate method calls but it still requires builder developers to implement validation logic. Hence, there is no real state machine for writing Groovy builders. Such a state machine would automatically check whether method calls are allowed based on a flow definition. This would require builder developers to only implement method bodies.
This is what the StateMachine class does. It's an experimental convenience class to build builders, a builder-builder if you want. StateMachine requires you to define states and the transformations that are allowed between them. Here's an example of a state machine that mimics the structure of a simple HTML file:
def machine = new StateMachine()
def execution = machine.define {
html {
head({
title().once()
}).once()
body({
p {
span()
to("div")
}
div {
to("p")
}
}).once()
}
}
This example create a builder (the object assigned to the execution variable) on which the methods in the flow definition can be called. The order and hierarchy in which they are defined has to be respected when methods are called on the builder:
execution.html {
head {
title()
}
body {
p {
span()
div {
p {
div {
p {
span()
}
}
}
}
}
}
}
execution.validateTransformations()
Calling a state or a transformation that is not defined will result in an error. Except, this example does not do anything. The methods that are called have no implementation. Let's change that by changing the flow definition first.
def machine = new StateMachine()
def execution = machine.define {
defaultAction = {
element ->
element.children().each {
"${it.name()}"(it)
}
}
html {
head({
title().once()
}).once()
body({
p {
span()
to("div")
}
div {
to("p")
}
}).once()
}
}
The defaultAction property sets an action for all methods that don't have one. The action takes one argument which is an XML element returned by XMLSlurper. It then calls for each child element the method corresponding to the child element's name. Let's call this builder:
def html = new XmlSlurper().parseText("""
<html>
<head>
<title></title>
</head>
<body>
<p><div></div></p>
<div><p></p></div>
<p><span/></p>
</body>
</html>
""")
execution."${html.name()}"(html)
execution.validateTransformations()
In the next installment I'll further demonstrate StateMachine's capabilities as a builder-builder by re-writing the Architecture Rules example from my previous post. If you want to have a sneak preview look at the StateMachineTest.groovy file attached to this article.
Happy coding!
| Attachment | Size |
|---|---|
| StateMachine.groovy | 11.91 KB |
| StateMachineTest.groovy | 7.11 KB |
| StateMachine.zip | 5.82 MB |
- Login or register to post comments
- 3809 reads
- Printer-friendly version
(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)










Comments
Dierk Koenig replied on Tue, 2008/02/05 - 2:51am
Hi Steven,
interesting perspective. I never thought of builders as a state machine (which they are, of course).
However, the most useful part would be to allow IDEs to provide developer support when using a builder, i.e. mark state machine violations. To that end, a machine definition like yours could help. The "builder-builder-DSL" could still be improved, though. I wouldn't go for the ".once()" approach because it is too limiting (it's the Java-style of doing builders). How about something that follows more the route of groovy MockFor?
Another approach would be to define it in terms of a grammar (think DTD or EBNF).
Speaking in terms of state machines would also suggest to define the structure as a state-transition-matrix.
just some thoughts
Dierk
Steven Devijver replied on Tue, 2008/02/05 - 3:24am
in response to: mitite
Dierk Koenig replied on Tue, 2008/02/05 - 8:05am
in response to: sdevijver
> Do you mean creating a builder from a BNF file?
No, just describing the allowed nesting such that IDEs can work on such a description.
I picture something slightly different from your approach: whoever builds a builder ;-) be it manual or semi-automatic also provides some formal description about it. Call it constraints if you like. We could have some convention where to put such a description. Any tool (IDEs, builder-builder, builder vallidation runtime, doc generators) would use that descripltion for their purpose.
regards
Dierk
Andres Almiray replied on Tue, 2008/02/05 - 12:21pm
in response to: mitite
Builders that extend from FactoryBuilderSupport can give you a list of their factories, but nothing related to the proper arrangment of nodes. It would be great if builders provide some sort of metadata in an standard way.
Cheers,
Andres
Tuomas Kassila replied on Sun, 2008/02/10 - 6:12am
The StateMachine is very interesting!
to(...) => _to(...)
I suggest that to(String name) method of StateMachine could be changed into _to(String name), because there are people who are using 'to' method name to an another purpose, almost I will do that. The name _to(...) is almost equally short than the to(...).
Thanks,
Tuomas
Jörg Gottschling replied on Fri, 2008/02/29 - 2:20am
Very nice idea. Dirk and Tuomas are right the syntax should be changed. But I would prefer not to use an underscore to mark a builder builder method. I think this meta builder method should be 'normal' methods, but all parts from the builded concrete builder should be marked as those. My proposal is to use a String each time, as you do with to("p") in your example.
def execution = machine.define {once 'html', {
once 'head', {
once 'title'
}
once 'body', {
any 'p', {
any 'span'
to 'div'
}
any 'div', {
to 'p'
}
}
}
}
Hey, look ... you get some code highlighting with this. ;-)