package cpw.mods.fml.common.eventhandler; import static org.objectweb.asm.Opcodes.*; import java.lang.reflect.Method; import java.util.HashMap; import org.apache.logging.log4j.ThreadContext; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Type; import com.google.common.collect.Maps; import cpw.mods.fml.common.ModContainer; public class ASMEventHandler implements IEventListener { private static int IDs = 0; private static final String HANDLER_DESC = Type.getInternalName(IEventListener.class); private static final String HANDLER_FUNC_DESC = Type.getMethodDescriptor(IEventListener.class.getDeclaredMethods()[0]); private static final ASMClassLoader LOADER = new ASMClassLoader(); private static final HashMap<Method, Class<?>> cache = Maps.newHashMap(); private static final boolean GETCONTEXT = Boolean.parseBoolean(System.getProperty("fml.LogContext", "false")); private final IEventListener handler; private final SubscribeEvent subInfo; private ModContainer owner; private String readable; public ASMEventHandler(Object target, Method method, ModContainer owner) throws Exception { this.owner = owner; handler = (IEventListener)createWrapper(method).getConstructor(Object.class).newInstance(target); subInfo = method.getAnnotation(SubscribeEvent.class); readable = "ASM: " + target + " " + method.getName() + Type.getMethodDescriptor(method); } @Override public void invoke(Event event) { if (owner != null && GETCONTEXT) { ThreadContext.put("mod", owner.getName()); } else if (GETCONTEXT) { ThreadContext.put("mod", ""); } if (handler != null) { if (!event.isCancelable() || !event.isCanceled() || subInfo.receiveCanceled()) { handler.invoke(event); } } if (GETCONTEXT) ThreadContext.remove("mod"); } public EventPriority getPriority() { return subInfo.priority(); } public Class<?> createWrapper(Method callback) { if (cache.containsKey(callback)) { return cache.get(callback); } ClassWriter cw = new ClassWriter(0); MethodVisitor mv; String name = getUniqueName(callback); String desc = name.replace('.', '/'); String instType = Type.getInternalName(callback.getDeclaringClass()); String eventType = Type.getInternalName(callback.getParameterTypes()[0]); /* System.out.println("Name: " + name); System.out.println("Desc: " + desc); System.out.println("InstType: " + instType); System.out.println("Callback: " + callback.getName() + Type.getMethodDescriptor(callback)); System.out.println("Event: " + eventType); */ cw.visit(V1_6, ACC_PUBLIC | ACC_SUPER, desc, null, "java/lang/Object", new String[]{ HANDLER_DESC }); cw.visitSource(".dynamic", null); { cw.visitField(ACC_PUBLIC, "instance", "Ljava/lang/Object;", null, null).visitEnd(); } { mv = cw.visitMethod(ACC_PUBLIC, "<init>", "(Ljava/lang/Object;)V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); mv.visitFieldInsn(PUTFIELD, desc, "instance", "Ljava/lang/Object;"); mv.visitInsn(RETURN); mv.visitMaxs(2, 2); mv.visitEnd(); } { mv = cw.visitMethod(ACC_PUBLIC, "invoke", HANDLER_FUNC_DESC, null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, desc, "instance", "Ljava/lang/Object;"); mv.visitTypeInsn(CHECKCAST, instType); mv.visitVarInsn(ALOAD, 1); mv.visitTypeInsn(CHECKCAST, eventType); mv.visitMethodInsn(INVOKEVIRTUAL, instType, callback.getName(), Type.getMethodDescriptor(callback), false); mv.visitInsn(RETURN); mv.visitMaxs(2, 2); mv.visitEnd(); } cw.visitEnd(); Class<?> ret = LOADER.define(name, cw.toByteArray()); cache.put(callback, ret); return ret; } private String getUniqueName(Method callback) { return String.format("%s_%d_%s_%s_%s", getClass().getName(), IDs++, callback.getDeclaringClass().getSimpleName(), callback.getName(), callback.getParameterTypes()[0].getSimpleName()); } private static class ASMClassLoader extends ClassLoader { private ASMClassLoader() { super(ASMClassLoader.class.getClassLoader()); } public Class<?> define(String name, byte[] data) { return defineClass(name, data, 0, data.length); } } public String toString() { return readable; } }