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