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        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    }