Groovy AS keyword for Map > Class binding - groovy

Given the following classes:
class A {
B b
int data
int data2
}
class B {
C c
String data
}
class C {
Date data
}
The code works fine:
Date now = new Date()
def a = [ data:42, data2:84, b:[ data:'BBB', c:[ data:now ] ] ] as A
assert a.b.c.data == now
assert a.data == 42
assert a.data2 == 84
Now if I omit the data2:84, the code still works fine except for the last assert of course.
BUT! If I "misspell" the property name like:
def a = [ data:42, data22:84, b:[ data:'BBB', c:[ data:now ] ] ] as A
I'm getting
java.lang.NullPointerException: Cannot get property 'c' on null object
and what I see is that only the A class gets instantiated with a no-arg constructor, and b and c properties are all nulls.
So, skipping != misspelling.
Hence 2 questions:
(rather phylosophycal). is this the expected behaviour? Shouldn't the misspelled props be skipped?
How can the as keyword be made "lenient" to skip the unknown props?
TIA
UPDATE:
A JIRA task is created https://issues.apache.org/jira/browse/GROOVY-9348

There is one main difference. When you use a map constructor that contains existing class fields, a regular A object is initialized. Here is what println a.dump() produces in such a case.
<A#7bab3f1a b=B#1e1a0406 data=42 data2=84>
However, if you put entries to your map that are not represented by the class fields, Groovy does not initialize A object but creates a proxy of A class instead.
<A1_groovyProxy#537f60bf $closures$delegate$map=[data:42, data22:84, b:[data:BBB, c:[data:Fri Dec 20 13:39:50 CET 2019]]] b=null data=0 data2=0>
This proxy does not initialize fields at all, but it stores your map passed with a constructor as an internal $closures$delegate$map field.
Take a look at the following analysis I made using your example.
DefaultGroovyMethods.asType(Map map, Class<?> clazz) throws internally GroovyCastException
org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object '{data=42, data22=84, b={data=BBB, c={data=Fri Dec 20 12:22:28 CET 2019}}}' with class 'java.util.LinkedHashMap' to class 'A' due to: org.codehaus.groovy.runtime.metaclass.MissingPropertyExceptionNoStack: No such property: data22 for class: A
Possible solutions: data2, data
Source: https://github.com/apache/groovy/blob/GROOVY_2_5_8/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java#L11813
The exception is caught and the fallback method is called:
return (T) ProxyGenerator.INSTANCE.instantiateAggregateFromBaseClass(map, clazz);
Source: https://github.com/apache/groovy/blob/GROOVY_2_5_8/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java#L11816
ProxyGenerator initializes ProxyGeneratorAdapter in the following method:
public GroovyObject instantiateAggregate(Map closureMap, List<Class> interfaces, Class clazz, Object[] constructorArgs) {
if (clazz != null && Modifier.isFinal(clazz.getModifiers())) {
throw new GroovyCastException("Cannot coerce a map to class " + clazz.getName() + " because it is a final class");
}
Map<Object,Object> map = closureMap != null ? closureMap : EMPTY_CLOSURE_MAP;
ProxyGeneratorAdapter adapter = createAdapter(map, interfaces, null, clazz);
return adapter.proxy(map, constructorArgs);
}
Source: https://github.com/apache/groovy/blob/GROOVY_2_5_8/src/main/groovy/groovy/util/ProxyGenerator.java#L162
This method has potentially a bug: in the createAdapter(map, interfaces, null, clazz) part, the null value represents delegatingClass object. When it is null, there is no delegation mechanism applied in the generated proxy class.
Last but not least, adapter.proxy(map, constructorArgs) instantiates a new object, which string dump representation looks like this:
<A1_groovyProxy#537f60bf $closures$delegate$map=[data:42, data22:84, b:[data:BBB, c:[data:Fri Dec 20 13:29:06 CET 2019]]] b=null data=0 data2=0>
As you can see, the map passed to the constructor is stored as $closure$delegate$map field. All A class values are initialized with their default values (null for b field, and 0 for remaining int fields.)
Now, ProxyGenerator class has a method that creates an adapter that supports delegation:
public GroovyObject instantiateDelegateWithBaseClass(Map closureMap, List<Class> interfaces, Object delegate, Class baseClass, String name) {
Map<Object,Object> map = closureMap != null ? closureMap : EMPTY_CLOSURE_MAP;
ProxyGeneratorAdapter adapter = createAdapter(map, interfaces, delegate.getClass(), baseClass);
return adapter.delegatingProxy(delegate, map, (Object[])null);
}
Source: https://github.com/apache/groovy/blob/GROOVY_2_5_8/src/main/groovy/groovy/util/ProxyGenerator.java#L203
I'm guessing that maybe if the ProxyGeneratorAdapter with a non-null delegatingClass was used, calling a.b would use a value from the internal delegate map, instead of the b field value. That's only my assumption.
The question is: is this a bug? It depends. As cfrick mentioned in one of the comments, initializing A with an incorrect map throws an explicit error, and you are done. Here this exception is suppressed and from the caller perspective, you have no idea what happened in the background. I run those tests using Groovy 2.5.8 and 3.0.0-RC1, the same behavior in both versions. Reporting this is an issue in the Apache Groovy JIRA project sounds reasonable, so you can get feedback from the Groovy core maintainers.

