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 }