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    }