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.
Related
I have set up a simple dummy class as follows, and used a static initialiser to update the metaClass:
class DynamicExtendableClass {
static String declaredStaticString = "declared static string"
static String getDeclaredMethodStaticString () {
"static method returning string"
}
static {
println "static initialiser - adding dynamic properties and methods to metaClass"
DynamicExtendableClass.metaClass.addedProperty = "added property to class metaClass"
DynamicExtendableClass.metaClass.getAddedMethod = { -> "added closure as method" }
DynamicExtendableClass.metaClass.static.getStaticAddedMethod = { -> "added closure as static method" }
}
}
I have a simple test case like this:
#Test
void testExtendedMetaClassStuff () {
DynamicExtendableClass testInstance = new DynamicExtendableClass()
assertEquals ("added property to class metaClass", testInstance.addedProperty)
assertEquals ("added closure as static method", testInstance.getStaticAddedMethod()) //calls getStaticAddedMethod - groovy trick
assertEquals ("added closure as method", testInstance.addedMethod) //works. calls getAddedMethod - groovy trick for getXxx as property
assertEquals ("added closure as static method", DynamicExtendableClass.staticAddedMethod ) //works class static class Closure
}
Which works only once you create a first instance of the class which forces a switch to ExpandoMetaClass for you.
If you don't do this first the default HandleMetaClassImpl doesn't work for this.
However to get this to work for static you have to create closure like getXxxx = {-> ...}, which if you call 'DynamicExtendableClass.staticAddedMethod' will sneakily invoke the closure for you.
However, there's not really a means to add a property capability here for '.static' as there is on the standard metaClass itself. All you can do is set a closure onto .static. Why is this?
The other problem is having to create an instance of the class first to force the switch to ExpandoMetaClass, is there not a simple way to force the metaClass change when declaring the class in the first class, before creating any instances ?
I want to add some static properties (later some methods maybe ) dynamically to a class, but all you can add is static closures, which is a little limiting on the scenario I had in mind.
PostScript
I managed to force a change of metaClass on class without having to create an instance, but it's a bit hard work:
#Test
void testMetaClassStatic () {
println DynamicExtendableClass.metaClass
MetaClassRegistry registry = GroovySystem.getMetaClassRegistry()
MetaClass origMC = registry.getMetaClass(DynamicExtendableClass)
assert origMC.getClass() == HandleMetaClass //default implementation
ExpandoMetaClass emc = new ExpandoMetaClass (DynamicExtendableClass, true, true)
emc.static.getStaticAddedMethod = {-> "static hello from my emc"}
emc.initialize()
registry.removeMetaClass(DynamicExtendableClass)
registry.setMetaClass(DynamicExtendableClass, emc)
assert DynamicExtendableClass.metaClass.getClass() == ExpandoMetaClass
assert DynamicExtendableClass.staticAddedMethod == "static hello from my emc"
registry.removeMetaClass(DynamicExtendableClass)
registry.setMetaClass(DynamicExtendableClass, origMC)
}
But doing this breaks my previously working tests (not sure why) with:
Could not initialize class extensible.DynamicExtendableClass
java.lang.NoClassDefFoundError: Could not initialize class extensible.DynamicExtendableClass
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
at org.codehaus.groovy.reflection.CachedConstructor.invoke(CachedConstructor.java:73)
at org.codehaus.groovy.runtime.callsite.ConstructorSite$ConstructorSiteNoUnwrapNoCoerce.callConstructor(ConstructorSite.java:108)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(CallSiteArray.java:59)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:263)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:268)
at extensible.DynamicExtendableClassTest.testExtendedMetaClassStuff(DynamicExtendableClassTest.groovy:22)
at ...
Another postscript
I did a little exploration with a debugger. 1st the metaClass.static returns a class of type ExpandoMetaClass.ExpandoMetaProperty which of itself isn't terribly useful. You can do a direct .#this$0 field access however which just points the same metaClass instance as the target class you start with.
Therefore ignoring this you can do a direct field grab on <yourClass>.metaClass.#expandoProperties (I tried to get this via reflection using:
PropertyValue expandoProperties = clazz.metaClass.getMetaPropertyValues().find{it.name == 'expandoProperties'}
List<MetaBeanProperty> MBprops2= properties.getValue()
Map m2 = MBprops.findAll{Modifier.isPublic(it.modifiers)}.collectEntries{[(it.name), it.getProperty(clazz)] }
but it doesn't get the same content as the direct field access does.
The direct field access returns a Map where the key is the string value of any added closures or properties added dynamically to the metaClass, and the value is a MetaBeanProperty reference.
On that MetaBeanProperty you can invoke the getProperty (object) using with the class metaClass or per instance metaClass - and it returns the value of that property (whether it's just a closure or a real property) for you. You can also test whether its static or not:
Map m4 = thisMc.#expandoProperties
MetaBeanProperty asm = m4['addedStaticMethod']
def val2 = asm.getProperty(clazz)
boolean isstatic = Modifier.isStatic(asm.modifiers)
Kind of brutal but it sort of works if you want to dynamically query the dynamic editions to the metaclass.
The problem of forcing the switch from default metaClass to the ExpandoMetaClass remains a problem. The best way seems to create a throw away class instance as this does the one time switch for you.
I tried to force this myself using the metaClass registry which you can do, but then the future create new instance for your class seems to stop working ie. doing somethings like this and putting the original back afterwords seems to break any future new <MyClass>() calls.
MetaClassRegistry registry = GroovySystem.getMetaClassRegistry()
MetaClass origMC = registry.getMetaClass(DynamicExtendableClass)
assert origMC.getClass() == MetaClassImpl //default implementation
def constructors = MetaClassImpl.getConstructors()
ExpandoMetaClass emc = new ExpandoMetaClass (DynamicExtendableClass, true, true)
emc.static.getStaticAddedMethod = {-> "static hello from my emc"}
emc.constructor = { new DynamicExtendableClass() }
emc.initialize()
registry.removeMetaClass(DynamicExtendableClass)
registry.setMetaClass(DynamicExtendableClass, emc)
assert DynamicExtendableClass.metaClass.getClass() == ExpandoMetaClass
assert DynamicExtendableClass.staticAddedMethod == "static hello from my emc"
registry.removeMetaClass(DynamicExtendableClass)
registry.setMetaClass(DynamicExtendableClass, origMC)
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 have two immutable groovy classes that have a few shared values that I'm trying to abstract to a parent class. However when I create the following, the second test case always fails. Although everything compiles correctly and no error is thrown at runtime, when I assign the parent property int he constructor, it is never set, resulting in a null value. I havent found any documentation that forbids this, but I'm wondering is this even possible? I've tried a number of configuration of Annotations and class-types (e.g. removing abstract from the parent) but nothing seems to work short of just removing the #Immutable tag altogether.
abstract class TestParent {
String parentProperty1
}
#ToString(includeNames = true)
#Immutable
class TestChild extends TestParent {
String childProperty1
String childProperty2
}
class TestCase {
#Test
void TestOne() {
TestChild testChild = new TestChild(
childProperty1: "childOne",
childProperty2: "childTwo",
parentProperty1: "parentOne"
)
assert testChild
assert testChild.parentProperty1
}
}
Based on the code for the ImmutableASTTransformation, the Map-arg constructor added by the createConstructorMapCommon method does not include a call to super(args) in the method body.
which means that immutable classes are self contained by default
Now if you want to do it you need to use composition instead of inheritance and this is an example of how you can do it :
import groovy.transform.*
#TupleConstructor
class A {
String a
}
#Immutable(knownImmutableClasses=[A])
class B {
#Delegate A base
String b
}
def b = new B(base: new A("a"), b: "b")
assert b.a
i hope this will help :)
I am new to groovy and just started exploring its metaprogramming capabilities. I got stuck with adding missing properties on a bean constructor call.
In a class to be used with FactoryBuilderSupport, I want to dynamically add those properties that are not yet defined and provided during the constructor call. Here is stripped-down version:
#Canonical
class MyClass {
def startDate
def additionalProperties = [:]
def void propertyMissing(String name, value) {
additionalProperties[name] = value
}
}
However, If I construct the class with unknown properties, the proprty is not added but I get a MissingPropertyException instead:
def thing = new MyClass(startDate: DateTime.now(), duration: 1234)
The property duration does not exist, and I expected it to be handled via propertyMissing. As far as I understand groovy, calling the tuple-constructor results in a no-argument constructor call followed by calls to the groovy-generated setters. So why do I get a MissingPropertyException?
As I am new to groovy, I am probably missing some basic AST or MOP rules. I would highly appreciate your help.
If you use #Canonical and you define the first class object with def like you are doing with startDate the annotation generates the following constructors:
#Canonical
class MyClass {
def startDate
def additionalProperties = [:]
def void propertyMissing(String name, value) {
additionalProperties[name] = value
}
}
// use reflection to see the constructors
MyClass.class.getConstructors()
Generated constructors:
public MyClass()
public MyClass(java.lang.Object)
public MyClass(java.util.LinkedHashMap)
public MyClass(java.lang.Object,java.lang.Object)
In the #Canonical documentation you can see the follow limitation:
Groovy's normal map-style naming conventions will not be available if the first property has type LinkedHashMap or if there is a single Map, AbstractMap or HashMap property
Due to public MyClass(java.util.LinkedHashMap) is generated you can't use tuple-constructor and you get MissingPropertyException.
Surprisingly if you define your first object (note that I say the first) with a type instead of using def, #Canonical annotation doesn't add the public MyClass(java.util.LinkedHashMap) and then your tuple-constructor call works, see the following code:
#Canonical
class MyClass {
java.util.Date startDate
def additionalProperties = [:]
def void propertyMissing(String name, value) {
additionalProperties[name] = value
}
}
// get the constructors
MyClass.class.getConstructors()
// now your code works
def thing = new MyClass(startDate: new java.util.Date(), duration: 1234)
Now the created constructors are:
public MyClass()
public MyClass(java.util.Date)
public MyClass(java.util.Date,java.lang.Object)
So since there isn't the public MyClass(java.util.LinkedHashMap) the limitation doesn't apply and you tuple-constructor call works.
In addition I want to say that since this solution works I can't argue why... I read the #Canonical documentation again and again and I don't see the part where this behavior is described, so I don't know why works this way, also I make some tries and I'm a bit confusing, only when the first element is def the public MyClass(java.util.LinkedHashMap) is created i.e:
#Canonical
class MyClass {
def a
int c
}
// get the constructors
MyClass.class.getConstructors()
First object defined as def...
public MyClass()
public MyClass(java.lang.Object)
public MyClass(java.util.LinkedHashMap) // first def...
public MyClass(java.lang.Object,int)
Now if I change the order:
#Canonical
class MyClass {
int c
def a
}
// get the constructors
MyClass.class.getConstructors()
Now first is not def and public MyClass(java.util.LinkedHashMap) is not generated:
public MyClass()
public MyClass(int)
public MyClass(int,java.lang.Object)
Hope this helps,
I am running into a problem while trying to use property access in Groovy. Take the following class:
class Foo {
Map m = [:]
String bar
void getProperty(String name) {
m.get name
}
def setProperty(String name, value) {
m.set name, value
}
String getBarString() {
return bar // local access, does not go through getProperty()
}
}
It overrides the getter and setter to simply place the values into a Map rather than into the object's normal property space. In the abstract this is a bit silly, but imagine that instead of placing the data into a map we were persisting it to a DB or something else useful.
Unfortunately, the following code now won't work:
foo = new Foo()
foo.bar = "blerg" // using foo.bar invokes the setProperty interceptor
assert foo.bar == "blerg" // this will work fine as foo.bar here uses the getProperty interceptor
assert foo.getBarString() == "blerg" // explosion and fire! getBarString accesses bar locally without going through the getProperty interceptor so null will actually be returned.
Certainly there are workarounds for this, setProperty could set both the MetaProperty and the Map value, etc. However, all of the strategies I've thought of require a lot of extra caution from the programmer to make sure they are accessing class properties in the exact way that they mean to.
Furthermore, some of the built in awesome stuff in Groovy (like #Delegate for example) uses direct MetaProperty access rather than going through getProperty so the following would never work:
class Meep {
String getMyMeep() {
return "MEEP!!!"
}
}
class Foo {
Map m = [:]
String bar
#Delegate Meep meep
void getProperty(String name) {
m.get name
}
def setProperty(String name, value) {
m.set name, value
}
String getBarString() {
return bar
}
}
foo = new Foo()
foo.meep = new Meep() // uses setProperty and so does not place the Meep in the Map m
foo.getMyMeep()
A null pointer exception is thrown on the last line as #Delegate uses MetaProperty direct access (effectively this.meep.getMyMeep() rather than the getProperty interceptor. Unfortunately 'meep' is null, though getProperty('meep') would not be.
In short what I'm looking for is a strategy to solve the following criteria:
intercept property read/write to enable automatic alternative data storage
transparent or near-transparent interface for other developers (I don't want to make other folks' lives significantly harder)
allow for local access of variables using the MetaProperty/this/etc. access methods
Thanks in advance!
You could use
foo.#meep = new Meep()
in order to directly access properties bypassing setProperty method.
That doesn't completely solves your problem though as the foo.meep still triggers set/getProperty.
Another way you could go about is by using getter and setter of the meet directly, i.e.
foo.setMeep(new Meep())
So, one unified way would be to define all of the variables as private and use get/set*PropertyName*
By using an AST Transformation I can do the following:
walk a class's structure and rename all local fields to something like x -> x.
add a getter/setter for each renamed field like this
def get_x_() {
x
}
...in order to access x as a field rather than as a Groovy property
- now apply the transformation on the following class
class Foo {
def x
def y
Map m = [:]
#Delegate Date date // for testing if non-local fields work
def getProperty(String name) {
if (this.respondsTo("get__${name}__")) // if this is one of our custom fields
return "get__${name}__"()
"get${Verifier.capitalize(name)}"() // pass to specific getter method
}
void setProperty {
if (this.respondsTo("set__${name}__")) {
"set__${name}__"(value)
m[name] = value
if (name == "x") y = x + 1
return
}
"set${Verifier.capitalize(name)}"(value)
}
}
now run a testing method like this:
public void testAST() {
def file = new File('./src/groovy/TestExample.groovy')
GroovyClassLoader invoker = new GroovyClassLoader()
def clazz = invoker.parseClass(file)
def out = clazz.newInstance()
out.x = 10
assert out.y == 11
out.y = 5
assert out.y == 5
out.x = 2
assert out.m.containsKey('x')
assert out.m.x == 2
assert out.m.y == 3
out.date = new Date()
assert out.time && out.time > 0
}
And everything should work out including m getting updated, date delegate method time getting accessed properly, etc.
-Glenn