package cpw.mods.fml.relauncher;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.security.CodeSigner;
import java.security.CodeSource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.jar.Attributes.Name;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.logging.Level;
import cpw.mods.fml.common.FMLLog;
public class RelaunchClassLoader extends URLClassLoader
{
private List<URL> sources;
private ClassLoader parent;
private List<IClassTransformer> transformers;
private Map<String, Class> cachedClasses;
private Set<String> invalidClasses;
private Set<String> classLoaderExceptions = new HashSet<String>();
private Set<String> transformerExceptions = new HashSet<String>();
private Map<Package,Manifest> packageManifests = new HashMap<Package,Manifest>();
private static Manifest EMPTY = new Manifest();
private static final String[] RESERVED = {"CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"};
private static final boolean DEBUG_CLASSLOADING = Boolean.parseBoolean(System.getProperty("fml.debugClassLoading", "false"));
public RelaunchClassLoader(URL[] sources)
{
super(sources, null);
this.sources = new ArrayList<URL>(Arrays.asList(sources));
this.parent = getClass().getClassLoader();
this.cachedClasses = new HashMap<String,Class>(1000);
this.invalidClasses = new HashSet<String>(1000);
this.transformers = new ArrayList<IClassTransformer>(2);
// ReflectionHelper.setPrivateValue(ClassLoader.class, null, this, "scl");
Thread.currentThread().setContextClassLoader(this);
// standard classloader exclusions
addClassLoaderExclusion("java.");
addClassLoaderExclusion("sun.");
addClassLoaderExclusion("org.lwjgl.");
addClassLoaderExclusion("cpw.mods.fml.relauncher.");
addClassLoaderExclusion("net.minecraftforge.classloading.");
// standard transformer exclusions
addTransformerExclusion("javax.");
addTransformerExclusion("org.objectweb.asm.");
addTransformerExclusion("com.google.common.");
}
public void registerTransformer(String transformerClassName)
{
try
{
transformers.add((IClassTransformer) loadClass(transformerClassName).newInstance());
}
catch (Exception e)
{
FMLRelaunchLog.log(Level.SEVERE, e, "A critical problem occured registering the ASM transformer class %s", transformerClassName);
}
}
@Override
public Class<?> findClass(String name) throws ClassNotFoundException
{
if (invalidClasses.contains(name))
{
throw new ClassNotFoundException(name);
}
for (String st : classLoaderExceptions)
{
if (name.startsWith(st))
{
return parent.loadClass(name);
}
}
if (cachedClasses.containsKey(name))
{
return cachedClasses.get(name);
}
for (String st : transformerExceptions)
{
if (name.startsWith(st))
{
try
{
Class<?> cl = super.findClass(name);
cachedClasses.put(name, cl);
return cl;
}
catch (ClassNotFoundException e)
{
invalidClasses.add(name);
throw e;
}
}
}
try
{
CodeSigner[] signers = null;
int lastDot = name.lastIndexOf('.');
String pkgname = lastDot == -1 ? "" : name.substring(0, lastDot);
String fName = name.replace('.', '/').concat(".class");
String pkgPath = pkgname.replace('.', '/');
URLConnection urlConnection = findCodeSourceConnectionFor(fName);
if (urlConnection instanceof JarURLConnection && lastDot > -1)
{
JarURLConnection jarUrlConn = (JarURLConnection)urlConnection;
JarFile jf = jarUrlConn.getJarFile();
if (jf != null && jf.getManifest() != null)
{
Manifest mf = jf.getManifest();
JarEntry ent = jf.getJarEntry(fName);
Package pkg = getPackage(pkgname);
getClassBytes(name);
signers = ent.getCodeSigners();
if (pkg == null)
{
pkg = definePackage(pkgname, mf, jarUrlConn.getJarFileURL());
packageManifests.put(pkg, mf);
}
else
{
if (pkg.isSealed() && !pkg.isSealed(jarUrlConn.getJarFileURL()))
{
FMLLog.severe("The jar file %s is trying to seal already secured path %s", jf.getName(), pkgname);
}
else if (isSealed(pkgname, mf))
{
FMLLog.severe("The jar file %s has a security seal for path %s, but that path is defined and not secure", jf.getName(), pkgname);
}
}
}
}
else if (lastDot > -1)
{
Package pkg = getPackage(pkgname);
if (pkg == null)
{
pkg = definePackage(pkgname, null, null, null, null, null, null, null);
packageManifests.put(pkg, EMPTY);
}
else if (pkg.isSealed())
{
FMLLog.severe("The URL %s is defining elements for sealed path %s", urlConnection.getURL(), pkgname);
}
}
byte[] basicClass = getClassBytes(name);
byte[] transformedClass = runTransformers(name, basicClass);
Class<?> cl = defineClass(name, transformedClass, 0, transformedClass.length, new CodeSource(urlConnection.getURL(), signers));
cachedClasses.put(name, cl);
return cl;
}
catch (Throwable e)
{
invalidClasses.add(name);
if (DEBUG_CLASSLOADING)
{
FMLLog.log(Level.FINEST, e, "Exception encountered attempting classloading of %s", name);
}
throw new ClassNotFoundException(name, e);
}
}
private boolean isSealed(String path, Manifest man)
{
Attributes attr = man.getAttributes(path);
String sealed = null;
if (attr != null) {
sealed = attr.getValue(Name.SEALED);
}
if (sealed == null) {
if ((attr = man.getMainAttributes()) != null) {
sealed = attr.getValue(Name.SEALED);
}
}
return "true".equalsIgnoreCase(sealed);
}
private URLConnection findCodeSourceConnectionFor(String name)
{
URL res = findResource(name);
if (res != null)
{
try
{
return res.openConnection();
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
else
{
return null;
}
}
private byte[] runTransformers(String name, byte[] basicClass)
{
for (IClassTransformer transformer : transformers)
{
basicClass = transformer.transform(name, basicClass);
}
return basicClass;
}
@Override
public void addURL(URL url)
{
super.addURL(url);
sources.add(url);
}
public List<URL> getSources()
{
return sources;
}
private byte[] readFully(InputStream stream)
{
try
{
ByteArrayOutputStream bos = new ByteArrayOutputStream(stream.available());
int r;
while ((r = stream.read()) != -1)
{
bos.write(r);
}
return bos.toByteArray();
}
catch (Throwable t)
{
FMLLog.log(Level.WARNING, t, "Problem loading class");
return new byte[0];
}
}
public List<IClassTransformer> getTransformers()
{
return Collections.unmodifiableList(transformers);
}
private void addClassLoaderExclusion(String toExclude)
{
classLoaderExceptions.add(toExclude);
}
void addTransformerExclusion(String toExclude)
{
transformerExceptions.add(toExclude);
}
public byte[] getClassBytes(String name) throws IOException
{
if (name.indexOf('.') == -1)
{
for (String res : RESERVED)
{
if (name.toUpperCase(Locale.ENGLISH).startsWith(res))
{
byte[] data = getClassBytes("_" + name);
if (data != null)
{
return data;
}
}
}
}
InputStream classStream = null;
try
{
URL classResource = findResource(name.replace('.', '/').concat(".class"));
if (classResource == null)
{
if (DEBUG_CLASSLOADING)
{
FMLLog.finest("Failed to find class resource %s", name.replace('.', '/').concat(".class"));
}
return null;
}
classStream = classResource.openStream();
if (DEBUG_CLASSLOADING)
{
FMLLog.finest("Loading class %s from resource %s", name, classResource.toString());
}
return readFully(classStream);
}
finally
{
if (classStream != null)
{
try
{
classStream.close();
}
catch (IOException e)
{
// Swallow the close exception
}
}
}
}
}