Related

Possibility to disable the property syntax for accessing a getter in groovy?

Let's I have a groovy class like:
class SomeClass {
String myProperty = 'foo'
}
Usually in groovy is will be totally valid to access the value using the property name or the getter - which usually gives the same result for SomeClass:
SomeClass someClass = new SomeClass()
assert someClass.myProperty == 'foo'
assert someClass.getMyProperty() == 'foo'
However - due to a flaw in the Jenkins Pipeline implementation - sometimes(!) you are forced to use the getter - as the plain property access will not work (when using some class hierarchy), see: JENKINS-47143. Bad thing is that the same code may work for some jobs while it doesn't for others:
SomeClass someClass = new SomeClass()
assert someClass.myProperty == 'foo' // sometimes throws 'property not found' error
assert someClass.getMyProperty() == 'foo'
Now I already have couple of unit tests for our Jenkins shared library - but what is missing would be a way to detect a property access, in short: A way to prohibit the property access so the unit tests will already complain in advance.
The following code:
class SomeClass {
String myProperty = 'foo'
}
SomeClass.metaClass.getProperty = { String name ->
throw new RuntimeException("tried to get property ${name}, property access only allowed via getXX() methods")
}
def s = new SomeClass()
println(s.myProperty) // a
println(s.getMyProperty()) // b
will throw an exception for line a but not throw an exception for line b. I suspect this will not be possible if SomeClass was written in java, but assuming a groovy class this could be a way to accomplish what you want.
Running the above will result in:
─➤ groovy solution.groovy
Caught: java.lang.RuntimeException: tried to get property myProperty, property access only allowed via getXX() methods
java.lang.RuntimeException: tried to get property myProperty, property access only allowed via getXX() methods
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
...

How can I use Arrow-kt's Some() in Groovy code?

I'm trying to test my Kotlin code, which has Arrow-kt types, using Spock in Groovy. However, I'm not able to use Arrow-kt's additions such as Some. For example, I have a test as follows:
#Unroll
def "add returns #expected for queryRecord #queryRecord"() {
given:
def ip = "ip"
def rule = "rule"
when:
def result = unit.add(ip, rule)
then:
1 * dynamoDBMapperMock.load(ActionRecord.class, ip) >> queryRecord
result == expected
where:
queryRecord | expected
new ActionRecord() | None.INSTANCE
null | Some(new ActionInternal("ip"))
}
While the first data row succeeds with no problems, the second one fails with the following error:
groovy.lang.MissingMethodException: No signature of method: package.name.EventSpec.Some() is applicable for argument types: (package.name.ActionInternal) values: [ActionInternal(dropletIp=null)]
Possible solutions: Mock(), Spy(), Stub(), dump(), Mock(groovy.lang.Closure), Mock(java.lang.Class)
I've tried .some() as well, but not to avail. Apparently Groovy can't access Kotlin extensions, but Some is simply a data class[1], so I'm not sure why I cannot use it in Groovy.
Yes, you can use Arrow Datatypes in Groovy, the result is not as idiomatic as in Kotlin because the library heavily depends on extension functions and functions in the companion object
Example
import arrow.core.Option
import static arrow.core.OptionKt.getOrElse
static main(args){
println 'What is your name?'
def name = Option.#Companion.fromNullable(System.in.newReader().readLine())
.filterNot { it.isEmpty() }
.map { it.toUpperCase() }
println("Welcome ${getOrElse(name) { 'Anonymous' }}")
}
Output
'Welcome JOHN' (or 'Welcome Anonymous' if the provided name is null or empty)
As you can see, to be able to use getOrElse extension function, we need to import it as a static method
Hint
Do not use Some directly unless you are absolutely sure the value is not null, otherwise, you should rely on Option.fromNullable to safely lift the value to the Option context (i.e create Some or None depending if the value is null or not)

Applying default groovy method parameter value when passing null

In Groovy, if I have:
def say(msg = 'Hello', name = 'world') {
"$msg $name!"
}
And then call:
say() // Hello world!
say("hi") // Hi world!
say(null) // null world!
Why is the last one getting interpreted literally as null and not applying the default value? Doesn't this defeat the purpose of default method argument values? I do get that passing null is different from not passing anything w/r/t argument length.
My problem here is that if I now have a method that takes a collection as an argument:
def items(Set<String> items = []) {
new HashSet<>(items)
}
This will throw a NullPointerException if I call items(null) but work fine if I just say items(). In order for this to work right, I have to change the line to be new HashSet<>(items ?: []) which, again, seems to defeat the entire purpose of having default method argument values.
What am I missing here?
In Groovy, default parameters generates overloaded methods. Thus, this:
def items(Set<String> items = []) {
new HashSet<>(items)
}
Will generate these two methods (I used javap to get these values):
public java.lang.Object items(java.util.Set<java.lang.String>);
public java.lang.Object items();
So when you call items(null) you are, in fact, passing some value, and items(Set) method will be used.
You can also refer to this question about default parameters.

