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