Skip to content

Instantly share code, notes, and snippets.

@GotoFinal
Created June 24, 2017 20:18
Show Gist options
  • Save GotoFinal/74393bbc88d2b89646c93a9617e04795 to your computer and use it in GitHub Desktop.
Save GotoFinal/74393bbc88d2b89646c93a9617e04795 to your computer and use it in GitHub Desktop.
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