Steven has posted 36 posts at DZone. View Full User Profile

StateMachine: a Builder-Builder for Groovy, part 1

02.04.2008
| 9236 views |
  • submit to reddit

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!

AttachmentSize
StateMachine.groovy11.91 KB
StateMachineTest.groovy7.11 KB
StateMachine.zip5.82 MB
Published at DZone with permission of its author, Steven Devijver.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags:

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: Dierk Koenig

Hey Dierk, Thanks for the feedback.
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.
Yep, that's on the todo list. I'm not sure how an IDE would inspect this builder though so I need to hook up with people who can explain me this.
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?
Sure, this needs to be improved. If you look at the StateMachine.groovy file you'll find the Transformation class. Any such methods need to be added there.
Another approach would be to define it in terms of a grammar (think DTD or EBNF).
Do you mean creating a builder from a BNF file?
Speaking in terms of state machines would also suggest to define the structure as a state-transition-matrix.
What this example does not show is that each level in the state machine definition is actually a separate state machine. The transitions (which are implicit here) create a link between two state machines. Once you go down one state machine you can't transform to a state defined in a higher state machine. So you can go down one level, not up, which is typical for builders. Steven

Dierk Koenig replied on Tue, 2008/02/05 - 8:05am in response to: Steven Devijver

> 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: Dierk Koenig

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. ;-) 

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.