Over a million developers have joined DZone.

Experimental DslHelper for more concise DSL support code

· Java Zone

Check out this 8-step guide to see how you can increase your productivity by skipping slow application redeploys and by implementing application profiling, as you code! Brought to you in partnership with ZeroTurnaround.

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!

The Java Zone is brought to you in partnership with ZeroTurnaround. Check out this 8-step guide to see how you can increase your productivity by skipping slow application redeploys and by implementing application profiling, as you code!

Topics:

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

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

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

{{ parent.tldr }}

{{ parent.urlSource.name }}