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]
Related
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;
}
}
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));
}
}
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)
}
}
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,
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.)