This patch implements support for INDEX.LIST handling in a URLClassLoader. It works by modifying the JarURLLoader to understand this index and to add it to other loaders which it creates.
I also removed the cache of URLLoaders from URLClassLoader. I don't think this cache provides much value, and I think it actively interferes with shared library loaders in libgcj. Comments? Tom 2006-05-18 Tom Tromey <[EMAIL PROTECTED]> PR classpath/27444: * gnu/java/net/loader/URLLoader.java (getClassPath): Documented. Changed return type. * java/net/URLClassLoader.java (urlloaders): Removed. (addURLImpl): Updated. * gnu/java/net/loader/JarURLLoader.java (initialized): New field. (indexSet): Likewise. (classPath): Changed type. (JarURLLoader): New constructor. (initialize): New method. (getResource): Use index set if it exists. (getClassPath): Updated. * gnu/java/net/IndexListParser.java (IndexListParser): Avoid NPE. (prefixes): New field. (headers): Removed. (IndexListParser): Fill in prefixes. (clearAll): Clear prefixes. (getHeaders): Changed return type. Index: gnu/java/net/IndexListParser.java =================================================================== RCS file: /cvsroot/classpath/classpath/gnu/java/net/IndexListParser.java,v retrieving revision 1.2 diff -u -r1.2 IndexListParser.java --- gnu/java/net/IndexListParser.java 14 May 2006 20:38:33 -0000 1.2 +++ gnu/java/net/IndexListParser.java 18 May 2006 14:40:28 -0000 @@ -41,7 +41,8 @@ import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.URL; -import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.jar.JarFile; /** @@ -67,7 +68,9 @@ public static final String JAR_INDEX_VERSION_KEY = "JarIndex-Version: "; double versionNumber; - ArrayList headers = new ArrayList(); + // Map each jar to the prefixes defined for the jar. + // This is intentionally kept in insertion order. + LinkedHashMap prefixes = new LinkedHashMap(); /** * Parses the given jarfile's INDEX.LIST file if it exists. @@ -94,22 +97,33 @@ // Blank line must be next line = br.readLine(); - if (!line.equals("")) + if (! "".equals(line)) { clearAll(); return; } - // May contain sections - line = br.readLine(); - while (line != null) + // May contain sections. + while ((line = br.readLine()) != null) { - headers.add(new URL(baseURL, line)); + URL jarURL = new URL(baseURL, line); + HashSet values = new HashSet(); - // Skip all names in the section - while (! (line = br.readLine()).equals("")); - line = br.readLine(); + // Read the names in the section. + while ((line = br.readLine()) != null) + { + // Stop at section boundary. + if ("".equals(line)) + break; + values.add(line.trim()); + } + prefixes.put(jarURL, values); + // Might have seen an early EOF. + if (line == null) + break; } + + br.close(); } // else INDEX.LIST does not exist } @@ -125,7 +139,7 @@ void clearAll() { versionNumber = 0; - headers.clear(); + prefixes = null; } /** @@ -149,12 +163,15 @@ } /** - * Gets the array list of all the headers found in the file. + * Gets the map of all the headers found in the file. + * The keys in the map are URLs of jars. The values in the map + * are Sets of package prefixes (and top-level file names), as + * specifed in INDEX.LIST. * - * @return an array list of all the headers. + * @return an map of all the headers, or null if no INDEX.LIST was found */ - public ArrayList getHeaders() + public LinkedHashMap getHeaders() { - return headers; + return prefixes; } } Index: gnu/java/net/loader/JarURLLoader.java =================================================================== RCS file: /cvsroot/classpath/classpath/gnu/java/net/loader/JarURLLoader.java,v retrieving revision 1.1 diff -u -r1.1 JarURLLoader.java --- gnu/java/net/loader/JarURLLoader.java 15 May 2006 23:29:36 -0000 1.1 +++ gnu/java/net/loader/JarURLLoader.java 18 May 2006 14:40:28 -0000 @@ -4,12 +4,15 @@ import java.io.IOException; import java.net.JarURLConnection; +import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.net.URLStreamHandlerFactory; import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Set; import java.util.StringTokenizer; -import java.util.Vector; import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarFile; @@ -21,14 +24,26 @@ */ public final class JarURLLoader extends URLLoader { - final JarFile jarfile; // The jar file for this url - final URL baseJarURL; // Base jar: url for all resources loaded from jar - - Vector classPath; // The "Class-Path" attribute of this Jar's manifest - - public JarURLLoader(URLClassLoader classloader, URLStreamHandlerCache cache, - URLStreamHandlerFactory factory, - URL baseURL, URL absoluteUrl) + // True if we've initialized -- i.e., tried open the jar file. + boolean initialized; + // The jar file for this url. + JarFile jarfile; + // Base jar: url for all resources loaded from jar. + final URL baseJarURL; + // The "Class-Path" attribute of this Jar's manifest. + ArrayList classPath; + // If not null, a mapping from INDEX.LIST for this jar only. + // This is a set of all prefixes and top-level files that + // ought to be available in this jar. + Set indexSet; + + // This constructor is used internally. It purposely does not open + // the jar file -- it defers this until later. This allows us to + // implement INDEX.LIST lazy-loading semantics. + private JarURLLoader(URLClassLoader classloader, URLStreamHandlerCache cache, + URLStreamHandlerFactory factory, + URL baseURL, URL absoluteUrl, + Set indexSet) { super(classloader, cache, factory, baseURL, absoluteUrl); @@ -40,13 +55,37 @@ sb.append("!/"); String jarURL = sb.toString(); + URL base = null; + try + { + base = new URL(null, jarURL, cache.get(factory, "jar")); + } + catch (MalformedURLException ignore) + { + // Ignore. + } + this.baseJarURL = base; this.classPath = null; - URL baseJarURL = null; + this.indexSet = indexSet; + } + + // This constructor is used by URLClassLoader. It will immediately + // try to read the jar file, in case we've found an index or a class-path + // setting. FIXME: it would be nice to defer this as well, but URLClassLoader + // makes this hard. + public JarURLLoader(URLClassLoader classloader, URLStreamHandlerCache cache, + URLStreamHandlerFactory factory, + URL baseURL, URL absoluteUrl) + { + this(classloader, cache, factory, baseURL, absoluteUrl, null); + initialize(); + } + + private void initialize() + { JarFile jarfile = null; try { - baseJarURL = new URL(null, jarURL, cache.get(factory, "jar")); - jarfile = ((JarURLConnection) baseJarURL.openConnection()).getJarFile(); @@ -54,30 +93,66 @@ Attributes attributes; String classPathString; - this.classPath = new Vector(); - - ArrayList indexListHeaders = new IndexListParser(jarfile, baseJarURL, baseURL).getHeaders(); - if (indexListHeaders.size() > 0) - this.classPath.addAll(indexListHeaders); + IndexListParser parser = new IndexListParser(jarfile, baseJarURL, + baseURL); + LinkedHashMap indexMap = parser.getHeaders(); + if (indexMap != null) + { + // Note that the index also computes + // the resulting Class-Path -- there are jars out there + // where the index lists some required jars which do + // not appear in the Class-Path attribute in the manifest. + this.classPath = new ArrayList(); + Iterator it = indexMap.entrySet().iterator(); + while (it.hasNext()) + { + LinkedHashMap.Entry entry = (LinkedHashMap.Entry) it.next(); + URL subURL = (URL) entry.getKey(); + Set prefixes = (Set) entry.getValue(); + if (subURL.equals(baseURL)) + this.indexSet = prefixes; + else + { + JarURLLoader subLoader = new JarURLLoader(classloader, + cache, + factory, subURL, + subURL, + prefixes); + // Note that we don't care if the sub-loader itself has an + // index or a class-path -- only the top-level jar + // file gets this treatment; its index should cover + // everything. + this.classPath.add(subLoader); + } + } + } else if ((manifest = jarfile.getManifest()) != null - && (attributes = manifest.getMainAttributes()) != null - && ((classPathString - = attributes.getValue(Attributes.Name.CLASS_PATH)) - != null)) - { + && (attributes = manifest.getMainAttributes()) != null + && ((classPathString + = attributes.getValue(Attributes.Name.CLASS_PATH)) + != null)) + { + this.classPath = new ArrayList(); StringTokenizer st = new StringTokenizer(classPathString, " "); while (st.hasMoreElements ()) - { - String e = st.nextToken (); - try - { - this.classPath.add(new URL(baseURL, e)); - } - catch (java.net.MalformedURLException xx) - { - // Give up - } - } + { + String e = st.nextToken (); + try + { + URL subURL = new URL(baseURL, e); + JarURLLoader subLoader = new JarURLLoader(classloader, + cache, factory, + subURL, subURL); + this.classPath.add(subLoader); + ArrayList extra = subLoader.getClassPath(); + if (extra != null) + this.classPath.addAll(extra); + } + catch (java.net.MalformedURLException xx) + { + // Give up + } + } } } catch (IOException ioe) @@ -85,18 +160,34 @@ /* ignored */ } - this.baseJarURL = baseJarURL; this.jarfile = jarfile; + this.initialized = true; } /** get resource with the name "name" in the jar url */ public Resource getResource(String name) { - if (jarfile == null) - return null; - if (name.startsWith("/")) name = name.substring(1); + if (indexSet != null) + { + // Trust the index. + String basename = name; + int offset = basename.lastIndexOf('/'); + if (offset != -1) + basename = basename.substring(0, offset); + if (! indexSet.contains(basename)) + return null; + // FIXME: if the index claim to hold the resource, and jar file + // doesn't have it, we're supposed to throw an exception. However, + // in our model this is tricky to implement, as another URLLoader from + // the same top-level jar may have an overlapping index entry. + } + + if (! initialized) + initialize(); + if (jarfile == null) + return null; JarEntry je = jarfile.getJarEntry(name); if (je != null) @@ -117,7 +208,7 @@ } } - public Vector getClassPath() + public ArrayList getClassPath() { return classPath; } Index: gnu/java/net/loader/URLLoader.java =================================================================== RCS file: /cvsroot/classpath/classpath/gnu/java/net/loader/URLLoader.java,v retrieving revision 1.1 diff -u -r1.1 URLLoader.java --- gnu/java/net/loader/URLLoader.java 15 May 2006 23:29:36 -0000 1.1 +++ gnu/java/net/loader/URLLoader.java 18 May 2006 14:40:28 -0000 @@ -42,7 +42,7 @@ import java.net.URLClassLoader; import java.net.URLStreamHandlerFactory; import java.security.CodeSource; -import java.util.Vector; +import java.util.ArrayList; import java.util.jar.Manifest; /** @@ -136,7 +136,11 @@ return null; } - public Vector getClassPath() + /** + * Return a list of new URLLoader objects representing any + * class path entries added by this container. + */ + public ArrayList getClassPath() { return null; } Index: java/net/URLClassLoader.java =================================================================== RCS file: /cvsroot/classpath/classpath/java/net/URLClassLoader.java,v retrieving revision 1.50 diff -u -r1.50 URLClassLoader.java --- java/net/URLClassLoader.java 15 May 2006 23:29:36 -0000 1.50 +++ java/net/URLClassLoader.java 18 May 2006 14:40:29 -0000 @@ -61,9 +61,8 @@ import java.security.PrivilegedAction; import java.security.SecureClassLoader; import java.security.cert.Certificate; +import java.util.ArrayList; import java.util.Enumeration; -import java.util.HashMap; -import java.util.Iterator; import java.util.Vector; import java.util.jar.Attributes; import java.util.jar.Manifest; @@ -131,14 +130,6 @@ // Class Variables /** - * A global cache to store mappings between URLLoader and URL, - * so we can avoid do all the homework each time the same URL - * comes. - * XXX - Keeps these loaders forever which prevents garbage collection. - */ - private static HashMap urlloaders = new HashMap(); - - /** * A cache to store mappings between handler factory and its * private protocol handler cache (also a HashMap), so we can avoid * creating handlers each time the same protocol comes. @@ -299,132 +290,114 @@ // Reset the toString() value. thisString = null; - // Check global cache to see if there're already url loader - // for this url. - URLLoader loader = (URLLoader) urlloaders.get(newUrl); - if (loader == null) + // Create a loader for this URL. + URLLoader loader = null; + String file = newUrl.getFile(); + String protocol = newUrl.getProtocol(); + + // If we have a file: URL, we want to make it absolute + // here, before we decide whether it is really a jar. + URL absoluteURL; + if ("file".equals (protocol)) { - String file = newUrl.getFile(); - String protocol = newUrl.getProtocol(); - - // If we have a file: URL, we want to make it absolute - // here, before we decide whether it is really a jar. - URL absoluteURL; - if ("file".equals (protocol)) - { - File dir = new File(file); - URL absUrl; - try - { - absoluteURL = dir.getCanonicalFile().toURL(); - } - catch (IOException ignore) - { - try - { - absoluteURL = dir.getAbsoluteFile().toURL(); - } - catch (MalformedURLException _) - { - // This really should not happen. - absoluteURL = newUrl; - } - } - } - else - { - // This doesn't hurt, and it simplifies the logic a - // little. - absoluteURL = newUrl; - } - - // First see if we can find a handler with the correct name. + File dir = new File(file); + URL absUrl; try { - Class handler = Class.forName(URL_LOADER_PREFIX + protocol); - Class[] argTypes = new Class[] { URLClassLoader.class, - URLStreamHandlerCache.class, - URLStreamHandlerFactory.class, - URL.class, - URL.class }; - Constructor k = handler.getDeclaredConstructor(argTypes); - loader - = (URLLoader) k.newInstance(new Object[] { this, - factoryCache, - factory, - newUrl, - absoluteURL }); - } - catch (ClassNotFoundException ignore) - { - // Fall through. - } - catch (NoSuchMethodException nsme) - { - // Programming error in the class library. - InternalError vme - = new InternalError("couldn't find URLLoader constructor"); - vme.initCause(nsme); - throw vme; - } - catch (InstantiationException inste) - { - // Programming error in the class library. - InternalError vme - = new InternalError("couldn't instantiate URLLoader"); - vme.initCause(inste); - throw vme; + absoluteURL = dir.getCanonicalFile().toURL(); } - catch (InvocationTargetException ite) + catch (IOException ignore) { - // Programming error in the class library. - InternalError vme - = new InternalError("error instantiating URLLoader"); - vme.initCause(ite); - throw vme; - } - catch (IllegalAccessException illae) - { - // Programming error in the class library. - InternalError vme - = new InternalError("invalid access to URLLoader"); - vme.initCause(illae); - throw vme; - } - - if (loader == null) - { - // If it is not a directory, use the jar loader. - if (! (file.endsWith("/") || file.endsWith(File.separator))) - loader = new JarURLLoader(this, factoryCache, factory, - newUrl, absoluteURL); - else if ("file".equals(protocol)) - loader = new FileURLLoader(this, factoryCache, factory, - newUrl, absoluteURL); - else - loader = new RemoteURLLoader(this, factoryCache, factory, - newUrl); + try + { + absoluteURL = dir.getAbsoluteFile().toURL(); + } + catch (MalformedURLException _) + { + // This really should not happen. + absoluteURL = newUrl; + } } - - // Cache it. - urlloaders.put(newUrl, loader); + } + else + { + // This doesn't hurt, and it simplifies the logic a + // little. + absoluteURL = newUrl; } - urlinfos.add(loader); + // First see if we can find a handler with the correct name. + try + { + Class handler = Class.forName(URL_LOADER_PREFIX + protocol); + Class[] argTypes = new Class[] { URLClassLoader.class, + URLStreamHandlerCache.class, + URLStreamHandlerFactory.class, + URL.class, + URL.class }; + Constructor k = handler.getDeclaredConstructor(argTypes); + loader + = (URLLoader) k.newInstance(new Object[] { this, + factoryCache, + factory, + newUrl, + absoluteURL }); + } + catch (ClassNotFoundException ignore) + { + // Fall through. + } + catch (NoSuchMethodException nsme) + { + // Programming error in the class library. + InternalError vme + = new InternalError("couldn't find URLLoader constructor"); + vme.initCause(nsme); + throw vme; + } + catch (InstantiationException inste) + { + // Programming error in the class library. + InternalError vme + = new InternalError("couldn't instantiate URLLoader"); + vme.initCause(inste); + throw vme; + } + catch (InvocationTargetException ite) + { + // Programming error in the class library. + InternalError vme + = new InternalError("error instantiating URLLoader"); + vme.initCause(ite); + throw vme; + } + catch (IllegalAccessException illae) + { + // Programming error in the class library. + InternalError vme + = new InternalError("invalid access to URLLoader"); + vme.initCause(illae); + throw vme; + } - Vector extraUrls = loader.getClassPath(); - if (extraUrls != null) - { - Iterator it = extraUrls.iterator(); - while (it.hasNext()) - { - URL url = (URL)it.next(); - URLLoader extraLoader = (URLLoader) urlloaders.get(url); - if (! urlinfos.contains (extraLoader)) - addURLImpl(url); - } - } + if (loader == null) + { + // If it is not a directory, use the jar loader. + if (! (file.endsWith("/") || file.endsWith(File.separator))) + loader = new JarURLLoader(this, factoryCache, factory, + newUrl, absoluteURL); + else if ("file".equals(protocol)) + loader = new FileURLLoader(this, factoryCache, factory, + newUrl, absoluteURL); + else + loader = new RemoteURLLoader(this, factoryCache, factory, + newUrl); + } + urlinfos.add(loader); + ArrayList extra = loader.getClassPath(); + if (extra != null) + urlinfos.addAll(extra); } }