Skip to content

Instantly share code, notes, and snippets.

@alex-ber
Last active July 31, 2021 13:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alex-ber/ee6cc2ba941771c01235d644a52e7a4c to your computer and use it in GitHub Desktop.
Save alex-ber/ee6cc2ba941771c01235d644a52e7a4c to your computer and use it in GitHub Desktop.
Dynamic enum prototype (don't use it!)
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
public enum Lock {
WS1_READ('WS1', true),
WS2_WRITE('WS2'),
;
private String enumName;
// multiple readers can get the same lock in the same time
private boolean forRead;
public Lock(String enumName) {
this(enumName, false);
}
public Lock(String name, boolean forRead) {
this.enumName = Objects.requireNonNull(name).toUpperCase();
this.forRead=forRead;
}
@Override
public String toString() {
return enumName;
}
public boolean isReadMode() {
return forRead;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((enumName == null) ? 0 : enumName.hashCode());
result = prime * result + (forRead ? 1231 : 1237);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Lock other = (Lock) obj;
if (enumName == null) {
if (other.enumName != null)
return false;
} else if (!enumName.equals(other.enumName))
return false;
if (forRead != other.forRead)
return false;
return true;
}
private static void cleanEnumCache(Class<?> enumClass){
blankField(enumClass, "enumConstantDirectory");
blankField(enumClass, "enumConstants");
}
private static void blankField(Class<?> enumClass, String fieldName){
ReflectionUtils.doWithLocalFields(Class.class, field ->{
if (field.getName().contains(fieldName)) {
ReflectionUtils.makeAccessible(field);
setFieldValue(field, enumClass, null);
}
});
}
private static Member removeModifier(Member field, int...modifier) {
int modifiers = field.getModifiers();
// blank out the final bit in the modifiers int
for(int m:modifier){
modifiers &= ~m;
}
Field modifiersField = ReflectionUtils.findField(Field.class, "modifiers");
ReflectionUtils.makeAccessible(modifiersField);
ReflectionUtils.setField(modifiersField, field, modifiers);
return field;
}
private static Field removeFinalModifier(Field field) {
return (Field)removeModifier(field, Modifier.FINAL);
}
private static void setFieldValue(Field field, Object target, Object value){
ReflectionUtils.makeAccessible(field);
field = removeFinalModifier(field);
Class<?>[] ingoreTypes = null;
Method fieldAccessorMethod = ReflectionUtils.findMethod(Field.class, "acquireFieldAccessor", ingoreTypes);
ReflectionUtils.makeAccessible(fieldAccessorMethod);
Object fieldAccessor = ReflectionUtils.invokeMethod(fieldAccessorMethod, field, false);
Method setMethod = ReflectionUtils.findMethod(fieldAccessor.getClass(), "set", ingoreTypes);
ReflectionUtils.makeAccessible(setMethod);
//ReflectionUtils.setField(field, target, value);
//field.set(target, value);
ReflectionUtils.invokeMethod(setMethod, fieldAccessor, target, value);
}
private static Field getValuesFiled(Class<? extends Enum<?>> enumType) {
// Lookup "$VALUES" holder in enum class and get previous enum instances
AtomicReference<Field> valuesField = new AtomicReference<Field>();
ReflectionUtils.doWithLocalFields(enumType, field ->{
if (field.getName().contains("$VALUES")) {
if(valuesField.compareAndSet(null, field)){
//this is final Field, ReflectionUtils.makeAccessible(field); will ignore it
//so direct use of setAccessible
field.setAccessible(true);
}
}
});
Field ret = valuesField.get();
Assert.notNull(ret, "$VALUES can't be null");
return ret;
}
private static Enum<?>[] getValues(Class<? extends Enum<?>> enumType, Field valuesField) {
// copy it and leave extra space
Enum<?>[] values = (Enum[]) ReflectionUtils.getField(valuesField, enumType);
int length = (values == null) ? 0 : values.length;
int newLength = length + 1;
return Arrays.copyOf(values, newLength, ((Enum[]) Array.newInstance(enumType, 0)).getClass());
}
private static Class<?>[] getEnumCtorType(Class<?>[] additionalParameterTypes){
Class<?>[] parameterTypes = new Class[additionalParameterTypes.length + 2];
parameterTypes[0] = String.class;
parameterTypes[1] = int.class;
System.arraycopy(additionalParameterTypes, 0, parameterTypes, 2, additionalParameterTypes.length);
return parameterTypes;
}
private static Enum<?> makeEnum(Class<?> enumClass, String value, int ordinal, Class<?>[] additionalTypes,
Object[] additionalValues) {
Object[] params = new Object[additionalValues.length + 2];
params[0] = value;
params[1] = Integer.valueOf(ordinal);
System.arraycopy(additionalValues, 0, params, 2, additionalValues.length);
Class<?>[] parameterTypes = getEnumCtorType(additionalTypes);
try {
Constructor<?> ctor = ReflectionUtils.accessibleConstructor(enumClass, parameterTypes);
Class<?>[] ingoreTypes = null;
Method ctorAccessorMethod = ReflectionUtils.findMethod(Constructor.class, "acquireConstructorAccessor", ingoreTypes);
ReflectionUtils.makeAccessible(ctorAccessorMethod);
Object ctorAccessor = ReflectionUtils.invokeMethod(ctorAccessorMethod, ctor);
Method newInstanceMethod = ReflectionUtils.findMethod(ctorAccessor.getClass(), "newInstance", ingoreTypes);
ReflectionUtils.makeAccessible(newInstanceMethod);
//return (Enum<?>)ctor.newInstance(params);
Object ret = ReflectionUtils.invokeMethod(newInstanceMethod, ctorAccessor, new Object[]{params});
return (Enum<?>)ret;
}
catch (Throwable ex) {
throw new IllegalStateException("Unable to instantiate dynamic enum", ex);
}
}
// inspired by https://www.niceideas.ch/roller2/badtrash/entry/java-create-enum-instances-dynamically
// as alternative see https://bojanv55.wordpress.com/2015/05/25/java-dynamic-enums/
public static void addEnum(String name) {
Class<? extends Enum<?>> enumType = Lock.class;
String enumName = name;
if (!Enum.class.isAssignableFrom(enumType)) {
throw new RuntimeException("class " + enumType + " is not an instance of Enum");
}
Field valuesField = getValuesFiled(enumType);
Enum<?>[] values = getValues(enumType, valuesField);
int length = (values == null) ? 0 : values.length;
assert length > 0;
int lastIndex = length - 1;
//Alex note hard-coded call to Lock(String enumName)
Enum<?> newValue = makeEnum(enumType, // The target enum class
enumName, // THE NEW ENUM INSTANCE TO BE DYNAMICALLY ADDED
lastIndex, new Class<?>[] { String.class },//could be used to pass values to the enum constuctor if needed,
new Object[] { name }); // could be used to pass values to the enum constuctor if needed
values[lastIndex] = newValue;
setFieldValue(valuesField, null, values);
cleanEnumCache(enumType);
}
}
@alex-ber
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment