【Java】DynamicEnumKit (动态追加枚举元素)

Implementing dynamic append enum nodes

import lombok.NonNull;
import lombok.SneakyThrows;
import sun.reflect.ConstructorAccessor;
import sun.reflect.FieldAccessor;
import sun.reflect.ReflectionFactory;

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

public class DynamicEnumKit {

    private static final ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();

    private static void setFieldValueFailsafe(@NonNull Field field, Object target, Object value)
            throws NoSuchFieldException, IllegalAccessException {

        // let's make the field accessible
        field.setAccessible(true);
        // next we change the modifier in the Field instance to
        // not be final anymore, thus tricking reflection into
        // letting us modify the static final field
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        int modifiers = modifiersField.getInt(field);
        // blank out the final bit in the modifiers int
        modifiers &= ~Modifier.FINAL;
        modifiersField.setInt(field, modifiers);
        FieldAccessor fa = reflectionFactory.newFieldAccessor(field, false);
        fa.set(target, value);
    }

    private static void blankField(@NonNull Class<?> enumType, @NonNull String fieldName)
            throws NoSuchFieldException, IllegalAccessException {

        Field field = Class.class.getDeclaredField(fieldName);
        field.setAccessible(true);
        setFieldValueFailsafe(field, enumType, null);
    }

    private static void cleanEnumCache(@NonNull Class<?> enumType)
            throws NoSuchFieldException, IllegalAccessException {

        blankField(enumType, "enumConstantDirectory"); // Sun (Oracle?!?) JDK 1.5/6
        blankField(enumType, "enumConstants"); // IBM JDK
    }

    private static ConstructorAccessor getConstructorAccessor(@NonNull Class<?> enumType,
                                                              @NonNull Class<?>[] additionalParamTypes)
            throws NoSuchMethodException {

        Class<?>[] paramTypes = new Class[additionalParamTypes.length + 2];
        paramTypes[0] = String.class;
        paramTypes[1] = int.class;
        System.arraycopy(additionalParamTypes, 0, paramTypes, 2, additionalParamTypes.length);
        return reflectionFactory.newConstructorAccessor(enumType.getDeclaredConstructor(paramTypes));
    }

    private static Object makeEnum(@NonNull Class<?> enumType,
                                   String value, int ordinal,
                                   @NonNull Class<?>[] additionalTypes,
                                   @NonNull Object[] additionalValues) throws Exception {

        Object[] param = new Object[additionalValues.length + 2];
        param[0] = value;
        param[1] = ordinal;
        System.arraycopy(additionalValues, 0, param, 2, additionalValues.length);
        return enumType.cast(getConstructorAccessor(enumType, additionalTypes).newInstance(param));
    }

    /**
     * Add an enum instance to the enum class given as argument
     *
     * @param <T>      the type of the enum (implicit)
     * @param enumType the class of the enum to be modified
     * @param enumName the name of the new enum instance to be added to the class.
     */
    @SneakyThrows
    @SuppressWarnings("unchecked")
    public static <T extends Enum<?>> void addEnum(@NonNull Class<T> enumType,
                                                   @NonNull String enumName,
                                                   @NonNull Class<?>[] additionalTypes,
                                                   @NonNull Object[] additionalValues) {

        // 0. Sanity checks
        if (!Enum.class.isAssignableFrom(enumType)) {
            throw new RuntimeException("class " + enumType + " is not an instance of Enum");
        }
        //noinspection SynchronizationOnLocalVariableOrMethodParameter
        synchronized (enumType) {
            // 1. Lookup "$VALUES" holder in enum class and get previous enum instances
            Field field = enumType.getDeclaredField("$VALUES");
            field.setAccessible(true);
            // 2. Copy it
            final T[] values = (T[]) field.get(enumType);
            // 3. build new enum
            final T newValue = (T) makeEnum(enumType, enumName, values.length, additionalTypes, additionalValues);
            // 4. add new value
            final T[] newValues = Arrays.copyOf(values, values.length + 1);
            newValues[values.length] = newValue;
            // 5. Set new values field
            setFieldValueFailsafe(field, null, newValues);
            // 6. Clean enum cache
            cleanEnumCache(enumType);
        }
    }

}
posted @ 2023-03-02 10:19  XKIND  阅读(200)  评论(0编辑  收藏  举报