/*
* This file is modified by Ivan Maidanski New instances can be created by two static
* Normally creating a Open issues:
* URL
s this class
* loader will retrieve classes and resources by fetching them from
* possible remote locations. Each URL
is searched in
* order in which it was added. If the file portion of the
* URL
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.
*
* newInstance()
methods or by three public
* contructors. Both ways give the option to supply an initial array
* of URL
s and (optionally) a parent classloader (that is
* different from the standard system class loader).URLClassLoader
throws a
* SecurityException
if a SecurityManager
is
* installed and the checkCreateClassLoader()
method does
* not return true. But the newInstance()
methods may be
* used by any code as long as it has permission to acces the given
* URL
s. URLClassLoaders
created by the
* newInstance()
methods also explicitly call the
* checkPackageAccess()
method of
* SecurityManager
if one is installed before trying to
* load a class. Note that only subclasses of
* URLClassLoader
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 getURLs()
method.
*
*
* definePackage()
and sealing work
* precisely?newInstance()
but do we have to use it in more
* places?URLStreamHandler
s has not been tested.
newInstance()
* or null when created through a normal constructor or when no
* SecurityManager
was installed.
*/
private final AccessControlContext securityContext;
// Helper classes
/**
* Creates a URLClassLoader that gets classes from the supplied URLs.
* To determine if this classloader may be created the constructor of
* the super class (SecureClassLoader
) 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 URLClassLoader
that gets classes from the supplied
* URL
s.
* To determine if this classloader may be created the constructor of
* the super class (SecureClassLoader
) 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
* newInstance(URL[])
method. Creates an
* URLClassLoader
with the given parent but without any
* URL
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
* URL
s to load from must be added by the
* newInstance()
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 (SecureClassLoader
) 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 URLStreamHandlerFactory
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;
// If this factory is not yet in factoryCache, add it.
factoryCache.add(factory);
addURLs(urls);
}
// 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;
// Create a loader for this URL.
URLLoader loader = null;
String file = newUrl.getFile();
try
{
file = gnu.java.net.protocol.file.Connection.unquote(file);
}
catch (MalformedURLException e)
{
// ignore
}
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);
try
{
absoluteURL = dir.getCanonicalFile().toURL();
}
catch (IOException ignore)
{
try
{
absoluteURL = dir.getAbsoluteFile().toURL();
}
catch (MalformedURLException _)
{
// This really should not happen.
absoluteURL = newUrl;
}
}
}
else
{
// This doesn't hurt, and it simplifies the logic a
// little.
absoluteURL = newUrl;
}
// First see if we can find a handler with the correct name.
try
{
Class handler = Class.forName(URL_LOADER_PREFIX + protocol);
Class[] argTypes = new Class[] { URLClassLoader.class,
URLStreamHandlerCache.class,
URLStreamHandlerFactory.class,
URL.class,
URL.class };
Constructor k = handler.getDeclaredConstructor(argTypes);
loader
= (URLLoader) k.newInstance(new Object[] { this,
factoryCache,
factory,
newUrl,
absoluteURL });
}
catch (ClassNotFoundException ignore)
{
// Fall through.
}
catch (NoSuchMethodException nsme)
{
// Programming error in the class library.
InternalError vme
= new InternalError("couldn't find URLLoader constructor");
vme.initCause(nsme);
throw vme;
}
catch (InstantiationException inste)
{
// Programming error in the class library.
InternalError vme
= new InternalError("couldn't instantiate URLLoader");
vme.initCause(inste);
throw vme;
}
catch (InvocationTargetException ite)
{
// Programming error in the class library.
InternalError vme
= new InternalError("error instantiating URLLoader");
vme.initCause(ite);
throw vme;
}
catch (IllegalAccessException illae)
{
// Programming error in the class library.
InternalError vme
= new InternalError("invalid access to URLLoader");
vme.initCause(illae);
throw vme;
}
if (loader == null)
{
// If it is not a directory, use the jar loader.
if (! (file.endsWith("/") || file.endsWith(File.separator)))
loader = new JarURLLoader(this, factoryCache, factory,
newUrl, absoluteURL);
else if ("file".equals(protocol))
loader = new FileURLLoader(this, factoryCache, factory,
newUrl, absoluteURL);
else
loader = new RemoteURLLoader(this, factoryCache, factory,
newUrl);
}
urlinfos.add(loader);
ArrayList extra = loader.getClassPath();
if (extra != null)
urlinfos.addAll(extra);
}
}
/**
* 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)
throw new ClassNotFoundException(className + " not found in " + this);
// 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.getLoader().getManifest();
if (manifest == null)
definePackage(packageName, null, null, null, null, null, null,
null);
else
definePackage(packageName, manifest,
resource.getLoader().getBaseURL());
}
// 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)
{
throw new ClassNotFoundException(className + " not found in " + this, ioe);
}
}
// 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;
}
/**
* 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
* SecureClassLoader.getPermissions()
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 URL
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();
try
{
file = gnu.java.net.protocol.file.Connection.unquote(file);
}
catch (MalformedURLException e)
{
// ignore
}
file = file.replace('/', File.separatorChar);
// If the file end in / it must be an directory.
if (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 URLClassLoader
that gets
* classes from the supplied URL
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 URL
s
*/
public static URLClassLoader newInstance(URL[] urls)
throws SecurityException
{
return newInstance(urls, null);
}
/**
* Creates a new instance of a URLClassLoader
that gets
* classes from the supplied URL
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 URL
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;
}
}
}