    001package cpw.mods.fml.relauncher;
    002
    003import java.io.ByteArrayOutputStream;
    004import java.io.IOException;
    005import java.io.InputStream;
    006import java.net.JarURLConnection;
    007import java.net.URL;
    008import java.net.URLClassLoader;
    009import java.net.URLConnection;
    010import java.security.CodeSigner;
    011import java.security.CodeSource;
    012import java.util.ArrayList;
    013import java.util.Arrays;
    014import java.util.Collections;
    015import java.util.HashMap;
    016import java.util.HashSet;
    017import java.util.List;
    018import java.util.Locale;
    019import java.util.Map;
    020import java.util.Set;
    021import java.util.jar.Attributes.Name;
    022import java.util.jar.Attributes;
    023import java.util.jar.JarEntry;
    024import java.util.jar.JarFile;
    025import java.util.jar.Manifest;
    026import java.util.logging.Level;
    027
    028import cpw.mods.fml.common.FMLLog;
    029
    030public class RelaunchClassLoader extends URLClassLoader
    031{
    032    private List<URL> sources;
    033    private ClassLoader parent;
    034
    035    private List<IClassTransformer> transformers;
    036    private Map<String, Class> cachedClasses;
    037    private Set<String> invalidClasses;
    038
    039    private Set<String> classLoaderExceptions = new HashSet<String>();
    040    private Set<String> transformerExceptions = new HashSet<String>();
    041    private Map<Package,Manifest> packageManifests = new HashMap<Package,Manifest>();
    042
    043    private static Manifest EMPTY = new Manifest();
    044
    045    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"};
    046
    047    private static final boolean DEBUG_CLASSLOADING = Boolean.parseBoolean(System.getProperty("fml.debugClassLoading", "false"));
    048
    049    public RelaunchClassLoader(URL[] sources)
    050    {
    051        super(sources, null);
    052        this.sources = new ArrayList<URL>(Arrays.asList(sources));
    053        this.parent = getClass().getClassLoader();
    054        this.cachedClasses = new HashMap<String,Class>(1000);
    055        this.invalidClasses = new HashSet<String>(1000);
    056        this.transformers = new ArrayList<IClassTransformer>(2);
    057//        ReflectionHelper.setPrivateValue(ClassLoader.class, null, this, "scl");
    058        Thread.currentThread().setContextClassLoader(this);
    059
    060        // standard classloader exclusions
    061        addClassLoaderExclusion("java.");
    062        addClassLoaderExclusion("sun.");
    063        addClassLoaderExclusion("org.lwjgl.");
    064        addClassLoaderExclusion("cpw.mods.fml.relauncher.");
    065        addClassLoaderExclusion("net.minecraftforge.classloading.");
    066
    067        // standard transformer exclusions
    068        addTransformerExclusion("javax.");
    069        addTransformerExclusion("org.objectweb.asm.");
    070        addTransformerExclusion("com.google.common.");
    071    }
    072
    073    public void registerTransformer(String transformerClassName)
    074    {
    075        try
    076        {
    077            transformers.add((IClassTransformer) loadClass(transformerClassName).newInstance());
    078        }
    079        catch (Exception e)
    080        {
    081            FMLRelaunchLog.log(Level.SEVERE, e, "A critical problem occured registering the ASM transformer class %s", transformerClassName);
    082        }
    083    }
    084    @Override
    085    public Class<?> findClass(String name) throws ClassNotFoundException
    086    {
    087        if (invalidClasses.contains(name))
    088        {
    089            throw new ClassNotFoundException(name);
    090        }
    091        for (String st : classLoaderExceptions)
    092        {
    093            if (name.startsWith(st))
    094            {
    095                return parent.loadClass(name);
    096            }
    097        }
    098
    099        if (cachedClasses.containsKey(name))
    100        {
    101            return cachedClasses.get(name);
    102        }
    103
    104        for (String st : transformerExceptions)
    105        {
    106            if (name.startsWith(st))
    107            {
    108                try
    109                {
    110                    Class<?> cl = super.findClass(name);
    111                    cachedClasses.put(name, cl);
    112                    return cl;
    113                }
    114                catch (ClassNotFoundException e)
    115                {
    116                    invalidClasses.add(name);
    117                    throw e;
    118                }
    119            }
    120        }
    121
    122        try
    123        {
    124            CodeSigner[] signers = null;
    125            int lastDot = name.lastIndexOf('.');
    126            String pkgname = lastDot == -1 ? "" : name.substring(0, lastDot);
    127            String fName = name.replace('.', '/').concat(".class");
    128            String pkgPath = pkgname.replace('.', '/');
    129            URLConnection urlConnection = findCodeSourceConnectionFor(fName);
    130            if (urlConnection instanceof JarURLConnection && lastDot > -1)
    131            {
    132                JarURLConnection jarUrlConn = (JarURLConnection)urlConnection;
    133                JarFile jf = jarUrlConn.getJarFile();
    134                if (jf != null && jf.getManifest() != null)
    135                {
    136                    Manifest mf = jf.getManifest();
    137                    JarEntry ent = jf.getJarEntry(fName);
    138                    Package pkg = getPackage(pkgname);
    139                    getClassBytes(name);
    140                    signers = ent.getCodeSigners();
    141                    if (pkg == null)
    142                    {
    143                        pkg = definePackage(pkgname, mf, jarUrlConn.getJarFileURL());
    144                        packageManifests.put(pkg, mf);
    145                    }
    146                    else
    147                    {
    148                        if (pkg.isSealed() && !pkg.isSealed(jarUrlConn.getJarFileURL()))
    149                        {
    150                            FMLLog.severe("The jar file %s is trying to seal already secured path %s", jf.getName(), pkgname);
    151                        }
    152                        else if (isSealed(pkgname, mf))
    153                        {
    154                            FMLLog.severe("The jar file %s has a security seal for path %s, but that path is defined and not secure", jf.getName(), pkgname);
    155                        }
    156                    }
    157                }
    158            }
    159            else if (lastDot > -1)
    160            {
    161                Package pkg = getPackage(pkgname);
    162                if (pkg == null)
    163                {
    164                    pkg = definePackage(pkgname, null, null, null, null, null, null, null);
    165                    packageManifests.put(pkg, EMPTY);
    166                }
    167                else if (pkg.isSealed())
    168                {
    169                    FMLLog.severe("The URL %s is defining elements for sealed path %s", urlConnection.getURL(), pkgname);
    170                }
    171            }
    172            byte[] basicClass = getClassBytes(name);
    173            byte[] transformedClass = runTransformers(name, basicClass);
    174            Class<?> cl = defineClass(name, transformedClass, 0, transformedClass.length, new CodeSource(urlConnection.getURL(), signers));
    175            cachedClasses.put(name, cl);
    176            return cl;
    177        }
    178        catch (Throwable e)
    179        {
    180            invalidClasses.add(name);
    181            if (DEBUG_CLASSLOADING)
    182            {
    183                FMLLog.log(Level.FINEST, e, "Exception encountered attempting classloading of %s", name);
    184            }
    185            throw new ClassNotFoundException(name, e);
    186        }
    187    }
    188
    189    private boolean isSealed(String path, Manifest man)
    190    {
    191        Attributes attr = man.getAttributes(path);
    192        String sealed = null;
    193        if (attr != null) {
    194            sealed = attr.getValue(Name.SEALED);
    195        }
    196        if (sealed == null) {
    197            if ((attr = man.getMainAttributes()) != null) {
    198                sealed = attr.getValue(Name.SEALED);
    199            }
    200        }
    201        return "true".equalsIgnoreCase(sealed);
    202    }
    203
    204    private URLConnection findCodeSourceConnectionFor(String name)
    205    {
    206        URL res = findResource(name);
    207        if (res != null)
    208        {
    209            try
    210            {
    211                return res.openConnection();
    212            }
    213            catch (IOException e)
    214            {
    215                throw new RuntimeException(e);
    216            }
    217        }
    218        else
    219        {
    220            return null;
    221        }
    222    }
    223
    224    private byte[] runTransformers(String name, byte[] basicClass)
    225    {
    226        for (IClassTransformer transformer : transformers)
    227        {
    228            basicClass = transformer.transform(name, basicClass);
    229        }
    230        return basicClass;
    231    }
    232
    233    @Override
    234    public void addURL(URL url)
    235    {
    236        super.addURL(url);
    237        sources.add(url);
    238    }
    239
    240    public List<URL> getSources()
    241    {
    242        return sources;
    243    }
    244
    245
    246    private byte[] readFully(InputStream stream)
    247    {
    248        try
    249        {
    250            ByteArrayOutputStream bos = new ByteArrayOutputStream(stream.available());
    251            int r;
    252            while ((r = stream.read()) != -1)
    253            {
    254                bos.write(r);
    255            }
    256
    257            return bos.toByteArray();
    258        }
    259        catch (Throwable t)
    260        {
    261            FMLLog.log(Level.WARNING, t, "Problem loading class");
    262            return new byte[0];
    263        }
    264    }
    265
    266    public List<IClassTransformer> getTransformers()
    267    {
    268        return Collections.unmodifiableList(transformers);
    269    }
    270
    271    private void addClassLoaderExclusion(String toExclude)
    272    {
    273        classLoaderExceptions.add(toExclude);
    274    }
    275
    276    void addTransformerExclusion(String toExclude)
    277    {
    278        transformerExceptions.add(toExclude);
    279    }
    280
    281    public byte[] getClassBytes(String name) throws IOException
    282    {
    283        if (name.indexOf('.') == -1)
    284        {
    285            for (String res : RESERVED)
    286            {
    287                if (name.toUpperCase(Locale.ENGLISH).startsWith(res))
    288                {
    289                    byte[] data = getClassBytes("_" + name);
    290                    if (data != null)
    291                    {
    292                        return data;
    293                    }
    294                }
    295            }
    296        }
    297
    298        InputStream classStream = null;
    299        try
    300        {
    301            URL classResource = findResource(name.replace('.', '/').concat(".class"));
    302            if (classResource == null)
    303            {
    304                if (DEBUG_CLASSLOADING)
    305                {
    306                    FMLLog.finest("Failed to find class resource %s", name.replace('.', '/').concat(".class"));
    307                }
    308                return null;
    309            }
    310            classStream = classResource.openStream();
    311            if (DEBUG_CLASSLOADING)
    312            {
    313                FMLLog.finest("Loading class %s from resource %s", name, classResource.toString());
    314            }
    315            return readFully(classStream);
    316        }
    317        finally
    318        {
    319            if (classStream != null)
    320            {
    321                try
    322                {
    323                    classStream.close();
    324                }
    325                catch (IOException e)
    326                {
    327                    // Swallow the close exception
    328                }
    329            }
    330        }
    331    }
    332}