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

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;
}
}

Related

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));
}
}

Unexpected behaviour for Groovy 'with' method - variable assignment silently failed

I have the following code:
import groovy.transform.ToString
#ToString(includeNames = true)
class Simple {
String creditPoints
}
Simple simple = new Simple()
simple.with {
creditPoints : "288"
}
println simple
Clearly, I made a mistake here with creditPoints : "288". It should have been creditPoints = "288".
I expected Groovy to fail at the runtime saying that I made a mistake and I should have used creditPoints = "288"but clearly it did not.
Since it did not fail then what did Groovy do with the closure I created?
From the Groovy compiler perspective, there is no mistake in your closure code. The compiler sees creditPoints : "288" as labeled statement which is a legal construction in the Groovy programming language. As the documentation says, label statement does not add anything to the resulting bytecode, but it can be used for instance by AST transformations (Spock Framework uses it heavily).
It becomes more clear and easy to understand if you format code more accurately to the label statement use case, e.g
class Simple {
String creditPoints
static void main(String[] args) {
Simple simple = new Simple()
simple.with {
creditPoints:
"288"
}
println simple
}
}
(NOTE: I put your script inside the main method body to show you its bytecode representation in the next section.)
Now when we know how compiler sees this construction, let's take a look and see what does the final bytecode look like. To do this we will decompile the .class file (I use IntelliJ IDEA for that - you simply open .class file in IDEA and it decompiles it for you):
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import groovy.lang.Closure;
import groovy.lang.GroovyObject;
import groovy.lang.MetaClass;
import groovy.transform.ToString;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
import org.codehaus.groovy.runtime.GeneratedClosure;
import org.codehaus.groovy.runtime.InvokerHelper;
#ToString
public class Simple implements GroovyObject {
private String creditPoints;
public Simple() {
MetaClass var1 = this.$getStaticMetaClass();
this.metaClass = var1;
}
public static void main(String... args) {
Simple simple = new Simple();
class _main_closure1 extends Closure implements GeneratedClosure {
public _main_closure1(Object _outerInstance, Object _thisObject) {
super(_outerInstance, _thisObject);
}
public Object doCall(Object it) {
return "288";
}
public Object call(Object args) {
return this.doCall(args);
}
public Object call() {
return this.doCall((Object)null);
}
public Object doCall() {
return this.doCall((Object)null);
}
}
DefaultGroovyMethods.with(simple, new _main_closure1(Simple.class, Simple.class));
DefaultGroovyMethods.println(Simple.class, simple);
Object var10000 = null;
}
public String toString() {
StringBuilder _result = new StringBuilder();
Boolean $toStringFirst = Boolean.TRUE;
_result.append("Simple(");
if ($toStringFirst == null ? false : $toStringFirst) {
Boolean var3 = Boolean.FALSE;
} else {
_result.append(", ");
}
if (this.getCreditPoints() == this) {
_result.append("(this)");
} else {
_result.append(InvokerHelper.toString(this.getCreditPoints()));
}
_result.append(")");
return _result.toString();
}
public String getCreditPoints() {
return this.creditPoints;
}
public void setCreditPoints(String var1) {
this.creditPoints = var1;
}
}
As you can see, your closure used with the with method is represented as an inner _main_closure1 class. This class extends Closure class, and it implements GeneratedClosure interface. The body of the closure is encapsulated in public Object doCall(Object it) method. This method only returns "288" string, which is expected - the last statement of the closure becomes a return statement by default. There is no label statement in the generated bytecode, which is also expected as labels get stripped at the CANONICALIZATION Groovy compiler phase.

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.

Groovy unable to access private property of super class using `#`

There is a third party java class in a library. Trying to extend that class in a Groovy Class. I would like to access that private property.
The problem is that it says
No such field: firstName for class: test2.Superuser
I am sure there must be a way to access the same and manipulate the property value using groovy.
Some posts suggests to use # before property name in order to access private property. But no luck.
Here is the sample code snippets.
User.java (a third party class of a library)
package test;
class User {
private String firstName;
private String name() {
firstName;
}
}
SuperUser.groovy (The one I am trying)
package test2
import test.User
class SuperUser extends User {
public static void main(String[] args) {
def suser = new SuperUser()
suser.#firstName = "John"
println suser.name()
}
}
Any help is appreciated.
Using below versions:
groovy : 1.8.0
jdk : 1.7.0
Java classes aren't able to access all of these private fields, properties and methods. It is only the other groovy scripts that are able to access it. The example below will demonstrate the same.
You should try to create both class file name as .groovy instead .java
User.groovy :
class User {
private String firstName;
private String name() {
firstName;
}
}
UserTest.groovy :-
class UserTest {
public static void main(String[] args) {
User user = new User();
user.#firstName = "John"
println user.name()
}
}
Output :- John
It's working fine with Java 8 and groovy-all-2.4.3
Note:- Follow this link for more details
Edited :- If you don't want to change super class because of third party code, you should try using java reflection to access private property of a class.
User.java is a Java class and not a Groovy class, so those variables are still private (unlike Groovy Variables which are always public to other Groovy classes).
So in the example above, unless the Java class includes some getters and setters, you will not be able to modify it's private members.
Maybe I'm late, but with your Java and Groovy version you can do the follow using meta programming:
package test2
import test.User
class SuperUser extends User {
public static void main(String[] args) {
def suser = new SuperUser()
User.metaClass.setProperty(suser,'firstName','John')
println User.metaClass.getMetaMethod('name',null).invoke(suser,null)
}
}
Or as other suggest in the traditional java reflection way:
package test2
import test.User
class SuperUser extends User {
public static void main(String[] args) {
def suser = new SuperUser()
def firstNameField = SuperUser.superclass.getDeclaredField('firstName')
firstNameField.setAccessible(true)
firstNameField.set(suser,"John")
def nameMethod = SuperUser.superclass.getDeclaredMethod('name')
nameMethod.setAccessible(true)
println nameMethod.invoke(suser,null)
}
}

Resources