Connection.java (getJarFile): download and cache remote jar files.
authorMark Wielaard <mark@klomp.org>
Mon, 9 Dec 2002 00:04:00 +0000 (00:04 +0000)
committerMark Wielaard <mark@gcc.gnu.org>
Mon, 9 Dec 2002 00:04:00 +0000 (00:04 +0000)
* gnu/gcj/protocol/jar/Connection.java (getJarFile): download and
cache remote jar files.
* gnu/gcj/runtime/VMClassLoader.java: Don't construct jar URL, only
add File.separator to URL when it is a directory.
* java/lang/ClassLoader.java: Add Classpath javadoc.
(parent): final.
(getParent): Add (disabled) security check.
(findLibrary): New default method.
* java/net/JarURLConnection.java (getManifest): Implement.
(getInputStream): Only create InputStream when entry exists.
(getHeaders): Only use jarFileURLConnection or JarEntry to set length
when they exist.
* java/net/URLClassLoader.java: New/Rewritten version from Classpath.

From-SVN: r59949

libjava/ChangeLog
libjava/gnu/gcj/protocol/jar/Connection.java
libjava/gnu/gcj/runtime/VMClassLoader.java
libjava/java/lang/ClassLoader.java
libjava/java/net/JarURLConnection.java
libjava/java/net/URLClassLoader.java

index 9e255b2cd57a4550fe1a8ab8133ad969aa9c88b2..15ce27c61654fac90098aa4d5d55690774d89b4b 100644 (file)
@@ -1,3 +1,19 @@
+2002-12-08  Mark Wielaard  <mark@klomp.org>
+
+       * gnu/gcj/protocol/jar/Connection.java (getJarFile): download and
+       cache remote jar files.
+       * gnu/gcj/runtime/VMClassLoader.java: Don't construct jar URL, only
+       add File.separator to URL when it is a directory.
+       * java/lang/ClassLoader.java: Add Classpath javadoc.
+       (parent): final.
+       (getParent): Add (disabled) security check.
+       (findLibrary): New default method.
+       * java/net/JarURLConnection.java (getManifest): Implement.
+       (getInputStream): Only create InputStream when entry exists.
+       (getHeaders): Only use jarFileURLConnection or JarEntry to set length
+       when they exist.
+       * java/net/URLClassLoader.java: New/Rewritten version from Classpath.
+
 2002-12-08  Mark Wielaard  <mark@klomp.org>
 
        * java/util/ResourceBundle.java (resourceBundleCache): Not final.
index 60641182f3ae1f06972ad606e0c8ceaa530ab848..c9db04997dc2c7d7317583a070d3cf84ee9e5b40 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (C) 1999  Free Software Foundation
+/* Copyright (C) 1999, 2002  Free Software Foundation
 
    This file is part of libgcj.
 
@@ -9,19 +9,21 @@ details.  */
 package gnu.gcj.protocol.jar;
 
 import java.net.URL;
+import java.net.URLConnection;
 import java.net.JarURLConnection;
 import java.net.URLStreamHandler;
 import java.net.MalformedURLException;
 import java.net.ProtocolException;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.File;
+import java.io.FileOutputStream;
 import java.util.jar.JarFile;
+import java.util.zip.ZipFile;
 import java.util.Hashtable;
 
 /**
  * Written using on-line Java Platform 1.2 API Specification.
- * Status: Needs a way to download jar files and store them in the local file
- * system.  I don't know how to do that in a portable way.  For now, it can only handle 
- * connections to a jar:file: url's.
  *
  * @author Kresten Krab Thorup <krab@gnu.org>
  * @date Aug 10, 1999.
@@ -70,14 +72,19 @@ public class Connection extends JarURLConnection
       }
     else
       {
-       /*
-         FIXME: Here we need to download and cache the jar
-         file in the local file system!  Stupid design.  Why
-         can't we just create a JarFile from a bag of bytes?
-       */
-
-       throw new java.io.IOException("cannot create jar file from " +
-                                     jarFileURL);
+       URLConnection urlconn = jarFileURL.openConnection();
+       InputStream is = urlconn.getInputStream();
+       byte[] buf = new byte[4*1024];
+       File f = File.createTempFile("cache", "jar");
+       FileOutputStream fos = new FileOutputStream(f);
+       int len = 0;
+       while((len = is.read(buf)) != -1)
+         fos.write(buf, 0, len);
+        fos.close();
+       // Always verify the Manifest, open read only and delete when done.
+       // XXX ZipFile.OPEN_DELETE not yet implemented.
+       // jf = new JarFile(f, true, ZipFile.OPEN_READ | ZipFile.OPEN_DELETE);
+       jarfile = new JarFile(f, true, ZipFile.OPEN_READ);
       }
 
     return jarfile;
