Platinum Partner
groovy,dsl

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!

{{ tag }}, {{tag}},

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}
{{ parent.authors[0].realName || parent.author}}

{{ parent.authors[0].tagline || parent.tagline }}

{{ parent.views }} ViewsClicks
Tweet

{{parent.nComments}}