I need to change (or remove whole) annotation value on runtime from one class. I got the exapmles from SO but this solution works only for class annotations, not for the field annotations. Any idea how to get this done? Thre reason for this is to make minor change in DB model definition classes to not use enum fields as inmemory db's not having this data type like MySQL does.
Here is working(partially) solution found on SO:
package annotations;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.util.Map;
public class AnnotationModification2 {
public static void main(String[] args) throws Exception {
Something annotation = (Something) Foobar.class.getAnnotations()[0];
System.out.println("oldAnnotation = " + Foobar.class.getAnnotation(Something.class).someProperty());
changeAnnotationValue(annotation, "someProperty", "another value");
System.out.println("modifiedAnnotation = " + Foobar.class.getAnnotation(Something.class).someProperty());
annotation = (Something) Foobar.class.getDeclaredField("name").getAnnotations()[0];
System.out.println("oldAnnotation = " + annotation.someProperty());
changeAnnotationValue(annotation, "someProperty", "another value");
System.out.println("modifiedAnnotation = " + annotation.someProperty());
System.out.println(Foobar.class.getDeclaredField("name").getAnnotation(Something.class).someProperty());
}
/**
* 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;
}
#Something(someProperty = "some value")
public static class Foobar {
#Something(someProperty = "Old field value!")
private String name;
}
#Retention(RetentionPolicy.RUNTIME)
#interface Something {
String someProperty();
}
}
Try to use "getField" method:
Something annotation = (Something) Foobar.class.getField("name").getAnnotations()[0];
Related
Imagine there is a class:
#Something(someProperty = "some value")
public class Foobar {
//...
}
Which is already compiled (I cannot control the source), and is part of the classpath when the jvm starts up. I would like to be able to change "some value" to something else at runtime, such that any reflection thereafter would have my new value instead of the default "some value".
Is this possible? If so, how?
Warning: Not tested on OSX - see comment from #Marcel
Tested on OSX. Works fine.
Since I also had the need to change annotation values at runtime, I revisited this question.
Here is a modified version of #assylias approach (many thanks for the inspiration).
/**
* 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;
}
Usage example:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface ClassAnnotation {
String value() default "";
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface FieldAnnotation {
String value() default "";
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface MethodAnnotation {
String value() default "";
}
#ClassAnnotation("class test")
public static class TestClass{
#FieldAnnotation("field test")
public Object field;
#MethodAnnotation("method test")
public void method(){
}
}
public static void main(String[] args) throws Exception {
final ClassAnnotation classAnnotation = TestClass.class.getAnnotation(ClassAnnotation.class);
System.out.println("old ClassAnnotation = " + classAnnotation.value());
changeAnnotationValue(classAnnotation, "value", "another class annotation value");
System.out.println("modified ClassAnnotation = " + classAnnotation.value());
Field field = TestClass.class.getField("field");
final FieldAnnotation fieldAnnotation = field.getAnnotation(FieldAnnotation.class);
System.out.println("old FieldAnnotation = " + fieldAnnotation.value());
changeAnnotationValue(fieldAnnotation, "value", "another field annotation value");
System.out.println("modified FieldAnnotation = " + fieldAnnotation.value());
Method method = TestClass.class.getMethod("method");
final MethodAnnotation methodAnnotation = method.getAnnotation(MethodAnnotation.class);
System.out.println("old MethodAnnotation = " + methodAnnotation.value());
changeAnnotationValue(methodAnnotation, "value", "another method annotation value");
System.out.println("modified MethodAnnotation = " + methodAnnotation.value());
}
The advantage of this approach is, that one does not need to create a new annotation instance. Therefore one doesn't need to know the concrete annotation class in advance. Also the side effects should be minimal since the original annotation instance stays untouched.
Tested with Java 8.
This code does more or less what you ask for - it is a simple proof of concept:
a proper implementation needs to also deal with the declaredAnnotations
if the implementation of annotations in Class.java changes, the code will break (i.e. it can break at any time in the future)
I have no idea if there are side effects...
Output:
oldAnnotation = some value
modifiedAnnotation = another value
public static void main(String[] args) throws Exception {
final Something oldAnnotation = (Something) Foobar.class.getAnnotations()[0];
System.out.println("oldAnnotation = " + oldAnnotation.someProperty());
Annotation newAnnotation = new Something() {
#Override
public String someProperty() {
return "another value";
}
#Override
public Class<? extends Annotation> annotationType() {
return oldAnnotation.annotationType();
}
};
Field field = Class.class.getDeclaredField("annotations");
field.setAccessible(true);
Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) field.get(Foobar.class);
annotations.put(Something.class, newAnnotation);
Something modifiedAnnotation = (Something) Foobar.class.getAnnotations()[0];
System.out.println("modifiedAnnotation = " + modifiedAnnotation.someProperty());
}
#Something(someProperty = "some value")
public static class Foobar {
}
#Retention(RetentionPolicy.RUNTIME)
#interface Something {
String someProperty();
}
This one works on my machine with Java 8. It changes the value of ignoreUnknown in the annotation #JsonIgnoreProperties(ignoreUnknown = true) from true to false.
final List<Annotation> matchedAnnotation = Arrays.stream(SomeClass.class.getAnnotations()).filter(annotation -> annotation.annotationType().equals(JsonIgnoreProperties.class)).collect(Collectors.toList());
final Annotation modifiedAnnotation = new JsonIgnoreProperties() {
#Override public Class<? extends Annotation> annotationType() {
return matchedAnnotation.get(0).annotationType();
} #Override public String[] value() {
return new String[0];
} #Override public boolean ignoreUnknown() {
return false;
} #Override public boolean allowGetters() {
return false;
} #Override public boolean allowSetters() {
return false;
}
};
final Method method = Class.class.getDeclaredMethod("getDeclaredAnnotationMap", null);
method.setAccessible(true);
final Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) method.invoke(SomeClass.class, null);
annotations.put(JsonIgnoreProperties.class, modifiedAnnotation);
SPRING can do this job very easily , might be useful for spring developer .
follow these steps :-
First Solution :-
1)create a Bean returning a value for someProperty . Here I injected the somePropertyValue with #Value annotation from DB or property file :-
#Value("${config.somePropertyValue}")
private String somePropertyValue;
#Bean
public String somePropertyValue(){
return somePropertyValue;
}
2)After this , it is possible to inject the somePropertyValue into the #Something annotation like this :-
#Something(someProperty = "#{#somePropertyValue}")
public class Foobar {
//...
}
Second solution :-
1) create getter setter in bean :-
#Component
public class config{
#Value("${config.somePropertyValue}")
private String somePropertyValue;
public String getSomePropertyValue() {
return somePropertyValue;
}
public void setSomePropertyValue(String somePropertyValue) {
this.somePropertyValue = somePropertyValue;
}
}
2)After this , it is possible to inject the somePropertyValue into the #Something annotation like this :-
#Something(someProperty = "#{config.somePropertyValue}")
public class Foobar {
//...
}
Try this solution for Java 8
public static void main(String[] args) throws Exception {
final Something oldAnnotation = (Something) Foobar.class.getAnnotations()[0];
System.out.println("oldAnnotation = " + oldAnnotation.someProperty());
Annotation newAnnotation = new Something() {
#Override
public String someProperty() {
return "another value";
}
#Override
public Class<? extends Annotation> annotationType() {
return oldAnnotation.annotationType();
}
};
Method method = Class.class.getDeclaredMethod("annotationData", null);
method.setAccessible(true);
Object annotationData = method.invoke(getClass(), null);
Field declaredAnnotations = annotationData.getClass().getDeclaredField("declaredAnnotations");
declaredAnnotations.setAccessible(true);
Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) declaredAnnotations.get(annotationData);
annotations.put(Something.class, newAnnotation);
Something modifiedAnnotation = (Something) Foobar.class.getAnnotations()[0];
System.out.println("modifiedAnnotation = " + modifiedAnnotation.someProperty());
}
#Something(someProperty = "some value")
public static class Foobar {
}
#Retention(RetentionPolicy.RUNTIME)
#interface Something {
String someProperty();
}
i am able to access and modify annotaions in this way in jdk1.8,but not sure why has no effect,
try {
Field annotationDataField = myObject.getClass().getClass().getDeclaredField("annotationData");
annotationDataField.setAccessible(true);
Field annotationsField = annotationDataField.get(myObject.getClass()).getClass().getDeclaredField("annotations");
annotationsField.setAccessible(true);
Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) annotationsField.get(annotationDataField.get(myObject.getClass()));
annotations.put(Something.class, newSomethingValue);
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
Annotation attribute values have to be constants - so unless you want to do some serious byte code manipulation it won't be possible. Is there a cleaner way, such as creating a wrapper class with the annotation you desire?
I am writing an insert query inside #Query annotation in a Spring application with PostGreSQL. So I am extending CRUD repository inside an interface that I have written.
#Repository
public interface PostGreRepository extends CrudRepository<FoodDetails,Long> {
#Modifying
#Query(value="insert into fooddetails(person_id,food_desc) select id,food_desc from person,food where id = " +
"person_id",nativeQuery = true)
void insertIntoPostGre();
}
Now I have the requirement to keep the query as a parameter in the application because it might change later. I cannot use #Value annotation inside an interface. So how can I parameterize this? Ideas?
Just as an idea, use reflection to change annotation value:
Disclaimer: changeAnnotationValue method is taken from here, I haven't run it myself
#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;
}
Using query as a parameter:
#Component
public class PostGreRepositoryParameterizer {
//...
#Value("query")
private String query;
public void modify() {
Method method = PostGreRepository.class.getMethod("insertIntoPostGre");
final Query queryAnnotation = method.getAnnotation(Query.class);
changeAnnotationValue(queryAnnotation, "value", query);
}
//...
}
I want to explain my issue with a simple example:
Foo:
#SomeXMLAnnotations
public class Foo {
// Bar is just a random class with its own XML annotations
#XmlElement(required = true)
Bar someBarObj;
boolean chosen = true;
boolean required = true;
public Foo(){
chosen = false;
}
public Foo(Bar someBarObj){
this.someBarObj = someBarObj;
}
}
MyClass:
#SomeXMLAnnotations
public class MyClass {
#XmlElement(required = false)
Foo anyFooObj;
#XmlElement(required = true)
Foo anyFooObjRequired;
public MyClass (){ }
public MyClass (Foo anyFooObj, Foo anyFooObjRequired){
this.anyFooObj = anyFooObj;
if(anyFooObj == null)
this.anyFooObj = new Foo();
/*
* This is the reason why i can't let 'anyFooObj' be 'null'.
* So 'anyFooObj' MUST be initialized somehow.
* It's needed for some internal logic, not JAXB.
*/
anyFooObj.required = false;
this.anyFooObjRequired = anyFooObjRequired;
}
}
Example Objects:
Foo fooRequired = new Foo(new Bar());
MyClass myObj = new MyClass(null, fooRequired);
When i try to marshal myObj now, it throws an exception like this:
org.eclipse.persistence.oxm.record.ValidatingMarshalRecord$MarshalSAXParseException;
cvc-complex-type.2.4.b: The content of element 'n0:anyFooObj ' is not complete.
One of '{"AnyNamespace":someBarObj}' is expected.
This happens because anyFooObj is initialized but it's required, member
someBarObj isn't.
Possible Solution:
I know i could add this method to MyClass:
void beforeMarshal(Marshaller m){
if(! anyFooObj.chosen)
anyFooObj= null;
}
}
But I have a lot of classes and those classes have a lot of not required fields.
So this solution would take ages and doesn't look like a proper solution as well.
My Question:
Is there a way to tell JAXB that it should treat empty objects like they were null? Or that it should ignore an element when it's not properly set. Something like this for example:
#XmlElement(required = false, ingnoreWhenNotMarshallable = true)
Foo anyFooObj;
NOTE:
I'm NOT the developer of the code. I just have to add JAXB to the project and make everything compatible with a given XSD file. I'm NOT allowed to change the relation between classes.
I think you're trying to make the JAXB marshaller do something it's really not designed to do, so I'd say you're into hack territory here. I'd recommend pushing back on the requirements to try and avoid having this problem in the first place.
That said, if you have to do it then given your requirement to avoid writing code for each class/field, I think you'll want to use reflection for this - I've included an example below that reflectively inspects the values of all fields.
Useful extensions would be:
Have it consider getter methods too
Make the null-setting behaviour opt-in by requiring the field has an additional annotation - you could name it #JAXBNullIfEmpty
Example.java:
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.StringWriter;
import java.lang.reflect.Field;
public class Example
{
public abstract static class JAXBAutoNullifierForEmptyOptionalFields
{
void beforeMarshal(Marshaller x)
{
try
{
for (Field field : this.getClass().getFields())
{
final XmlElement el = field.getAnnotation(XmlElement.class);
// If this is an optional field, it has a value & it has no fields populated then we should replace it with null
if (!el.required())
{
if (JAXBAutoNullifierForEmptyOptionalFields.class.isAssignableFrom(field.getType()))
{
final JAXBAutoNullifierForEmptyOptionalFields val = (JAXBAutoNullifierForEmptyOptionalFields) field.get(
this);
if (val != null && !val.hasAnyElementFieldsPopulated())
field.set(this, null); // No fields populated, replace with null
}
}
}
}
catch (IllegalAccessException e)
{
throw new RuntimeException("Error determining if class has all required fields: " + this, e);
}
}
boolean hasAnyElementFieldsPopulated()
{
for (Field field : this.getClass().getFields())
{
try
{
if (field.isAnnotationPresent(XmlElement.class))
{
// Retrieve value
final Object val = field.get(this);
// If the value is non-null then at least one field has been populated
if (val != null)
{
return true;
}
}
}
catch (IllegalAccessException e)
{
throw new RuntimeException("Error determining if class has any populated JAXB fields: " + this, e);
}
}
// There were no fields with a non-null value
return false;
}
}
#XmlRootElement
public static class MyJAXBType extends JAXBAutoNullifierForEmptyOptionalFields
{
#XmlElement
public String someField;
#XmlElement
public MyJAXBType someOtherField;
public MyJAXBType()
{
}
public MyJAXBType(final String someField, MyJAXBType someOtherField)
{
this.someField = someField;
this.someOtherField = someOtherField;
}
}
public static void main(String[] args) throws Exception
{
final Marshaller marshaller = JAXBContext.newInstance(MyJAXBType.class).createMarshaller();
MyJAXBType innerValue = new MyJAXBType(); // Unpopulated inner value
MyJAXBType value = new MyJAXBType("some text value", innerValue);
final StringWriter sw = new StringWriter();
marshaller.marshal(value, sw); // Omits "someOtherField"
System.out.println(sw.toString());
}
}
Is there an easy way to copy an object's property's onto another object of a different class which has the same field names using direct field access - i.e. when one of the classes does not have getters or setters for the fields? I can use org.springframework.beans.BeanUtils#copyProperties(Object source, Object target) when they both have getter and setter methods, but what can I do when they don't?
It may also be relevant that the fields are public.
I know that I can write my own code to do this using reflection, but I'm hoping that there's some library that provides a one-liner.
I didn't find a 3rd-party library to do this quite how I wanted. I'll paste my code here in case it is useful to anyone:
import java.lang.reflect.Field;
import java.util.AbstractMap;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An alternative to Spring's BeanUtils#copyProperties for classes that don't have getters and setters.
*/
public class FieldCopier {
private static final Logger log = LoggerFactory.getLogger(FieldCopier.class);
/** Always use the same instance, so that we can cache the fields. */
private static final FieldCopier instance = new FieldCopier();
/** Caching the paired fields cuts the time taken by about 25% */
private final Map<Map.Entry<Class<?>, Class<?>>, Map<Field, Field>> PAIRED_FIELDS = new ConcurrentHashMap<>();
/** Caching the fields cuts the time taken by about 50% */
private final Map<Class<?>, Field[]> FIELDS = new ConcurrentHashMap<>();
public static FieldCopier instance() {
return instance;
}
private FieldCopier() {
// do not instantiate
}
public <S, T> T copyFields(S source, T target) {
Map<Field, Field> pairedFields = getPairedFields(source, target);
for (Field sourceField : pairedFields.keySet()) {
Field targetField = pairedFields.get(sourceField);
try {
Object value = getValue(source, sourceField);
setValue(target, targetField, value);
} catch(Throwable t) {
throw new RuntimeException("Failed to copy field value", t);
}
}
return target;
}
private <S, T> Map<Field, Field> getPairedFields(S source, T target) {
Class<?> sourceClass = source.getClass();
Class<?> targetClass = target.getClass();
Map.Entry<Class<?>, Class<?>> sourceToTarget = new AbstractMap.SimpleImmutableEntry<>(sourceClass, targetClass);
PAIRED_FIELDS.computeIfAbsent(sourceToTarget, st -> mapSourceFieldsToTargetFields(sourceClass, targetClass));
Map<Field, Field> pairedFields = PAIRED_FIELDS.get(sourceToTarget);
return pairedFields;
}
private Map<Field, Field> mapSourceFieldsToTargetFields(Class<?> sourceClass, Class<?> targetClass) {
Map<Field, Field> sourceFieldsToTargetFields = new HashMap<>();
Field[] sourceFields = getDeclaredFields(sourceClass);
Field[] targetFields = getDeclaredFields(targetClass);
for (Field sourceField : sourceFields) {
if (sourceField.getName().equals("serialVersionUID")) {
continue;
}
Field targetField = findCorrespondingField(targetFields, sourceField);
if (targetField == null) {
log.warn("No target field found for " + sourceField.getName());
continue;
}
if (Modifier.isFinal(targetField.getModifiers())) {
log.warn("The target field " + targetField.getName() + " is final, and so cannot be written to");
continue;
}
sourceFieldsToTargetFields.put(sourceField, targetField);
}
return Collections.unmodifiableMap(sourceFieldsToTargetFields);
}
private Field[] getDeclaredFields(Class<?> clazz) {
FIELDS.computeIfAbsent(clazz, Class::getDeclaredFields);
return FIELDS.get(clazz);
}
private <S> Object getValue(S source, Field sourceField) throws IllegalArgumentException, IllegalAccessException {
sourceField.setAccessible(true);
return sourceField.get(source);
}
private <T> void setValue(T target, Field targetField, Object value) throws IllegalArgumentException, IllegalAccessException {
targetField.setAccessible(true);
targetField.set(target, value);
}
private Field findCorrespondingField(Field[] targetFields, Field sourceField) {
for (Field targetField : targetFields) {
if (sourceField.getName().equals(targetField.getName())) {
if (sourceField.getType().equals(targetField.getType())) {
return targetField;
} else {
log.warn("Different types for field " + sourceField.getName()
+ " source " + sourceField.getType() + " and target " + targetField.getType());
return null;
}
}
}
return null;
}
}
Write a simple utility class for that and you got your one liner... this task is IMHO to easy to use a library for it.
Just keep in mind to make your fields accessible if they aren't by default. Here are two functions you could adapt from our codebase:
public void injectIntoObject(Object o, Object value) {
try {
getField().set(o, value);
} catch (IllegalArgumentException e) {
throw new RuntimeException("Illegal argument while injecting property '"+name+"' of class '"+beanDef.getName()+"' in object '"+o+"' to '"+value+"'. Got one of type "+value.getClass().getCanonicalName()+" but needed one of "+type.getCanonicalName()+"!",e);
} catch (IllegalAccessException e) {
getField().setAccessible(true);
try {
getField().set(o, value);
} catch (IllegalArgumentException e1) {
throw new RuntimeException("Illegal argument while injecting property '"+name+"' of class '"+beanDef.getName()+"' in object '"+o+"' to '"+value+"'. Got one of type "+value.getClass().getCanonicalName()+" but needed one of "+type.getCanonicalName()+"!",e);
} catch (IllegalAccessException e1) {
throw new RuntimeException("Access exception while injecting property '"+name+"' of class '"+beanDef.getName()+"' in object '"+o+"' to '"+value+"'!",e);
}
} catch (Exception e) {
throw new RuntimeException("Exception while setting property '"+name+"' of class '"+beanDef.getName()+"' in object '"+o+"' to '"+value+"'!",e);
}
}
public Object extractFromObject(Object o) {
try {
return getField().get(o);
} catch (IllegalArgumentException e) {
throw new RuntimeException("Illegal argument while read property '"+name+"' of class '"+beanDef.getName()+"' in object '"+o+"' but needed one of "+type.getCanonicalName()+"!",e);
} catch (IllegalAccessException e) {
getField().setAccessible(true);
try {
return getField().get(o);
} catch (IllegalArgumentException e1) {
throw new RuntimeException("Illegal argument while read property '"+name+"' of class '"+beanDef.getName()+"' in object '"+o+"' but needed one of "+type.getCanonicalName()+"!",e);
} catch (IllegalAccessException e1) {
throw new RuntimeException("Access exception while read property '"+name+"' of class '"+beanDef.getName()+"' in object '"+o+"'!",e);
}
} catch (Exception e) {
throw new RuntimeException("Exception while read property '"+name+"' of class '"+beanDef.getName()+"' in object '"+o+"'!",e);
}
}
getField() returns a java.lang.Field, should be easy to implement.
I would strongly suggest that you avoid using reflection for this, as it leads to code that is difficult to understand and maintain. (Reflection is ok for testing and when creating frameworks, other than this it probably creates more problems than it solves.)
Also, if a property of an object needs to be accessed by something other than the object, it needs a scope that is not private (or an accessor/getter that is not private). That is the whole point of variable scopes. Keeping a variable private without accessors, and then using it anyways through reflection is just wrong, and will just lead to problems, as you are creating code that lies to the reader.
public class MyClass {
private Integer someInt;
private String someString;
private List<Double> someList;
//...
}
public class MyOtherClass {
private Integer someInt;
private String someString;
private List<Double> someList;
private boolean somethingElse;
public copyPropertiesFromMyClass(final MyClass myClass) {
this.someInt = myClass.getSomeInt();
this.someString = myClass.getSomeString();
this.someList = new ArrayList<>(myClass.getSomeList());
}
}
In javascript, I can do this:
function MyObject(obj) {
for (var property in obj) {
this[property] = obj[property];
}
}
Can I do anything close in Java?
class MyObject {
String myProperty;
public MyObject(HashMap<String, String> props) {
// for each key in props where the key is also the name of
// a property in MyObject, can I assign the value to this.[key]?
}
}
Not that I disagree with Joel's answer, but I do not think it is not quite that difficult, if you essentially just want a best effort. Essentially check if it is there, and if it is try to set. If it works great if not, oh well we tried. For example:
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class MyObject {
protected String lorem;
protected String ipsum;
protected int integer;
public MyObject(Map<String, Object> valueMap){
for (String key : valueMap.keySet()){
setField(key, valueMap.get(key));
}
}
private void setField(String fieldName, Object value) {
Field field;
try {
field = getClass().getDeclaredField(fieldName);
field.set(this, value);
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Map<String, Object> valueMap = new HashMap<String, Object>();
valueMap.put("lorem", "lorem Value");
valueMap.put("ipsum", "ipsum Value");
valueMap.put("integer", 100);
valueMap.put("notThere", "Nope");
MyObject f = new MyObject(valueMap);
System.out.println("lorem => '"+f.lorem+"'");
System.out.println("ipsum => '"+f.ipsum+"'");
System.out.println("integer => '"+f.integer+"'");
}
}
Yes, you can do it by reflection with something along the following lines:
/**
* Returns a list of all Fields in this object, including inherited fields.
*/
private List<Field> getFields() {
List<Field> list = new ArrayList<Field>();
getFields(list, getClass());
return list;
}
/**
* Adds the fields of the provided class to the List of Fields.
* Recursively adds Fields also from super classes.
*/
private List<Field> getFields(List<Field> list, Class<?> startClass) {
for (Field field : startClass.getDeclaredFields()) {
list.add(field);
}
Class<?> superClass = startClass.getSuperclass();
if(!superClass.equals(Object.class)) {
getFields(list, superClass);
}
}
public void setParameters(Map<String, String> props) throws IllegalArgumentException, IllegalAccessException {
for(Field field : getFields()) {
if (props.containsKey(field.getName())) {
boolean prevAccessible = field.isAccessible();
if (!prevAccessible) {
/*
* You're not allowed to modify this field.
* So first, you modify it to make it modifiable.
*/
field.setAccessible(true);
}
field.set(this, props.get(field.getName()));
/* Restore the mess you made */
field.setAccessible(prevAccessible);
}
}
}
However, if you are not very familiar with Java, this approach should be avoided if at all possible, as it is somewhat dangerous and error prone. For instance, there is no guarantee that the Field you are attempting to set are actually expecting a String. If it is the case that they are not, your program will crash and burn.
First, I would use a map if at all possible:
class MyObject {
// String myProperty; // ! not this
HashMap<String,String> myProperties; // use this instead
}
but let's say you wanted to set the fields dynamically.
public MyObject(HashMap<String, String> props) {
for (Map.Entry<String,String> entry : props.entrySet()) {
Field field = this.getClass().getField(entry.getKey());
field.set(this, entry.getValue());
}
}
of course, you will want to use a try/catch in the above constructor.
Well, if you really want to go down the reflection raod, then I suggest to have a look at the Introspector class and get the list of PropertyDescriptors from the BeanInfo.