How is this Map of String to String possible as a Map of String to a list of String in Groovy? - groovy

Saw some working code in a project that is like the one below (of course that's just an example) but I'm extremely confused at how this is possible as a Map<String, String> and not Map<String, List<String>>?
Looked it up in the Groovy doc but I didn't see anything that explains this concept. Could someone explain if this is indeed a legit concept in Groovy?
static final Map<String, String> NAMES = [
"Male": ["Bill", "Bryant", "Jack"],
"Female": ["Lily", "Haley", "Mary"]
]

It happens because of Java's Generic Type Erasure. If you look at the internal type signatures, you will see that NAMES field is just a type of java.util.Map. Take a look at the descriptor of that field.
$ javap -s -p SomeClass
Compiled from "SomeClass.groovy"
public class SomeClass implements groovy.lang.GroovyObject {
private static final java.util.Map<java.lang.String, java.lang.String> NAMES;
descriptor: Ljava/util/Map;
private static org.codehaus.groovy.reflection.ClassInfo $staticClassInfo;
descriptor: Lorg/codehaus/groovy/reflection/ClassInfo;
public static transient boolean __$stMC;
descriptor: Z
private transient groovy.lang.MetaClass metaClass;
descriptor: Lgroovy/lang/MetaClass;
private static java.lang.ref.SoftReference $callSiteArray;
descriptor: Ljava/lang/ref/SoftReference;
public SomeClass();
descriptor: ()V
public static void main(java.lang.String...);
descriptor: ([Ljava/lang/String;)V
protected groovy.lang.MetaClass $getStaticMetaClass();
descriptor: ()Lgroovy/lang/MetaClass;
public groovy.lang.MetaClass getMetaClass();
descriptor: ()Lgroovy/lang/MetaClass;
public void setMetaClass(groovy.lang.MetaClass);
descriptor: (Lgroovy/lang/MetaClass;)V
static {};
descriptor: ()V
public static java.util.Map<java.lang.String, java.lang.String> getNAMES();
descriptor: ()Ljava/util/Map;
private static void $createCallSiteArray_1(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
private static org.codehaus.groovy.runtime.callsite.CallSiteArray $createCallSiteArray();
descriptor: ()Lorg/codehaus/groovy/runtime/callsite/CallSiteArray;
private static org.codehaus.groovy.runtime.callsite.CallSite[] $getCallSiteArray();
descriptor: ()[Lorg/codehaus/groovy/runtime/callsite/CallSite;
}
Now, that' not all. If you decompile the Groovy bytecode to the Java readable code, you will find something like this.
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import groovy.lang.GroovyObject;
import groovy.lang.MetaClass;
import groovy.transform.Generated;
import java.util.Map;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.codehaus.groovy.runtime.callsite.CallSite;
public class SomeClass implements GroovyObject {
private static final Map<String, String> NAMES;
#Generated
public SomeClass() {
CallSite[] var1 = $getCallSiteArray();
super();
MetaClass var2 = this.$getStaticMetaClass();
this.metaClass = var2;
}
public static void main(String... args) {
CallSite[] var1 = $getCallSiteArray();
var1[0].callStatic(SomeClass.class, var1[1].call(NAMES));
}
static {
Map var0 = ScriptBytecodeAdapter.createMap(new Object[]{"Male", ScriptBytecodeAdapter.createList(new Object[]{"Bill", "Bryant", "Jack"}), "Female", ScriptBytecodeAdapter.createList(new Object[]{"Lily", "Haley", "Mary"})});
NAMES = var0;
}
#Generated
public static Map<String, String> getNAMES() {
return NAMES;
}
}
At this level, NAMES field signature matches Map<String, String>. But look at the static constructor and how this field gets initialized. The NAMES field of type Map<String, String> gets initialized with a raw Map type. Also, the ScriptBytecodeAdapter.createMap method returns a raw map, but it could return a parameterized map as well - you will see the same effect. If that map wasn't a raw map, the compiler would complain and throw an error because of incompatible types. But a raw map essentially allows you to assign a map that stores values that are incompatible with the parameters.
You can get the same effect in pure Java. Take a look at the following example:
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
final class SomeJavaClass {
private static final Map<String, String> NAMES;
static {
final Map map = new HashMap();
map.put("Male", Arrays.asList("Bill", "Brian", "Jack"));
NAMES = map;
}
public static void main(String[] args) {
System.out.println(NAMES);
System.out.println(((Object) NAMES.get("Male")).getClass());
}
}
When we execute its main method, we will get the following output:
{Male=[Bill, Brian, Jack]}
class java.util.Arrays$ArrayList
In this example, we had to cast NAMES.get("Male") to Object, because otherwise, we would get a ClassCastException - an ArrayList cannot be cast to String. But when you cast to Object explicitly, you can get an ArrayList from a map that, by definition, should keep only string values.
This is known Java behavior - generic types get erased so the raw types can be compatible with pre Java 1.5 versions. Groovy for its dynamic capabilities operates often on raw classes like Map, or List, and thus it can silently overcome some of those limitations.
If you want to use more restricted type checks, you can use #groovy.transform.TypeChecked annotation to keep Groovy's dynamic behavior and add more restrictive type checks. With this annotation added, your class won't compile anymore.

Related

How to apply `#POJO` to classes via config script?

I have a few classes which I'd like to keep as POJO. Manually annotating each of these would be troublesome, for both updating all current ones and adding future such classes.
I have a SourceAwareCustomizer able to identify all these classes. However I do not know how to apply the #POJO via the config script.
I tried ast(POJO), and I would get an error:
Provided class doesn't look like an AST #interface
I dug in the code a bit and found that #POJO is not an AST transformation (it's not annotated with #GroovyASTTransformationClass.
Is there a way to apply #POJO, or maybe a random annotation, to a class via the config script?
POJO is not an AST transformation.
Compare POJO source to ToString (for example). In POJO the GroovyASTTransformationClass annotation is missing..
I can't make #POJO working without #CompileStatic..
So, here is my try with groovy 4.0.1:
congig.groovy
import org.codehaus.groovy.ast.ClassNode
import org.codehaus.groovy.ast.AnnotationNode
import groovy.transform.stc.POJO
import groovy.transform.CompileStatic
withConfig(configuration) {
inline(phase:'SEMANTIC_ANALYSIS'){Object...args->
if(args.size()>2){
ClassNode cn = args[2]
if( cn.getSuperClass().name=="java.lang.Object" ) {
if( !cn.annotations.find{it.classNode.name==POJO.class.name} ) {
cn.addAnnotation( new AnnotationNode(new ClassNode(POJO.class)) )
//can't see how POJO could work without compile static in groovy 4.0.1
if( !cn.annotations.find{it.classNode.name==CompileStatic.class.name} )
cn.addAnnotation( new AnnotationNode(new ClassNode(CompileStatic.class)) )
}
}
println "class = $cn"
println "annotations = ${cn.getAnnotations()}"
}
}
}
A.groovy
class A{
String id
}
compile command line:
groovyc --configscript config.groovy A.groovy
generated class
public class A
{
private String id;
#Generated
public A() {}
#Generated
public String getId() {
return this.id;
}
#Generated
public void setId(final String id) {
this.id = id;
}
}

Is there a way to pass list of enums to step in cucumber 4.x and java

let's say I have example enum class
public enum Name { FIRST_NAME, LAST_NAME;}
and I have a such step
Then followed name types are listed:
| FIRST_NAME |
| LAST_NAME |
in which I want to pass List like
#Then("^followed name types are listed:$")
public void followedNameTypesAreListed(List<Name> nameTypes){...}
I'm currently migrating to cucumber 4.x and what i figured out is that i can register custom DataTableType like
typreRegistry.defineDataTableType(new DataTableType(Name.class,
(TableCellTransformer<Name>) Name::valueOf)
but doing it for every single enum class doesn't sound very efficient, isn't there any other way to handle list for any enum class?
One quick way to do this would be to us an object mapper as the default cell transformer. The object mapper will then be used in all situations where a cell is mapped to a single object and no existing data table type has been defined.
You could use jackson-databind for this.
In Cucumber v4:
package com.example.app;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.cucumber.core.api.TypeRegistry;
import io.cucumber.core.api.TypeRegistryConfigurer;
import java.util.Locale;
public class ParameterTypes implements TypeRegistryConfigurer {
private final ObjectMapper objectMapper = new ObjectMapper();
#Override
public Locale locale() {
return Locale.ENGLISH;
}
#Override
public void configureTypeRegistry(TypeRegistry typeRegistry) {
typeRegistry.setDefaultDataTableCellTransformer(objectMapper::convertValue);
}
}
And in v5:
package com.example.app;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.cucumber.java.DefaultDataTableCellTransformer;
import java.lang.reflect.Type;
public class DataTableSteps {
private final ObjectMapper objectMapper = new ObjectMapper();
#DefaultDataTableCellTransformer
public Object defaultTransformer(Object fromValue, Type toValueType) {
return objectMapper.convertValue(fromValue, objectMapper.constructType(toValueType));
}
}

How to access public class member called 'properties'?

Suppose I have the following Java-class from 3rd party library:
public class Itm {
public final Map<String, String> properties = ['foo': 'bar']
}
With the following code println new Itm().properties I expect to get a Map: [[foo:bar]]
But the result is:
[class:class Itm]
I realized that if I create the same class in Groovy, but declare properties field without public modifier, I get an expected result. But the class I work with has public access modifier. So in this case how can I access public field called properties, not default Groovy's getProperties(Object self)?
You can use Groovy's direct field access operator obj.#field. This operator omits using the getter method and accesses the object field directly. Let's say we have the following Java class:
Itm.java
import java.util.HashMap;
import java.util.Map;
public class Itm {
public final Map<String, String> properties = new HashMap<String,String>() {{
put("foo", "bar");
}};
}
And the following Groovy script that uses it:
println new Itm().#properties
The output is:
[foo:bar]

Is the #Repeatable annotation not supported by Groovy?

I'm coding in Groovy and am having trouble with the Java 8 #Repeatable meta-annotation. I think I'm doing everything right, but it appears that Groovy is not recognizing #Repeatable. Here's my sample code; I'm expecting the information from both annotations to get stored in MyAnnotationArray:
import java.lang.annotation.*
class MyClass
{
#MyAnnotation(value = "val1")
#MyAnnotation(value = "val2")
void annotatedMethod()
{
println("annotated method called")
}
public static void main(String... args)
{
MyClass ob = new MyClass()
ob.annotatedMethod()
java.lang.reflect.Method m = ob.getClass().getMethod("annotatedMethod")
List annos = m.getAnnotations()
println("annos = $annos")
}
}
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#Repeatable(MyAnnotationArray)
public #interface MyAnnotation
{
String value() default "val0";
}
public #interface MyAnnotationArray
{
MyAnnotation[] MyAnnotationArray()
}
What happens is that I get this error:
Caught: java.lang.annotation.AnnotationFormatError: Duplicate annotation for class: interface MyAnnotation: #MyAnnotation(value=val2)
java.lang.annotation.AnnotationFormatError: Duplicate annotation for class: interface MyAnnotation: #MyAnnotation(value=val2)
Which is exactly what I get if I leave out the #Repeatable meta-annotation.
The code works fine if I leave out one of the duplicate MyAnnotations; then there is no error, and I then can read the annotation value as expected.
Is it possible that Groovy doesn't support the #Repeatable meta-annotation? I couldn't find any documentation that states this outright, though this page hints that maybe this is the case (scroll down to item 88).
seems to be not supported
i used java 1.8 and groovy 2.4.11
after compiling and de-compilig the same code i got this:
java:
#MyAnnotationArray({#MyAnnotation("val1"), #MyAnnotation("val2")})
public void annotatedMethod()
{
System.out.println("annotated method called");
}
groovy:
#MyAnnotation("val1")
#MyAnnotation("val2")
public void annotatedMethod()
{
System.out.println("annotated method called");null;
}
so, as workaround in groovy use
//note the square brackets
#MyAnnotationArray( [#MyAnnotation("val1"), #MyAnnotation("val2")] )
public void annotatedMethod()
{
System.out.println("annotated method called");
}
full script (because there were some errors in annotation declaration)
import java.lang.annotation.*
class MyClass
{
//#MyAnnotation(value = "val1")
//#MyAnnotation(value = "val2")
#MyAnnotationArray( [#MyAnnotation("val1"), #MyAnnotation("val2")] )
public void annotatedMethod()
{
System.out.println("annotated method called");
}
public static void main(String... args)
{
MyClass ob = new MyClass()
ob.annotatedMethod()
java.lang.reflect.Method m = ob.getClass().getMethod("annotatedMethod")
List annos = m.getAnnotations()
println("annos = $annos")
}
}
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#Repeatable(MyAnnotationArray)
public #interface MyAnnotation
{
String value() default "val0";
}
#Retention(RetentionPolicy.RUNTIME)
public #interface MyAnnotationArray
{
MyAnnotation[] value()
}
also tried against groovy 3.0.0-SNAPSHOT - the result is the same as for 2.4.11
Yes, Groovy has supported "repeatable" annotations for a long time even in Java 5 so long as retention policy was only SOURCE. This is what allows multiple #Grab statements for instance without the outer #Grapes container annotation. Being only retained in SOURCE makes them useful for AST transformations and within the Groovy compiler itself (and other source processors) but not really useful anywhere else. We don't currently support #Repeatable at all but plan to in a future version.

Does groovy automatically generate no-args constructor?

In java, no-args constructor is generated when you didn't explicitly specify a constructor, and by the time you write your constructor (with arguments), you need now to explicitly specify that no-args constructor.
How about in groovy, do you also need to explicitly specify this no-args constructor when writing your own constructor ( with arguments ) or gvm implicitly generates this?
How about in groovy, do you also need to explicitly specify this
no-args constructor when writing your own constructor ( with arguments
) or gvm implicitly generates this?
GVM doesn't have anything to do with any of this.
If you define any constructors that accept arguments then a no-arg constructor will not generated. If you want it, you need to define it.
$ cat Demo.groovy
public class Demo {
public Demo(String s) {}
}
$ groovyc Demo.groovy
$ javap Demo
Compiled from "Demo.groovy"
public class Demo implements groovy.lang.GroovyObject {
public static transient boolean __$stMC;
public Demo(java.lang.String);
protected groovy.lang.MetaClass $getStaticMetaClass();
public groovy.lang.MetaClass getMetaClass();
public void setMetaClass(groovy.lang.MetaClass);
public java.lang.Object invokeMethod(java.lang.String, java.lang.Object);
public java.lang.Object getProperty(java.lang.String);
public void setProperty(java.lang.String, java.lang.Object);
}

Resources