2

I have a simple annotation for editing keys of a map on runtime:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface UpdateKey
{
    String oldKey() default "";    //The key that needs to be replaced

    String newKey() default "";    //The new key

}

And i am using it on a method in some class:

public class CustomMap
{
    @UpdateKey(oldKey = "description", newKey = "productName")
    public void editMap(Map<String,String> map) throws NoSuchMethodException
    {
        //get the annotation values
        UpdateKey updateKey = this.getClass().getMethod("editMap").getAnnotation(UpdateKey.class);

        //save value and delete old key
        Object obj = map.remove(updateKey.oldKey());

        //set the value to the new key
        map.put(updateKey.newKey(),obj);
    }
}

But nothing seems to work to manipulate this annotation before calling the method, i already tried :

        // get the edit map method
        Method method = CustomMap.class.getDeclaredMethod("editMap");

        // get the annotation 
        UpdateKey updateKeyAnnotation = method.getDeclaredAnnotation(UpdateKey.class);

        Object handler = Proxy.getInvocationHandler(updateKeyAnnotation);

        //this is retrieving the reflection class fields NOT my annotation fields
        sout(handler.getClass().getDeclaredFields());

        Field f;

        //of course this will fail since there is no oldKey field in the reflection class
        f = handler.getClass().getDeclaredField("oldKey");  

Bahij.Mik
  • 1,358
  • 2
  • 9
  • 22
  • read [this](https://stackoverflow.com/questions/1805200/retrieve-java-annotation-attribute). i think there is a List/Array where you can remove/add/change them – fuggerjaki61 Mar 14 '20 at 13:57

2 Answers2

2

Man, you were just 2 steps away from the solution. After you retrieved invocation handler you have to change it's field memberValues. Here is the complete code:

import java.lang.annotation.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

public class NewApp {

    public static void main(String[] args) throws NoSuchMethodException, NoSuchFieldException, IllegalAccessException {


        // get the edit map method
        Method method = CustomMap.class.getDeclaredMethod("editMap", Map.class);

        // get the annotation
        UpdateKey updateKeyAnnotation = method.getDeclaredAnnotation(UpdateKey.class);
        System.out.println("current annotation: " + updateKeyAnnotation);

        Object handler = Proxy.getInvocationHandler(updateKeyAnnotation);

        System.out.println(Arrays.toString(handler.getClass().getDeclaredFields()));

        // field memberValues contains annotation attributes and their values
        // just modify it
        Field memberValues = handler.getClass().getDeclaredField("memberValues");
        memberValues.setAccessible(true);
        System.out.println(memberValues.get(handler));

        Map<String, String> annotationAttributes = new HashMap<>();
        annotationAttributes.put("newKey", "otherProduct");
        annotationAttributes.put("oldKey", "otherDescription");
        memberValues.set(handler, annotationAttributes);


        System.out.println("changed annotation: " + updateKeyAnnotation);
    }
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface UpdateKey {
    String oldKey() default "";    //The key that needs to be replaced

    String newKey() default "";    //The new key
}

class CustomMap {

    @UpdateKey(oldKey = "description", newKey = "productName")
    public void editMap(Map<String, String> map) throws NoSuchMethodException {
        //get the annotation values
        UpdateKey updateKey = this.getClass().getMethod("editMap").getAnnotation(UpdateKey.class);

        //save value and delete old key
        Object obj = map.remove(updateKey.oldKey());

        //set the value to the new key
        map.put(updateKey.newKey(), obj.toString());
    }
}

output of this code

current annotation: @UpdateKey(newKey=productName, oldKey=description)
[private static final long sun.reflect.annotation.AnnotationInvocationHandler.serialVersionUID, private final java.lang.Class sun.reflect.annotation.AnnotationInvocationHandler.type, private final java.util.Map sun.reflect.annotation.AnnotationInvocationHandler.memberValues, private transient volatile java.lang.reflect.Method[] sun.reflect.annotation.AnnotationInvocationHandler.memberMethods, static final boolean sun.reflect.annotation.AnnotationInvocationHandler.$assertionsDisabled]
{newKey=productName, oldKey=description}
changed annotation: @UpdateKey(oldKey=otherDescription, newKey=otherProduct)
  • Thanks man! this works! On another note, I had to also change the getDeclaredMethod to getMethod when fetching the method so that the new annotation values are actually changed inside the class once instantiated. – Bahij.Mik Mar 14 '20 at 21:22
  • 1
    Do you have some hints for me? I tried the exact way, but I still receive the following exception: `java.lang.reflect.InaccessibleObjectException: Unable to make field private final java.util.Map sun.reflect.annotation.AnnotationInvocationHandler.memberValues accessible: module java.base does not "opens sun.reflect.annotation" to unnamed module @400008bf` – Murolack Dec 09 '22 at 16:18
0

For those who have issues as in the comment above (can't make comments yet), add --add-opens=java.base/sun.reflect.annotation=ALL-UNNAMED to VM options. This package has lots of default access modifiers, hence we need to tweak it with this option