From this answer it's possible to add annotations to Java classes during runtime by creating and installing a new internal AnnotationData object. I was curious if it would be possible for a Field. It seems like the way a Field handles annotations is pretty different from how a Class handles them.
I've been able to successfully add an annotation to the declaredAnnotations field of the Field class with the following class:
public class FieldRuntimeAnnotations {
  private static final Field DECLARED_ANNOTATIONS_FIELD;
  private static final Method DECLARED_ANNOTATIONS_METHOD;
  static {
    try {
      DECLARED_ANNOTATIONS_METHOD = Field.class.getDeclaredMethod("declaredAnnotations");
      DECLARED_ANNOTATIONS_METHOD.setAccessible(true);
      DECLARED_ANNOTATIONS_FIELD = Field.class.getDeclaredField("declaredAnnotations");
      DECLARED_ANNOTATIONS_FIELD.setAccessible(true);
    } catch (NoSuchMethodException | NoSuchFieldException | ClassNotFoundException e) {
            throw new IllegalStateException(e);
    }
  }
  // Public access method
  public static <T extends Annotation> void putAnnotationToField(Field f, Class<T> annotationClass, Map<String, Object> valuesMap) {
    T annotationValues = TypeRuntimeAnnotations.annotationForMap(annotationClass, valuesMap);
    try {
        Object annotationData = DECLARED_ANNOTATIONS_METHOD.invoke(f);
        // Get declared annotations
        Map<Class<? extends Annotation>, Annotation> declaredAnnotations =
                (Map<Class<? extends Annotation>, Annotation>) DECLARED_ANNOTATIONS_FIELD.get(f);
        // Essentially copy our original annotations to a new LinkedHashMap
        Map<Class<? extends Annotation>, Annotation> newDeclaredAnnotations = new LinkedHashMap<>(declaredAnnotations);
        newDeclaredAnnotations.put(annotationClass, annotationValues);
        DECLARED_ANNOTATIONS_FIELD.set(f, newDeclaredAnnotations);
    } catch (IllegalAccessException | InvocationTargetException e) {
            throw new IllegalStateException(e);
    }
  }
}
However, the field's declaring class does not get updated with the proper ReflectionData. So essentially I need to "install" the new field information with its declaring class, but I am having trouble of figuring out how.
To make it clearer what I'm asking, the 3rd assertion in my test here fails:
public class RuntimeAnnotationsTest {
  @Retention(RetentionPolicy.RUNTIME)
  @Target({ElementType.TYPE, ElementType.FIELD})
  public @interface TestAnnotation {}
  public static class TestEntity {
    private String test;
  }
  @Test
  public void testPutAnnotationToField() throws NoSuchFieldException {
    // Confirm class does not have annotation
    TestAnnotation annotation = TestEntity.class.getDeclaredField("test").getAnnotation(TestAnnotation.class);
    Assert.assertNull(annotation);
    Field f = TestEntity.class.getDeclaredField("test");
    f.setAccessible(true);
    FieldRuntimeAnnotations.putAnnotationToField(f, TestAnnotation.class, new HashMap<>());
    // Make sure field annotation gets set
    Assert.assertNotNull(f.getAnnotation(TestAnnotation.class));
    // Make sure the class that contains that field is also updated -- THIS FAILS
    Assert.assertNotNull(TestEntity.class.getDeclaredField("test").getAnnotation(TestAnnotation.class));
  }
} 
I understand what I'm trying to achieve is rather ridiculous, but I'm enjoying the exercise :D ... Any thoughts?
Aucun commentaire:
Enregistrer un commentaire