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 (Exception e)
044            {
045                e.printStackTrace();
046            }
047    
048            return bytes;
049        }
050    
051        @SuppressWarnings("unchecked")
052        private boolean buildEvents(ClassNode classNode) throws Exception
053        {
054            Class<?> parent = this.getClass().getClassLoader().loadClass(classNode.superName.replace('/', '.'));
055            if (!Event.class.isAssignableFrom(parent))
056            {
057                return false;
058            }
059    
060            boolean hasSetup = false;
061            boolean hasGetListenerList = false;
062            boolean hasDefaultCtr = false;
063    
064            Class<?> listenerListClazz = Class.forName("net.minecraftforge.event.ListenerList", false, getClass().getClassLoader());
065            Type tList = Type.getType(listenerListClazz);
066    
067            for (MethodNode method : (List<MethodNode>)classNode.methods)
068            {
069                    if (method.name.equals("setup") &&
070                        method.desc.equals(Type.getMethodDescriptor(VOID_TYPE)) &&
071                        (method.access & ACC_PROTECTED) == ACC_PROTECTED)
072                    {
073                        hasSetup = true;
074                    }
075                    if (method.name.equals("getListenerList") &&
076                        method.desc.equals(Type.getMethodDescriptor(tList)) &&
077                        (method.access & ACC_PUBLIC) == ACC_PUBLIC)
078                    {
079                        hasGetListenerList = true;
080                    }
081                    if (method.name.equals("<init>") &&
082                        method.desc.equals(Type.getMethodDescriptor(VOID_TYPE)))
083                    {
084                        hasDefaultCtr = true;
085                    }
086            }
087    
088            if (hasSetup)
089            {
090                    if (!hasGetListenerList)
091                    {
092                            throw new RuntimeException("Event class defines setup() but does not define getListenerList! " + classNode.name);
093                    }
094                    else
095                    {
096                            return false;
097                    }
098            }
099    
100            Type tSuper = Type.getType(classNode.superName);
101    
102            //Add private static ListenerList LISTENER_LIST
103            classNode.fields.add(new FieldNode(ACC_PRIVATE | ACC_STATIC, "LISTENER_LIST", tList.getDescriptor(), null, null));
104    
105            /*Add:
106             *      public <init>()
107             *      {
108             *              super();
109             *      }
110             */
111            MethodNode method = new MethodNode(ASM4, ACC_PUBLIC, "<init>", getMethodDescriptor(VOID_TYPE), null, null);
112            method.instructions.add(new VarInsnNode(ALOAD, 0));
113            method.instructions.add(new MethodInsnNode(INVOKESPECIAL, tSuper.getInternalName(), "<init>", getMethodDescriptor(VOID_TYPE)));
114            method.instructions.add(new InsnNode(RETURN));
115            if (!hasDefaultCtr)
116            {
117                classNode.methods.add(method);
118            }
119    
120            /*Add:
121             *      protected void setup()
122             *      {
123             *              super.setup();
124             *              if (LISTENER_LIST != NULL)
125             *              {
126             *                      return;
127             *              }
128             *              LISTENER_LIST = new ListenerList(super.getListenerList());
129             *      }
130             */
131            method = new MethodNode(ASM4, ACC_PROTECTED, "setup", getMethodDescriptor(VOID_TYPE), null, null);
132            method.instructions.add(new VarInsnNode(ALOAD, 0));
133            method.instructions.add(new MethodInsnNode(INVOKESPECIAL, tSuper.getInternalName(), "setup", getMethodDescriptor(VOID_TYPE)));
134            method.instructions.add(new FieldInsnNode(GETSTATIC, classNode.name, "LISTENER_LIST", tList.getDescriptor()));
135            LabelNode initLisitener = new LabelNode();
136            method.instructions.add(new JumpInsnNode(IFNULL, initLisitener));
137            method.instructions.add(new InsnNode(RETURN));
138            method.instructions.add(initLisitener);
139            method.instructions.add(new FrameNode(F_SAME, 0, null, 0, null));
140            method.instructions.add(new TypeInsnNode(NEW, tList.getInternalName()));
141            method.instructions.add(new InsnNode(DUP));
142            method.instructions.add(new VarInsnNode(ALOAD, 0));
143            method.instructions.add(new MethodInsnNode(INVOKESPECIAL, tSuper.getInternalName(), "getListenerList", getMethodDescriptor(tList)));
144            method.instructions.add(new MethodInsnNode(INVOKESPECIAL, tList.getInternalName(), "<init>", getMethodDescriptor(VOID_TYPE, tList)));
145            method.instructions.add(new FieldInsnNode(PUTSTATIC, classNode.name, "LISTENER_LIST", tList.getDescriptor()));
146            method.instructions.add(new InsnNode(RETURN));
147            classNode.methods.add(method);
148    
149            /*Add:
150             *      public ListenerList getListenerList()
151             *      {
152             *              return this.LISTENER_LIST;
153             *      }
154             */
155            method = new MethodNode(ASM4, ACC_PUBLIC, "getListenerList", getMethodDescriptor(tList), null, null);
156            method.instructions.add(new FieldInsnNode(GETSTATIC, classNode.name, "LISTENER_LIST", tList.getDescriptor()));
157            method.instructions.add(new InsnNode(ARETURN));
158            classNode.methods.add(method);
159            return true;
160        }
161    
162    }