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

Experimental DslHelper for more concise DSL support code

DZone's Guide to

Experimental DslHelper for more concise DSL support code

· Java Zone ·
Free Resource

Take 60 minutes to understand the Power of the Actor Model with "Designing Reactive Systems: The Role Of Actors In Distributed Architecture". Brought to you in partnership with Lightbend.

As a follow up to the previous DSL article I propose DslHelper, an experimental class to further simplify DSL support code.

Consider this DSL:

architecture {
    rules {
        "beans-web" {

        }
    }
}

To implement the "beans-web"() method call (lines 3 to 5) I would have to write this code:

class RulesDelegate {
    private Configuration configuration
    RulesDelegate(Configuration configuration) {
        this.configuration = configuration
    }
    def methodMissing(String name, Object args) {
        if (args.length == 1) {
            if (args[0] instanceof Closure) {
                Rule rule = new Rule(name)

                args[0].delegate = new RuleDelegate(rule)
                args[0].resolveStrategy = Closure.DELEGATE_FIRST

                args[0]()

                this.configuration.addRule rule
            } else {
                throw new MissingMethodException(name, this.class, args as Object[])
            }
        } else {
            throw new MissingMethodException(name, this.class, args as Object[])
        }
    }
}

The methodMissing() method (lines 6 to 23) automatically gets called by Groovy whenever a method is not found (missing) on an object. So, by adding missingMethod() to any class you can intercept all calls to methods that do not exist which is exactly what you need to implement a builder.

However, the methodMissing() implementation is less than appealing. I only want to execute my code if I get one Closure object as arguments. But since the method signature of methodMissing() is dictated by Groovy I have to check argument count and type myself.

If only I could implement missingMethod() like this:

def methodMissing(String name, Closure cl) {
    Rule rule = new Rule(name)

    cl.delegate = new RuleDelegate(rule)
    cl.resolveStrategy = Closure.DELEGATE_FIRST

    cl()

    this.configuration.addRule rule
}

Not only is this less code, it's also more readable. Alas, Groovy will not call this method. So we need to give Groovy a hand :-)

Enter DslHelper, an experimental class I wrote that supports strongly typed methodMissing() methods!

def methodMissing(String name, Object args) {
    return DslHelper.methodMissing(this, name, args)
}
def methodMissing(String name, Closure cl) {
    Rule rule = new Rule(name)

    cl.delegate = new RuleDelegate(rule)
    cl.resolveStrategy = Closure.DELEGATE_FIRST

    cl()

    this.configuration.addRule rule
}

By calling DslHelper.methodMissing() in the methodMissing(String,Object) method I delegate the call to methodMissing(String,Closure). To simply things further I can also extend DslHelper:

class RulesDelegate extends DslHelper {
    private Configuration configuration
    RulesDelegate(Configuration configuration) {
        this.configuration = configuration
    }
    def methodMissing(String name, Closure cl) {
        Rule rule = new Rule(name)

        cl.delegate = new RuleDelegate(rule)
        cl.resolveStrategy = Closure.DELEGATE_FIRST

        cl()

        this.configuration.addRule rule
    }
}

DslHelper provides the non-static methodMissing(String,Object) method, letting you concentrate on your DSL syntax.

You can override methodMissing() as many times as you want, DslHelper will do its best to match arguments to a method. Take for example this DSL:

architecture {
    rules {
        "beans-web" {

        }

        "web-beans"(ignore: true) {

        }
    }
}

The "web-beans"() method will pass two arguments to methodMissing(): a Map and a Closure (Groovy converts named arguments to a Map object).

You can extend or call DslHelper and concentrate on methodMissing():

class RulesDelegate extends DslHelper {
    private Configuration configuration
    RulesDelegate(Configuration configuration) {
        this.configuration = configuration
    }
    def methodMissing(String name, Closure cl) {
        // implementation
    }
    def methodMissing(String name, Map params, Closure cl) {
        // implementation
    }
}

DslHelper will also help you with optional arguments in your DSL syntax:

architecture {
    rules {
        "beans-web" {

        }

        "web-beans"(ignore: true) {

        }

        "io-beans"(silent: true, "option1", "option2") {

        }
    }
}

To receive these String object add a String[] array to methodMissing() (The Map object must come first). DslHelper will pass the String values as a String[] array, even if only one String value is passed.

class RulesDelegate extends DslHelper {
    private Configuration configuration
    RulesDelegate(Configuration configuration) {
        this.configuration = configuration
    }
    def methodMissing(String name, Closure cl) {
        // implementation
    }
    def methodMissing(String name, Map params, Closure cl) {
        // implementation
    }
    def methodMissing(String name, Map params, String[] options, Closure cl) {
        // implementation
    }
    def methodMissing(String name, String[] options, Closure cl) {
        // added for completeness
    }
}

DslHelper is experimental, so use it at your own risk.

Happy coding!

Learn how the Actor model provides a simple but powerful way to design and implement reactive applications that can distribute work across clusters of cores and servers. Brought to you in partnership with Lightbend.

Topics:

Opinions expressed by DZone contributors are their own.

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

{{ parent.tldr }}

{{ parent.urlSource.name }}