001package org.nuxeo.osgi.util.jar;
002
003import java.io.IOException;
004import java.lang.reflect.Field;
005import java.lang.reflect.InvocationTargetException;
006import java.lang.reflect.Method;
007import java.net.URL;
008import java.net.URLClassLoader;
009import java.util.List;
010import java.util.Map;
011import java.util.jar.JarFile;
012
013public class URLJarFileIntrospector {
014
015    protected Method factoryGetMethod;
016
017    protected Method factoryCloseMethod;
018
019    protected Field jarField;
020
021    protected Method getJarFileMethod;
022
023    Field ucpField;
024
025    Field lmapField;
026
027    Field loadersField;
028
029    Field jarFileFactoryField;
030
031    Object factory;
032
033    public URLJarFileIntrospector() throws URLJarFileIntrospectionError {
034        try {
035            ucpField = URLClassLoader.class.getDeclaredField("ucp");
036            ucpField.setAccessible(true);
037            Class<?> ucpClass = loadClass("sun.misc.URLClassPath");
038            lmapField = ucpClass.getDeclaredField("lmap");
039            lmapField.setAccessible(true);
040            loadersField = ucpClass.getDeclaredField("loaders");
041            loadersField.setAccessible(true);
042            Class<?> jarLoaderClass = loadClass("sun.misc.URLClassPath$JarLoader");
043            jarField = jarLoaderClass.getDeclaredField("jar");
044            jarField.setAccessible(true);
045            getJarFileMethod = jarLoaderClass.getDeclaredMethod("getJarFile", new Class<?>[] { URL.class });
046            getJarFileMethod.setAccessible(true);
047            Class<?> jarURLConnectionClass = loadClass("sun.net.www.protocol.jar.JarURLConnection");
048            jarFileFactoryField = jarURLConnectionClass.getDeclaredField("factory");
049            jarFileFactoryField.setAccessible(true);
050            factory = jarFileFactoryField.get(null);
051            Class<?> factoryClass = loadClass("sun.net.www.protocol.jar.JarFileFactory");
052            factoryGetMethod = factoryClass.getMethod("get", new Class<?>[] { URL.class });
053            factoryGetMethod.setAccessible(true);
054            factoryCloseMethod = factoryClass.getMethod("close", new Class<?>[] { JarFile.class });
055            factoryCloseMethod.setAccessible(true);
056        } catch (NoSuchFieldException | SecurityException | ClassNotFoundException | NoSuchMethodException
057                | IllegalArgumentException | IllegalAccessException cause) {
058            throw new URLJarFileIntrospectionError("Cannot introspect url class loader jar files", cause);
059        }
060    }
061
062    protected Object fetchFactory() throws URLJarFileIntrospectionError {
063        try {
064            return jarFileFactoryField.get(null);
065        } catch (IllegalArgumentException | IllegalAccessException cause) {
066            throw new URLJarFileIntrospectionError("Cannot access to factory", cause);
067        }
068    }
069
070    protected static Class<?> loadClass(String name) throws ClassNotFoundException {
071        return URLJarFileIntrospector.class.getClassLoader().loadClass(name);
072    }
073
074    public JarFileCloser newJarFileCloser(ClassLoader loader) throws URLJarFileIntrospectionError {
075        return new URLJarFileCloser(this, loader);
076    }
077
078    protected URLClassLoaderCloser newURLClassLoaderCloser(URLClassLoader loader) throws URLJarFileIntrospectionError {
079        try {
080            Object ucp = ucpField.get(loader);
081            Map<?, ?> index = (Map<?, ?>) lmapField.get(ucp);
082            List<?> loaders = (List<?>) loadersField.get(ucp);
083            return new URLClassLoaderCloser(this, index, loaders);
084        } catch (IllegalArgumentException | IllegalAccessException cause) {
085            throw new URLJarFileIntrospectionError("Cannot unwrap url class loader fields", cause);
086        }
087    }
088
089    public void close(URL location) throws IOException {
090        JarFile jar = null;
091        try {
092            jar = (JarFile) factoryGetMethod.invoke(factory, new Object[] { location });
093            factoryCloseMethod.invoke(factory, jar);
094        } catch (IllegalAccessException e) {
095            throw new RuntimeException("Cannot use reflection on jar file factory", e);
096        } catch (InvocationTargetException e) {
097            throw new RuntimeException("Cannot use reflection on jar file factory", e);
098        }
099        jar.close();
100    }
101}