index 77d21606621fc4b9468608bc063bc73a6c9f25a9..fd0c32c2c57dbb39c271be84efab3ab138d121b4 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (C) 1999, 2001  Free Software Foundation
+/* Copyright (C) 1999, 2001, 2002  Free Software Foundation
 
    This file is part of libgcj.
 
@@ -33,23 +33,10 @@ public final class VMClassLoader extends java.net.URLClassLoader
        String e = st.nextToken ();
        try
          {
-           if (e.endsWith(".jar") || e.endsWith (".zip"))
-             {
-               File archive = new File (e);
-               try {
-                 p.addElement(new URL("jar", "", -1, "file://"
-                                      + archive.getCanonicalPath ()
-                                      + "!/"));
-               } catch (IOException ex) {
-                 // empty
-               }
-             }
-           else if (e.endsWith ("/"))
-             p.addElement (new URL("file", "", -1, e));
-           else if (new File (e).isDirectory ())
-             p.addElement (new URL("file", "", -1, e + "/"));
+           if (!e.endsWith (File.separator) && new File (e).isDirectory ())
+             p.addElement (new URL("file", "", -1, e + File.separator));
            else
-             /* Ignore path element. */;
+             p.addElement (new URL("file", "", -1, e));
          } 
        catch (java.net.MalformedURLException x)
          {
index 1b3b310cab3b776d03223f5fdded61b751afcdf6..ea6546cf6ee6b1516a07151cebd2afd389a38484 100644 (file)
@@ -13,7 +13,6 @@ package java.lang;
 import java.io.InputStream;
 import java.io.IOException;
 import java.net.URL;
-import java.net.URLConnection;
 import java.security.AllPermission;
 import java.security.CodeSource;
 import java.security.Permission;
@@ -23,13 +22,68 @@ import java.security.ProtectionDomain;
 import java.util.*;
 
 /**
- * The class <code>ClassLoader</code> is intended to be subclassed by
- * applications in order to describe new ways of loading classes,
- * such as over the network.
+ * The ClassLoader is a way of customizing the way Java gets its classes
+ * and loads them into memory.  The verifier and other standard Java things
+ * still run, but the ClassLoader is allowed great flexibility in determining
+ * where to get the classfiles and when to load and resolve them. For that
+ * matter, a custom ClassLoader can perform on-the-fly code generation or
+ * modification!
  *
- * @author  Kresten Krab Thorup
+ * <p>Every classloader has a parent classloader that is consulted before
+ * the 'child' classloader when classes or resources should be loaded.   
+ * This is done to make sure that classes can be loaded from an hierarchy of
+ * multiple classloaders and classloaders do not accidentially redefine   
+ * already loaded classes by classloaders higher in the hierarchy.
+ *   
+ * <p>The grandparent of all classloaders is the bootstrap classloader, which
+ * loads all the standard system classes as implemented by GNU Classpath. The
+ * other special classloader is the system classloader (also called
+ * application classloader) that loads all classes from the CLASSPATH
+ * (<code>java.class.path</code> system property). The system classloader
+ * is responsible for finding the application classes from the classpath,
+ * and delegates all requests for the standard library classes to its parent
+ * the bootstrap classloader. Most programs will load all their classes
+ * through the system classloaders.
+ *
+ * <p>The bootstrap classloader in GNU Classpath is implemented as a couple of
+ * static (native) methods on the package private class
+ * <code>java.lang.VMClassLoader</code>, the system classloader is an
+ * instance of <code>gnu.java.lang.SystemClassLoader</code>
+ * (which is a subclass of <code>java.net.URLClassLoader</code>).
+ *
+ * <p>Users of a <code>ClassLoader</code> will normally just use the methods
+ * <ul>
+ *  <li> <code>loadClass()</code> to load a class.</li>
+ *  <li> <code>getResource()</code> or <code>getResourceAsStream()</code>
+ *       to access a resource.</li>
+ *  <li> <code>getResources()</code> to get an Enumeration of URLs to all
+ *       the resources provided by the classloader and its parents with the
+ *       same name.</li>
+ * </ul>
+ *
+ * <p>Subclasses should implement the methods
+ * <ul>
+ *  <li> <code>findClass()</code> which is called by <code>loadClass()</code>
+ *       when the parent classloader cannot provide a named class.</li>
+ *  <li> <code>findResource()</code> which is called by
+ *       <code>getResource()</code> when the parent classloader cannot provide
+ *       a named resource.</li>
+ *  <li> <code>findResources()</code> which is called by
+ *       <code>getResource()</code> to combine all the resources with the
+ *       same name from the classloader and its parents.</li>
+ *  <li> <code>findLibrary()</code> which is called by
+ *       <code>Runtime.loadLibrary()</code> when a class defined by the
+ *       classloader wants to load a native library.</li>
+ * </ul>
+ *
+ * @author John Keiser
+ * @author Mark Wielaard
+ * @author Eric Blake
+ * @author Kresten Krab Thorup
+ * @see Class
+ * @since 1.0
+ * @status still missing 1.4 functionality
  */
-
 public abstract class ClassLoader
 {
   /**
@@ -73,12 +127,40 @@ public abstract class ClassLoader
   // Package visible for use by Class.
   Map classAssertionStatus;
 
-  private ClassLoader parent;
+  /**
+   * The classloader that is consulted before this classloader.
+   * If null then the parent is the bootstrap classloader.
+   */
+  private final ClassLoader parent;
+
+  /**
+   * All packages defined by this classloader. It is not private in order to
+   * allow native code (and trusted subclasses) access to this field.
+   */
   private HashMap definedPackages = new HashMap();
 
+  /**
+   * Returns the parent of this classloader. If the parent of this
+   * classloader is the bootstrap classloader then this method returns
+   * <code>null</code>. A security check may be performed on
+   * <code>RuntimePermission("getClassLoader")</code>.
+   *
+   * @throws SecurityException if the security check fails
+   * @since 1.2
+   */
   public final ClassLoader getParent ()
   {
-    /* FIXME: security */
+    // Check if we may return the parent classloader
+    SecurityManager sm = System.getSecurityManager();
+    if (sm != null)
+      {
+       /* FIXME: security, getClassContext() not implemented.
+       Class c = VMSecurityManager.getClassContext()[1];
+       ClassLoader cl = c.getClassLoader();
+       if (cl != null && cl != this)
+         sm.checkPermission(new RuntimePermission("getClassLoader"));
+       */
+      }
     return parent;
   }
 
@@ -449,7 +531,8 @@ public abstract class ClassLoader
            else
              {
                InternalError e
-                 = new InternalError ("unexpected exception during linking");
+                 = new InternalError ("unexpected exception during linking: "
+                                      + clazz.getName());
                e.initCause (x);
                throw e;
              }
@@ -521,6 +604,8 @@ public abstract class ClassLoader
    * null when the package is not defined by this classloader or one of its
    * parents.
    *
+   * @param name the package name to find
+   * @return the package, if defined
    * @since 1.2
    */
   protected Package getPackage(String name)
@@ -546,6 +631,7 @@ public abstract class ClassLoader
   /**
    * Returns all Package objects defined by this classloader and its parents.
    *
+   * @return an array of all defined packages
    * @since 1.2
    */
   protected Package[] getPackages()
@@ -577,6 +663,26 @@ public abstract class ClassLoader
     return allPackages;
   }
 
+  /**
+   * Called by <code>Runtime.loadLibrary()</code> to get an absolute path
+   * to a (system specific) library that was requested by a class loaded
+   * by this classloader. The default implementation returns
+   * <code>null</code>. It should be implemented by subclasses when they
+   * have a way to find the absolute path to a library. If this method
+   * returns null the library is searched for in the default locations
+   * (the directories listed in the <code>java.library.path</code> system
+   * property).
+   *
+   * @param name the (system specific) name of the requested library
+   * @return the full pathname to the requested library, or null
+   * @see Runtime#loadLibrary()
+   * @since 1.2
+   */
+  protected String findLibrary(String name)
+  {
+    return null;
+  }
+
   /** 
    * Returns a class found in a system-specific way, typically
    * via the <code>java.class.path</code> system property.  Loads the 
index a90c7f3303715b8ce4e1a72ed617c067d96907dc..6f4cd1fe7ea3bc5e770fc2bf150036e7a0cfbad6 100644 (file)
@@ -134,7 +134,11 @@ public abstract class JarURLConnection extends URLConnection
     if (jarfile != null)
       {
        // this is the easy way...
-       return jarfile.getInputStream (jarfile.getEntry (element));
+       ZipEntry entry = jarfile.getEntry(element);
+       if (entry != null)
+         return jarfile.getInputStream (entry);
+       else
+         return null;
       }
     else
       {
@@ -320,12 +324,17 @@ public abstract class JarURLConnection extends URLConnection
     // to add others later and for consistency, we'll implement it this way.
 
     // Add the only header we know about right now:  Content-length.
-    long len;
+    long len = -1;
 
     if (element == null)
-      len = jarFileURLConnection.getContentLength ();
+      if (jarFileURLConnection != null)
+       len = jarFileURLConnection.getContentLength ();
     else
-      len = getJarEntry ().getSize ();
+      {
+       JarEntry entry = getJarEntry();
+       if (entry != null)
+         len = entry.getSize ();
+      }
 
     String line = "Content-length: " + len;
     hdrVec.addElement(line);
@@ -381,7 +390,6 @@ public abstract class JarURLConnection extends URLConnection
   {
     JarFile file = getJarFile ();
 
-    // FIXME: implement this
-    return null;
+    return (file != null) ? file.getManifest() : null;
   }
 }
index 5e059cf9cb63cb896d21f9acffd5c332d3f959bc..e37a81a5021679181cf5ca309558cb3b30ed89aa 100644 (file)
-/* Copyright (C) 1999, 2000, 2002  Free Software Foundation
+/* URLClassLoader.java --  ClassLoader that loads classes from one or more URLs
+   Copyright (C) 1999, 2000, 2001, 2002 Free Software Foundation, Inc.
 
-   This file is part of libgcj.
+This file is part of GNU Classpath.
 
-This software is copyrighted work licensed under the terms of the
-Libgcj License.  Please consult the file "LIBGCJ_LICENSE" for
-details.  */
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING.  If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version. */
 
 package java.net;
 
-import java.io.*;
-import java.util.jar.*;
-import java.util.Enumeration;
-import java.util.Vector;
+import java.io.ByteArrayOutputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FilterInputStream;
+import java.io.FilePermission;
+import java.io.InputStream;
+import java.io.IOException;
+import java.security.AccessController;
+import java.security.AccessControlContext;
 import java.security.CodeSource;
 import java.security.SecureClassLoader;
+import java.security.PrivilegedAction;
 import java.security.PermissionCollection;
 import java.security.cert.Certificate;
+import java.util.Enumeration;
+import java.util.Vector;
+import java.util.HashMap;
+import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.zip.ZipException;
 
 /**
+ * A secure class loader that can load classes and resources from
+ * multiple locations.  Given an array of <code>URL</code>s this class
+ * loader will retrieve classes and resources by fetching them from
+ * possible remote locations.  Each <code>URL</code> is searched in
+ * order in which it was added.  If the file portion of the
+ * <code>URL</code> ends with a '/' character then it is interpreted
+ * as a base directory, otherwise it is interpreted as a jar file from
+ * which the classes/resources are resolved.
+ *
+ * <p>New instances can be created by two static
+ * <code>newInstance()</code> methods or by three public
+ * contructors. Both ways give the option to supply an initial array
+ * of <code>URL</code>s and (optionally) a parent classloader (that is
+ * different from the standard system class loader).</p>
+ *
+ * <p>Normally creating a <code>URLClassLoader</code> throws a
+ * <code>SecurityException</code> if a <code>SecurityManager</code> is
+ * installed and the <code>checkCreateClassLoader()</code> method does
+ * not return true.  But the <code>newInstance()</code> methods may be
+ * used by any code as long as it has permission to acces the given
+ * <code>URL</code>s.  <code>URLClassLoaders</code> created by the
+ * <code>newInstance()</code> methods also explicitly call the
+ * <code>checkPackageAccess()</code> method of
+ * <code>SecurityManager</code> if one is installed before trying to
+ * load a class.  Note that only subclasses of
+ * <code>URLClassLoader</code> can add new URLs after the
+ * URLClassLoader had been created. But it is always possible to get
+ * an array of all URLs that the class loader uses to resolve classes
+ * and resources by way of the <code>getURLs()</code> method.</p>
+ *
+ * <p>Open issues:
+ * <ul>
+ *
+ * <li>Should the URLClassLoader actually add the locations found in
+ * the manifest or is this the responsibility of some other
+ * loader/(sub)class?  (see <a
+ * href="http://java.sun.com/products/jdk/1.4/docs/guide/extensions/spec.html">
+ * Extension Mechanism Architecture - Bundles Extensions</a>)</li>
+ *
+ * <li>How does <code>definePackage()</code> and sealing work
+ * precisely?</li>
+ *
+ * <li>We save and use the security context (when a created by
+ * <code>newInstance()</code> but do we have to use it in more
+ * places?</li>
+ *
+ * <li>The use of <code>URLStreamHandler</code>s has not been tested.</li>
+ *
+ * </ul>
+ * </p>
+ *
  * @since 1.2
+ *
+ * @author Mark Wielaard (mark@klomp.org)
+ * @author Wu Gansha (gansha.wu@intel.com)
  */
 public class URLClassLoader extends SecureClassLoader
 {
-  // The URLStreamHandlerFactory
-  URLStreamHandlerFactory factory = null;
+  // Class Variables
 
-  // `path' contains simply the URL's we're using for the searching.
-  private Vector path; 
+  /**
+   * 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
+   * create handlers each time the same protocol comes.
+   */
+  private static HashMap factoryCache = new HashMap(5);
 
-  // If path[n] is a zip/jar, then this holds a JarURLConnection for
-  // that thing, otherwise, path[n] is null.
-  private Vector info; 
+  // Instance variables
 
-  private URLStreamHandler getHandler0 (String protocol)
-  {
-    if (factory != null)
-      return factory.createURLStreamHandler(protocol);
-    else
-      return null;
-  }
+  /** Locations to load classes from */
+  private final Vector urls = new Vector();
 
   /**
-   * Createa a new URL class loader object
-   *
-   * @exception SecurityException If a security manager exists and its
-   * checkCreateClassLoader method doesn't allow creation of a class loader
+   * Store pre-parsed information for each url into this vector 
+   * each element is a URL loader, corresponding to the URL of 
+   * the same index in "urls"
    */
-  public URLClassLoader (URL[] urls)
-  { 
-    this (urls, null, null);
-  }
+  private final Vector urlinfos = new Vector();
+    
+  /** Factory used to get the protocol handlers of the URLs */
+  private final URLStreamHandlerFactory factory;
+
   /**
-   * Createa a new URL class loader object
-   *
-   * @exception SecurityException If a security manager exists and its
-   * checkCreateClassLoader method doesn't allow creation of a class loader
+   * The security context when created from <code>newInstance()</code>
+   * or null when created through a normal constructor or when no
+   * <code>SecurityManager</code> was installed.
    */
-  public URLClassLoader (URL[] urls, ClassLoader parent)
-  { 
-    this (urls, parent, null);
-  }
+  private final AccessControlContext securityContext;
 
-  // A File URL may actually be a Jar URL.  Convert if possible.
-  private URL jarFileize (URL url)
+  // Helper classes
+  /** 
+   * A <code>URLLoader</code> contains all logic to load resources from a
+   * given base <code>URL</code>.
+   */
+  static abstract class URLLoader
   {
-    if (! url.getProtocol ().equals ("jar"))
-      {
-       String f = url.getFile ();
+    /**
+     * Our classloader to get info from if needed.
+     */
+    final URLClassLoader classloader;
 
-       // If it ends with '/' we'll take it for a directory,
-       // otherwise it's a jar file.  This is how JDK 1.2 defines
-       // it, so we will not try to be smart here.
-       if (f.charAt (f.length ()-1) != '/')
-         {
-           try
-             {
-               url = new URL ("jar", "", -1, (url.toExternalForm ())+"!/", 
-                              getHandler0 ("jar"));
-             } 
-           catch (MalformedURLException x)
-             {
-               /* ignore */
-             }
-         }
-      }
-    return url;
-  }
+    /**
+     * The base URL from which all resources are loaded.
+     */
+    final URL baseURL;
 
-  protected void addURL (URL url)
-  {
-    JarURLConnection conn = null;
+    /**
+     * A <code>CodeSource</code> without any associated certificates.
+     * It is common for classes to not have certificates associated
+     * with them.  If they come from the same <code>URLLoader</code>
+     * then it is safe to share the associated <code>CodeSource</code>
+     * between them since <code>CodeSource</code> is immutable.
+     */
+    final CodeSource noCertCodeSource;
+
+    URLLoader(URLClassLoader classloader, URL baseURL)
+    {
+      this.classloader = classloader;
+      this.baseURL = baseURL;
+      this.noCertCodeSource = new CodeSource(baseURL, null);
+    }
+
+    /**
+     * Returns a <code>Resource</code> loaded by this
+     * <code>URLLoader</code>, or <code>null</code> when no
+     * <code>Resource</code> with the given name exists.
+     */
+    abstract Resource getResource(String s);
+
+    /**
+     * Returns the <code>Manifest</code> associated with the
+     * <code>Resource</code>s loaded by this <code>URLLoader</code> or
+     * <code>null</code> there is no such <code>Manifest</code>.
+     */
+    Manifest getManifest()
+    {
+      return null;
+    }
+  }
     
-    // Convert a Jar File URL into Jar URL if possible.
-    url = jarFileize (url);
+  /** 
+   * A <code>Resource</code> represents a resource in some
+   * <code>URLLoader</code>. It also contains all information (e.g.,
+   * <code>URL</code>, <code>CodeSource</code>, <code>Manifest</code> and
+   * <code>InputStream</code>) that is necessary for loading resources
+   * and creating classes from a <code>URL</code>.
+   */
+  static abstract class Resource
+  {
+    final URLLoader loader;
+    final String name;
 
-    path.addElement (url);
+    Resource(URLLoader loader, String name)
+    {
+      this.loader = loader;
+      this.name = name;
+    }
 
-    if (url.getProtocol ().equals ("jar"))
-      {
-       try
-         {
-           conn = (JarURLConnection) url.openConnection ();
-         }
-       catch (java.io.IOException x)
-         {
-           /* ignore */
-         }
-      }
+    /**
+     * Returns the non-null <code>CodeSource</code> associated with
+     * this resource.
+     */
+    CodeSource getCodeSource()
+    {
+      Certificate[] certs = getCertificates();
+      if (certs != null)
+       return loader.noCertCodeSource;
+      else
+       return new CodeSource(loader.baseURL, certs);
+    }
+
+    /**
+     * Returns <code>Certificates</code> associated with this
+     * resource, or null when there are none.
+     */
+    Certificate[] getCertificates()
+    {
+      return null;
+    }
+
+    /**
+     * Return a <code>URL</code> that can be used to access this resource.
+     */
+    abstract URL getURL();
 
-    info.addElement (conn);
+    /**
+     * Returns the size of this <code>Resource</code> in bytes or
+     * <code>-1</code> when unknown.
+     */
+    abstract int getLength();
+
+    /**
+     * Returns the non-null <code>InputStream</code> through which
+     * this resource can be loaded.
+     */
+    abstract InputStream getInputStream() throws IOException;
   }
 
   /**
-   * Createa a new URL class loader object
-   *
-   * @exception SecurityException If a security manager exists and its
-   * checkCreateClassLoader method doesn't allow creation of a class loader
+   * A <code>JarURLLoader</code> is a type of <code>URLLoader</code>
+   * only loading from jar url.
    */
-  public URLClassLoader (URL[] urls, ClassLoader parent,
-                        URLStreamHandlerFactory fac)
-  { 
-    super (parent);
-
-    SecurityManager s = System.getSecurityManager();
-    if (s != null)
-      s.checkCreateClassLoader();
-
-    factory = fac;
+  final static class JarURLLoader extends URLLoader
+  {
+    final JarFile jarfile; // The canonical jar file for this url
+    final URL baseJarURL;  // Base jar: url for all resources loaded from jar
 
-    if (urls == null || urls.length == 0)
-      {
-       path = new Vector (1);
-       info = new Vector (1);
-       return;
-      }
+    public JarURLLoader(URLClassLoader classloader, URL baseURL)
+    {
+      super(classloader, baseURL);
+        
+      // cache url prefix for all resources in this jar url
+      String external = baseURL.toExternalForm();
+      StringBuffer sb = new StringBuffer(external.length() + 6);
+      sb.append("jar:");
+      sb.append(external);
+      sb.append("!/");
+      String jarURL = sb.toString();
 
-    path = new Vector (urls.length);
-    info = new Vector (urls.length);
+      URL baseJarURL = null;
+      JarFile jarfile = null;
+      try
+       {
+         baseJarURL
+           = new URL(null, jarURL, classloader.getURLStreamHandler("jar"));
+         jarfile
+           = ((JarURLConnection) baseJarURL.openConnection()).getJarFile();
+       }
+      catch (IOException ioe) { /* ignored */ }
 
-    for (int i = 0; i < urls.length; i++)
-      {
-       // Convert a Jar File URL into a Jar URL if possible.
-       URL u = jarFileize(urls[i]);
+      this.baseJarURL = baseJarURL;
+      this.jarfile = jarfile;
+    }
+    
+    /** get resource with the name "name" in the jar url */
+    Resource getResource(String name)
+    {
+      if (jarfile == null)
+       return null;
 
-       path.addElement (u);
+      JarEntry je = jarfile.getJarEntry(name);
+      if(je != null)
+       return new JarURLResource(this, name, je);
+      else
+       return null;
+    }
 
-       if (u.getProtocol ().equals ("jar"))
-         {
-           JarURLConnection conn = null;
-           try
-             {
-               conn = (JarURLConnection) u.openConnection ();
-             }
-           catch (java.io.IOException x)
-             {
-               /* ignore */
-             }
-           info.addElement (conn);
-         }
-       else
-         {
-           info.addElement (null);
-         }
-      }
+    Manifest getManifest()
+    {
+      try
+       {
+         return (jarfile == null) ? null : jarfile.getManifest();
+       }
+      catch (IOException ioe)
+       {
+         return null;
+       }
+    }
+      
   }
 
-  public URL[] getURLs ()
+  final static class JarURLResource extends Resource
   {
-    URL[] urls = new URL[path.size()];
-    path.copyInto (urls);
-    return urls;
+    private final JarEntry entry;
+
+    JarURLResource(JarURLLoader loader, String name, JarEntry entry)
+    {
+      super(loader, name);
+      this.entry = entry;
+    }
+
+    InputStream getInputStream() throws IOException
+    {
+      return ((JarURLLoader)loader).jarfile.getInputStream(entry);
+    }
+
+    int getLength()
+    {
+      return (int)entry.getSize();
+    }
+
+    Certificate[] getCertificates()
+    {
+      return entry.getCertificates();
+    }
+                
+    URL getURL()
+    {
+      try
+       {
+         return new URL(((JarURLLoader)loader).baseJarURL, name,
+                        loader.classloader.getURLStreamHandler("jar"));
+       }
+      catch(MalformedURLException e)
+       {
+         throw new InternalError(e.toString());
+       }
+    }
   }
+
   /**
-   * Returns an Enumeration of URLs representing all of the resources on the
-   * URL search path having the specified name
-   *
-   * @exception IOException If an error occurs
+   * Loader for remote directories.
    */
-  public Enumeration findResources (String name)
+  final static class RemoteURLLoader extends URLLoader
   {
-    Vector results = new Vector ();
+    final private String protocol;
 
-    for (int i = 0; i < path.size(); i++)
-      {
-       URL u = (URL)path.elementAt (i);
-               
-       try {
-         JarURLConnection conn = (JarURLConnection) info.elementAt (i);
-         
-         if (conn != null)
+    RemoteURLLoader(URLClassLoader classloader, URL url)
+    {
+      super(classloader, url);
+      protocol = url.getProtocol();
+    }
+
+    /**
+     * Get a remote resource.
+     * Returns null if no such resource exists.
+     */
+    Resource getResource(String name)
+    {
+      try
+       {
+         URL url = new URL(baseURL, name,
+                           classloader.getURLStreamHandler(protocol));
+         URLConnection connection = url.openConnection();
+
+         // Open the connection and check the stream
+         // just to be sure it exists.
+         int length = connection.getContentLength();
+         InputStream stream = connection.getInputStream();
+
+         // We can do some extra checking if it is a http request
+         if (connection instanceof HttpURLConnection)
            {
-             if (conn.getJarFile().getJarEntry (name) != null)
-               results.addElement (new URL(u, name,
-                                       getHandler0 (u.getProtocol())));
+             int response
+               = ((HttpURLConnection)connection).getResponseCode();
+             if (response/100 != 2)
+               return null;
            }
+
+         if (stream != null)
+           return new RemoteResource(this, name, url, stream, length);
          else
-           {
-             URL p = new URL (u, name, getHandler0 (u.getProtocol()));
-                           
-             InputStream is = p.openStream();
-             if (is != null)
-               {
-                 is.close();
-                 results.addElement (p);
-               }
-           }
-                   
-         // if we get an exception ... try the next path element
-       } catch (IOException x) {
-         continue;
+           return null;
        }
-      }
-       
-    return results.elements ();
+      catch (IOException ioe)
+       {
+         return null;
+       }
+    }
   }
 
-  public URL findResource (String name)
+  /**
+   * A resource from some remote location.
+   */
+  final static class RemoteResource extends Resource
   {
-    for (int i = 0; i < path.size(); i++)
-      {
-       URL u = (URL)path.elementAt (i);
-       
-       try {
-         JarURLConnection conn = (JarURLConnection) info.elementAt (i);
-         
-         if (conn != null)
-           {
-             if (conn.getJarFile().getJarEntry (name) != null)
-               return new URL(u, name, getHandler0 (u.getProtocol()));
-           }
-         else
-           {
-             URL p = new URL (u, name, getHandler0 (u.getProtocol()));
+    final private URL url;
+    final private InputStream stream;
+    final private int length;
 
-             InputStream is = p.openStream();
-             if (is != null)
-               {
-                 is.close();
-                 return p;
-               }
-           }
-       
-         // if we get an exception ... try the next path element
-       } catch (IOException x) {
-         continue;
+    RemoteResource(RemoteURLLoader loader, String name, URL url,
+                  InputStream stream, int length)
+    {
+      super(loader, name);
+      this.url = url;
+      this.stream = stream;
+      this.length = length;
+    }
+
+    InputStream getInputStream() throws IOException
+    {
+      return stream;
+    }
+                        
+    public int getLength()
+    {
+      return length;
+    }
+                
+    public URL getURL()
+    {
+      return url;
+    }
+  }
+
+  /**
+   * A <code>FileURLLoader</code> is a type of <code>URLLoader</code>
+   * only loading from file url.
+   */
+  final static class FileURLLoader extends URLLoader
+  {
+    File dir;   //the canonical file for this file url
+
+    FileURLLoader(URLClassLoader classloader, URL url)
+    {
+      super(classloader, url);
+      // Note that this must be a "file" protocol URL.
+      dir = new File(url.getFile());
+    }
+    
+    /** get resource with the name "name" in the file url */
+    Resource getResource(String name)
+    {
+      File file = new File(dir, name);
+      if (file.exists() && !file.isDirectory())
+       return new FileResource(this, name, file);
+      else
+       return null;
+    }
+  }
+
+  final static class FileResource extends Resource
+  {
+    final File file;
+
+    FileResource(FileURLLoader loader, String name, File file)
+    {
+      super(loader, name);
+      this.file = file;
+    }
+
+    InputStream getInputStream() throws IOException
+    {
+      return new FileInputStream(file);
+    }
+                        
+    public int getLength()
+    {
+      return (int)file.length();
+    }
+                
+    public URL getURL()
+    {
+      try
+       {
+         return new URL(loader.baseURL, name,
+                        loader.classloader.getURLStreamHandler("file"));
        }
-      }
+      catch(MalformedURLException e)
+       {
+         throw new InternalError(e.toString());
+       }
+    }
+  }
+    
+  // Constructors
 
-    return null;
+  /**
+   * Creates a URLClassLoader that gets classes from the supplied URLs.
+   * To determine if this classloader may be created the constructor of
+   * the super class (<code>SecureClassLoader</code>) is called first, which
+   * can throw a SecurityException. Then the supplied URLs are added
+   * in the order given to the URLClassLoader which uses these URLs to
+   * load classes and resources (after using the default parent ClassLoader).
+   *
+   * @exception SecurityException if the SecurityManager disallows the
+   * creation of a ClassLoader.
+   * @param urls Locations that should be searched by this ClassLoader when
+   * resolving Classes or Resources.
+   * @see SecureClassLoader
+   */
+  public URLClassLoader(URL[] urls) throws SecurityException
+  {
+    super();
+    this.factory = null;
+    this.securityContext = null;
+    addURLs(urls);
   }
 
   /**
-   * Finds and loads the class with the specified name from the
-   * URL search path
+   * Private constructor used by the static
+   * <code>newInstance(URL[])</code> method.  Creates an
+   * <code>URLClassLoader</code> without any <code>URL</code>s
+   * yet. This is used to bypass the normal security check for
+   * creating classloaders, but remembers the security context which
+   * will be used when defining classes.  The <code>URL</code>s to
+   * load from must be added by the <code>newInstance()</code> method
+   * in the security context of the caller.
    *
-   * @exception ClassNotFoundException If the class could not be found
+   * @param securityContext the security context of the unprivileged code.
    */
-  protected Class findClass (String name)
-    throws ClassNotFoundException
+  private URLClassLoader(AccessControlContext securityContext)
   {
-    if (name == null)
-      throw new ClassNotFoundException ("null");
+    super();
+    this.factory = null;
+    this.securityContext = securityContext;
+  }
 
-    try 
-      {
-       URL url = getResource (name.replace ('.', '/') + ".class");
+  /**
+   * Creates a <code>URLClassLoader</code> that gets classes from the supplied
+   * <code>URL</code>s.
+   * To determine if this classloader may be created the constructor of
+   * the super class (<code>SecureClassLoader</code>) is called first, which
+   * can throw a SecurityException. Then the supplied URLs are added
+   * in the order given to the URLClassLoader which uses these URLs to
+   * load classes and resources (after using the supplied parent ClassLoader).
+   * @exception SecurityException if the SecurityManager disallows the
+   * creation of a ClassLoader.
+   * @exception SecurityException 
+   * @param urls Locations that should be searched by this ClassLoader when
+   * resolving Classes or Resources.
+   * @param parent The parent class loader used before trying this class
+   * loader.
+   * @see SecureClassLoader
+   */
+  public URLClassLoader(URL[] urls, ClassLoader parent)
+    throws SecurityException
+  {
+    super(parent);
+    this.factory = null;
+    this.securityContext = null;
+    addURLs(urls);
+  }
 
-       if (url == null)
-         throw new ClassNotFoundException (name);
+  /**
+   * Private constructor used by the static
+   * <code>newInstance(URL[])</code> method.  Creates an
+   * <code>URLClassLoader</code> with the given parent but without any
+   * <code>URL</code>s yet. This is used to bypass the normal security
+   * check for creating classloaders, but remembers the security
+   * context which will be used when defining classes.  The
+   * <code>URL</code>s to load from must be added by the
+   * <code>newInstance()</code> method in the security context of the
+   * caller.
+   *
+   * @param securityContext the security context of the unprivileged code.
+   */
+  private URLClassLoader(ClassLoader parent,
+                        AccessControlContext securityContext)
+  {
+    super(parent);
+    this.factory = null;
+    this.securityContext = securityContext;
+  }
 
-       URLConnection connection = url.openConnection ();
-       InputStream is = connection.getInputStream ();
+  /**
+   * Creates a URLClassLoader that gets classes from the supplied URLs.
+   * To determine if this classloader may be created the constructor of
+   * the super class (<CODE>SecureClassLoader</CODE>) is called first, which
+   * can throw a SecurityException. Then the supplied URLs are added
+   * in the order given to the URLClassLoader which uses these URLs to
+   * load classes and resources (after using the supplied parent ClassLoader).
+   * It will use the supplied <CODE>URLStreamHandlerFactory</CODE> to get the
+   * protocol handlers of the supplied URLs.
+   * @exception SecurityException if the SecurityManager disallows the
+   * creation of a ClassLoader.
+   * @exception SecurityException 
+   * @param urls Locations that should be searched by this ClassLoader when
+   * resolving Classes or Resources.
+   * @param parent The parent class loader used before trying this class
+   * loader.
+   * @param factory Used to get the protocol handler for the URLs.
+   * @see SecureClassLoader
+   */
+  public URLClassLoader(URL[] urls,
+                       ClassLoader parent,
+                       URLStreamHandlerFactory factory)
+    throws SecurityException
+  {
+    super(parent);
+    this.securityContext = null;
+    this.factory = factory;
+    addURLs(urls);
 
-       int len = connection.getContentLength ();
-       byte[] data = new byte[len];
+    // If this factory is still not in factoryCache, add it,
+    //   since we only support three protocols so far, 5 is enough 
+    //   for cache initial size
+    synchronized(factoryCache)
+      {
+       if(factory != null && factoryCache.get(factory) == null)
+         factoryCache.put(factory, new HashMap(5));
+      }
+  }
 
-       int left = len;
-       int off  = 0;
-       while (left > 0)
-         {
-           int c = is.read (data, off, len-off);
-           if (c == -1 || c == 0)
-             throw new InternalError ("premature end of file");
-           left -= c;
-           off += c;
-         }
+  // Methods
 
-       // Now construct the CodeSource (if loaded from a jar file)
-       CodeSource source = null;
-       if (url.getProtocol().equals("jar"))
-         {
-           Certificate[] certificates =
-             ((JarURLConnection) connection).getCertificates();
-           String u = url.toExternalForm ();
-           u = u.substring (4); //skip "jar:"
-           int i = u.indexOf ('!');
-           if (i >= 0)
-             u = u.substring (0, i);
-           url = new URL(u);
-
-           source = new CodeSource(url, certificates);
-         }
-       else if (url.getProtocol().equals("file"))
+  /**
+   * Adds a new location to the end of the internal URL store.
+   * @param newUrl the location to add
+   */
+  protected void addURL(URL newUrl)
+  {
+    synchronized(urlloaders)
+      {
+       if (newUrl == null)
+         return; // Silently ignore...
+        
+       // check global cache to see if there're already url loader
+       // for this url
+       URLLoader loader = (URLLoader)urlloaders.get(newUrl);
+       if (loader == null)
          {
-           try
-             {
-               String u = url.toExternalForm();
-               // Skip "file:" and then get canonical directory name.
-               File f = new File(u.substring(5));
-               f = f.getCanonicalFile();
-               url = new URL("file", "", f.getParent());
-               source = new CodeSource (url, null);
-             }
-           catch (IOException ignore)
-             {
-             }
+           String file = newUrl.getFile();
+           // Check that it is not a directory
+           if (!(file.endsWith("/") || file.endsWith(File.separator)))
+             loader = new JarURLLoader(this, newUrl);
+           else // it's a url that point to a jar file
+             if ("file".equals(newUrl.getProtocol()))
+               loader = new FileURLLoader(this, newUrl);
+             else
+               loader = new RemoteURLLoader(this, newUrl);
+
+           // cache it
+           urlloaders.put(newUrl, loader);
          }
 
-       return defineClass (name, data, 0, len, source);
-      } 
-    catch (java.io.IOException x)
-      {
-       throw new ClassNotFoundException(name);
+       urls.add(newUrl);
+       urlinfos.add(loader);
       }
   }
 
+  /**
+   * Adds an array of new locations to the end of the internal URL store.
+   * @param newUrls the locations to add
+   */
+  private void addURLs(URL[] newUrls)
+  {
+    for (int i = 0; i < newUrls.length; i++)
+    {
+      addURL(newUrls[i]);
+    }
+  }
+
   /** 
    * Defines a Package based on the given name and the supplied manifest
    * information. The manifest indicates the tile, version and
@@ -362,36 +731,231 @@ public class URLClassLoader extends SecureClassLoader
     // Look if the Manifest indicates that this package is sealed
     // XXX - most likely not completely correct!
     // Shouldn't we also check the sealed attribute of the complete jar?
-    // http://java.sun.com/products/jdk/1.3/docs/guide/extensions/spec.html#bundled
+    // http://java.sun.com/products/jdk/1.4/docs/guide/extensions/spec.html#bundled
     // But how do we get that jar manifest here?
     String sealed = attr.getValue(Attributes.Name.SEALED);
     if ("false".equals(sealed))
-      {
-       // Make sure that the URL is null so the package is not
-       // sealed.
-       url = null;
-      }
+    {
+      // make sure that the URL is null so the package is not sealed
+      url = null;
+    }
 
     return definePackage(name, specTitle, specVersion, specVendor,
                         implTitle, implVersion, implVendor, url);
   }
 
   /**
-   * Returns the permissions needed to access a particular code source.
-   * These permissions includes those returned by
-   * <CODE>SecureClassLoader.getPermissions</CODE> and the actual permissions
-   * to access the objects referenced by the URL of the code source.
-   * The extra permissions added depend on the protocol and file portion of
-   * the URL in the code source. If the URL has the "file" protocol ends with
-   * a / character then it must be a directory and a file Permission to read
-   * everthing in that directory and all subdirectories is added. If the URL
-   * had the "file" protocol and doesn't end with a / character then it must
-   * be a normal file and a file permission to read that file is added. If the
-   * URL has any other protocol then a socket permission to connect and accept
-   * connections from the host portion of the URL is added.
+   * Finds (the first) class by name from one of the locations. The locations
+   * are searched in the order they were added to the URLClassLoader.
+   *
+   * @param className the classname to find
+   * @exception ClassNotFoundException when the class could not be found or
+   * loaded
+   * @return a Class object representing the found class
+   */
+  protected Class findClass(final String className)
+    throws ClassNotFoundException
+  {
+    // Just try to find the resource by the (almost) same name
+    String resourceName = className.replace('.', '/') + ".class";
+    Resource resource = findURLResource(resourceName);
+    if (resource == null)
+      throw new ClassNotFoundException(className + " not found in " + urls);
+
+    // Try to read the class data, create the CodeSource, Package and
+    // construct the class (and watch out for those nasty IOExceptions)
+    try
+      {
+       byte [] data;
+       InputStream in = resource.getInputStream();
+       int length = resource.getLength();
+       if (length != -1)
+         {
+           // We know the length of the data.
+           // Just try to read it in all at once
+           data = new byte[length];
+           int pos = 0;
+           while(length - pos > 0)
+             {
+               int len = in.read(data, pos, length - pos);
+               if (len == -1)
+                 throw new EOFException("Not enough data reading from: "
+                                        + in);
+               pos += len;
+             }
+         }
+       else
+         {
+           // We don't know the data length.
+           // Have to read it in chunks.
+           ByteArrayOutputStream out = new ByteArrayOutputStream(4096);
+           byte b[] = new byte[4096];
+           int l = 0;
+           while (l != -1)
+             {
+               l = in.read(b);
+               if (l != -1)
+                 out.write(b, 0, l);
+             }
+           data = out.toByteArray();
+         }
+       final byte[] classData = data;
+
+       // Now get the CodeSource
+       final CodeSource source = resource.getCodeSource();
+       
+       // Find out package name
+       String packageName = null;
+       int lastDot = className.lastIndexOf('.');
+       if (lastDot != -1)
+         packageName = className.substring(0, lastDot);
+       
+       if (packageName != null && getPackage(packageName) == null)
+         {
+           // define the package
+           Manifest manifest = resource.loader.getManifest();
+           if (manifest == null)
+             definePackage(packageName,
+                           null, null, null, null, null, null, null);
+           else
+             definePackage(packageName, manifest, resource.loader.baseURL);
+         }
+       
+       // And finally construct the class!
+       SecurityManager sm = System.getSecurityManager();
+       if (sm != null && securityContext != null)
+         {
+           return (Class)AccessController.doPrivileged
+             (new PrivilegedAction()
+               {
+                 public Object run()
+                 {
+                   return defineClass(className, classData,
+                                      0, classData.length,
+                                      source);
+                 }
+               }, securityContext);
+         }
+       else
+         return defineClass(className, classData,
+                            0, classData.length,
+                            source);
+      }
+    catch (IOException ioe)
+      {
+       throw new ClassNotFoundException(className, ioe);
+      }
+  }
+
+  /**
+   * Finds the first occurrence of a resource that can be found. The locations
+   * are searched in the order they were added to the URLClassLoader.
+   *
+   * @param resourceName the resource name to look for
+   * @return the URLResource for the resource if found, null otherwise
+   */
+  private Resource findURLResource(String resourceName)
+  {
+    int max = urls.size();
+    for (int i = 0; i < max; i++)
+      {
+       URLLoader loader = (URLLoader)urlinfos.elementAt(i);
+       if (loader == null)
+         continue;
+       
+       Resource resource = loader.getResource(resourceName);
+       if (resource != null)
+         return resource;
+      }
+    return null;
+  }
+
+  /**
+   * Finds the first occurrence of a resource that can be found.
+   *
+   * @param resourceName the resource name to look for
+   * @return the URL if found, null otherwise
+   */
+  public URL findResource(String resourceName)
+  {
+    Resource resource = findURLResource(resourceName);
+    if (resource != null)
+      return resource.getURL();
+    
+    // Resource not found
+    return null;
+  }
+
+  /**
+   * If the URLStreamHandlerFactory has been set this return the appropriate
+   * URLStreamHandler for the given protocol, if not set returns null.
+   *
+   * @param protocol the protocol for which we need a URLStreamHandler
+   * @return the appropriate URLStreamHandler or null
+   */
+  URLStreamHandler getURLStreamHandler(String protocol)
+  {
+    if (factory == null)
+      return null;
+
+    URLStreamHandler handler;
+    synchronized (factoryCache)
+      {
+       // check if there're handler for the same protocol in cache
+       HashMap cache = (HashMap)factoryCache.get(factory);
+       handler = (URLStreamHandler)cache.get(protocol);
+       if(handler == null)
+         {
+           // add it to cache
+           handler = factory.createURLStreamHandler(protocol);
+           cache.put(protocol, handler);
+         }
+      }
+    return handler;
+  }
+
+  /**
+   * Finds all the resources with a particular name from all the locations.
+   *
+   * @exception IOException when an error occurs accessing one of the
+   * locations
+   * @param resourceName the name of the resource to lookup
+   * @return a (possible empty) enumeration of URLs where the resource can be
+   * found
+   */
+  public Enumeration findResources(String resourceName) throws IOException
+  {
+    Vector resources = new Vector();
+    int max = urls.size();
+    for (int i = 0; i < max; i++)
+      {
+       URLLoader loader = (URLLoader)urlinfos.elementAt(i);
+       Resource resource = loader.getResource(resourceName);
+       if (resource != null)
+         resources.add(resource.getURL());
+      }
+    return resources.elements();
+  }
+
+  /**
+   * Returns the permissions needed to access a particular code
+   * source.  These permissions includes those returned by
+   * <code>SecureClassLoader.getPermissions()</code> and the actual
+   * permissions to access the objects referenced by the URL of the
+   * code source.  The extra permissions added depend on the protocol
+   * and file portion of the URL in the code source. If the URL has
+   * the "file" protocol ends with a '/' character then it must be a
+   * directory and a file Permission to read everything in that
+   * directory and all subdirectories is added. If the URL had the
+   * "file" protocol and doesn't end with a '/' character then it must
+   * be a normal file and a file permission to read that file is
+   * added. If the <code>URL</code> has any other protocol then a
+   * socket permission to connect and accept connections from the host
+   * portion of the URL is added.
+   *
    * @param source The codesource that needs the permissions to be accessed
    * @return the collection of permissions needed to access the code resource
-   * @see SecureClassLoader.getPermissions()
+   * @see java.security.SecureClassLoader#getPermissions()
    */
   protected PermissionCollection getPermissions(CodeSource source)
   {
@@ -408,7 +972,7 @@ public class URLClassLoader extends SecureClassLoader
       {
        String file = url.getFile();
        // If the file end in / it must be an directory
-       if (file.endsWith("/"))
+       if (file.endsWith("/") || file.endsWith(File.separator))
          {
            // Grant permission to read everything in that directory and
            // all subdirectories
@@ -424,35 +988,81 @@ public class URLClassLoader extends SecureClassLoader
     else
       {
        // Grant permission to connect to and accept connections from host
-         String host = url.getHost();
+       String host = url.getHost();
+       if (host != null)
          permissions.add(new SocketPermission(host, "connect,accept"));
       }
 
     return permissions;
   }
+    
+  /**
+   * Returns all the locations that this class loader currently uses the
+   * resolve classes and resource. This includes both the initially supplied
+   * URLs as any URLs added later by the loader.
+   * @return All the currently used URLs
+   */
+  public URL[] getURLs()
+  {
+    return (URL[]) urls.toArray(new URL[urls.size()]);
+  }
 
   /**
-   * Creates a new instance of a URLClassLoader that gets classes from the
-   * supplied URLs. This class loader will have as parent the standard
-   * system class loader.
-   * @param urls the initial URLs used to resolve classes and resources
+   * Creates a new instance of a <code>URLClassLoader</code> that gets
+   * classes from the supplied <code>URL</code>s. This class loader
+   * will have as parent the standard system class loader.
+   *
+   * @param urls the initial URLs used to resolve classes and
+   * resources
+   *
+   * @exception SecurityException when the calling code does not have
+   * permission to access the given <code>URL</code>s
    */
-  public static URLClassLoader newInstance(URL[] urls) throws
-    SecurityException
+  public static URLClassLoader newInstance(URL urls[])
+    throws SecurityException
   {
-    return new URLClassLoader(urls);
+    return newInstance(urls, null);
   }
 
   /**
-   * Creates a new instance of a URLClassLoader that gets classes from the
-   * supplied URLs and with the supplied loader as parent class loader.
-   * @param urls the initial URLs used to resolve classes and resources
+   * Creates a new instance of a <code>URLClassLoader</code> that gets
+   * classes from the supplied <code>URL</code>s and with the supplied
+   * loader as parent class loader.
+   *
+   * @param urls the initial URLs used to resolve classes and
+   * resources
    * @param parent the parent class loader
+   *
+   * @exception SecurityException when the calling code does not have
+   * permission to access the given <code>URL</code>s
    */
-  public static URLClassLoader newInstance(URL[] urls,
-                                          ClassLoader parent)
+  public static URLClassLoader newInstance(URL urls[],
+                                          final ClassLoader parent)
     throws SecurityException
   {
-    return new URLClassLoader(urls, parent);
+    SecurityManager sm = System.getSecurityManager();
+    if (sm == null)
+      return new URLClassLoader(urls, parent);
+    else
+      {
+       final Object securityContext = sm.getSecurityContext();
+       // XXX - What to do with anything else then an AccessControlContext?
+       if (!(securityContext instanceof AccessControlContext))
+         throw new SecurityException
+           ("securityContext must be AccessControlContext: "
+            + securityContext);
+       
+       URLClassLoader loader =
+         (URLClassLoader)AccessController.doPrivileged(new PrivilegedAction()
+           {
+             public Object run()
+             {
+               return new URLClassLoader
+                 (parent, (AccessControlContext)securityContext);
+             }
+           });
+       loader.addURLs(urls);
+       return loader;
+      }
   }
 }