I'm trying to update my DTO with Reflection. The problem is that some fields in my DTO are enums and I get an error that I can not set the enum field to String.
DTO:
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Data
#Builder
#Table(name = "xxx")
public class Model {
#Id
#Column(name = "id")
private String runId;
#Column(name = "name")
private String name;
#Column(name = "status")
#Enumerated(EnumType.STRING)
private ExecutionStatus status;
}
Controller:
#PatchMapping(path = "/{id}", consumes = "application/json")
public ResponseEntity<Void> partialUpdateModel(#PathVariable String id, #RequestBody Map<Object, Object> fields)
throws Exception {
Optional<Model> model= service.getById(id);
if (model.isPresent()) {
fields.forEach((key, value) -> {
Field field = ReflectionUtils.findField(Model.class, (String) key);
field.setAccessible(true);
ReflectionUtils.setField(field, model.get(), value);
});
So when it comes to the enum field, the field can not be set. It says
cannot set ExecutionStatus to String
What you are trying to do is this:
model.status = "STATUS_1";
// incompatible types: java.lang.String cannot be converted to so.A.ExecutionStatus
What you apparently want to do is finding the enum constant of the specified enum type with the specified string. That's what the Enum.valueOf or YourEnum.valueOf method is doing.
Example:
enum ExecutionStatus {
STATUS_1,
STATUS_2,
}
static class Model {
public String runId;
public String name;
public ExecutionStatus status;
#Override
public String toString() {
return "Model{" +
"runId='" + runId + '\'' +
", name='" + name + '\'' +
", status=" + status +
'}';
}
}
public static void main(String[] args) {
Map<Object, Object> fields = Map.of(
"runId", "MyRunId",
"name", "MyName",
"status", "STATUS_1"
);
Model model = new Model();
fields.forEach((key, value) -> {
Field field = null;
try {
field = Model.class.getDeclaredField((String) key);
field.setAccessible(true);
if (field.getType().isEnum()) {
// First variant (YourEnum.valueOf(String)
Method valueOf = field.getType().getMethod("valueOf", String.class);
Object enumConstant = valueOf.invoke(null, value);
field.set(model, enumConstant);
// Alternative (Enum.valueOf(Class, String) (cast is safe due to isEnum)
field.set(model, Enum.valueOf((Class<Enum>) field.getType(), (String) value));
} else {
field.set(model, value);
}
} catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
});
System.out.println(model);
}
You can do like this, Also understand reflection is costly it involves types that are being dynamically resolved, i'd recommend writing setters or constructor instead
if ("ExecutionStatus".equalsIgnoreCase(field.getType().getSimpleName())) {
ReflectionUtils.setField(field, model.get(), ExecutionStatus.valueOf(value));
} else {
ReflectionUtils.setField(field, model.get(), value);
}
This is because your source map is of type <Object, Object>
What you try to do is to set a field of type ExecutionStatus reading a value of type Object. Types on your fields must match. First convert a value to ExecutionStatus then use the method .setField(..).
Related
I want to allow to sort by every field in the class, without having to write switch/ if statements.
My idea was to find the Field that matches given string value by name and then, with Stream API neatly sort. IntelliJ screamed that i need to surround it with try-catch, so it is not so neatly looking, but that's not important, as it does not work.
private List<MyEntity> getSorted(List<MyEntity> list, SearchCriteria criteria) {
Field sortByField = findFieldInListByName(getFieldList(MyEntity.class), criteria.getSortBy());
return list.stream().sorted(Comparator.comparing(entity-> {
try {
return (MyEntity) sortByField.get(entity);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return entity;
})).collect(Collectors.toList());
}
In the MyEntity class I have added Comparable interface, but I am not sure what should be in the body of Compare(), as I dont want to specify how to compare objects, because it will change based on the selected sorting.
EDIT: Added Entity below:
#Data
#Entity
#Table(name = "role_management", schema = "mdr")
#Builder
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode
public class MyEntity implements Comparable{
#Id
#Column(name = "uuid", unique = true, insertable = false, updatable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID uuid;
#ManyToOne
#JoinColumn(name = "user_id", nullable = false)
private UserEntity user;
#Basic
#NonNull
#Column(name = "role")
private String role;
#Basic
#Column(name = "action")
#Enumerated(EnumType.STRING)
private RoleAction action;
#Basic
#Column(name = "goal")
#Enumerated(EnumType.STRING)
private RoleGoal goal;
#Column(name = "date")
private LocalDateTime date;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "reporter_id", referencedColumnName = "uuid")
private UserEntity reporter;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "authorizer_id", referencedColumnName = "uuid")
private UserEntity authorizer;
#Basic
#Column(name = "ezd")
private String ezd;
#Basic
#Column(name = "is_last")
private boolean isMostRecent;
#Override
public int compareTo(Object o) {
return 0;
}
}
EDIT 2: My code based on the #Sweeper solution:
UserEntity (nullable)
#Override
public int compareTo(UserEntity other) {
if (other == null) {
return 1;
}
return this.getMail().compareTo(other.getMail());
}
Comparator:
public static Comparator getSortComparator(Field sortByField) {
return Comparator.nullsLast(Comparator.comparing(entity -> {
try {
Object fieldValue = sortByField.get(entity);
if (!(fieldValue instanceof Comparable<?>) && fieldValue != null) {
throw new IllegalArgumentException("...");
}
return (Comparable) fieldValue;
} catch (IllegalAccessException e) {
throw new MdrCommonException(e.getLocalizedMessage());
}
}));
}
MyEntity should not implement Comparable. It is the fields, by which you are going to sort the list of MyEntity objects, that needs to be Comparable. For example, if you are sorting by the field user, which is a UserEntity, then UserEntity is the thing that needs to be comparable, not MyEntity.
The lambda's job should just be to check that the fields are indeed Comparable, and throw an exception if they are not.
Since you don't know the types of the fields at compile time, however, you'd have to use a raw type here. The comparing call would look like this:
Comparator.comparing(entity -> {
try {
Object fieldValue = sortByField.get(entity);
// This check still passes if the type of fieldValue implements Comparable<U>,
// where U is an unrelated type from the type of fieldValue, but this is the
// best we can do here, since we don't know the type of field at compile time
if (!(fieldValue instanceof Comparable<?>) && fieldValue != null) {
throw new IllegalArgumentException("Field is not comparable!");
}
return (Comparable)fieldValue;
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
})
You can create automatically comparators for any field of any class using reflection but is better create specific comparators (will be typechecked).
Your entity is a normal class with normal fields then, the usual Java sorting machinery should do the job:
Basically, if you define one comparator for every field (even deep fields into your entity):
public final static Comparator<MyEntity> ByField1 = comparing(MyEntity::getField1);
public final static Comparator<MyEntity> ByField2 = comparing(MyEntity::getField2);
public final static Comparator<MyEntity> ByField3 = comparing(MyEntity::getField3);
public final static Comparator<MyEntity> ByDeep1 = comparing(a -> a.getField4().getDeep1());
public final static Comparator<MyEntity> ByDeep2 = comparing(a -> a.getField4().getDeep2());
public final static Comparator<MyEntity> ByDeep3 = comparing(a -> a.getField4().getDeep3());
You can sort using complex sorting expressions:
data.stream()
.sorted(ByField2.reversed().thenComparing(ByDeep2))
.forEach(System.out::println);
a full example could be
public static void main(String[] args) {
List<MyEntity> data =
Stream.of("Row1", "Row2").flatMap(field1 ->
Stream.of(101, 102).flatMap(field2 ->
Stream.of(true, false).flatMap(field3 ->
Stream.of("Row1", "Row2").flatMap(deep1 ->
Stream.of(101, 102).flatMap(deep2 ->
Stream.of(true, false).map(deep3 ->
new MyEntity(field1, field2, field3, new MyDeepField(deep1, deep2, deep3))))))))
.collect(toList());
data.stream()
.sorted(ByField2.reversed().thenComparing(ByDeep2))
.forEach(System.out::println);
}
#Getter
#Setter
#AllArgsConstructor
static class MyDeepField {
private String deep1;
private Integer deep2;
private Boolean deep3;
}
#Getter
#Setter
#AllArgsConstructor
static class MyEntity {
private String field1;
private Integer field2;
private Boolean field3;
private MyDeepField field4;
public final static Comparator<MyEntity> ByField1 = comparing(MyEntity::getField1);
public final static Comparator<MyEntity> ByField2 = comparing(MyEntity::getField2);
public final static Comparator<MyEntity> ByField3 = comparing(MyEntity::getField3);
public final static Comparator<MyEntity> ByDeep1 = comparing(a -> a.getField4().getDeep1());
public final static Comparator<MyEntity> ByDeep2 = comparing(a -> a.getField4().getDeep2());
public final static Comparator<MyEntity> ByDeep3 = comparing(a -> a.getField4().getDeep3());
#Override
public String toString() {
return "MyEntity{" +
"field1='" + field1 + '\'' +
", field2=" + field2 +
", field3=" + field3 +
", deep1=" + field4.getDeep1() +
", deep2=" + field4.getDeep2() +
", deep3=" + field4.getDeep3() +
'}';
}
}
with output
MyEntity{field1='Row1', field2=102, field3=true, deep1=Row1, deep2=101, deep3=true}
MyEntity{field1='Row1', field2=102, field3=true, deep1=Row1, deep2=101, deep3=false}
...
MyEntity{field1='Row2', field2=101, field3=false, deep1=Row2, deep2=102, deep3=true}
MyEntity{field1='Row2', field2=101, field3=false, deep1=Row2, deep2=102, deep3=false}
The criteria field into your SearchCriteria class is some field of type Comparator<MyEntity> or a mapping using an enumeration or parsing string expressions or so...
I have some classes like below:
#Getter
#Setter
class Person{
#JsonProperty("cInfo")
private ContactInformation contactInfo;
private String name;
private String position;
}
#Getter
#Setter
class ContactInformation{
#JsonProperty("pAddress")
private Address address;
}
#Getter
#Setter
class Address{
private String street;
private String district;
}
And what I am going to do is writing an Utils method for the Person object that take one parameter which is the attributeName as String and return the getter value for this attribute.
Ex:
attributeName = name -> return person.getName()
attributeName = position -> return person.getPosition()
attributeName = cInfo.pAddress.street -> return person.getContactInfo().getAddress().getStreet()
attributeName = cInfo.pAddress.district -> return person.getContactInfo().getAddress().getDistrict()
Below is what I've done: I loop through all the fields in the Person object and check if the attributeName equal to either the JsonProperty's Name or the Field's Name then I will return this getter.
Object result;
Field[] fields = Person.class.getDeclaredFields();
for (Field field : fields) {
JsonProperty jsonProperty = field.getDeclaredAnnotation(JsonProperty.class);
if (jsonProperty != null && jsonProperty.value().equals(attributeName)) {
result = Person.class.getMethod("get" + capitalize(field.getName())).invoke(person);
} else {
if (field.getName().equals(attributeName)) {
result = person.class.getMethod("get" + capitalize(field.getName()))
.invoke(person);
}
}
}
This worked but only with the fields that locate direct in the Person class, ex: name, position. With the fields inside of contactInfo or address I am still getting stuck there. Can anyone give me some hint here how can I do it?
Thank you!
Because path like a.b.c related to different objects. So you need to. split by point and for each token call get and use obtained result for next token
UPDATE: something like:
private static Object invkGen(Object passedObj, String attributeName) throws Exception {
final String[] split = attributeName.split("\\.");
Object result = passedObj;
for (String s : split) {
if (result == null) {
break;
}
result = invk(result, s);
}
return result;
}
private static Object invk(Object passedObj, String attributeName) throws Exception {
Object result = null;
final Field[] fields = passedObj.getClass().getDeclaredFields();
for (Field field : fields) {
JsonProperty jsonProperty = field.getDeclaredAnnotation(JsonProperty.class);
if (jsonProperty != null && jsonProperty.value().equals(attributeName)) {
result = Person.class.getMethod("get" + capitalize(field.getName())).invoke(passedObj);
} else {
if (field.getName().equals(attributeName)) {
result = passedObj.getClass().getMethod("get" + capitalize(field.getName()))
.invoke(passedObj);
}
}
}
return result;
}
I have a HashMap of about 300 Key/String Value pairs and a POJO with about 12 string attributes where the names match the key names.
I would like to know how to get the HashMap values into the POJO?
I made this start which uses relfection and a loop but wasn't sure how to dynamically construct the setter method name, and apparently reflection is a bad idea anyway...but FWIW:
public void writeToFile(Map<String, String> currentSale) throws IOException {
SaleExport saleExport = new SaleExport();
Field[] fields = saleExport.getClass().getDeclaredFields();
for (Field field : fields ) {
System.out.println(field.getName());
saleExport.set +field(saleExport.get(field));
I have used map struct once before but it does not appear to support HashMaps.
UPDATE
This answer looks similar to what I want to do but gave a stack error on fields that didn't map:
Exception in thread "Thread-2" java.lang.IllegalArgumentException: Unrecognized field "Physical" (class com.SaleExport), not marked as ignorable (6 known properties: "date", "city", "surname", "streetName", "salesNo", "salesSurname"])
at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: com.SalesExport["Physical"])
at com.fasterxml.jackson.databind.ObjectMapper._convert(ObjectMapper.java:3738)
at com.fasterxml.jackson.databind.ObjectMapper.convertValue(ObjectMapper.java:3656)
at com.CSVExport.writeToFile(CSVExport.java:20)
at com.JFrameTest.writefiletoDB(JFrameTest.java:135)
at com.JFrameTest$FileWorkerThread.run(JFrameTest.java:947)
To ignore the errors I tried :
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
But then nothing got mapped.
If i have understood your question correctly - you want to put the value of map into the member variable of the Pojo based on key.
Try below approach.
Main Class as follows
package org.anuj.collections.map;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class ConverMapToPojo {
public static void main(String[] args) {
Map<String, String> map = getMap();
Set<String> keySet = map.keySet();
String fieldName = null;
Pojo pojo = new Pojo();
Field[] field = Pojo.class.getDeclaredFields();
for (Field f : field) {
fieldName = f.getName();
if (keySet.contains(fieldName)) {
pojo = setField(fieldName, map.get(fieldName), pojo);
}
}
System.out.println("fName = " + pojo.getfName());
System.out.println("lName = " + pojo.getlName());
}
private static Pojo setField(String fieldName, String value, Pojo pojo) {
switch (fieldName) {
case "fName":
pojo.setfName(value);
break;
case "lName":
pojo.setlName(value);
break;
}
return pojo;
}
private static Map<String, String> getMap() {
Map<String, String> map = new HashMap<String, String>();
map.put("fName", "stack");
map.put("lName", "overflow");
return map;
}
}
Pojo Class as follows -
public class Pojo {
private String fName;
private String lName;
public String getfName() {
return fName;
}
public void setfName(String fName) {
this.fName = fName;
}
public String getlName() {
return lName;
}
public void setlName(String lName) {
this.lName = lName;
}
}
The Result comes out to be
fName = stack
lName = overflow
Try using Dozer mapping to map HashMap to POJO.You can look at MapStruct too.
Hi I don't know if it's a good Idea but you could convert your map to a json and convert json to your POJO.
You could use gson.
You can inject something in a pojo member variable through e.g. a method like this. I do not say that it is a good way to do this, but it can be done like this.
import java.lang.reflect.Field;
import java.util.HashMap;
/**
* #author Ivo Woltring
*/
public class HashMapKeyStuff {
/**
* The most simple cdi like method.
*
* #param injectable the object you want to inject something in
* #param fieldname the fieldname to inject to
* #param value the value to assign to the fieldname
*/
public static void injectField(final Object injectable, final String fieldname, final Object value) {
try {
final Field field = injectable.getClass()
.getDeclaredField(fieldname);
final boolean origionalValue = field.isAccessible();
field.setAccessible(true);
field.set(injectable, value);
field.setAccessible(origionalValue);
} catch (final NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
private void doIt() {
HashMap<String, String> foo = new HashMap<>();
foo.put("hello", "world");
foo.put("message", "You are great");
MyPOJO pojo = new MyPOJO();
for (final String key : foo.keySet()) {
injectField(pojo, key, foo.get(key));
}
System.out.println("pojo = " + pojo);
}
public static void main(String[] args) {
new HashMapKeyStuff().doIt();
}
}
class MyPOJO {
private String hello;
private String message;
public String getHello() {
return this.hello;
}
public String getMessage() {
return this.message;
}
#Override
public String toString() {
return "MyPOJO{" +
"hello='" + hello + '\'' +
", message='" + message + '\'' +
'}';
}
}
I think you had the right idea in your code. I understand your POJO to contain only a subset of the fields represented in the HashMap, so you just iterate through the fields, populating them from the HashMap as you find them.
public SaleExport toSaleExport(Map<String,String> currentSale) {
SaleExport saleExport=new SaleExport();
Field[] fields=SaleExport.class.getDeclaredFields();
for (Field field : fields) {
String name=field.getName();
if (currentSale.containsKey(name)) {
field.set(saleExport, currentSale.get(name));
}
}
return saleExport;
}
I created a custom annotation called CrudSearchable and have defined some attributes there. However, the attributes I am assigning are already visible from the bean. Is there a way I can grab these values without having to redefine them manually?
// Bean
public class MockUser {
#CrudSearchable(attribute = "name",
parentClass = MockUser.class)
private String name;
#CrudSearchable(attribute = "group",
parentClass = MockUser.class,
mappedClass = MockGroup.class)
private MockGroup group;
// Get/Set, Equals/Hashcode, etc...
}
// Annotation Class
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface CrudSearchable {
String attribute();
boolean searchable() default true;
Class<?> mappedClass() default CrudSearchable.class;
Class<?> parentClass() default Object.class;
}
Where attribute is the attribute name, parentClass is the class literal using the annotation, and mapped class is the nested class object if applicatable.
MockUser obj = new MockUser();
Class<?> c = obj.getClass();
Field[] fields = c.getDeclaredFields();
CrudSearchable annotation = fields[0].getAnnotation(CrudSearchable.class);
System.out.println("attribute: " + annotation.attribute() +
"searchable: " + annotation.searchable());
Hope this helps
I found the answer on another Question. This is what I was looking for.
// Put in Reflector.java
/**
* Changes the annotation value for the given key of the given annotation to newValue and returns
* the previous value.
*/
#SuppressWarnings("unchecked")
public static Object changeAnnotationValue(Annotation annotation, String key, Object newValue){
Object handler = Proxy.getInvocationHandler(annotation);
Field f;
try {
f = handler.getClass().getDeclaredField("memberValues");
} catch (NoSuchFieldException | SecurityException e) {
throw new IllegalStateException(e);
}
f.setAccessible(true);
Map<String, Object> memberValues;
try {
memberValues = (Map<String, Object>) f.get(handler);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
Object oldValue = memberValues.get(key);
if (oldValue == null || oldValue.getClass() != newValue.getClass()) {
throw new IllegalArgumentException();
}
memberValues.put(key,newValue);
return oldValue;
}
Once I have this I can call the method below in the Constructor to alter the annotations I use.
// Put in Service Class
public void modifySearchable(Class<?> clazz) {
for(Field f : clazz.getDeclaredFields()){
CrudSearchable[] searchableArray = f.getDeclaredAnnotationsByType(CrudSearchable.class);
for(CrudSearchable searchable : searchableArray){
if(searchable == null){
continue;
}
Reflector.alterAnnotation(searchable, "attribute", f.getName());
Reflector.alterAnnotation(searchable, "parentClass", clazz);
if(!(searchable.mappedAttribute().equals(""))){
String mappedGetter = "get" +
searchable.mappedAttribute().substring(0, 1).toUpperCase() +
searchable.mappedAttribute().substring(1);
Reflector.alterAnnotation(searchable, "mappedClass", f.getType());
Reflector.alterAnnotation(searchable, "mappedGetter", mappedGetter);
}
}
}
}
// Changed Bean
#Id
#GeneratedValue
#Column(name = "MOCK_USER_ID")
private Long id;
#Column(name = "MOCK_USER_NAME")
#CrudSearchable
private String name;
#ManyToOne
#JoinColumn(name = "MOCK_GROUP", nullable = false)
#CrudSearchable(mappedAttribute = "name")
private MockGroup group;
public MockUser(){
super();
new Searchable<>().modifySearchable(this.getClass());
}
Seems like a lot to change the values instead of having the user define them, but I believe that it will make the code more user friendly.
Hope this helps someone. I found the answer on this post: Modify a class definition's annotation string parameter at runtime. Check it out!
I have a class named ClBranch.java like below:
#Entity
#Table(name = "PROVINCE")
public class PROVINCE implements Serializable {
#Id
#Column(name="PR_CODE", length = 50)
private String provinceCode
#Column(name="PR_NAME", length = 500)
private String provinceName
......
getter-setter.
}
This is my code:
public static String getClassAnnotationValue(Class classType, Class annotationType, String attributeName) {
String value = null;
Annotation annotation = classType.getAnnotation(annotationType);
if (annotation != null) {
try {
value = (String) annotation.annotationType().getMethod(attributeName).invoke(annotation);
} catch (Exception ex) {
ex.printStackTrace();
}
}
return value;
}
String columnName = getClassAnnotationValue(PROVINCE .class, Column.class, "name");
By this way, I only get ColumnName as PROVINCE. I can not get ColumnName. How can I do it?
The #Column annotation is defined on the fields, not on the class. So you must query annotation values from the private fields:
String columnName = getAnnotationValue(PROVINCE.class.getDeclaredField("provinceCode"), Column.class, "name");
To be able to pass Field objects to your method, change the type of your classType parameter from Class to AnnotatedElement. Then you can pass classes, fields, parameters or methods:
public static String getAnnotationValue(AnnotatedElement element, Class annotationType, String attributeName) {
...
}