Groovy #Canonical and #TupleConstructor choking with abstract base class - groovy

I have the following Groovy classes:
#Canonical
abstract class BaseEntity {
Long id
String refId
}
#Canonical
#TupleConstructor(includeSuperFields = true, includeFields = true)
#ToString(includeSuperProperties = true)
class GroceryItem extends BaseEntity {
String name
Integer quantity
}
Then at runtime I'm creating an instance of GroceryItem:
GroceryItem cheeseWedges = new GroceryItem(1L,
'067e6162-3b6f-4ae2-a171-2470b63dff00', 'Cheese Wedges', 4)
When this constructor runs I get the following exception:
groovy.lang.GroovyRuntimeException: Could not find matching constructor for: com.example.myapp.GroceryItem(java.lang.Long, java.lang.String, java.lang.String, java.lang.Integer)
at groovy.lang.MetaClassImpl.invokeConstructor(MetaClassImpl.java:1732)
at groovy.lang.MetaClassImpl.invokeConstructor(MetaClassImpl.java:1532)
at org.codehaus.groovy.runtime.callsite.MetaClassConstructorSite.callConstructor(MetaClassConstructorSite.java:49)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(CallSiteArray.java:60)
What's going on here? What's the fix? Is the fact that BaseEntity is abstract causing problems here? I seem to remember having a similar issue with these annotations + ABC's a few years ago.

Hope you might aware that Canonical itself is combination of ToString, TupleConstructor and EqualsAndHashCode.
Here since you are explicitly specifying TupleConsturctor with options, Canonical may be removed for sub-class i.e., GroceryItem.
Apart from it, need to includeSuperProperties=true options for TupleConsturctor in order to achieve the desired behaviour. Here is blog which you can refer for more details.
Since the base class is abstract, annotation is not required.
In case if the base class is regular class, and you want to call super() of base class constructor, then callSuper=true option can be included to TupleConstructor annotation of child class. Of course, Canonical would be required that time for base class as well.
In case if a property is defined with access modifier in base class, say public String description then includeSuperFields=true option needs to be added to TupleConstructor of child class.
Here is fixed code snippet:
import groovy.transform.*
abstract class BaseEntity {
Long id
String refId
}
#TupleConstructor(includeSuperProperties=true)
#ToString(includeSuperProperties=true)
class GroceryItem extends BaseEntity {
String name
Integer quantity
}
def item = new GroceryItem(1L,'067e6162-3b6f-4ae2-a171-2470b63dff00', 'Cheese Wedges', 4)
println item.toString()
You can quickly try it online demo

Related

groovy immutable object with parent class

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 :)

Add a missing property in a groovy #Canonical bean constructor call?

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,

Groovy #Immutable class with (im)mutable property

I'm trying to use Groovy #groovy.transform.Immutable to implement classes with properties of unsupported "immutable" types. In my case it is java.io.File
For example, having class like
#groovy.transform.Immutable class TwoFiles {
File file1,file2
}
gives me following compile error
Groovyc: #Immutable processor doesn't know how to handle field 'file1' of type 'java.io.File' while compiling class TwoFiles.
#Immutable classes only support properties with effectively immutable types including:
- Strings, primitive types, wrapper types, BigInteger and BigDecimal, enums
- other #Immutable classes and known immutables (java.awt.Color, java.net.URI)
- Cloneable classes, collections, maps and arrays, and other classes with special handling (java.util.Date)
Other restrictions apply, please see the groovydoc for #Immutable for further details
One option I found it to extend java.io.File to make it Cloneable but I'm not happy with this solution. Following code compiles and works, but having own subclass of java.io.File is not what I'd like.
#groovy.transform.Immutable class TwoCloneableFiles {
FileCloneable file1,file2
class FileCloneable extends File implements Cloneable{
FileCloneable(String s) {
super(s)
}
// ... and other constructors ...
}
}
So my question is: Is there any other option how to use java.io.File directly in such class?
Possibly to mark java.io.File as "known immutable" for the purpose of #groovy.transform.Immutable (same as it seems to be done for java.awt.Color, java.net.URI)?
Have you tried using knownImmutableClasses to specify File? Something like this should work:
#groovy.transform.Immutable(knownImmutableClasses = [File])
class TwoFiles {
File file1,file2
}
(With File, you could probably also get rougly the effect you want with the following:
#groovy.transform.Immutable
class TwoFiles {
String file1,file2
public File getFile1() {return new File(file1)}
public File getFile2() {return new File(file2)}
}
def f = new TwoFiles("/", "/Users")
assert f.file1.class == File
)

"def Type" declarations in groovy classes

Suddenly I've realized that I can write
class Person {
def String name
}
My question is - what is the difference between code provided above and classic:
class Person {
String name
}
Why first form even exists?
There's no difference. Adding def to the beginning of a type definition does nothing. However, it is allowed by the parser.
One way to check stuff like this, is to fire up the groovyConsole, and run the AST Browser (which for the script)
class Person {
def String name
}
Shows:
public class Person extends java.lang.Object {
private java.lang.String name
}

Accessing static field in annotation

Im trying use a Java annotation in a Groovy class but have trouble to set a static field of a java class as a parameter:
The Annotation: Id.java
package x.y.annotations;
import java.lang.annotation.ElementType;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface Id {
public Class<Adapter> adapter();
public Class<Object> targetType();
public String targetAttribute();
public String onDelete();
}
The java class with the static fields: XPerson.java
package x.y.static.domain;
public class XPerson {
public static String ID;
}
And the groovy class, where the problem occurs: Person.groovy
package x.y.domain
import x.y.annotations.Id
import x.y.static.domain.XPerson
class Person {
#Id(adapter = Adapter, targetType = XPerson, targetAttribute = XPerson.ID, onDelete = "delete")
long id
}
Eclipse marks the "targetAttribute = XPerson.ID" part with:
Groovy:expected 'x.y.domain.XPerson.ID' to be an inline constant of type java.lang.String not a property expression in #x.y.annotations.Id
I also tried things like "XPerson.#ID" or defining a getter for the ID field, but nothing helped.
Any hints would be great.
Regards,
michael
I have found a related issue in the Groovy JIRA. It is a bug. Should work. See https://issues.apache.org/jira/browse/GROOVY-3278
Annotation values may only be compile-time constant expressions. Making the field final is an option. (With the caveat that the field can't be initialized in a static initializer/etc. as the snippet implies.)

Resources