import java.lang.reflect.Constructor; | |
import java.lang.reflect.Field; | |
import java.lang.reflect.Method; | |
import java.lang.reflect.Modifier; | |
import java.util.Arrays; | |
import sun.misc.Unsafe; | |
import sun.reflect.ConstructorAccessor; | |
public class EnumHack { | |
public static void main(String[] args) throws Throwable { | |
System.out.println(Arrays.toString(Monster.class.getEnumConstants())); | |
reflectionWay(); | |
System.out.println(Arrays.toString(Monster.class.getEnumConstants())); | |
unsafeWay(); | |
System.out.println(Arrays.toString(Monster.class.getEnumConstants())); | |
} | |
public static void unsafeWay() throws Throwable { | |
Constructor<?> constructor = Unsafe.class.getDeclaredConstructors()[0]; | |
constructor.setAccessible(true); | |
Unsafe unsafe = (Unsafe) constructor.newInstance(); | |
Monster enumValue = (Monster) unsafe.allocateInstance(Monster.class); | |
Field ordinalField = Enum.class.getDeclaredField("ordinal"); | |
makeAccessible(ordinalField); | |
ordinalField.setInt(enumValue, 5); | |
Field nameField = Enum.class.getDeclaredField("name"); | |
makeAccessible(nameField); | |
nameField.set(enumValue, "LION"); | |
Field entityClassField = Monster.class.getDeclaredField("entityClass"); | |
makeAccessible(entityClassField); | |
entityClassField.set(enumValue, Lion.class); | |
Field entityIdField = Monster.class.getDeclaredField("entityId"); | |
makeAccessible(entityIdField); | |
entityIdField.set(enumValue, "Lion"); | |
registerValue(enumValue); | |
} | |
public static void reflectionWay() throws Throwable { | |
Class<Monster> monsterClass = Monster.class; | |
// first we need to find our constructor, and make it accessible | |
Constructor<?> constructor = monsterClass.getDeclaredConstructors()[0]; | |
constructor.setAccessible(true); | |
// this is this same code as in constructor.newInstance, but we just skipped all that useless enum checks ;) | |
// ConstructorAccessor ca = constructorAccessor; | |
// if (ca == null) { ca = acquireConstructorAccessor(); } | |
// T inst = (T) ca.newInstance(initargs); | |
// then we need to find real, internal, constructor accessor | |
Field constructorAccessorField = Constructor.class.getDeclaredField("constructorAccessor"); | |
constructorAccessorField.setAccessible(true); | |
// sun.reflect.ConstructorAccessor -> itnernal class, we should not use it, if you need use it, it would be better to actually not import it, but use | |
// it only via reflections. (as package may change, and will in java 9) | |
ConstructorAccessor ca = (ConstructorAccessor) constructorAccessorField.get(constructor); | |
if (ca == null) { | |
Method acquireConstructorAccessorMethod = Constructor.class.getDeclaredMethod("acquireConstructorAccessor"); | |
acquireConstructorAccessorMethod.setAccessible(true); | |
ca = (ConstructorAccessor) acquireConstructorAccessorMethod.invoke(constructor); | |
} | |
// note that real conststructor contains 2 additional parameters, name and oridinal | |
Monster enumValue = (Monster) ca.newInstance(new Object[]{"CAERBANNOG_RABBIT", 4, CaerbannogRabbit.class, | |
"caerbannograbbit"}); | |
// you can call that using reflections too, reflecting reflections are best part of java ;) | |
registerValue(enumValue); | |
} | |
private static void registerValue(Monster enumValue) throws Throwable { | |
// and now we need to replace values reference from final field. | |
Field $VALUESField = Monster.class.getDeclaredField("$VALUES"); | |
makeAccessible($VALUESField); | |
// just copy old values to new array and add our new field. | |
Monster[] oldValues = (Monster[]) $VALUESField.get(null); | |
Monster[] newValues = new Monster[oldValues.length + 1]; | |
System.arraycopy(oldValues, 0, newValues, 0, oldValues.length); | |
newValues[oldValues.length] = enumValue; | |
$VALUESField.set(null, newValues); | |
Field enumConstantsField = Class.class.getDeclaredField("enumConstants"); | |
makeAccessible(enumConstantsField); | |
enumConstantsField.set(Monster.class, null); | |
Field enumConstantDirectoryField = Class.class.getDeclaredField("enumConstantDirectory"); | |
makeAccessible(enumConstantDirectoryField); | |
enumConstantDirectoryField.set(Monster.class, null); | |
} | |
static void makeAccessible(Field field) throws Exception { | |
field.setAccessible(true); | |
// note that every field is just copy of real field, so changed modifiers affects only this Field instance, if you will get this same field again, it | |
// will be final again. | |
Field modifiersField = Field.class.getDeclaredField("modifiers"); | |
modifiersField.setAccessible(true); | |
modifiersField.setInt(field, field.getModifiers() & ~ Modifier.FINAL); | |
} | |
public enum Monster { | |
ZOMBIE(Zombie.class, "zombie"), | |
ORK(Ork.class, "ork"), | |
WOLF(Wolf.class, "wolf"); | |
private final Class<? extends Entity> entityClass; | |
private final String entityId; | |
Monster(Class<? extends Entity> entityClass, String entityId) { | |
this.entityClass = entityClass; | |
this.entityId = "monster:" + entityId; | |
} | |
public Class<? extends Entity> getEntityClass() { return this.entityClass; } | |
public String getEntityId() { return this.entityId; } | |
public Entity create() { | |
try { | |
return entityClass.newInstance(); | |
} | |
catch (InstantiationException | IllegalAccessException e) { | |
throw new InternalError(e); | |
} | |
} | |
} | |
} | |
interface Entity {} | |
class Zombie implements Entity {} | |
class Ork implements Entity {} | |
class Wolf implements Entity {} | |
class CaerbannogRabbit implements Entity {} | |
class Lion implements Entity {} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment