001    package net.minecraftforge.transformers;
002    
003    import java.util.List;
004    
005    import net.minecraftforge.event.Event;
006    import net.minecraftforge.event.ListenerList;
007    
008    import org.objectweb.asm.*;
009    import org.objectweb.asm.tree.*;
010    import static org.objectweb.asm.Opcodes.*;
011    import static org.objectweb.asm.Type.*;
012    import static org.objectweb.asm.ClassWriter.*;
013    
014    import cpw.mods.fml.relauncher.IClassTransformer;
015    
016    public class EventTransformer implements IClassTransformer
017    {
018        public EventTransformer()
019        {
020        }
021    
022        @Override
023        public byte[] transform(String name, byte[] bytes)
024        {
025            if (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    
166    }