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 ?
As you all mentionned in the comments' section, adding enum
values at runtime is a very bad idea, breaking the contract of an enum.
So, I've modified all the cases into a POJO object, maintaining a map.
In a simplified format, AEnum
becomes :
public class AEnum extends DynamicEnum { // DynamicEnum has utility methods in order to act as close as a real enum.
@Getter
private String label;
private static final Map<String, AEnum> map = new LinkedHashMap<>();
protected AEnum (String name, String label) {
this.name = name;
this.label= label;
}
public static AEnum addInMap(String name, String label) {
AEnum value = new DossierSousType(name, label);
map.put(name, value);
return value;
}
}
We read the dynamic values from the database, so I've made an utility class to load everything.
public static <T extends DynamicEnum> T addEnum(final Class<T> type, final String name, final String label) throws TechnicalException {
try {
Method method = type.getDeclaredMethod("addInMap", String.class, String.class);
return (T) method.invoke(null, name, label);
} catch (... e) {
// ...
}
}
Then :
addEnum(AEnum.class, "TEST", "labelTest");
addEnum(AEnum.class, "TEST2", "labelTest2");
AEnum.getAll() // returns a list with the two entries
Moreover, if we use this "false enum" inside a persisted Entity
, we have a converter to manage conversion between String
and AEnum
.
@Entity
@Table(name = TABLE_NAME)
...
public class MyEntity {
@Column(name = COLUMN_TYPE)
@Convert(converter = AEnumConverter.class)
private AEnum type;
AEnumConverter
implements javax.persistence.AttributeConverter
:
@Converter
public class AEnumConverter implements AttributeConverter<AEnum , String> {
@Override
public String convertToDatabaseColumn(AEnum type) {
return type != null ? type.getName() : null;
}
@Override
public AEnum convertToEntityAttribute(String type) {
return AEnum .getEnum(type);
}
}
With this mechanism, everything works perfectly, and we no longer need sun.reflect.*
!