I think it would be best to keep a reference to an Annotation object in addition to your Field (or Object), and update the Annotation reference as you change its values. This way, when the implementation of annotations in Class.java changes, your code is less likely to break.
The answer linked in the question comments is useful for dealing with annotations containing a single element, but if you have multiple elements that you need to set, here is a more general solution that makes use of a proxy:
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) throws Exception {
Foo foo = new Foo();
Field field = foo.getClass().getDeclaredFields()[0];
Anno anno = field.getAnnotation(Anno.class);
System.out.println(String.format("Old properties: %s, %s, %s", anno.value(), anno.bar(), anno.barr()));
Anno anno2 = (Anno) setAttrValue(anno, Anno.class, "value", "new");
System.out.println(String.format("New properties: %s, %s, %s", anno2.value(), anno2.bar(), anno2.barr()));
Anno anno3 = (Anno) setAttrValue(anno2, Anno.class, "bar", "new bar");
System.out.println(String.format("New properties: %s, %s, %s", anno3.value(), anno3.bar(), anno3.barr()));
}
public static Annotation setAttrValue(Annotation anno, Class<? extends Annotation> type, String attrName, Object newValue) throws Exception {
InvocationHandler handler = new AnnotationInvocationHandler(anno, attrName, newValue);
Annotation proxy = (Annotation) Proxy.newProxyInstance(anno.getClass().getClassLoader(), new Class[]{type}, handler);
return proxy;
}
}
class AnnotationInvocationHandler implements InvocationHandler {
private Annotation orig;
private String attrName;
private Object newValue;
public AnnotationInvocationHandler(Annotation orig, String attrName, Object newValue) throws Exception {
this.orig = orig;
this.attrName = attrName;
this.newValue = newValue;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// "override" the return value for the property we want
if (method.getName().equals(attrName) && args == null)
return newValue;
// keep other properties and methods we want like equals() and hashCode()
else {
Class<?>[] paramTypes = toClassArray(args);
return orig.getClass().getMethod(method.getName(), paramTypes).invoke(orig, args);
}
}
private static Class<?>[] toClassArray(Object[] arr) {
if (arr == null)
return null;
Class<?>[] classArr = new Class[arr.length];
for (int i=0; i<arr.length; i++)
classArr[i] = arr[i].getClass();
return classArr;
}
}
class Foo {
@Anno(value="old", bar="bar", barr="barr")
public Object field1;
}
@Retention(RetentionPolicy.RUNTIME)
@interface Anno {
String value();
String bar();
String barr();
}
Program output:
Old properties: old, bar, barr
New properties: new, bar, barr
New properties: new, new bar, barr