amsmota amsmota - 4 months ago 27
Groovy Question

How to access Closure properties from inside closure code?

I'm banging my head against this wall for more than a week now, let me explain briefly what I'm trying to achieve.

I have a DSL defined like this (brief example)

TestingSpec
.newInstance()
.sayHello() --> print "Hello "+something
.sayGoddbye() --> print "Goddbye"+something


Note the
something
in there that is not passed anywhere intentionally, because I want to limit the scope of use of my DSL by the will be users, so what I actually want is to somehow "inject" the
something
in that DSL instance programmatically. This is being done by using a Closure.

Myscript.execute( {
TestingSpec
.newInstance()
.sayHello() --> print "Hello "+something
.sayGoddbye() --> print "Goddbye"+something
})


and
MyScript
will invoke it by passing the
something
value to it, either by passing a parameter, or by adding a property or by adding a Binding (don't know what's the best, really)

close.call("Toni")

close.metaClass.something = "Toni"

def binding = new Binding()
binding.setVariable("something", "Toni")
close.setBinding(binding)


But now I'm stuck in what I thought it was going to be easy, how from the TestingSpec code can I access the Closure
something
? For instance

public newInstance() {
def instance = new TestingSpec()
instance.something = *MY CLOSURE SOMETHING*
return instance
}


I've tried several options, like messing with the
this
,
owner
and
delegate
of the closure, but couldn't do it.

Any hint will be very welcome...

Cheers.

Answer

I don't think that's doable. TestingSpec is too high on the stack to be aware he is being invoked from a closure. I'd like to suggest two solutions

1. Pass something to TestingSpec programmatically.

Myscript.execute( {
    TestingSpec
        .newInstance(something)
        .sayHello()   --> print "Hello "+something
        .sayGoddbye() --> print "Goddbye"+something 
})

2. Make the TestingSpec instance be created by MyScript

I like this solution more. It is about MyScript.execute({}) being responsible for creating and handling the lifecycle of TestingSpec:

class TestingSpec {
    def something
    def sayHello() {
        println "Hello " + something
        this
    }
    def sayGoddbye() {
        println "Goddbye " + something
        this
    }
}

class Myscript {
    static TestingSpec testingSpec
    static execute(closure) {
        def binding = new Binding(something: 'test for echo')
        testingSpec = new TestingSpec(something: binding.something)
        binding.testingSpec = testingSpec
        closure.binding = binding
        closure()
    }
}

Myscript.execute( {
    testingSpec // this is an instance, and not a static call
        .sayHello()   // print "Hello "+something
        .sayGoddbye() // print "Goddbye"+something 
})

Output:

$ groovy Spec.groovy 
Hello test for echo
Goddbye test for echo

3. Delegate the closure to testingSpec

If you set the closure delegate to testingSpec, you can invoke its methods without referencing testingSpec directly, providing a very clean code. Note i also updated the example to remove MyScript:

class TestingSpec {
    static TestingSpec testingSpec
    static execute(closure) {
        def binding = new Binding(something: 'test for echo')
        testingSpec = new TestingSpec(something: binding.something)
        binding.testingSpec = testingSpec
        closure.delegate = testingSpec
        closure.binding = binding
        closure()
    }

    def something
    def sayHello() {
        println "Hello " + something
        this
    }
    def sayGoddbye() {
        println "Goddbye " + something
        this
    }

}

TestingSpec.execute( {
    sayHello()   // print "Hello "+something
    sayGoddbye() // print "Goddbye"+something 
})

4. Inject testingSpec as a parameter into your closure

As per your answer, a simple suggestion: consider giving a testingSpec var that you've built yourself to the user, since it allows more control and customization to you. Think a simple inversion of control (this allows testingSpec to see dynamic variables in the closure's binding):

.withDistinctNames({ testingSpec ->
    testingSpec
        .from(name)
        .withAddress(email) 
        .sendEmail(template) 
})

With a full test:

class TestingSpec {
    def commands = []
    static newInstance(listOfNames) {
        new TestingSpec()
    }

    def sayHello() { commands << "sayHello"; this }
    def waveGoddbye() { commands << "waveGoddbye"; this }
    def withAddress(address) { commands << "withAddress $address"; this }
    def sendEmail(template) { commands << "sendEmail $template"; this }

    def withDistinctNames(closure) {
        commands << "withDistinctNames"
        closure.binding = new Binding(address: "sunset boulevard", template: 'email is $email')
        closure(this)
        this
    }
}

test = TestingSpec
    .newInstance([:])
    .sayHello()
    .withDistinctNames({ me ->
        me
            .withAddress(address)
            .sendEmail(template)        
    })
    .waveGoddbye()

assert test.commands == [
    'sayHello', 
    'withDistinctNames', 
    'withAddress sunset boulevard', 
    'sendEmail email is $email', 
    'waveGoddbye'
]
Comments