-
-
Save GotoFinal/74393bbc88d2b89646c93a9617e04795 to your computer and use it in GitHub Desktop.
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.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