When switching from Groovy 2 to Groovy 3, ModelMapper 2.4.4 seems to now be failing to convert objects. ModelMapper itself does not throw an error, but rather just returns an object whose metaClass is still the initial class rather than the new post-conversion class.
This is demonstrated in the below code which, when run with Groovy 3 (tested with 3.0.2 and 3.0.9), then throws java.lang.IllegalArgumentException: object is not an instance of declaring class when accessing any of the properties of the returned object post-ModelMapping. This error does not happen when run in Groovy 2 (2.5.15).
Dependencies:
org.modelmapper:modelmapper:2.4.4
org.codehaus.groovy:groovy:3.0.9
import org.modelmapper.ModelMapper
class TestClass {
String fieldA
String fieldB
}
class TestClassDto {
String fieldA
String fieldB
}
TestClassDto test = new TestClassDto(fieldA: 'anyA', fieldB: 'anyB')
System.out.println(new ModelMapper().map(test, TestClass).fieldA)
The issue is in the fact that metaClass gets automatically mapped (so, testclass.metaClass gets replaces with TestClass.metaClass and groovy considers the final object to be an instance of TestClass). There are multiple ways to fix this.
You can explicitly set the metaclass after the mapping has been done:
def 'testMapping'() {
given:
TestClassDto test = new TestClassDto(fieldA: 'anyA', fieldB: 'anyB')
def mapped = new ModelMapper().map(test, TestClass)
mapped.metaClass = TestClass.metaClass
expect:
mapped.fieldA == 'anyA'
}
Alternatively, use #CompileStatic so that metaclass isn't generated at all.
Or you can even configure Modelmapper to skip metaclass:
mapper.typeMap(TestClassDto, TestClass)
.addMappings({ it.skip(GroovyObject::setMetaClass) } as ExpressionMap)
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)
Consider the following code:
object SomeObjectA {
object SomeObjectB {
val a = "test"
}
}
val X = SomeObjectA
typealias Y = SomeObjectA
SomeObjectA.SomeObjectB // works
X.SomeObjectB // error
Y.SomeObjectB // error
I cannot refer to a nested object (in an outer object) using val or typealias which are referring to the outer object. Why?
the compiler error is comes from java, and the kotlin objects convert to java classes as below:
public final class SomeObjectA {
private SomeObjectA() {/**/}
public static final SomeObjectA INSTANCE = new SomeObjectA();
public static final class SomeObjectB {
private SomeObjectB() {/**/}
public static final SomeObjectB INSTANCE = new SomeObjectB();
}
}
SomeObjectA.SomeObjectB is compiled to java code as below:
SomeObjectA.SomeObjectB.INSTANCE;
SomeObjectA is compiled to java code as below:
SomeObjectA.INSTANCE
we know kotlin is base on java, and java don't allow access the nested classes via instance reference, if you do the compiler will reports an error:"Error: java: unexpected type required: class,package found: variable", for example:
SomeObjectA a = SomeObjectA.INSTANCE;
SomeObjectB b = a.SomeObjectB.INSTANCE;// error
// ^--- compiler don't know where to go? package&class or variable?
the code below, kotlin compiler will transforms the java compiler error as: "Error: Kotlin: Nested object 'SomeObjectB' accessed via instance reference".
val a = SomeObjectA;
val b = a.SomeObjectB;
// ^--- Error
Type aliases do not introduce new types. They are equivalent to the corresponding underlying types.
so the two statements below are equality:
val a = SomeObjectA;
typealias a2 = SomeObjectA;
avoiding to the use of typealias causing unnecessary compiler error, kotlin doesn't include all nested classes in typealias.
What you described happens because SomeObjectA in your example is simultaneously a name of an object and the name of its class.
So to access SomeObjectB, you need to use the <classname>.<classname> syntax. That is why X.SomeObjectB doesn't compile (<object>.<classname> is unsupported)
P.S. This doesn't really explain your second problem with typealias. It looks like like a bug to me, but I'm not sure.
I'm having trouble with (or I'm confused about) using #Immutability.
Given this class:
#Immutable
class TestField {
String key
}
I would expect I could not set a key on an instance like:
class TestFieldSpec extends Specification {
def 'do stuff'() {
when:
def t = new TestField('a')
println t
t.key = 'b'
println t
then:
t
}
}
However, when I run the test, I do not see a ReadOnlyPropertyException:
Running TestFieldSpec
TestField(a)
TestField(b)
This has been the case for me using both the groovy-eclipse-compiler and gmaven-plus with Maven.
Groovy version 2.4.7.
If I run javap on the class file created by both, I see:
public final void setKey(java.lang.String);
When I do the same in GroovyConsole though, things work how I'd expect them to.
#groovy.transform.Immutable
class TestField {
String key
}
def f = new TestField('a')
f.key = 'b'
Produces:
Exception thrown
groovy.lang.ReadOnlyPropertyException: Cannot set readonly property: key for class: TestField
at TestField.setProperty(ConsoleScript1)
at ConsoleScript1.run(ConsoleScript1:7)
In the Groovy AST Browser, at Phase Finalization, I do not see the setter.
Any insight would be greatly appreciated.
Thanks!
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 trying to override the functionality of a method of a java type instance in my Groovy code but I am getting a classcast exception.
I looked at the guide posted here but I can not get it to work.
Since my actual problem is a bit of mess, below is some runnable example code that fails with the same error.
In the example I want to override the substring method of an instance of the java.lang.String class. In reality I want to override a method of an instance of a class that does not have a corresponding Groovy implementation, so the answer to my example is not to simply use a Groovy string instance.
class example {
static void main(args) {
java.lang.String hey = new java.lang.String("hey")
ExpandoMetaClass emc = new ExpandoMetaClass( java.lang.String, false )
emc.substring = {
"This is not a very good substring implementation"
}
emc.initialize()
def proxiedHey = new groovy.util.Proxy().wrap(hey)
proxiedHey.setMetaClass(emc)
printf proxiedHey.toString()
printf proxiedHey.substring(1)
}
}
The above example fails at line 12, i.e printf meh.toString(). The exception thrown is
Caught: java.lang.ClassCastException:
groovy.util.Proxy cannot be cast to
java.lang.CharSequence at
example.main(test.groovy:12)
So, any ideas on what I am doing wrong or if there is another way to solve my problem of adding and/or overriding methods of a java type instance?
I am using Groovy version 1.7.4.
You are creating an ExpandoMetaClass for java.lang.String, but assigning it to a groovy.util.Proxy. Make a metaClass for groovy.util.Proxy instread, like so:
java.lang.String hey = new java.lang.String("hey")
def proxiedHey = new groovy.util.Proxy().wrap(hey)
ExpandoMetaClass emc = new ExpandoMetaClass( groovy.util.Proxy, false )
emc.substring = {
"This is not a very good substring implementation"
}
emc.initialize()
proxiedHey.setMetaClass(emc)
printf proxiedHey.toString()
printf proxiedHey.substring(1)
Have you looked at Pimp my Library Pattern which allows you to add using Groovy Categories. You might find it more convenient and easy to understand in your case.
#Category(String)
class StringSubstrCategory {
def substring( int n) {
"This is not a very good substring implementation"
}
}
use (StringSubstrCategory) {
"hey".substring(1)
}