Last active
July 31, 2021 13:32
-
-
Save alex-ber/ee6cc2ba941771c01235d644a52e7a4c to your computer and use it in GitHub Desktop.
Dynamic enum prototype (don't use it!)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Another alternative https://blog.gotofinal.com/java/diorite/breakingjava/2017/06/24/dynamic-enum.html