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}