Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

StateMachine: a Builder-Builder for Groovy, part 1

DZone's Guide to

StateMachine: a Builder-Builder for Groovy, part 1

· Java Zone
Free Resource

Download Microservices for Java Developers: A hands-on introduction to frameworks and containers. Brought to you in partnership with Red Hat.

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!

Download Building Reactive Microservices in Java: Asynchronous and Event-Based Application Design. Brought to you in partnership with Red Hat

Topics:

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

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

{{ parent.tldr }}

{{ parent.urlSource.name }}