Creating and overriding Java Enum Objects at Runti

2019-09-02 06:23发布

问题:

I'm trying to work with this enum and add new materials. Anything not already removed has hard dependencies elsewhere, even still, this is nearly at the java byte limit according to the mods author so there isn't really a lot of room to work with anyway.

GregoriousT mentioned "There is one way. Overmind hacked the Enum using Reflection to add his own stuff. No Idea how he did that and also no idea how long he takes to reply to things if you ask him."

Enum we're talking about: http://pastebin.com/g0aJ2Qjd

So I simply ask, how would I go about this?

This is what my current attempt throws [FML]: Variable m:1|newInstance|public java.lang.Object sun.reflect.DelegatingConstructorAccessorImpl.newInstance(java.lang.Object[]) throws java.lang.InstantiationException,java.lang.IllegalArgumentException,java.lang.reflect.InvocationTargetException|false before the client crashes. (Log code removed for easy reading)

Current attempt:

public class MaterialsNew {

public static void getGregMaterials() throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, SecurityException{
Utils.LOG_WARNING("Stepping through the process of Greg's materials.");

    Constructor<?> con = Materials.class.getDeclaredConstructors()[0]; 
java.lang.reflect.Method[] methods = con.getClass().getDeclaredMethods();
for (java.lang.reflect.Method m1 : methods) { 
    if (m1.getName().equals("acquireConstructorAccessor")) { 
        m1.setAccessible(true);
        m1.invoke(con, new Object[0]);}
} 
Field[] fields = con.getClass().getDeclaredFields(); 
Object ca = null;
for (Field f : fields) { 
    if (f.getName().equals("constructorAccessor")) {
    f.setAccessible(true); 
    ca = f.get(con); 
    } 
} 
Method m = ca.getClass().getMethod( "newInstance", new Class[] { Object[].class }); 
m.setAccessible(true);
Materials v = (Materials) m.invoke(ca, new Object[] { new Object[] { "NEWMATERIAL", Integer.MAX_VALUE } }); 
System.out.println(v.getClass() + ":" + v.name() + ":" + v.ordinal());}}

Any help or suggestions appreciated, they guys over at the Forge IRC weren't really sure either.

回答1:

JVMs are supposed to prevent such sneaky enum instance creations. So you have to either use a flaw that soon might get solved or hack such deep into the JRE that the slightest change may break it.

Here is a trick which works with Oracle’s current JRE 8, perhaps JRE 7 as well, and is surprisingly simple:

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.util.EnumSet;

public class EnumHack {
    public static void main(String[] args) throws Throwable {
        Constructor<Thread.State> c
            = Thread.State.class.getDeclaredConstructor(String.class, int.class);
        c.setAccessible(true);
        MethodHandle h=MethodHandles.lookup().unreflectConstructor(c);
        Thread.State state=(Thread.State)h.invokeExact("FLYING", 42);
        System.out.println("created Thread.State "+state+"("+state.ordinal()+')');
        System.out.println(EnumSet.allOf(Thread.State.class).contains(state));
    }
}

But don’t expect this solution to persist…