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++ }
}
I think the problem has something to do with closure delegates, but I cannot quite wrap my head around the docs.
This behavior is caused by groovy.lang.Script class and the fact that it overrides following methods:
Object getProperty(String property)
void setProperty(String property, Object newValue)
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.
Workaround
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.
Found a possible solution, using closure delegate:
#groovy.transform.Field def stuff = [
myField : 0
]
incrementField()
assert stuff.myField == 1
def incrementField() {
def body = { myField++ }
body.resolveStrategy = Closure.DELEGATE_FIRST
body.delegate = stuff
1.times body
}
Related
A closure with a delegate that is not a groovy-object (e.g. coming from a normal java-library), will never call the 'methodMissing' object added to that delegate using it's metaclass, if the call is made 'implicit' (i.e. not calling it explicitly on 'delegate' within the closure.)
The code below does an explicit and an implicit call to a non-existing method; it does so on a Groovy-class instance, a GString and a non-groovy object. The only one that fails is the implicit call to a non-groovy-object (i.c. ArrayList).
(You can see and run the same code online:
https://groovyconsole.appspot.com/edit/5200829376102400)
Not sure if this is a bug or a limitation - references to methodMissing defined through metaClass are pretty scarce. Any insightful comments would be welcome.
class ClosureDelegate {
def testMissingMethod(def someObject) {
someObject.metaClass.methodMissing = { String name, args ->
println name
}
def closure = {
delegate.anything()
anything() // this one fails on non-groovyclasses
}
closure.delegate = someObject
closure.resolveStrategy = Closure.DELEGATE_ONLY
closure()
}
}
class TestObject {}
println "testing with TestObject"
new ClosureDelegate().testMissingMethod(new TestObject())
println "testing with GString"
new ClosureDelegate().testMissingMethod("${new Date()}")
println "testing with ArrayList"
new ClosureDelegate().testMissingMethod(new ArrayList())
testing with TestObject
anything
anything
testing with GString
anything
anything
testing with ArrayList
anything
Caught: groovy.lang.MissingMethodException: No signature of method: ClosureDelegate$_testMissingMethod_closure2.anything() is applicable for argument types: () values: []
Possible solutions: toString(), toString(), any(), any()
According to the ClosureMetaClass implementation, this behavior is expected. Take a look at the following part that starts at line 275 in that file:
switch (resolveStrategy) {
case Closure.TO_SELF:
break;
case Closure.DELEGATE_ONLY:
method = getDelegateMethod(closure, delegate, methodName, argClasses);
callObject = delegate;
if (method == null) {
invokeOnDelegate = delegate != closure && (delegate instanceof GroovyObject);
}
break;
Source: src/main/java/org/codehaus/groovy/runtime/metaclass/ClosureMetaClass.java#L275-L284
The invokeOnDelegate boolean flag verifies if the delegate object extends GroovyObject (the default parent class for all Groovy classes.) When you execute your code with Groovy classes, this flag is set to true and the anything method from the delegate object gets invoked. In the case of non-Groovy classes, MethodMissingException is thrown.
You can debug this behavior by setting the breakpoint in AbstractCallSite.callCurrent(GroovyObject receiver) line 160. You will see that in all cases the method anything is not found in the closure, but in the first two cases invokeOnDelegate is evaluated to true, and the invokeMethod on delegate object is executed. It does not happen in the third case, because ArrayList is not an instance of GroovyObject.
I am experimenting with some dynamic variable creation with GroovyShell and encountered an issue. First, the working code:
static def defVar(def glob) {
glob.setVariable('test', new Test())
}
class MyBinding extends Binding {
}
class Test {
def call() {
println("--- hello ---")
}
}
Binding glob = new MyBinding()
GroovyShell shell = new GroovyShell(glob)
defVar(glob)
shell.parse('test()').run()
This gives me the expected output:
--- hello ---
However, I want to call setVariable() dynamically when getVariable() is called, something like this:
static def defVar(def glob) {
glob.setVariable('test', new Test())
}
class MyBinding extends Binding {
def getVariable(String name) {
if (! hasVariable('test')) {
BindingTest.defVar(this)
}
return super.getVariable(name)
}
}
class Test {
def call() {
println("--- hello ---")
}
}
Binding glob = new MyBinding()
GroovyShell shell = new GroovyShell(glob)
//defVar(glob)
shell.parse('test()').run()
But this fails with the below error:
Caught: groovy.lang.MissingMethodException: No signature of method: Script1.test() is applicable for argument types: () values: []
Possible solutions: getAt(java.lang.String), use([Ljava.lang.Object;), use(java.lang.Class, groovy.lang.Closure), use(java.util.List, groovy.lang.Closure), wait(), wait(long)
groovy.lang.MissingMethodException: No signature of method: Script1.test() is applicable for argument types: () values: []
Possible solutions: getAt(java.lang.String), use([Ljava.lang.Object;), use(java.lang.Class, groovy.lang.Closure), use(java.util.List, groovy.lang.Closure), wait(), wait(long)
at Script1.run(Script1.groovy:1)
at Script1$run.call(Unknown Source)
at BindingTest.run(BindingTest.groovy:23)
When I added tracing code like this:
class MyBinding extends Binding {
def getVariable(String name) {
if (! hasVariable(name)) {
BindingTest.defVar(this)
}
println("getVariable: ${name}: ${super.getVariable(name).getClass().getName()}")
return super.getVariable(name)
}
void setVariable (String name, def val) {
println("setVariable: ${name}: ${val.getClass().getName()}")
super.setVariable(name, val)
}
def getProperty(String name) {
println("getProperty: ${name}: ${super.getProperty(name)}")
return super.getProperty(name)
}
void setProperty (String name, def val) {
println("setProperty: ${name}: ${val.getClass().getName()}")
super.setProperty(name, val)
}
}
In the working case, I get the below output:
setVariable: test: Test
--- hello ---
In the non-working case, I get this output:
setVariable: test: Test
getVariable: test: Test
Caught: groovy.lang.MissingMethodException: No signature of method: Script1.test() is applicable for argument types: () values: []
...
Two questions:
In the working scenario, why is there no getVariable?
In the non-working scenario, why is the Test object returned by getVariable getting rejected?
Note that this issue is specific to callable values. If I set a simple value such as a string, to test, then both approaches work fine. E.g., with this sort of a change:
...
static def defVar(def glob) {
glob.setVariable('test', '--- hello ---')
}
...
shell.parse('println(test)').run()
I get the below identical output with both approaches:
setVariable: test: java.lang.String
getVariable: test: java.lang.String
setVariable: test: java.lang.String
--- hello ---
Though, I am not sure why setVariable gets called twice. I couldn't find any documentation explaining these puzzling behaviors. Could anybody here shed some light on them?
Please note, all the code snippets have been simplified for the ease of demonstrating the problem rather than for their intended purpose
When you use a property as a callable fallback, the Binding.getVariable() method does not get involved. This behavior is controlled by the metaclass, and in your case, it all drives to the execution of the MetaClassImpl.invokePropertyOrMissing() method. This method determines if
test()
should invoke test.call() (in case of an existing property), or should it fallback to the missingMethod() method. Here is what this method implementation looks like:
private Object invokePropertyOrMissing(Object object, String methodName, Object[] originalArguments, boolean fromInsideClass, boolean isCallToSuper) {
// if no method was found, try to find a closure defined as a field of the class and run it
Object value = null;
final MetaProperty metaProperty = this.getMetaProperty(methodName, false);
if (metaProperty != null)
value = metaProperty.getProperty(object);
else {
if (object instanceof Map)
value = ((Map)object).get(methodName);
}
if (value instanceof Closure) { // This test ensures that value != this If you ever change this ensure that value != this
Closure closure = (Closure) value;
MetaClass delegateMetaClass = closure.getMetaClass();
return delegateMetaClass.invokeMethod(closure.getClass(), closure, CLOSURE_DO_CALL_METHOD, originalArguments, false, fromInsideClass);
}
if (object instanceof Script) {
Object bindingVar = ((Script) object).getBinding().getVariables().get(methodName);
if (bindingVar != null) {
MetaClass bindingVarMC = ((MetaClassRegistryImpl) registry).getMetaClass(bindingVar);
return bindingVarMC.invokeMethod(bindingVar, CLOSURE_CALL_METHOD, originalArguments);
}
}
return invokeMissingMethod(object, methodName, originalArguments, null, isCallToSuper);
}
Source: https://github.com/apache/groovy/blob/GROOVY_2_5_X/src/main/groovy/groovy/lang/MetaClassImpl.java#L1262-L1287
Now, pay attention to the branch if (object instanceof Script) and how the binding variable gets retrieved. It tries to retrieve test variable from binding object using:
Object bindingVar = ((Script) object).getBinding().getVariables().get(methodName);
Your code would work if it was:
Object bindingVar = ((Script) object).getBinding().getVariable(methodName);
instead. But it's not.
You can make your second case working if you override getVariables() method instead of getVariable(String name), for instance:
class MyBinding extends Binding {
#Override
Map getVariables() {
return super.getVariables() + [
test: new Test()
]
}
}
Of course, your final implementation might be much more sophisticated. (E.g. you could get super.getVariables() map first, check which variables are missing and add a default variable only if the initial map was missing given variable.) But this is up to you.
Alternatively, consider using methodMissing instead of the binding variable fallback. It could make your code much easier to read and reason about.
ok - tried looking /reading and not sure i have an answer to this.
I have a Utility class which wraps a static ConcurrentLinkedQueue internally.
The utility class itself adds some static methods - i dont expect to call new to create an instance of the Utility.
I want to intercept the getProperty calls the utility class - and implement these internally in the class definition
I can achieve this by adding the following to the utility classes metaclass, before i use it
UnitOfMeasure.metaClass.static.propertyMissing = {name -> println "accessed prop called $name"}
println UnitOfMeasure.'Each'
however what i want to do is declare the interception in the class definition itself. i tried this in the class definition - but it never seems to get called
static def propertyMissing (receiver, String propName) {
println "prop $propName, saught"
}
i also tried
static def getProperty (String prop) { println "accessed $prop"}
but this isnt called either.
So other than adding to metaClass in my code/script before i use, how can declare the in the utility class that want to capture property accesses
the actual class i have looks like this at present
class UnitOfMeasure {
static ConcurrentLinkedQueue UoMList = new ConcurrentLinkedQueue(["Each", "Per Month", "Days", "Months", "Years", "Hours", "Minutes", "Seconds" ])
String uom
UnitOfMeasure () {
if (!UoMList.contains(this) )
UoMList << this
}
static list () {
UoMList.toArray()
}
static getAt (index) {
def value = null
if (index in 0..(UoMList.size() -1))
value = UoMList[index]
else if (index instanceof String) {
Closure matchClosure = {it.toUpperCase().contains(index.toUpperCase())}
def position = UoMList.findIndexOf (matchClosure)
if (position != -1)
value = UoMList[position]
}
value
}
static def propertyMissing (receiver, String propName) {
println "prop $propName, saught"
}
//expects either a String or your own closure, with String will do case insensitive find
static find (match) {
Closure matchClosure
if (match instanceof Closure)
matchClosure = match
if (match instanceof String) {
matchClosure = {it.toUpperCase().contains(match.toUpperCase())}
}
def inlist = UoMList.find (matchClosure)
}
static findWithIndex (match) {
Closure matchClosure
if (match instanceof Closure)
matchClosure = match
else if (match instanceof String) {
matchClosure = {it.toUpperCase().contains(match.toUpperCase())}
}
def position = UoMList.findIndexOf (matchClosure)
position != -1 ? [UoMList[position], position] : ["Not In List", -1]
}
}
i'd appreciate the secret of doing this for a static utility class rather than instance level property interception, and doing it in class declaration - not by adding to metaClass before i make the calls.
just so you can see the actual class, and script that calls - i've attached these below
my script thats calling the class looks like this
println UnitOfMeasure.list()
def (uom, position) = UnitOfMeasure.findWithIndex ("Day")
println "$uom at postition $position"
// works UnitOfMeasure.metaClass.static.propertyMissing = {name -> println "accessed prop called $name"}
println UnitOfMeasure[4]
println UnitOfMeasure.'Per'
which errors like this
[Each, Per Month, Days, Months, Years, Hours, Minutes, Seconds]
Days at postition 2
Years
Caught: groovy.lang.MissingPropertyException: No such property: Per for class: com.softwood.portfolio.UnitOfMeasure
Possible solutions: uom
groovy.lang.MissingPropertyException: No such property: Per for class: com.softwood.portfolio.UnitOfMeasure
Possible solutions: uom
at com.softwood.scripts.UoMTest.run(UoMTest.groovy:12)
Static version of propertyMissing method is called $static_propertyMissing:
static def $static_propertyMissing(String name) {
// do something
}
This method gets invoked by MetaClassImpl at line 1002:
protected static final String STATIC_METHOD_MISSING = "$static_methodMissing";
protected static final String STATIC_PROPERTY_MISSING = "$static_propertyMissing";
// ...
protected Object invokeStaticMissingProperty(Object instance, String propertyName, Object optionalValue, boolean isGetter) {
MetaClass mc = instance instanceof Class ? registry.getMetaClass((Class) instance) : this;
if (isGetter) {
MetaMethod propertyMissing = mc.getMetaMethod(STATIC_PROPERTY_MISSING, GETTER_MISSING_ARGS);
if (propertyMissing != null) {
return propertyMissing.invoke(instance, new Object[]{propertyName});
}
} else {
// .....
}
// ....
}
Example:
class Hello {
static def $static_propertyMissing(String name) {
println "Hello, $name!"
}
}
Hello.World
Output:
Hello, World!
I'm trying to put into the field an object that supports a call operation, and then to call him. I can do it without intermediate reading fields in a variable?
My attempt looks like this:
class CallableObjectDynamic {
def call() {
return "5"
}
}
class MyClassDynamic {
CallableObjectDynamic field = new CallableObjectDynamic()
}
class GroovyRunnerDynamic {
static String make(int arg1) {
MyClassDynamic x = new MyClassDynamic()
return x.field()
}
}
But I receive groovy.lang.MissingMethodException.
What can you do? Or can anyone give a proof where it's written that we can't call the field?
Membership (.) has lower order of precedence than function/method/call invocation (()). Thus this line:
return x.field()
is interpreted as "invoke the 'field' method on the 'x' object".
To get Groovy to parse the code as you desire, the minimal change would be to regroup using parentheses, as follows:
return (x.field)()
which is (ultimately) interpreted as "invoke the 'call' method on the 'field' object member of the 'x' object", as desired.
It is trivial issue. Not required to have parenthesis for field.
Change from:
return x.field()
To:
return x.field
If you want to execute call method further, then use below code snippet.
Note that static method return type is changed.
class CallableObjectDynamic {
def call() {
return "5"
}
}
class MyClassDynamic {
CallableObjectDynamic field = new CallableObjectDynamic()
}
class GroovyRunnerDynamic {
static def make(int arg1) {
MyClassDynamic x = new MyClassDynamic()
return x.field
}
}
GroovyRunnerDynamic.make(1).call()
Output would be : 5
Not sure why argument to make method is done here, seems to be not used in the above code.
Alternatively, you can change
class GroovyRunnerDynamic {
static def make(int arg1) {
MyClassDynamic x = new MyClassDynamic()
return x.field.call()
}
}
GroovyRunnerDynamic.make(1)
EDIT: Based on OP's implicit call.
Not really sure how it is working, but the below does implicit call. Just assign x.field to a variable and just add parenthesis for that as shown below.
class GroovyRunnerDynamic {
static String make(int arg1) {
MyClassDynamic x = new MyClassDynamic()
def fun = x.field
fun()
}
}
GroovyRunnerDynamic.make(1)
I am looking for a flexible way of "modifying" (copying with some values changed) immutable objects in groovy. There is a copyWith method but it allows you only to replace some properties of the object. It doesn't seem to be convenient enough.
Let's say we have a set of classes representing a domain design of some system:
#Immutable(copyWith = true)
class Delivery {
String id
Person recipient
List<Item> items
}
#Immutable(copyWith = true)
class Person {
String name
Address address
}
#Immutable(copyWith = true)
class Address {
String street
String postalCode
}
Let's assume I need to change street of delivery recipient. In case of regular mutable object it is just fine to perform:
delivery.recipient.address.street = newStreet
or (perhaps useful in some cases):
delivery.with {recipient.address.street = newStreet}
When it comes to do the same with immutable objects the best way according to my knowledge would be:
def recipient = delivery.recipient
def address = recipient.address
delivery.copyWith(recipient:
recipient.copyWith(address:
address.copyWith(street: newStreet)))
It is actually needed for Spock integration test code so readability and expressiveness matters. The version above cannot be used "on the fly" so in order to avoid creating tons of helper methods, I have implemented my own copyOn (since copyWith was taken) method for that which makes it possible to write:
def deliveryWithNewStreet = delivery.copyOn { it.recipient.address.street = newStreet }
I wonder however if there is an ultimate solution for that, present in groovy or provided by some external library. Thanks
For the sake of completeness I provide my implementation of copyOn method. It goes as follows:
class CopyingDelegate {
static <T> T copyOn(T source, Closure closure) {
def copyingProxy = new CopyingProxy(source)
closure.call(copyingProxy)
return (T) copyingProxy.result
}
}
class CopyingProxy {
private Object nextToCopy
private Object result
private Closure copyingClosure
private final Closure simplyCopy = { instance, property, value -> instance.copyWith(createMap(property, value)) }
private final def createMap = { property, value -> def map = [:]; map.put(property, value); map }
CopyingProxy(Object nextToCopy) {
this.nextToCopy = nextToCopy
copyingClosure = simplyCopy
}
def propertyMissing(String propertyName) {
def partialCopy = copyingClosure.curry(nextToCopy, propertyName)
copyingClosure = { object, property, value ->
partialCopy(object.copyWith(createMap(property, value)))
}
nextToCopy = nextToCopy.getProperties()[propertyName]
return this
}
void setProperty(String property, Object value) {
result = copyingClosure.call(nextToCopy, property, value)
reset()
}
private void reset() {
nextToCopy = result
copyingClosure = simplyCopy
}
}
It is then just a matter of adding the delegated method in Delivery class:
Delivery copyOn(Closure closure) {
CopyingDelegate.copyOn(this, closure)
}
High level explanation:
First of all it is required to notice that the code of: delivery.recipient.address.street = newStreet is interpreted as:
Accessing recipient property of delivery object
Accessing address of what was the result of the above
Assigning property street with the value of newStreet
Of course the class CopyingProxy does not have any of those properties, so propertyMissing method will be involved.
So as you can see it is a chain of propertyMissing method invocations terminated by running setProperty.
Base case
In order to implement the desired functionality we maintain two fields: nextToCopy (which is delivery at the beginning) and copyingClosure (which is initialised as a simple copy using copyWith method provided by #Immutable(copyWith = true) transformation).
At this point if we had a simple code like delivery.copyOn { it.id = '123' } then it would be evaluated as delivery.copyWith [id:'123'] according to simplyCopy and setProperty implementations.
Recursive step
Let's now see how would it work with one more level of copying: delivery.copyOn { it.recipient.name = 'newName' }.
First of all we will set initial values of nextToCopy and copyingClosure while creating CopyingProxy object same way as in the previous example.
Let's now analyse what would happen during first propertyMissing(String propertyName) call. So we would capture current nextToCopy (delivery object), copyingClosure (simple copying based on copyWith) and propertyName (recipient) in a curried function - partialCopy.
Then this copying will be incorporated in a closure
{ object, property, value -> partialCopy(object.copyWith(createMap(property, value))) }
which becomes our new copyingClosure. In the next step this copyingClojure is invoked in the way described in Base Case part.
Conclusion
We have then executed: delivery.recipient.copyWith [name:'newName']. And then the partialCopy applied to the result of that giving us delivery.copyWith[recipient:delivery.recipient.copyWith(name:'newName')]
So it's basically a tree of copyWith method invocations.
On top of that you can see some fiddling with result field and reset function. It was required to support more than one assignments in one closure:
delivery.copyOn {
it.recipient.address.street = newStreet
it.id = 'newId'
}