package cpw.mods.fml.common.asm.transformers;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import net.minecraft.launchwrapper.IClassTransformer;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Sets;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.ModAPIManager;
import cpw.mods.fml.common.discovery.ASMDataTable;
import cpw.mods.fml.common.discovery.ASMDataTable.ASMData;
import cpw.mods.fml.relauncher.FMLRelaunchLog;
public class ModAPITransformer implements IClassTransformer {
private static final boolean logDebugInfo = Boolean.valueOf(System.getProperty("fml.debugAPITransformer", "false"));
private ListMultimap<String, ASMData> optionals;
@Override
public byte[] transform(String name, String transformedName, byte[] basicClass)
{
String lookupName = name;
if(name.endsWith("$class"))
{
lookupName = name.substring(0, name.length() - 6);
}
if (optionals == null || !optionals.containsKey(lookupName))
{
return basicClass;
}
ClassNode classNode = new ClassNode();
ClassReader classReader = new ClassReader(basicClass);
classReader.accept(classNode, 0);
if (logDebugInfo) FMLRelaunchLog.finer("Optional removal - found optionals for class %s - processing", name);
for (ASMData optional : optionals.get(lookupName))
{
String modId = (String) optional.getAnnotationInfo().get("modid");
if (Loader.isModLoaded(modId) || ModAPIManager.INSTANCE.hasAPI(modId))
{
if (logDebugInfo) FMLRelaunchLog.finer("Optional removal skipped - mod present %s", modId);
continue;
}
if (logDebugInfo) FMLRelaunchLog.finer("Optional on %s triggered - mod missing %s", name, modId);
if (optional.getAnnotationInfo().containsKey("iface"))
{
Boolean stripRefs = (Boolean)optional.getAnnotationInfo().get("striprefs");
if (stripRefs == null) stripRefs = Boolean.FALSE;
stripInterface(classNode,(String)optional.getAnnotationInfo().get("iface"), stripRefs);
}
else
{
stripMethod(classNode, (String)optional.getObjectName());
}
}
if (logDebugInfo) FMLRelaunchLog.finer("Optional removal - class %s processed", name);
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
classNode.accept(writer);
return writer.toByteArray();
}
private void stripMethod(ClassNode classNode, String methodDescriptor)
{
if(classNode.name.endsWith("$class"))
{
String subName = classNode.name.substring(0, classNode.name.length() - 6);
int pos = methodDescriptor.indexOf('(') + 1;
methodDescriptor = methodDescriptor.substring(0, pos) + 'L' + subName + ';' + methodDescriptor.substring(pos);
}
for (ListIterator<MethodNode> iterator = classNode.methods.listIterator(); iterator.hasNext();)
{
MethodNode method = iterator.next();
if (methodDescriptor.equals(method.name+method.desc))
{
iterator.remove();
if (logDebugInfo) FMLRelaunchLog.finer("Optional removal - method %s removed", methodDescriptor);
return;
}
}
if (logDebugInfo) FMLRelaunchLog.finer("Optional removal - method %s NOT removed - not found", methodDescriptor);
}
private void stripInterface(ClassNode classNode, String interfaceName, boolean stripRefs)
{
String ifaceName = interfaceName.replace('.', '/');
boolean found = classNode.interfaces.remove(ifaceName);
if (found && logDebugInfo) FMLRelaunchLog.finer("Optional removal - interface %s removed", interfaceName);
if (!found && logDebugInfo) FMLRelaunchLog.finer("Optional removal - interface %s NOT removed - not found", interfaceName);
if (found && stripRefs)
{
if (logDebugInfo) FMLRelaunchLog.finer("Optional removal - interface %s - stripping method signature references", interfaceName);
for (Iterator<MethodNode> iterator = classNode.methods.iterator(); iterator.hasNext();)
{
MethodNode node = iterator.next();
if (node.desc.contains(ifaceName))
{
if (logDebugInfo) FMLRelaunchLog.finer("Optional removal - interface %s - stripping method containing reference %s", interfaceName, node.name);
iterator.remove();
}
}
if (logDebugInfo) FMLRelaunchLog.finer("Optional removal - interface %s - all method signature references stripped", interfaceName);
}
else if (found)
{
if (logDebugInfo) FMLRelaunchLog.finer("Optional removal - interface %s - NOT stripping method signature references", interfaceName);
}
}
public void initTable(ASMDataTable dataTable)
{
optionals = ArrayListMultimap.create();
Set<ASMData> interfaceLists = dataTable.getAll("cpw.mods.fml.common.Optional$InterfaceList");
addData(unpackInterfaces(interfaceLists));
Set<ASMData> interfaces = dataTable.getAll("cpw.mods.fml.common.Optional$Interface");
addData(interfaces);
Set<ASMData> methods = dataTable.getAll("cpw.mods.fml.common.Optional$Method");
addData(methods);
}
private Set<ASMData> unpackInterfaces(Set<ASMData> packedInterfaces)
{
Set<ASMData> result = Sets.newHashSet();
for (ASMData data : packedInterfaces)
{
@SuppressWarnings("unchecked")
List<Map<String,Object>> packedList = (List<Map<String,Object>>) data.getAnnotationInfo().get("value");
for (Map<String,Object> packed : packedList)
{
ASMData newData = data.copy(packed);
result.add(newData);
}
}
return result;
}
private void addData(Set<ASMData> interfaces)
{
for (ASMData data : interfaces)
{
optionals.put(data.getClassName(),data);
}
}
}