package codechicken.core.asm;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Map;
import java.util.Stack;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import net.minecraft.launchwrapper.Launch;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes;
import net.minecraft.launchwrapper.IClassTransformer;
import net.minecraft.launchwrapper.LaunchClassLoader;
import static codechicken.core.launch.CodeChickenCorePlugin.logger;
public class DelegatedTransformer implements IClassTransformer
{
private static ArrayList<IClassTransformer> delegatedTransformers;
private static Method m_defineClass;
private static Field f_cachedClasses;
public DelegatedTransformer()
{
delegatedTransformers = new ArrayList<IClassTransformer>();
try
{
m_defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, Integer.TYPE, Integer.TYPE);
m_defineClass.setAccessible(true);
f_cachedClasses = LaunchClassLoader.class.getDeclaredField("cachedClasses");
f_cachedClasses.setAccessible(true);
}
catch(Exception e)
{
throw new RuntimeException(e);
}
}
@Override
public byte[] transform(String name, String tname, byte[] bytes)
{
if (bytes == null) return null;
for(IClassTransformer trans : delegatedTransformers)
bytes = trans.transform(name, tname, bytes);
return bytes;
}
public static void addTransformer(String transformer, JarFile jar, File jarFile)
{
logger.debug("Adding CCTransformer: " + transformer);
try
{
byte[] bytes;
bytes = Launch.classLoader.getClassBytes(transformer);
if(bytes == null)
{
String resourceName = transformer.replace('.', '/')+".class";
ZipEntry entry = jar.getEntry(resourceName);
if(entry == null)
throw new Exception("Failed to add transformer: "+transformer+". Entry not found in jar file "+jarFile.getName());
bytes = readFully(jar.getInputStream(entry));
}
defineDependancies(bytes, jar, jarFile);
Class<?> clazz = defineClass(transformer, bytes);
if(!IClassTransformer.class.isAssignableFrom(clazz))
throw new Exception("Failed to add transformer: "+transformer+" is not an instance of IClassTransformer");
IClassTransformer classTransformer;
try
{
classTransformer = (IClassTransformer) clazz.getDeclaredConstructor(File.class).newInstance(jarFile);
}
catch(NoSuchMethodException nsme)
{
classTransformer = (IClassTransformer) clazz.newInstance();
}
delegatedTransformers.add(classTransformer);
}
catch(Exception e)
{
e.printStackTrace();
}
}
private static void defineDependancies(byte[] bytes, JarFile jar, File jarFile) throws Exception
{
defineDependancies(bytes, jar, jarFile, new Stack<String>());
}
private static void defineDependancies(byte[] bytes, JarFile jar, File jarFile, Stack<String> depStack) throws Exception
{
ClassReader reader = new ClassReader(bytes);
DependancyLister lister = new DependancyLister(Opcodes.ASM4);
reader.accept(lister, 0);
depStack.push(reader.getClassName());
for(String dependancy : lister.getDependancies())
{
if(depStack.contains(dependancy))
continue;
try
{
Launch.classLoader.loadClass(dependancy.replace('/', '.'));
}
catch(ClassNotFoundException cnfe)
{
ZipEntry entry = jar.getEntry(dependancy+".class");
if(entry == null)
throw new Exception("Dependency "+dependancy+" not found in jar file "+jarFile.getName());
byte[] depbytes = readFully(jar.getInputStream(entry));
defineDependancies(depbytes, jar, jarFile, depStack);
logger.debug("Defining dependancy: "+dependancy);
defineClass(dependancy.replace('/', '.'), depbytes);
}
}
depStack.pop();
}
private static Class<?> defineClass(String classname, byte[] bytes) throws Exception
{
Class<?> clazz = (Class<?>) m_defineClass.invoke(Launch.classLoader, classname, bytes, 0, bytes.length);
((Map<String, Class<?>>)f_cachedClasses.get(Launch.classLoader)).put(classname, clazz);
return clazz;
}
public static byte[] readFully(InputStream stream) throws IOException
{
ByteArrayOutputStream bos = new ByteArrayOutputStream(stream.available());
int r;
while ((r = stream.read()) != -1)
{
bos.write(r);
}
return bos.toByteArray();
}
}