| /* URLClassLoader.java -- ClassLoader that loads classes from one or more URLs |
| Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006 |
| Free Software Foundation, Inc. |
| |
| This file is part of GNU Classpath. |
| |
| 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., 51 Franklin Street, Fifth Floor, Boston, MA |
| 02110-1301 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.ByteArrayOutputStream; |
| import java.io.EOFException; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FilePermission; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.security.AccessControlContext; |
| import java.security.AccessController; |
| import java.security.CodeSource; |
| import java.security.PermissionCollection; |
| import java.security.PrivilegedAction; |
| import java.security.SecureClassLoader; |
| import java.security.cert.Certificate; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.StringTokenizer; |
| import java.util.Vector; |
| import java.util.jar.Attributes; |
| import java.util.jar.JarEntry; |
| import java.util.jar.JarFile; |
| import java.util.jar.Manifest; |
| import gnu.gcj.runtime.SharedLibHelper; |
| import gnu.gcj.Core; |
| import gnu.java.net.protocol.core.CoreInputStream; |
| |
| /** |
| * 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 |
| { |
| // 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 |
| * create handlers each time the same protocol comes. |
| */ |
| private static HashMap factoryCache = new HashMap(5); |
| |
| // Instance variables |
| |
| /** Locations to load classes from */ |
| private final Vector urls = new Vector(); |
| |
| /** |
| * Store pre-parsed information for each url into this vector: each |
| * element is a URL loader. A jar file has its own class-path |
| * attribute which adds to the URLs that will be searched, but this |
| * does not add to the list of urls. |
| */ |
| private final Vector urlinfos = new Vector(); |
| |
| /** Factory used to get the protocol handlers of the URLs */ |
| private final URLStreamHandlerFactory factory; |
| |
| /** |
| * 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. |
| */ |
| private final AccessControlContext securityContext; |
| |
| // Helper classes |
| |
| /** |
| * A <code>URLLoader</code> contains all logic to load resources from a |
| * given base <code>URL</code>. |
| */ |
| abstract static class URLLoader |
| { |
| /** |
| * Our classloader to get info from if needed. |
| */ |
| final URLClassLoader classloader; |
| |
| /** |
| * The base URL from which all resources are loaded. |
| */ |
| final URL baseURL; |
| |
| /** |
| * 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, baseURL, baseURL); |
| } |
| |
| URLLoader(URLClassLoader classloader, URL baseURL, URL overrideURL) |
| { |
| this.classloader = classloader; |
| this.baseURL = baseURL; |
| this.noCertCodeSource = new CodeSource(overrideURL, null); |
| } |
| |
| /** |
| * Returns a <code>Class</code> loaded by this |
| * <code>URLLoader</code>, or <code>null</code> when this loader |
| * either can't load the class or doesn't know how to load classes |
| * at all. |
| */ |
| Class getClass(String className) |
| { |
| return 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; |
| } |
| |
| Vector getClassPath() |
| { |
| return null; |
| } |
| } |
| |
| /** |
| * 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>. |
| */ |
| abstract static class Resource |
| { |
| final URLLoader loader; |
| |
| Resource(URLLoader loader) |
| { |
| this.loader = loader; |
| } |
| |
| /** |
| * 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(); |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * A <code>JarURLLoader</code> is a type of <code>URLLoader</code> |
| * only loading from jar url. |
| */ |
| static 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, URL baseURL, |
| URL absoluteUrl) |
| { |
| super(classloader, baseURL, absoluteUrl); |
| |
| // 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(); |
| |
| this.classPath = null; |
| URL baseJarURL = null; |
| JarFile jarfile = null; |
| try |
| { |
| baseJarURL = |
| new URL(null, jarURL, classloader.getURLStreamHandler("jar")); |
| |
| jarfile = |
| ((JarURLConnection) baseJarURL.openConnection()).getJarFile(); |
| |
| Manifest manifest; |
| Attributes attributes; |
| String classPathString; |
| |
| if ((manifest = jarfile.getManifest()) != null |
| && (attributes = manifest.getMainAttributes()) != null |
| && ((classPathString |
| = attributes.getValue(Attributes.Name.CLASS_PATH)) |
| != null)) |
| { |
| this.classPath = new Vector(); |
| |
| StringTokenizer st = new StringTokenizer(classPathString, " "); |
| while (st.hasMoreElements ()) |
| { |
| String e = st.nextToken (); |
| try |
| { |
| URL url = new URL(baseURL, e); |
| this.classPath.add(url); |
| } |
| catch (java.net.MalformedURLException xx) |
| { |
| // Give up |
| } |
| } |
| } |
| } |
| catch (IOException ioe) |
| { |
| /* ignored */ |
| } |
| |
| 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; |
| |
| if (name.startsWith("/")) |
| name = name.substring(1); |
| |
| JarEntry je = jarfile.getJarEntry(name); |
| if (je != null) |
| return new JarURLResource(this, name, je); |
| else |
| return null; |
| } |
| |
| Manifest getManifest() |
| { |
| try |
| { |
| return (jarfile == null) ? null : jarfile.getManifest(); |
| } |
| catch (IOException ioe) |
| { |
| return null; |
| } |
| } |
| |
| Vector getClassPath() |
| { |
| return classPath; |
| } |
| } |
| |
| static final class JarURLResource extends Resource |
| { |
| private final JarEntry entry; |
| private final String name; |
| |
| JarURLResource(JarURLLoader loader, String name, JarEntry entry) |
| { |
| super(loader); |
| this.entry = entry; |
| this.name = name; |
| } |
| |
| InputStream getInputStream() throws IOException |
| { |
| return ((JarURLLoader) loader).jarfile.getInputStream(entry); |
| } |
| |
| int getLength() |
| { |
| return (int) entry.getSize(); |
| } |
| |
| Certificate[] getCertificates() |
| { |
| // We have to get the entry from the jar file again, because the |
| // certificates will not be available until the entire entry has |
| // been read. |
| return ((JarEntry) ((JarURLLoader) loader).jarfile.getEntry(name)) |
| .getCertificates(); |
| } |
| |
| URL getURL() |
| { |
| try |
| { |
| return new URL(((JarURLLoader) loader).baseJarURL, name, |
| loader.classloader.getURLStreamHandler("jar")); |
| } |
| catch (MalformedURLException e) |
| { |
| InternalError ie = new InternalError(); |
| ie.initCause(e); |
| throw ie; |
| } |
| } |
| } |
| |
| /** |
| * Loader for remote directories. |
| */ |
| static final class RemoteURLLoader extends URLLoader |
| { |
| private final String protocol; |
| |
| 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) |
| { |
| int response = |
| ((HttpURLConnection) connection).getResponseCode(); |
| if (response / 100 != 2) |
| return null; |
| } |
| |
| if (stream != null) |
| return new RemoteResource(this, name, url, stream, length); |
| else |
| return null; |
| } |
| catch (IOException ioe) |
| { |
| return null; |
| } |
| } |
| } |
| |
| /** |
| * A resource from some remote location. |
| */ |
| static final class RemoteResource extends Resource |
| { |
| private final URL url; |
| private final InputStream stream; |
| private final int length; |
| |
| RemoteResource(RemoteURLLoader loader, String name, URL url, |
| InputStream stream, int length) |
| { |
| super(loader); |
| 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>SoURLLoader</code> is a type of <code>URLLoader</code> |
| * that loads classes and resources from a shared library. |
| */ |
| final static class SoURLLoader extends URLLoader |
| { |
| SharedLibHelper helper; |
| |
| SoURLLoader(URLClassLoader classloader, URL url) |
| { |
| this(classloader, url, url); |
| } |
| |
| SoURLLoader(URLClassLoader classloader, URL url, URL overrideURL) |
| { |
| super(classloader, url, overrideURL); |
| helper = SharedLibHelper.findHelper(classloader, url.getFile(), |
| noCertCodeSource, true); |
| } |
| |
| Class getClass(String className) |
| { |
| return helper.findClass(className); |
| } |
| |
| Resource getResource(String name) |
| { |
| URL url = helper.findResource(name); |
| if (url == null) |
| return null; |
| return new SoResource(this, url); |
| } |
| } |
| |
| final static class SoResource extends Resource |
| { |
| SoResource(SoURLLoader loader, URL url) |
| { |
| super(loader); |
| this.url = url; |
| } |
| |
| InputStream getInputStream() throws IOException |
| { |
| URLConnection conn = url.openConnection(); |
| return conn.getInputStream(); |
| } |
| |
| public int getLength() |
| { |
| // FIXME we could find this by asking the core object. |
| return -1; |
| } |
| |
| public URL getURL () |
| { |
| return url; |
| } |
| |
| final URL url; |
| } |
| |
| /** |
| * A <code>FileURLLoader</code> is a type of <code>URLLoader</code> |
| * only loading from file url. |
| */ |
| static final class FileURLLoader extends URLLoader |
| { |
| File dir; //the file for this file url |
| |
| FileURLLoader(URLClassLoader classloader, URL url, URL absoluteUrl) |
| { |
| super(classloader, url, absoluteUrl); |
| dir = new File(absoluteUrl.getFile()); |
| } |
| |
| /** get resource with the name "name" in the file url */ |
| Resource getResource(String name) |
| { |
| try |
| { |
| File file = new File(dir, name).getCanonicalFile(); |
| if (file.exists() && !file.isDirectory()) |
| return new FileResource(this, file); |
| } |
| catch (IOException e) |
| { |
| // Fall through... |
| } |
| return null; |
| } |
| } |
| |
| static final class FileResource extends Resource |
| { |
| final File file; |
| |
| FileResource(FileURLLoader loader, File file) |
| { |
| super(loader); |
| this.file = file; |
| } |
| |
| InputStream getInputStream() throws IOException |
| { |
| // Delegate to the URL content handler mechanism to retrieve an |
| // HTML representation of the directory listing if a directory |
| if (file.isDirectory()) |
| { |
| URL url = getURL(); |
| return url.openStream(); |
| } |
| // Otherwise simply return a FileInputStream |
| return new FileInputStream(file); |
| } |
| |
| public int getLength() |
| { |
| // Delegate to the URL content handler mechanism to retrieve the |
| // length of the HTML representation of the directory listing if |
| // a directory, or -1 if an exception occurs opening the directory. |
| if (file.isDirectory()) |
| { |
| URL url = getURL(); |
| try |
| { |
| URLConnection connection = url.openConnection(); |
| return connection.getContentLength(); |
| } |
| catch (IOException e) |
| { |
| return -1; |
| } |
| } |
| // Otherwise simply return the file length |
| return (int) file.length(); |
| } |
| |
| public URL getURL() |
| { |
| try |
| { |
| return file.toURL(); |
| } |
| catch (MalformedURLException e) |
| { |
| InternalError ie = new InternalError(); |
| ie.initCause(e); |
| throw ie; |
| } |
| } |
| } |
| |
| /** |
| * A <code>CoreURLLoader</code> is a type of <code>URLLoader</code> |
| * only loading from core url. |
| */ |
| static final class CoreURLLoader extends URLLoader |
| { |
| private String dir; |
| |
| CoreURLLoader(URLClassLoader classloader, URL url) |
| { |
| super(classloader, url); |
| dir = baseURL.getFile(); |
| } |
| |
| /** get resource with the name "name" in the core url */ |
| Resource getResource(String name) |
| { |
| Core core = Core.find (dir + name); |
| if (core != null) |
| return new CoreResource(this, name, core); |
| return null; |
| } |
| } |
| |
| static final class CoreResource extends Resource |
| { |
| private final Core core; |
| private final String name; |
| |
| CoreResource(CoreURLLoader loader, String name, Core core) |
| { |
| super(loader); |
| this.core = core; |
| this.name = name; |
| } |
| |
| InputStream getInputStream() throws IOException |
| { |
| return new CoreInputStream(core); |
| } |
| |
| public int getLength() |
| { |
| return core.length; |
| } |
| |
| public URL getURL() |
| { |
| try |
| { |
| return new URL(loader.baseURL, name, |
| loader.classloader.getURLStreamHandler("core")); |
| } |
| catch (MalformedURLException e) |
| { |
| InternalError ie = new InternalError(); |
| ie.initCause(e); |
| throw ie; |
| } |
| } |
| } |
| |
| // Constructors |
| |
| /** |
| * 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). |
| * |
| * @param urls Locations that should be searched by this ClassLoader when |
| * resolving Classes or Resources. |
| * @exception SecurityException if the SecurityManager disallows the |
| * creation of a ClassLoader. |
| * @see SecureClassLoader |
| */ |
| public URLClassLoader(URL[] urls) throws SecurityException |
| { |
| super(); |
| this.factory = null; |
| this.securityContext = null; |
| addURLs(urls); |
| } |
| |
| /** |
| * 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). |
| * @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. |
| * @exception SecurityException if the SecurityManager disallows the |
| * creation of a ClassLoader. |
| * @exception SecurityException |
| * @see SecureClassLoader |
| */ |
| public URLClassLoader(URL[] urls, ClassLoader parent) |
| throws SecurityException |
| { |
| super(parent); |
| this.factory = null; |
| this.securityContext = null; |
| addURLs(urls); |
| } |
| |
| // Package-private to avoid a trampoline constructor. |
| /** |
| * Package-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. |
| */ |
| URLClassLoader(ClassLoader parent, AccessControlContext securityContext) |
| { |
| super(parent); |
| this.factory = null; |
| this.securityContext = securityContext; |
| } |
| |
| /** |
| * 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. |
| * @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. |
| * @exception SecurityException if the SecurityManager disallows the |
| * creation of a ClassLoader. |
| * @exception SecurityException |
| * @see SecureClassLoader |
| */ |
| public URLClassLoader(URL[] urls, ClassLoader parent, |
| URLStreamHandlerFactory factory) |
| throws SecurityException |
| { |
| super(parent); |
| this.securityContext = null; |
| this.factory = factory; |
| addURLs(urls); |
| |
| // 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)); |
| } |
| } |
| |
| // Methods |
| |
| /** |
| * Adds a new location to the end of the internal URL store. |
| * @param newUrl the location to add |
| */ |
| protected void addURL(URL newUrl) |
| { |
| urls.add(newUrl); |
| addURLImpl(newUrl); |
| } |
| |
| private void addURLImpl(URL newUrl) |
| { |
| synchronized (this) |
| { |
| if (newUrl == null) |
| return; // Silently ignore... |
| |
| // 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) |
| { |
| 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; |
| } |
| |
| // Check that it is not a directory |
| if ("gcjlib".equals(protocol)) |
| loader = new SoURLLoader(this, newUrl); |
| else if (! (file.endsWith("/") || file.endsWith(File.separator))) |
| loader = new JarURLLoader(this, newUrl, absoluteURL); |
| else if ("file".equals(protocol)) |
| loader = new FileURLLoader(this, newUrl, absoluteURL); |
| else if ("core".equals(protocol)) |
| loader = new CoreURLLoader(this, newUrl); |
| else |
| loader = new RemoteURLLoader(this, newUrl); |
| |
| // Cache it. |
| urlloaders.put(newUrl, loader); |
| } |
| |
| urlinfos.add(loader); |
| |
| 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); |
| } |
| } |
| |
| } |
| } |
| |
| /** |
| * Adds an array of new locations to the end of the internal URL |
| * store. Called from the the constructors. Should not call to the |
| * protected addURL() method since that can be overridden and |
| * subclasses are not yet in a good state at this point. |
| * jboss 4.0.3 for example depends on this. |
| * |
| * @param newUrls the locations to add |
| */ |
| private void addURLs(URL[] newUrls) |
| { |
| for (int i = 0; i < newUrls.length; i++) |
| { |
| urls.add(newUrls[i]); |
| addURLImpl(newUrls[i]); |
| } |
| } |
| |
| /** |
| * Look in both Attributes for a given value. The first Attributes |
| * object, if not null, has precedence. |
| */ |
| private String getAttributeValue(Attributes.Name name, Attributes first, |
| Attributes second) |
| { |
| String result = null; |
| if (first != null) |
| result = first.getValue(name); |
| if (result == null) |
| result = second.getValue(name); |
| return result; |
| } |
| |
| /** |
| * Defines a Package based on the given name and the supplied manifest |
| * information. The manifest indicates the title, version and |
| * vendor information of the specification and implementation and whether the |
| * package is sealed. If the Manifest indicates that the package is sealed |
| * then the Package will be sealed with respect to the supplied URL. |
| * |
| * @param name The name of the package |
| * @param manifest The manifest describing the specification, |
| * implementation and sealing details of the package |
| * @param url the code source url to seal the package |
| * @return the defined Package |
| * @throws IllegalArgumentException If this package name already exists |
| * in this class loader |
| */ |
| protected Package definePackage(String name, Manifest manifest, URL url) |
| throws IllegalArgumentException |
| { |
| // Compute the name of the package as it may appear in the |
| // Manifest. |
| StringBuffer xform = new StringBuffer(name); |
| for (int i = xform.length () - 1; i >= 0; --i) |
| if (xform.charAt(i) == '.') |
| xform.setCharAt(i, '/'); |
| xform.append('/'); |
| String xformName = xform.toString(); |
| |
| Attributes entryAttr = manifest.getAttributes(xformName); |
| Attributes attr = manifest.getMainAttributes(); |
| |
| String specTitle |
| = getAttributeValue(Attributes.Name.SPECIFICATION_TITLE, |
| entryAttr, attr); |
| String specVersion |
| = getAttributeValue(Attributes.Name.SPECIFICATION_VERSION, |
| entryAttr, attr); |
| String specVendor |
| = getAttributeValue(Attributes.Name.SPECIFICATION_VENDOR, |
| entryAttr, attr); |
| String implTitle |
| = getAttributeValue(Attributes.Name.IMPLEMENTATION_TITLE, |
| entryAttr, attr); |
| String implVersion |
| = getAttributeValue(Attributes.Name.IMPLEMENTATION_VERSION, |
| entryAttr, attr); |
| String implVendor |
| = getAttributeValue(Attributes.Name.IMPLEMENTATION_VENDOR, |
| entryAttr, attr); |
| |
| // 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.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; |
| |
| return definePackage(name, |
| specTitle, specVendor, specVersion, |
| implTitle, implVendor, implVersion, |
| url); |
| } |
| |
| /** |
| * 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"; |
| int max = urlinfos.size(); |
| Resource resource = null; |
| for (int i = 0; i < max && resource == null; i++) |
| { |
| URLLoader loader = (URLLoader)urlinfos.elementAt(i); |
| if (loader == null) |
| continue; |
| |
| Class k = loader.getClass(className); |
| if (k != null) |
| return k; |
| |
| resource = loader.getResource(resourceName); |
| } |
| if (resource == null) |
| { |
| String message = className + " not found"; |
| if (runtimeInitialized()) |
| message += " in " + this; |
| throw new ClassNotFoundException(message); |
| } |
| |
| // 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(); |
| try |
| { |
| 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(); |
| } |
| } |
| finally |
| { |
| in.close(); |
| } |
| 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(); |
| Class result = null; |
| if (sm != null && securityContext != null) |
| { |
| result = (Class)AccessController.doPrivileged |
| (new PrivilegedAction() |
| { |
| public Object run() |
| { |
| return defineClass(className, classData, |
| 0, classData.length, |
| source); |
| } |
| }, securityContext); |
| } |
| else |
| result = defineClass(className, classData, 0, classData.length, source); |
| |
| // Avoid NullPointerExceptions. |
| Certificate[] resourceCertificates = resource.getCertificates(); |
| if(resourceCertificates != null) |
| super.setSigners(result, resourceCertificates); |
| |
| return result; |
| } |
| catch (IOException ioe) |
| { |
| ClassNotFoundException cnfe; |
| cnfe = new ClassNotFoundException(className + " not found in " + this); |
| cnfe.initCause(ioe); |
| throw cnfe; |
| } |
| } |
| |
| // Cached String representation of this URLClassLoader |
| private String thisString; |
| |
| /** |
| * Returns a String representation of this URLClassLoader giving the |
| * actual Class name, the URLs that are searched and the parent |
| * ClassLoader. |
| */ |
| public String toString() |
| { |
| synchronized (this) |
| { |
| if (thisString == null) |
| { |
| StringBuffer sb = new StringBuffer(); |
| sb.append(this.getClass().getName()); |
| sb.append("{urls=[" ); |
| URL[] thisURLs = getURLs(); |
| for (int i = 0; i < thisURLs.length; i++) |
| { |
| sb.append(thisURLs[i]); |
| if (i < thisURLs.length - 1) |
| sb.append(','); |
| } |
| sb.append(']'); |
| sb.append(", parent="); |
| sb.append(getParent()); |
| sb.append('}'); |
| thisString = sb.toString(); |
| } |
| return thisString; |
| } |
| } |
| |
| /** |
| * 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 = urlinfos.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. |
| * |
| * @param resourceName the name of the resource to lookup |
| * @return a (possible empty) enumeration of URLs where the resource can be |
| * found |
| * @exception IOException when an error occurs accessing one of the |
| * locations |
| */ |
| public Enumeration findResources(String resourceName) |
| throws IOException |
| { |
| Vector resources = new Vector(); |
| int max = urlinfos.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 java.security.SecureClassLoader#getPermissions(CodeSource) |
| */ |
| protected PermissionCollection getPermissions(CodeSource source) |
| { |
| // XXX - This implementation does exactly as the Javadoc describes. |
| // But maybe we should/could use URLConnection.getPermissions()? |
| // First get the permissions that would normally be granted |
| PermissionCollection permissions = super.getPermissions(source); |
| |
| // Now add any extra permissions depending on the URL location. |
| URL url = source.getLocation(); |
| String protocol = url.getProtocol(); |
| if (protocol.equals("file")) |
| { |
| String file = url.getFile(); |
| |
| // If the file end in / it must be an directory. |
| if (file.endsWith("/") || file.endsWith(File.separator)) |
| { |
| // Grant permission to read everything in that directory and |
| // all subdirectories. |
| permissions.add(new FilePermission(file + "-", "read")); |
| } |
| else |
| { |
| // It is a 'normal' file. |
| // Grant permission to access that file. |
| permissions.add(new FilePermission(file, "read")); |
| } |
| } |
| else |
| { |
| // Grant permission to connect to and accept connections from host |
| 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 <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 |
| * |
| * @return the 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) |
| throws SecurityException |
| { |
| return newInstance(urls, null); |
| } |
| |
| /** |
| * 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 |
| * |
| * @return the 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, final ClassLoader parent) |
| throws SecurityException |
| { |
| 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; |
| } |
| } |
| |
| /** |
| * Tell whether runtime initialization is complete. |
| * |
| * @return whether runtime initialization is complete. |
| */ |
| private static native boolean runtimeInitialized(); |
| } |