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

Reply via email to