I am trying to modify a script variable from inside a closure in a function. The problem can be distilled down to this:
@groovy.transform.Field int myField = 0
incrementField()
assert myField == 1
def incrementField() {
1.times { myField++ }
}
This behavior is caused by groovy.lang.Script
class and the fact that it overrides following methods:
Closure you have shown in the example uses delegate
set to a script object and that's why both overridden methods get executed when you try to access or modify field defined in a script.
Now let's see what happens when your example reaches closure
{ myField++ }
Firstly, getProperty("myField")
is called to return a value associated with this property. This method is implemented as:
public Object getProperty(String property) {
try {
return binding.getVariable(property);
} catch (MissingPropertyException e) {
return super.getProperty(property);
}
}
Source: https://github.com/apache/groovy/blob/GROOVY_2_4_X/src/main/groovy/lang/Script.java#L54
binding
object contains only one variable in the beginning - closure's args
array. If we take a look at implementation of binding.getVariable(property)
method we will see:
public Object getVariable(String name) {
if (variables == null)
throw new MissingPropertyException(name, this.getClass());
Object result = variables.get(name);
if (result == null && !variables.containsKey(name)) {
throw new MissingPropertyException(name, this.getClass());
}
return result;
}
Source: https://github.com/apache/groovy/blob/GROOVY_2_4_X/src/main/groovy/lang/Binding.java#L56
In our case MissingPropertyException
is being thrown, so Script.getProperty(property)
method returns a value of field myField
defined in our Groovy script - 0
. Then Groovy increments this value by 1 and tries to set this new value to a field myField
. In this case Script.setProperty(property, value)
is being called:
public void setProperty(String property, Object newValue) {
if ("binding".equals(property))
setBinding((Binding) newValue);
else if("metaClass".equals(property))
setMetaClass((MetaClass)newValue);
else
binding.setVariable(property, newValue);
}
Source: https://github.com/apache/groovy/blob/GROOVY_2_4_X/src/main/groovy/lang/Script.java#L62
As you can see it sets this new value using bindings
object. If we display binding.variables
we will see that now this internal map contains two entries: args -> [:]
and myField -> 1
. It explains why assertion in your script always fails. Body of the closure you have defined never reaches myField
field from the script class.
If you are not satisfied with the fact that Script
class overrides setProperty(property, value)
method you can always override it by hand in your script and use same implementation as GroovyObjectSupport.setProperty(property, value)
. Simply add below method to your Groovy script:
@Override
void setProperty(String property, Object newValue) {
getMetaClass().setProperty(this, property, newValue)
}
Now closure defined in incrementField
will set a new value to a class field instead of to a bindings
object. Of course it may cause some weird side effects, you have to be aware of that. I hope it helps.