Dynamic enum with OpenJDK 11 using reflection

I'm working on a project running with JDK8 and we want to migrate it to OpenJDK11.

But, there is legacy code that creates enums dynamically at runtime (using reflection and sun.reflect.* packages) :

public class EnumUtil {
    static Object makeEnum(...) {
        ...
        enumClass.cast(sun.reflect.ReflectionFactory.getReflectionFactory() .newConstructorAccessor(constructor).newInstance(params));
    }
}

Or

    // before, field is made accessible, the modifier too
    sun.reflect.FieldAccessor fieldAccessor = sun.reflect.ReflectionFactory.getReflectionFactory().newFieldAccessor(field, false);
    field.set(target, value);

For example, let's say we have the enum AEnum :

public enum AEnum {
    ; // no values at compile time

    private String label;

    private AEnum (String label) {
        this.label = label;
    }

Then, we add enum values like this :

EnumUtil.addEnum(MyEnum.class, "TEST", "labelTest");

Finally, we have, at runtime, a value AEnum.TEST (not with that direct call, but with Enum.valueOf) with the label = labelTest.

Unfortunately, sun.reflect.* classes are no longer available in OpenJDK11.

I've tried using jdk.internal.reflect.ConstructorAccessor but I'm getting the error java: package jdk.internal.reflect does not exist. And I don't think it's a good idea to rely on jdk.internal.* classes.

Is there any OpenJDK11 alternative to create enums at runtime ?


Solution 1:

This answer contains a working approach which uses only the standard API and still works, even with JDK 17.

Since it uses a JDK type for the example, which requires an --add-opens java.base/java.lang=… argument at startup time, here’s an example using its own enum type which does not require any modifications to the environment.

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.util.EnumSet;

class EnumHack {
    public static void main(String[] args) throws Throwable {
        System.out.println(Runtime.version());
        Constructor<Example> c
            = Example.class.getDeclaredConstructor(String.class, int.class);
        c.setAccessible(true);
        MethodHandle h = MethodHandles.lookup().unreflectConstructor(c);
        Example baz = (Example) h.invokeExact("BAZ", 42);
        System.out.println("created Example " + baz + "(" + baz.ordinal() + ')');
        EnumSet<Example> set = EnumSet.allOf(Example.class);
        System.out.println(set.contains(baz));
        set.add(baz);
        System.out.println(set);
    }

    enum Example {
        FOO, BAR
    }
}

Since it doesn’t require special setup, it can be demonstrated on Ideone

12.0.1+12
created Example BAZ(42)
false

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 42 out of bounds for length 2
    at java.base/java.util.RegularEnumSet$EnumSetIterator.next(RegularEnumSet.java:105)
    at java.base/java.util.RegularEnumSet$EnumSetIterator.next(RegularEnumSet.java:78)
    at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:472)
    at java.base/java.lang.String.valueOf(String.java:3042)
    at java.base/java.io.PrintStream.println(PrintStream.java:897)
    at EnumHack.main(Main.java:18)

This not only shows that the hack does its job, but also some of the problems cause by this. The set supposed to contain all elements doesn’t contain the new constant and after adding it manually, the resulting inconsistent state produces exceptions on subsequent operations.

So, such enum constant must not be used with the standard tools for enum types, which matches what has been said in the question’s comments, it loses the advantage of being an enum type. In fact, it’s worse than that.

So the approach shown above should only be used as a temporary work-around, or not at all.