In which sequence does method call work in groovy?

I am using groovy 2.3.8
I am trying to figure out how method calls work in groovy. Specifically if we have a Java class hierarchy each having a metaClass like below
class A {
}
A.metaClass.hello = {
"hello superclass"
}
class B extends A {
}
B.metaClass.hello = {
"hello subclass"
}
If I use new B().hello() I get hello subclass. If I remove meta class of B then I get hello superclass.
Based on changing the above example I think groovy goes in the below sequence to find which method to call
method-in-subclass's-metaclass ?: subclass-metho ?: method-in-superclass's metaclass ?: method-in-superclass
So how does groovy lookup which method to call?
Well, the hierarchy is the expected object oriented programming method overloading, which is what you witnessed. What differs is the dispatching. Instead of starting with a method lookup in instance's class, it begins with the MOP (meta object protocol).
In layman's terms, because the MOP is programmable, so is the way methods are invoked :)
How it works
The following diagram from Groovy's documentation shows how methods are looked up.
What's not clear in the diagram is that there's an instance metaclass as well, and it comes before the class's metaclass.
Something that may help is looking at an object's or class's .metaClass.methods Methods added through inheritance, traits, metaclass, etc are listed in a flat list. The inheritance hierarchy is flattened. .metaClass.metaMethods on the other hand seems to contain methods added via the GDK. From the list I could not tell method precedence :(
Based on observation, the rule seems to be this: the last MetaClass standing wins.
class A { }
class B extends A { }
A.metaClass.hello = {
"hello superclass"
}
B.metaClass.hello = {
"hello subclass"
}
def b = new B()
assert b.hello() == "hello subclass"
b.metaClass = A.metaClass
assert b.hello() == "hello superclass"

how to retrieve nested properties in groovy

I'm wondering what is the best way to retrieve nested properties in Groovy, taking a given Object and arbitrary "property" String. I would like to something like this:
someGroovyObject.getProperty("property1.property2")
I've had a hard time finding an example of others wanting to do this, so maybe I'm not understanding some basic Groovy concept. It seems like there must be some elegant way to do this.
As reference, there is a feature in Wicket that is exactly what I'm looking for, called the PropertyResolver:
http://wicket.apache.org/apidocs/1.4/org/apache/wicket/util/lang/PropertyResolver.html
Any hints would be appreciated!
I don't know if Groovy has a built-in way to do this, but here are 2 solutions. Run this code in the Groovy Console to test it.
def getProperty(object, String property) {
property.tokenize('.').inject object, {obj, prop ->
obj[prop]
}
}
// Define some classes to use in the test
class Name {
String first
String second
}
class Person {
Name name
}
// Create an object to use in the test
Person person = new Person(name: new Name(first: 'Joe', second: 'Bloggs'))
// Run the test
assert 'Joe' == getProperty(person, 'name.first')
/////////////////////////////////////////
// Alternative Implementation
/////////////////////////////////////////
def evalProperty(object, String property) {
Eval.x(object, 'x.' + property)
}
// Test the alternative implementation
assert 'Bloggs' == evalProperty(person, 'name.second')
Groovy Beans let you access fields directly. You do not have to define getter/setter methods. They get generated for you. Whenever you access a bean property the getter/setter method is called internally. You can bypass this behavior by using the .# operator. See the following example:
class Person {
String name
Address address
List<Account> accounts = []
}
class Address {
String street
Integer zip
}
class Account {
String bankName
Long balance
}
def person = new Person(name: 'Richardson Heights', address: new Address(street: 'Baker Street', zip: 22222))
person.accounts << new Account(bankName: 'BOA', balance: 450)
person.accounts << new Account(bankName: 'CitiBank', balance: 300)
If you are not dealing with collections you can simply just call the field you want to access.
assert 'Richardson Heights' == person.name
assert 'Baker Street' == person.address.street
assert 22222 == person.address.zip
If you want to access a field within a collection you have to select the element:
assert 'BOA' == person.accounts[0].bankName
assert 300 == person.accounts[1].balance​​​​​​​​​
You can also use propertyMissing. This is what you might call Groovy's built-in method.
Declare this in your class:
def propertyMissing(String name) {
if (name.contains(".")) {
def (String propertyname, String subproperty) = name.tokenize(".")
if (this.hasProperty(propertyname) && this."$propertyname".hasProperty(subproperty)) {
return this."$propertyname"."$subproperty"
}
}
}
Then refer to your properties as desired:
def properties = "property1.property2"
assert someGroovyObject."$properties" == someValue
This is automatically recursive, and you don't have to explicitly call a method. This is only a getter, but you can define a second version with parameters to make a setter as well.
The downside is that, as far as I can tell, you can only define one version of propertyMissing, so you have to decide if dynamic path navigation is what you want to use it for.
See
https://stackoverflow.com/a/15632027/2015517
It uses ${} syntax that can be used as part of GString

Resources