Michael Rueegg Michael Rueegg - 2 months ago 15
Groovy Question

Strange behaviour with binding in Groovy based DSL

I have a Groovy based DSL in which I want to access variables from declared bindings. Here's my DSL implementation:

class Bar {
String barVal

void bar(String bar) {
this.barVal = bar
}
}

class Foo {
Bar bar

void foo(@DelegatesTo(value = Bar, strategy = Closure.DELEGATE_FIRST) closure) {
def bar = new Bar()
def code = closure.rehydrate(bar, null, null)
code()
this.bar = bar
}
}

abstract class MyScript extends Script {

Foo dslEntryPoint(@DelegatesTo(value = Foo, strategy = Closure.DELEGATE_FIRST) closure) {
def foo = new Foo()
def code = closure.rehydrate(foo, null, null)
code()
foo
}
}


And here's an example of my DSL and how I run it:

def DSL_NOT_WORKING = """
dslEntryPoint() {
foo {
bar magicValue
}
}
"""

def DSL_OK = """
def myMagicValue = magicValue
dslEntryPoint() {
foo {
bar myMagicValue
}
}
"""

CompilerConfiguration config = new CompilerConfiguration(CompilerConfiguration.DEFAULT)
config.scriptBaseClass = MyScript.class.name
GroovyClassLoader groovyClassLoader = new GroovyClassLoader(getClass().getClassLoader(), config)
Class<Script> clazz = groovyClassLoader.parseClass(DSL_NOT_WORKING)
Binding binding = new Binding()
binding.setVariable('magicValue', '42')
Script script = InvokerHelper.createScript(clazz, binding)
Foo foo = script.run() as Foo
assert foo.bar.barVal == '42'


As you can see, when I bind the variable with
def
(
DSL_OK
), I can access its value in my DSL, whereas otherwise (
DSL_NOT_WORKING
) I get a

java.lang.NullPointerException: Cannot get property 'magicValue' on null object


What am I doing wrong? How can I access the variable
magicValue
without the
def
helper declaration? I think it has something to do with calling
rehydrate
on the closure where I could pass
this
instead of
null
, but this does not seem to work with nested object hierarchies (Foo->Bar).

Thanks,
Michael

Answer

The problem was my usage of rehydrate which overrides the owner and delegate with null. Instead of bookkeeping the owner and delegate in the nested object hierarchy, it is much simpler to just use a plain delegate instead:

Foo dslEntryPoint(@DelegatesTo(Foo) closure) {
  Foo foo = new Foo()
  closure.delegate = foo
  closure.call()
  foo
}
Comments