Interrogating Arbitrary Groovy Closures for Values

2010/01/24

Inspired by this question on stackoverflow, I decided to create a utility class that allowed me to determine generically what calls a closure makes (without actually letting it make any calls). This lets me see what it’s trying to do before letting it actually do it.

It works by overriding the delegate of the closure (a delegate intercepts all method and property get/set calls that have not already been dealt with). It assigns it to an instance of the ClosureInterrogator which implements the propertyMissing and methodMissing methods. Any property or set method that is called gets saved in a map that is returned to the caller.

class ClosureInterrogator {
    private Map closureValueMap = [:]
 
    static Map extractValuesFromClosure(Closure closure) {
        def interrogator = new ClosureInterrogator(closure)
        return interrogator.closureValueMap
    }
 
    private ClosureInterrogator(Closure closure) {
        def oldResolveStrategy = closure.getResolveStrategy()
        def oldDelegate = closure.getDelegate()
        closure.delegate = this
        closure.resolveStrategy = Closure.DELEGATE_FIRST
 
        try {
            closure()
        } finally {        
            closure.setDelegate(oldDelegate)
            closure.setResolveStrategy(oldResolveStrategy)
        }
    }
 
    // property getter
    def propertyMissing(String name) {
        return closureValueMap[name]
    }
 
    // property setter
    def propertyMissing(String name, value) {
        closureValueMap[name] = value
    }
 
    def methodMissing(String name, args) {
        if (args.size() == 1) {
            closureValueMap[name] = args[0]
        } else {
            closureValueMap[name] = args
        }
    }
}

This technique is the basis for all groovy mocking techniques and libraries, it’s also the main technique used to create a DSL with groovy.

Here is a sample class that has a closure (“something”) that we want to extract the values from.

class SomeClass {
    static something = {
        key1 "value1"              // calls methodMissing("key1", ["value1"])
        key2("value2")             // calls methodMissing("key2", ["value2"])
        key3 = "value3"            // calls propertyMissing("key3", "value3")
        key4 "foo", "bar", "baz"   // calls methodMissing("key4", ["foo","bar","baz"])
    }
}

If we call “extractValuesFromClosure” on the “something” closure, we’ll get back a map that has all of the values we want in it.

def closureValueMap = ClosureInterrogator.extractValuesFromClosure(new SomeClass().something)
 
assert "value1" == closureValueMap."key1"  // calls propertyMissing("key1")
assert "value2" == closureValueMap."key2"  // calls propertyMissing("key2")
assert "value3" == closureValueMap."key3"  // calls propertyMissing("key3")
assert ["foo", "bar", "baz"] == closureValueMap."key4"  // calls propertyMissing("key4")

This class will work with any type of closure, such as this closure used by the grails mail plugin to send mail.

def mailClosure = {
   to "fred@g2one.com","ginger@g2one.com"
   from "john@g2one.com"
   cc "marge@g2one.com", "ed@g2one.com"
   bcc "joe@g2one.com"
   subject "Hello John"
   body 'this is some text'
}
 
def results = ClosureInterrogator.extractValuesFromClosure(mailClosure)
println results
// prints:
// [to:[fred@g2one.com, ginger@g2one.com], from:john@g2one.com, cc:[marge@g2one.com, ed@g2one.com], bcc:joe@g2one.com, subject:Hello John, body:this is some text]
 
assert results.to.every { it.endsWith("g2one.com") }

If we wanted to assert that within a test environment, we’re only sending e-mail to a specific domain, this would be one technique to assert it before we actually send the e-mail off.

ClosureInterrogator is generic enough that it could also be used on a number of Grails/GORM domain closures such as “constraints” and “mapping”.

There are 4 comments in this article:

  1. 2010/01/25Wolfgang say:

    Thanks for the tip!

    Just one thing: I would wrap calling closure() in a try {} finally {} statement, so that the delegate and resolve strategy are reset, even if an exception was thrown. Another way would be to clone() the closure, as described by Peter Ledbrook in his blog: http://www.cacoethes.co.uk/blog/groovyandgrails/supporting-reloading-in-your-grails-plugins

  2. 2010/01/25tednaleid say:

    @Wolfgang – good suggestion about the try/finally (cloning would also work). I’ve modified the post to include it.

  3. 2010/01/25Tweets that mention Interrogating Arbitrary Groovy Closures for Values - Ted Naleid -- Topsy.com say:

    [...] This post was mentioned on Twitter by Debasish Ghosh, Dmitriy Kopylenko, HamletDRC, Ted Naleid, Zak Jacobson and others. Zak Jacobson said: @bradfordcross Look familiar? how to interrogate Groovy closure values without evaluating the closure: http://is.gd/6YVKl (via @TedNaleid) [...]

  4. 2010/01/26Blog bookmarks 01/26/2010 « My Diigo bookmarks say:

    [...] Interrogating Arbitrary Groovy Closures for Values – Ted Naleid [...]

Write a comment: