I wanted to implement functionally, which allows to add unknown properties to the class, during the attempt to set it without using map of dynamic properties.
As Groovy allows to do it using metaClass I used it in propertyMissing method.
class Item {
def propertyMissing(String name, value) {
this.class.metaClass."$name" = value
}
}
But I ran into a weird behavior.
def i1 = new Item()
i1.prop = "value"
println i1.properties // [class:class Item]
println i1.prop // null
i1.metaClass.field = "555"
println i1.properties // [prop:null, class:class Item, field:555]
println i1.prop // null
i1.prop = "value1"
println i1.properties // [prop:value1, class:class Item, field:555]
println i1.prop // value1
Also If I access metaClass before trying to set prop in the example it won't add it anymore
def i1 = new Item()
i1.metaClass.unkn = "1111"
i1.prop = "value"
println i1.properties // [class:class Item, unkn:1111]
println i1.prop // null
i1.metaClass.field = "555"
println i1.properties // [class:class Item, unkn:1111, field:555]
println i1.prop // null
i1.prop = "value1"
println i1.properties // [class:class Item, unkn:1111, field:555]
println i1.prop // null
Why it has such behaviour?
When you update dynamically the metaclass of an object, Groovy replace the metaclass with an ExpandoMetaClass. It's a special implementation of a MetaClass which support adding and removing properties/methods.
However, in your example, Item is a GroovyObject, which have a persistent field on the MetaClass. this field is not updated when the MetaClass is exchanged : Only the metaclass in the registry is replaced by an ExpandoMetaClass. This kind of code can work with a javaobject, because this object doesn't have a field, and the resolution class->metaclass is done every time groovy access the metaclass.
In you know you are going to add properties on a groovy object, you should explicitly set an ExpandoMetaClass :
class Item {
def Item() {
def mc = new ExpandoMetaClass(Item, false, true)
mc.initialize()
this.metaClass = mc
}
def propertyMissing(String name, value) {
this.metaClass."$name" = value
}
}
One of the issues you have is that you're trying to add a property to the class MetaClass instead of the instance MetaClass. And because you're adding the property after creating the instance, the instance doesn't see it. For example, this code fails to print the property:
class A { }
def a = new A()
A.metaClass.prop = 'value'
println a.prop
The error is rather interesting: groovy.lang.MissingPropertyException: No such property: prop for class: A
Possible solutions: prop
However, even if you change the code to use the instance MetaClass it still doesn't work:
class Item {
def propertyMissing(String name, value) {
metaClass."$name" = value
}
}
def i1 = new Item()
i1.prop = 'value'
assert i1.prop == 'value'
The error provides a clue:
groovy.lang.MissingPropertyException: No such property: prop for class: groovy.lang.MetaClassImpl
The MetaClass which provides the Map-like functionality is ExpandoMetaClass. Objects don't typically get this type of MetaClass until you do something like this:
instance.metaClass.prop = 'value'
So the fact that the MetaClass is not an ExpandoMetaClass means that replacement process is not happening. It's likely that propertyMissing() gets called too late in the MOP process to use the MetaClass in this way.
You mentioned that you want to add properties without using a Map of dynamic properties. However, ExpandoMetaClass, which is what you're attempting to use indirectly, uses...
...Maps of dynamic properties! You can see it here.
The easiest way to achieve the behavior you're looking for is to extend Expando:
class Item extends Expando {
def anotherProperty = 'Hello'
}
def i1 = new Item()
i1.prop = 'value'
assert i1.prop == 'value'
assert i1.anotherProperty == 'Hello'
Expando does all of the work for you. If you want to see how it works, read this.
Related
I am using Groovy to create a package that I use in ReadyApi.
In a Groovy script test step, I do the following:
class B {
String value
boolean isSomething
}
class A {
String name
B propB
public A() {
this.name = "Maydan"
}
}
def x = (A) new A().with { propB = new B(value: "Abc", isSomething: true) }
And I get the following error:
org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object 'B#6c218cea' with class 'B' to class 'A'
error at line: 15
Does someone know why? It doesn't make any sense to me.
Kind regards.
PS: I would like to create an instance of class A (by using its parameterless constructor) and setting its field propB in a single statement
You need to return your A object from the .with closure. You may do it like that:
def x = (A) new A().with { propB = new B(value: "Abc", isSomething: true); return it}
but to me personally it looks a little bit odd. I would do it like that:
def x = new A(propB: new B(value: "Abc", isSomething: true))
The same effect, but more compact and readable. It doesn't require to change your A and B definitions, this "map constructor" works out of the box in Groovy, it will call your parameterless constructor and then assigns the necessary fields (propB in your case).
I am trying to understand how the resolution of Groovy's closure is being done in below code,
foo {
a=10
b=20
}
def foo(Closure closure) {
def params = [:]
closure.resolveStrategy = Closure.DELEGATE_FIRST
closure.delegate = params
closure()
println params.a
println params.b
}
In the implementation of foo, the value is never assigned to params, then how come params get the values for a & b
I have read about Groovy closure but couldn't really get as to how this works???
the following line defines property and method resolve strategy for closure DELEGATE_FIRST. that means to get/set/call any property/method it will go to delegate object first and when there is no such property/method it will go to owner object.
closure.resolveStrategy = Closure.DELEGATE_FIRST
and the next line sets delegate object of the closure to params
closure.delegate = params
and finally your closure just sets two properties. and those properties set on delegate object (the param at this moment)
{
a=10
b=20
}
In Groovy in Action, 2nd Edition published in 2015 in chapter 8.4.5 they say that categories can be used to add GroovyObject methods:
Category method names can well take the form of property accessors
(pretending property access), operator methods, and GroovyObject
methods. MOP hook methods cannot be added through a category class.
This is a restriction as of Groovy 2.4. The feature may become
available in later versions.
which I interpreted as you can add getMetaClass(), setMetaClass(MetaClass), getProperty(String), setProperty(String, Object) and invokeMethod(String, Object) but you can't add methodMissing(String, Object) or propertyMissing(String)
but when I tried to add invokeMethod() and getProperty() through a category it didn't have any effect:
class MyClass{}
a = new MyClass()
#Category(MyClass)
class MyCategory {
def missingMethod(String name, def args) { "missingMethod" } // GINA says no MOP hook method
def invokeMethod(String name, def args) { "invokeMethod" } // but GroovyObject method should be fine
def getProperty(String name) { "missingProperty" }
def getMyProperty() { "prop1" }
}
use(MyCategory) {
assert "missingMethod" == a.missingMethod('a', 'b') // methods are the
assert "invokeMethod" == a.invokeMethod('a', 'b')
assert "prop1" == a.myProperty
// but they are not in effect
// assert "missingMethod" == a.method1() // MissingMethodException
// assert "invokeMethod" == a.method2() // MssingMethodException
// assert "missingProperty" == a.property // MissingPropertyException
}
So far invokeMethod, getProperty and getMetaClass so there is only other two methods from GroovyObject left : setMetaClass and setProperty but since the getter versions of those don't work I suspect the setter versions won't work either. So I can't really add any GroovyObject method at all in this way.
In this other SO question: get vs getProperty in groovy there is some discussion about the MOP and the only answer points to the "add method to the metaclass instead of using categories" solution. But my question is different, is it really possible or not to use a category to add invokeMethod or methodMissing?
So what is the right way (if any) to add GroovyObject methods via categories then?
Current Groovy 2.4.x can't do this but it has been added for 2.5.0-rc-3, 2.6.0-alpha-4, 3.0.0-alpha-3. So future versions of Groovy will allow redefining methodMissing and propertyMissing via a category classes. This feature was added on commit ad664b1 on May 2018 and was tracked by GROOVY-3867
// since groovy 2.5.0-rc-3
class X{ def bar(){1}}
class XCat{ static bar(X x){2}}
class XCat2{ static bar(X x){3}}
class XCat3{ static methodMissing(X x, String name, args) {4}}
class XCat4{ static propertyMissing(X x, String name) {"works"}}
def x = new X()
shouldFail(MissingPropertyException) {
assert x.baz != "works" // accessing x.baz should throw MPE
}
use(XCat4) {
assert x.baz == "works"
}
shouldFail(MissingPropertyException) {
assert x.baz != "works" // accessing x.baz should throw MPE
}
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
}
It seems that setting a default value for a class property, is not honored by #Builder.
#Test
void test() {
assert Foo.builder().build().getProp() != null // fail
}
#Builder
class Foo {
Map prop = [:]
}
I'll probably fix this by overriding the build method but how?
Not really sure about the implementation of builder() method of #Builder.
I believe you need to initialize the properties / members of the class, then do .build() to create the instance of the class.
Here is the example:
import groovy.transform.builder.Builder
#Builder
class Foo {
Map prop
}
def map = [a:1, b:2]
def f = Foo.builder().prop(map).build()
assert map == f.prop // or you can use f.getProp()
You can quickly try it online Demo
If you notice, the demo example shows how you can initialize multiple properties while building the object.