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 }