    001package net.minecraftforge.transformers;
    002
    003import java.util.List;
    004
    005import net.minecraftforge.event.Event;
    006import net.minecraftforge.event.ListenerList;
    007
    008import org.objectweb.asm.*;
    009import org.objectweb.asm.tree.*;
    010import static org.objectweb.asm.Opcodes.*;
    011import static org.objectweb.asm.Type.*;
    012import static org.objectweb.asm.ClassWriter.*;
    013
    014import cpw.mods.fml.relauncher.IClassTransformer;
    015
    016public class EventTransformer implements IClassTransformer
    017{
    018    public EventTransformer()
    019    {
    020    }
    021
    022    @Override
    023    public byte[] transform(String name, String transformedName, byte[] bytes)
    024    {
    025        if (bytes == null || name.equals("net.minecraftforge.event.Event") || name.startsWith("net.minecraft.") || name.indexOf('.') == -1)
    026        {
    027            return bytes;
    028        }
    029        ClassReader cr = new ClassReader(bytes);
    030        ClassNode classNode = new ClassNode();
    031        cr.accept(classNode, 0);
    032
    033        try
    034        {
    035            if (buildEvents(classNode))
    036            {
    037                ClassWriter cw = new ClassWriter(COMPUTE_MAXS | COMPUTE_FRAMES);
    038                classNode.accept(cw);
    039                return cw.toByteArray();
    040            }
    041            return bytes;
    042        }
    043        catch (ClassNotFoundException ex)
    044        {
    045            // Discard silently- it's just noise
    046        }
    047        catch (Exception e)
    048        {
    049            e.printStackTrace();
    050        }
    051
    052        return bytes;
    053    }
    054
    055    @SuppressWarnings("unchecked")
    056    private boolean buildEvents(ClassNode classNode) throws Exception
    057    {
    058        Class<?> parent = this.getClass().getClassLoader().loadClass(classNode.superName.replace('/', '.'));
    059        if (!Event.class.isAssignableFrom(parent))
    060        {
    061            return false;
    062        }
    063
    064        boolean hasSetup = false;
    065        boolean hasGetListenerList = false;
    066        boolean hasDefaultCtr = false;
    067
    068        Class<?> listenerListClazz = Class.forName("net.minecraftforge.event.ListenerList", false, getClass().getClassLoader());
    069        Type tList = Type.getType(listenerListClazz);
    070
    071        for (MethodNode method : (List<MethodNode>)classNode.methods)
    072        {
    073                if (method.name.equals("setup") &&
    074                    method.desc.equals(Type.getMethodDescriptor(VOID_TYPE)) &&
    075                    (method.access & ACC_PROTECTED) == ACC_PROTECTED)
    076                {
    077                    hasSetup = true;
    078                }
    079                if (method.name.equals("getListenerList") &&
    080                    method.desc.equals(Type.getMethodDescriptor(tList)) &&
    081                    (method.access & ACC_PUBLIC) == ACC_PUBLIC)
    082                {
    083                    hasGetListenerList = true;
    084                }
    085                if (method.name.equals("<init>") &&
    086                    method.desc.equals(Type.getMethodDescriptor(VOID_TYPE)))
    087                {
    088                    hasDefaultCtr = true;
    089                }
    090        }
    091
    092        if (hasSetup)
    093        {
    094                if (!hasGetListenerList)
    095                {
    096                        throw new RuntimeException("Event class defines setup() but does not define getListenerList! " + classNode.name);
    097                }
    098                else
    099                {
    100                        return false;
    101                }
    102        }
    103
    104        Type tSuper = Type.getType(classNode.superName);
    105
    106        //Add private static ListenerList LISTENER_LIST
    107        classNode.fields.add(new FieldNode(ACC_PRIVATE | ACC_STATIC, "LISTENER_LIST", tList.getDescriptor(), null, null));
    108
    109        /*Add:
    110         *      public <init>()
    111         *      {
    112         *              super();
    113         *      }
    114         */
    115        MethodNode method = new MethodNode(ASM4, ACC_PUBLIC, "<init>", getMethodDescriptor(VOID_TYPE), null, null);
    116        method.instructions.add(new VarInsnNode(ALOAD, 0));
    117        method.instructions.add(new MethodInsnNode(INVOKESPECIAL, tSuper.getInternalName(), "<init>", getMethodDescriptor(VOID_TYPE)));
    118        method.instructions.add(new InsnNode(RETURN));
    119        if (!hasDefaultCtr)
    120        {
    121            classNode.methods.add(method);
    122        }
    123
    124        /*Add:
    125         *      protected void setup()
    126         *      {
    127         *              super.setup();
    128         *              if (LISTENER_LIST != NULL)
    129         *              {
    130         *                      return;
    131         *              }
    132         *              LISTENER_LIST = new ListenerList(super.getListenerList());
    133         *      }
    134         */
    135        method = new MethodNode(ASM4, ACC_PROTECTED, "setup", getMethodDescriptor(VOID_TYPE), null, null);
    136        method.instructions.add(new VarInsnNode(ALOAD, 0));
    137        method.instructions.add(new MethodInsnNode(INVOKESPECIAL, tSuper.getInternalName(), "setup", getMethodDescriptor(VOID_TYPE)));
    138        method.instructions.add(new FieldInsnNode(GETSTATIC, classNode.name, "LISTENER_LIST", tList.getDescriptor()));
    139        LabelNode initLisitener = new LabelNode();
    140        method.instructions.add(new JumpInsnNode(IFNULL, initLisitener));
    141        method.instructions.add(new InsnNode(RETURN));
    142        method.instructions.add(initLisitener);
    143        method.instructions.add(new FrameNode(F_SAME, 0, null, 0, null));
    144        method.instructions.add(new TypeInsnNode(NEW, tList.getInternalName()));
    145        method.instructions.add(new InsnNode(DUP));
    146        method.instructions.add(new VarInsnNode(ALOAD, 0));
    147        method.instructions.add(new MethodInsnNode(INVOKESPECIAL, tSuper.getInternalName(), "getListenerList", getMethodDescriptor(tList)));
    148        method.instructions.add(new MethodInsnNode(INVOKESPECIAL, tList.getInternalName(), "<init>", getMethodDescriptor(VOID_TYPE, tList)));
    149        method.instructions.add(new FieldInsnNode(PUTSTATIC, classNode.name, "LISTENER_LIST", tList.getDescriptor()));
    150        method.instructions.add(new InsnNode(RETURN));
    151        classNode.methods.add(method);
    152
    153        /*Add:
    154         *      public ListenerList getListenerList()
    155         *      {
    156         *              return this.LISTENER_LIST;
    157         *      }
    158         */
    159        method = new MethodNode(ASM4, ACC_PUBLIC, "getListenerList", getMethodDescriptor(tList), null, null);
    160        method.instructions.add(new FieldInsnNode(GETSTATIC, classNode.name, "LISTENER_LIST", tList.getDescriptor()));
    161        method.instructions.add(new InsnNode(ARETURN));
    162        classNode.methods.add(method);
    163        return true;
    164    }
    165}