mercredi 29 juillet 2020

Setting a final field for an ENUM variable

I want to change the final value of an attribute "name" declared in my enum class using reflection with the name of the enum itself to use it in custom annotations. But I am facing a strange behaviour which I am not able to debug.

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;


@Service
@Slf4j
public class EnumNameEditor {

    public static void throwErrorForNotDefinedErrorCodes() throws Exception {
        Reflections reflections = new Reflections("com.");
        Set<Class<? extends CustomClass>> classes = reflections.getSubTypesOf(CustomClass.class);
        log.info("classes: {}",classes);
        for (Class c : classes) {
            if(c.isEnum()) {
                changeNameInEnum(c);
            }
        }
    }

    private static<C extends Enum<C>> void changeNameInEnum(Class<C> c) throws Exception {
        C[] codes = c.getEnumConstants();
        String uniqueIdentifier = "uniqueIdentifier";
        String name = "name";
        log.info("c: {}",Arrays.asList(codes));
        for (C e : codes) {
            System.out.println("\n\n=============== changing: " + e.ordinal());
            getValue(c,name,e);
            setValue(c,e);
            System.out.println("Ee : " + e);
            getValue(c,name,e);
            setFieldValue(c,e);
        }

        codes = c.getEnumConstants();
        log.info("after c: {}",Arrays.asList(codes));
    }

    private static <C extends Enum<C>> void setFieldValue(Class<C> c, C e) throws Exception {
        System.out.println("e: "+ e);
        Field $VALUESField = c.getDeclaredField("$VALUES");
        makeAccessible($VALUESField);
        C[] oldValues = (C[]) $VALUESField.get(null);
        oldValues[e.ordinal()] = e;
        $VALUESField.set(null, oldValues);

        $VALUESField = Class.class.getDeclaredField("enumConstants");
        makeAccessible($VALUESField);
        $VALUESField.set(c, oldValues);

        try {
            $VALUESField = Class.class.getDeclaredField("enumConstantDirectory");
            makeAccessible($VALUESField);
            Map<String,C> map = (Map<String, C>) $VALUESField.get(c);
            System.out.println("map: " + map);
            if(map != null) {
                map.put(e.name(),e);
                $VALUESField.set(c, map);
            }

        } catch (Exception exc) {
            exc.printStackTrace();
            log.debug("exception while setting new enum values in enumConstantDirectory for class: {}",c);
        }


    }

    static<C extends Enum<C>> Object getValue(Class<C> c, String fname, C e) throws Exception {
        Field field = c.getDeclaredField(fname);
        makeAccessible(field);
        Object value = field.get(e);
        System.out.println("value defined: " + value + " for: " + fname);
        return value;
    }

    static<C extends Enum<C>> C setValue(Class<C> c, C e) throws Exception {
        Field field = c.getDeclaredField("name");
        makeAccessible(field);
        field.set(e,e.name());
        return e;
    }

    static void makeAccessible(Field field) throws Exception {
        field.setAccessible(true);
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(field, field.getModifiers() & ~ Modifier.FINAL);
    }

    public static void main(String[] args) {
        try {
            System.out.println("name: " + GenericResponseErrorCodes.UNEXPECTED_ERROR.name);
            throwErrorForNotDefinedErrorCodes();
            System.out.println("name: " + GenericResponseErrorCodes.UNEXPECTED_ERROR.name);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}

My Enum class is as below.

public enum  GenericResponseErrorCodes implements CustomClass {

    UNEXPECTED_ERROR(1,"Unexpected error has occurred. Please try again later."),
    UNEXPECTED_ERROR2(2,"Unexpected error2 has occurred. Please try again later."),
    UNEXPECTED_ERROR3(3,"Unexpected error3 has occurred. Please try again later.","def.prop"),
    UNEXPECTED_ERROR4(4,"Unexpected error4 has occurred. Please try again later."),
    UNEXPECTED_ERROR5(5,"Unexpected error5 has occurred. Please try again later.");



    final String uniqueIdentifier = "G";
    String key;
    String message;
    Integer code;
    public final String name = "name";

    GenericResponseErrorCodes( Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    GenericResponseErrorCodes(Integer code, String message, String key) {
        this.code = code;
        this.key = key;
        this.message = message;
    }

}

I am getting the below logs when I execute the main function.

name: test_name

c: [GenericResponseErrorCodes(uniqueIdentifier=G, key=null, message=Unexpected error has occurred. Please try again later., code=1, name=test_name), GenericResponseErrorCodes(uniqueIdentifier=G, key=null, message=Unexpected error2 has occurred. Please try again later., code=2, name=test_name), GenericResponseErrorCodes(uniqueIdentifier=G, key=def.prop, message=Unexpected error3 has occurred. Please try again later., code=3, name=test_name), GenericResponseErrorCodes(uniqueIdentifier=G, key=null, message=Unexpected error4 has occurred. Please try again later., code=4, name=test_name), GenericResponseErrorCodes(uniqueIdentifier=G, key=null, message=Unexpected error5 has occurred. Please try again later., code=5, name=test_name)]


=============== changing: 0
value defined: test_name for: name
Ee : GenericResponseErrorCodes(uniqueIdentifier=G, key=null, message=Unexpected error has occurred. Please try again later., code=1, name=test_name)
value defined: UNEXPECTED_ERROR for: name


=============== changing: 1
value defined: test_name for: name
Ee : GenericResponseErrorCodes(uniqueIdentifier=G, key=null, message=Unexpected error2 has occurred. Please try again later., code=2, name=test_name)
value defined: UNEXPECTED_ERROR2 for: name


=============== changing: 2
value defined: test_name for: name
Ee : GenericResponseErrorCodes(uniqueIdentifier=G, key=def.prop, message=Unexpected error3 has occurred. Please try again later., code=3, name=test_name)
value defined: UNEXPECTED_ERROR3 for: name


=============== changing: 3
value defined: test_name for: name
Ee : GenericResponseErrorCodes(uniqueIdentifier=G, key=null, message=Unexpected error4 has occurred. Please try again later., code=4, name=test_name)
value defined: UNEXPECTED_ERROR4 for: name


=============== changing: 4
value defined: test_name for: name
Ee : GenericResponseErrorCodes(uniqueIdentifier=G, key=null, message=Unexpected error5 has occurred. Please try again later., code=5, name=test_name)
value defined: UNEXPECTED_ERROR5 for: name

after c: [GenericResponseErrorCodes(uniqueIdentifier=G, key=null, message=Unexpected error has occurred. Please try again later., code=1, name=test_name), GenericResponseErrorCodes(uniqueIdentifier=G, key=null, message=Unexpected error2 has occurred. Please try again later., code=2, name=test_name), GenericResponseErrorCodes(uniqueIdentifier=G, key=def.prop, message=Unexpected error3 has occurred. Please try again later., code=3, name=test_name), GenericResponseErrorCodes(uniqueIdentifier=G, key=null, message=Unexpected error4 has occurred. Please try again later., code=4, name=test_name), GenericResponseErrorCodes(uniqueIdentifier=G, key=null, message=Unexpected error5 has occurred. Please try again later., code=5, name=test_name)]
name: test_name

Process finished with exit code 0

I am not able to identify why the name accessed using reflection is different from the one present in the enum object? Also how should I change the name field permanently? Thanks in advance.

PS: I have taken reference from this blog.





Aucun commentaire:

Enregistrer un commentaire