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