/**************************************************************************
* Parts copyright (c) 2009, 2010, 2015 by Chris Gray, KIFFER Ltd. *
* All rights reserved. *
* *
* Redistribution and use in source and binary forms, with or without *
* modification, are permitted provided that the following conditions *
* are met: *
* 1. Redistributions of source code must retain the above copyright *
* notice, this list of conditions and the following disclaimer. *
* 2. Redistributions in binary form must reproduce the above copyright *
* notice, this list of conditions and the following disclaimer in the *
* documentation and/or other materials provided with the distribution. *
* 3. Neither the name of KIFFER Ltd nor the names of other contributors *
* may be used to endorse or promote products derived from this *
* software without specific prior written permission. *
* *
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED *
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF *
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. *
* IN NO EVENT SHALL KIFFER LTD OR OTHER CONTRIBUTORS BE LIABLE FOR ANY *
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL *
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS *
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) *
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, *
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING *
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE *
* POSSIBILITY OF SUCH DAMAGE. *
**************************************************************************/
package java.lang;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.CodeSource;
import java.security.Policy;
import java.security.ProtectionDomain;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Properties;
import java.util.Vector;
import wonka.vm.JDWP;
import wonka.vm.NativeLibrary;
import wonka.vm.SystemClassLoader;
public abstract class ClassLoader {
/**
** The application class loader, used to load the starting class and
** (confusingly) the one returned by getSystemClassLoader().
*/
static ClassLoader applicationClassLoader;
/**
** The extension class loader, used to load installed extensions.
*/
static ClassLoader extensionClassLoader;
/**
** The real system class loader, i.e. the one which loads from bootclasspath
** those classes (and resources) not loaded by the bootstrap class loader.
*/
static ClassLoader systemClassLoader;
/**
** Support for Assertions.
*/
class AssertionStatus {
/** The default assertion status.
*/
private boolean dflt;
/** The assertion status which has been specified for various packages.
** Maps String(package name) -> Boolean (assertion status).
*/
private Map pkg = new HashMap();
/** The assertion status which has been specified for various classes.
** Maps String(fq class name) -> Boolean (assertion status).
*/
private Map clazz = new HashMap();
void setDefault(boolean enabled) {
dflt = enabled;
}
Boolean getDefaultStatus() {
return Boolean.valueOf(dflt);
}
Boolean getPackageStatus(String packageName) {
String prefix = packageName;
Object status = pkg.get(packageName);
while (status == null) {
int lastDot = prefix.lastIndexOf('.');
if (lastDot < 0) {
break;
}
prefix = prefix.substring(0, lastDot);
status = pkg.get(prefix);
}
return (Boolean) status;
}
void setForPackage(String packageName, boolean enabled) {
pkg.put(packageName, new Boolean(enabled));
}
Boolean getClassStatus(String className) {
return (Boolean) clazz.get(className);
}
void setForClass(String className, boolean enabled) {
clazz.put(className, new Boolean(enabled));
}
void clear() {
dflt = false;
pkg = new HashMap();
clazz = new HashMap();
}
}
/**
** The default protection domain. Has a CodeSource of (null,null)
** and default Permissions.
*/
private static ProtectionDomain defaultProtectionDomain;
AssertionStatus assertionStatus = new AssertionStatus();
/** The packages which have been defined by this ClassLoader.
** Only includes those for which a definePackage() was done.
** The key is the package name, with as associated data the
** relevant Package object.
*/
protected final Hashtable packages = new Hashtable();
/** The code sources of the packages which have been defined by
** this ClassLoader.
** Only includes those for which a definePackage() was done.
** The key is the Package object, with as associated data the
** relevant CodeSource.
*/
private HashMap package_sources;
/**
** List of libraries which were loaded by this ClassLoader.
** Used so that the NativeLibrary objects will only be finalized and
** reclaimed when this ClassLoader becomes unreachable.
*/
private Vector loadedLibraries = new Vector();
/**
** Every ClassLoader has a parent, except the system ClassLoader,
** which has (would have) no parent - or rather its parent is
** the bootstrap class loader, represented as 'null'.
*/
private ClassLoader parent;
/**
** Our name, as returned by toString()
*/
private String ownname;
/**
* Set true iff this is one of systemClassLoader, extensionClassLoader, or applicationClassLoader.
*/
private boolean systemDefined;
/**
* Link to a resource monitor on builds where this feature is enabled.
*/
private Object resourceMonitor;
/**
** Get a reference to the default protection domain.
** A cheap substitute for having a static initialiser.
*/
static synchronized ProtectionDomain get_defaultProtectionDomain() {
if (wonka.vm.SecurityConfiguration.ENABLE_SECURITY_CHECKS && defaultProtectionDomain == null) {
CodeSource cs = new CodeSource(null,null);
defaultProtectionDomain = new ProtectionDomain(cs,Policy.getPolicy().getPermissions(cs));
}
return defaultProtectionDomain;
}
/**
** Identify the most useful classloader available - the applicationClassLoader
** if already defined, but failing that the extensionClassLoader or
** systemClassLoader. Used to load resources (as opposed to classes)
** to avoid an infinite regress involving system.properties
** (we use systemClassLoader to load system.properties, so
** this needs to be in bootclasspath).
** Walks like a hack and quux like hack, so it probably is a hack.
*/
private static ClassLoader getPertinentClassLoader() {
ClassLoader loader = applicationClassLoader;
if (loader == null) {
loader = extensionClassLoader;
}
if (loader == null) {
systemClassLoader = SystemClassLoader.getInstance();
systemClassLoader.systemDefined = true;
loader = systemClassLoader;
}
return loader;
}
private static void permissionCheck(String permission) {
if (wonka.vm.SecurityConfiguration.ENABLE_SECURITY_CHECKS) {
SecurityManager sm = System.theSecurityManager;
if (sm != null) {
sm.checkPermission(new RuntimePermission(permission));
}
}
}
/** Any ClassLoader created using the default constructor will
** have as its parent the system ClassLoader.
*/
protected ClassLoader() throws SecurityException {
this(applicationClassLoader);
}
/**
** This new-to-Java-1.2 constructor allows the parent to be
** specified by the caller.
*/
protected ClassLoader(ClassLoader parent) throws SecurityException {
JDWP.registerClassLoader(this);
if (getCallingClassLoader() != null || applicationClassLoader != null) {
if (wonka.vm.SecurityConfiguration.ENABLE_SECURITY_CHECKS) {
SecurityManager sm = System.theSecurityManager;
if (sm!=null) {
sm.checkCreateClassLoader();
}
}
}
/**
** if parent is null we should replace it with the applicationClassLoader
*/
this.parent = parent == null ? applicationClassLoader : parent;
this.create();
}
private native void create();
private native boolean checkClassName(String classname);
/**
** As of Java 1.2 this method is no longer abstract. Instead it
** implements a default policy which can be be described as
** "responsible delegation".
*/
protected synchronized Class loadClass(String classname, boolean resolve)
throws ClassNotFoundException
{
if (ownname == null) {
ownname = toString();
}
if (!checkClassName(classname)) {
throw new ClassNotFoundException(this+": Illegal class name: "+classname);
}
Class loaded = _findLoadedClass(classname);
if (loaded == null) {
try {
if (parent == null) {
loaded = findClass(classname);
}
else {
loaded = parent.loadClass(classname, false);
}
}
catch (ClassNotFoundException e) {
loaded = findClass(classname);
// If that throws a ClassNotFoundException then so be it.
}
}
if (resolve) {
try {
resolveClass(loaded);
}
catch (NullPointerException e) {}
}
return loaded;
}
/**
** If no 'resolve' parameter is supplied then the default is 'false'
** (N.B. in Java 1.1 it was 'true'!)
*/
public synchronized Class loadClass(String classname) throws ClassNotFoundException {
return loadClass(classname, false);
}
/**
** The default implementation of findClass() is just a mugtrap: any
** real ClassLoader must override it.
*/
protected Class findClass(String classname)
throws ClassNotFoundException
{
throw new ClassNotFoundException(classname);
}
/** @deprecated Replaced by defineClass(String, byte[], int, int)
*/
protected final Class defineClass(byte[] data, int offset, int length) throws ClassFormatError {
return defineClass(null, data, offset, length);
}
/** Turns a bag of bytecodes into a Class and register it in the namespace of this ClassLoader.
** The class is not yet resolved.
**
** (TODO) If the package implied by the class name
** already exists and has other signers, throw a SecurityException.
** If the ProtectionDomain is null, use the default protection domain.
*/
protected final Class defineClass(String classname, byte data[], int offset, int length, ProtectionDomain pd)
throws ClassFormatError
{
if (offset < 0 || length < 0 || offset > data.length - length) {
throw new ArrayIndexOutOfBoundsException();
}
if (ownname == null) {
ownname = toString();
}
if (wonka.vm.SecurityConfiguration.ENABLE_SECURITY_CHECKS) {
int dot = classname.lastIndexOf('.');
String package_name = dot < 0 ? "" : classname.substring(0, dot);
ProtectionDomain actual_pd;
CodeSource actual_source;
Package existing_package;
CodeSource existing_source;
if (applicationClassLoader == null) {
// Don't try to get defaultProtectionDomain yet, it won't work
actual_pd = null;
actual_source = null;
}
else if (pd == null) {
actual_pd = get_defaultProtectionDomain();
actual_source = null;
}
else {
actual_pd = pd;
actual_source = pd.getCodeSource();
}
// TODO: if destination package has other signers, throw SecurityException
// Note that the list of signers is defined by the first class to be added to a package.
synchronized(this) {
if (package_sources == null) {
package_sources = new HashMap();
existing_package = null;
}
else {
existing_package = getPackage(package_name);
}
if (existing_package == null) {
existing_source = null;
}
else {
existing_source = (CodeSource)package_sources.get(existing_package);
if (existing_source == null) {
package_sources.put(existing_package, actual_source);
}
else {
if (!existing_source.equals(actual_source)) {
throw new SecurityException("attempt to create class "+classname+" from "+actual_source+" when package "+package_name+" was already defined from "+existing_source);
}
}
}
}
return _defineClass(classname,data,offset,length,actual_pd);
}
else {
return _defineClass(classname, data, offset, length, null);
}
}
/**
** If no ProtectionDomain is specified, the default domain is used.
*/
protected final Class defineClass(String classname, byte data[], int offset, int length)
throws ClassFormatError
{
return defineClass(classname,data,offset,length,get_defaultProtectionDomain());
}
/**
** The deprecated method defineClass(byte data[], int offset, int length)
** is not provided. :-P
*/
private native Class _defineClass(String classname, byte data[], int offset, int length, ProtectionDomain pd)
throws ClassFormatError;
/** Mutant version used to define Proxy classes. Some checks are by-passed.
** Invoked from java.lang.reflect.Proxy using reflection.
*/
final Class defineProxyClass(String classname, byte data[], int
offset, int length)
throws NullPointerException, ArrayIndexOutOfBoundsException,
ClassFormatError
{
ProtectionDomain pd = get_defaultProtectionDomain();
Class c = _defineClass(classname, data, offset, length, pd);
setProxyFlag(c);
return c;
}
/**
** Set a class's "Proxy" flag.
*/
private static native void setProxyFlag(Class c);
/**
** resolveClass forces all constants in the constant pool of c to be resolved.
*/
native protected final void resolveClass(Class c)
throws NullPointerException;
/**
** findSystemClass tries to load the named class using the system ClassLoader
*/
protected final Class findSystemClass(String classname)
throws ClassNotFoundException
{
ClassLoader loader = getPertinentClassLoader();
if (loader == null) {
throw new ClassNotFoundException("SystemClassLoader not yet defined");
}
return loader.findClass(classname);
}
/**
** findLoadedClass looks for a class in the cache of classes for which
** this ClassLoader was the defining ClassLoader.
*/
protected final Class findLoadedClass(String classname) {
if (ownname == null) {
ownname = toString();
}
return _findLoadedClass(classname);
}
native private Class _findLoadedClass(String classname);
/**
** findResource returns null: real resource-aware ClassLoaders
** override this method.
*/
protected URL findResource(String resname) {
return null;
}
/**
** findResources returns an empty Enumeration: real resource-aware
** ClassLoaders override this method.
*/
protected Enumeration findResources(String resname)
throws IOException
{
return new Enumeration() {
public boolean hasMoreElements() { return false; }
public Object nextElement() { return null; }
};
}
/**
** getResource retrieves the named resource using "responsible
** delegation", rather like loadClass (except that there is no
** equivalent to "findLoadedClass").
*/
public URL getResource(String resname) {
if (ownname == null) {
ownname = toString();
}
URL loaded = null;
if (parent != null) {
loaded = parent.getResource(resname);
}
if (loaded == null) {
loaded = findResource(resname);
}
return loaded;
}
/**
** getResources retrieves all the resource matching "name" in a
** responsibly delegating manner. We first call getResources on
** our parent ClassLoader, and then use a Vector to merge this with
** our own resource (if any). TODO: make this more efficient.
*/
public final Enumeration getResources(String resname) throws IOException {
if (ownname == null) {
ownname = toString();
}
Vector merged = new Vector();
if (parent != null) {
Enumeration enum1 = parent.getResources(resname);
while (enum1.hasMoreElements()) {
merged.addElement(enum1.nextElement());
}
}
Enumeration enum2 = findResources(resname);
while (enum2.hasMoreElements()) {
merged.addElement(enum2.nextElement());
}
return merged.elements();
}
/** Get all resources using the system class loader.
* @throws IOException
*/
public static Enumeration getSystemResources(String resname) throws IOException {
ClassLoader loader = getPertinentClassLoader();
if (loader == null) {
return null; // Hm, should really be an empty enumeration???
}
return loader.getResources(resname);
}
/**
** Set the signers of a class.
*/
protected final void setSigners(Class cl, Object[] signers) {
cl.signers = signers;
}
/**
** Get the identity of the system ClassLoader.
** Don't tell this to everybody!
*/
public static synchronized ClassLoader getSystemClassLoader() {
ClassLoader result = applicationClassLoader == null ? extensionClassLoader : applicationClassLoader;
if (result != null) {
ClassLoader caller = getCallingClassLoader();
if (!isSystemClassLoader(caller)) {
permissionCheck("getClassLoader");
}
}
return result;
}
/** Get the identity of the parent ClassLoader.
** This too is sensitive information: only classes loaded by the
** bootstrap class loader or an ancestor of this ClassLoader have
** an automatic right to know, anyone else needs special permission.
*/
public final ClassLoader getParent() {
ClassLoader caller = getCallingClassLoader();
if (!isDelegationAncestor(caller)) {
permissionCheck("getClassLoader");
}
return parent;
}
static native ClassLoader getCallingClassLoader();
boolean isDelegationAncestor(ClassLoader putative_ancestor) {
return putative_ancestor == null || putative_ancestor == this || parent != null && parent.isDelegationAncestor(putative_ancestor);
}
// No longer final in 1.2
/** Get a system resource in the form of an InputStream.
*/
public static InputStream getSystemResourceAsStream(String resname) {
ClassLoader systemClassLoader = getPertinentClassLoader();
if (systemClassLoader == null) {
return null;
}
return systemClassLoader.getResourceAsStream(resname);
}
/** Get a resource in the form of an InputStream.
*/
public InputStream getResourceAsStream(String resname) {
URL resource = getResource(resname);
if (resource == null) {
return null;
}
else {
try {
return resource.openStream();
}
catch (IOException ioe) {
return null;
}
}
}
/** Get a resource using the system class loader.
*/
public static URL getSystemResource(String resname) {
ClassLoader systemClassLoader = getPertinentClassLoader();
if (systemClassLoader == null) {
return null;
}
return systemClassLoader.getResource(resname);
}
/** Define a package by name.
** The name must be unique.
*/
protected synchronized Package definePackage (String pkgname,
String spectitle, String specversion, String specvendor,
String impltitle, String implversion, String implvendor,
URL sealbase)
throws IllegalArgumentException
{
if (ownname == null) {
ownname = toString();
}
Package p = (Package)packages.get(pkgname);
if (p != null) {
throw new IllegalArgumentException("duplicate package name: "+pkgname);
}
p = new Package(pkgname, spectitle, specversion, specvendor, impltitle, implversion, implvendor, sealbase);
packages.put(pkgname,p);
return p;
}
/** Get the Package object associated with the given package name.
** If none found, returns null.
*/
protected Package getPackage(String pkgname) {
Package p = (Package)packages.get(pkgname);
if (p == null) {
if (parent != null) {
p = parent.getPackage(pkgname);
}
}
return p;
}
/** Get all Package objects known to this ClassLoader and its ancestors,
** as an array of all things.
*/
protected Package[] getPackages() {
Package[] ancestorpackages = null;
Enumeration ownpackages = null;
int total = 0;
if (parent != null) {
ancestorpackages = parent.getPackages();
total = ancestorpackages.length;
}
if (packages != null) {
ownpackages = packages.keys();
total += packages.size();
}
Package[] package_array = new Package[total];
if (ancestorpackages != null) {
System.arraycopy(ancestorpackages, 0, package_array, 0, ancestorpackages.length);
}
if (ownpackages != null) {
int i = ancestorpackages.length;
while(ownpackages.hasMoreElements()) {
package_array[i++] = (Package)ownpackages.nextElement();
}
}
return package_array;
}
/** default behaviour is to return null.
*/
protected String findLibrary(String libname) {
return null;
}
private static native String getCommandLineClasspath();
private static void useExtDirs(String extdirs) {
Vector v1;
Vector v2;
URL[] urls;
int i;
int j;
int l;
int sz;
File f;
String dirname;
String filename;
String[] jars;
v1 = new Vector();
i = extdirs.indexOf(':');
while (i != -1) {
dirname = extdirs.substring(0,i);
f = new File(dirname);
if (f.isDirectory()) {
v1.add(dirname);
}
// non-directories are silently ignored.
extdirs = extdirs.substring(i+1);
i = extdirs.indexOf(':');
}
f = new File(extdirs);
if (f.isDirectory()) {
v1.add(extdirs);
}
sz = v1.size();
v2 = new Vector();
for (i = 0; i < sz; ++i) {
dirname = (String)v1.elementAt(i);
f = new File(dirname);
jars = f.list();
l = jars != null ? jars.length : 0;
for (j = 0; j < l; ++j) {
filename = dirname + File.separator + jars[j];
if (filename.endsWith(".jar") || filename.endsWith(".JAR") || filename.endsWith(".zip") || filename.endsWith(".ZIP")) {
v2.add(filename);
}
}
v2.add(dirname);
}
sz = v2.size();
urls = new URL[sz];
j = 0;
for (i=0 ; i < sz ; i++){
String urlname = (String)v2.get(i);
urlname = "file:" + urlname;
try {
urls[j++] = new URL(urlname);
}
catch(java.net.MalformedURLException mue) {
mue.printStackTrace();
}
}
extensionClassLoader = wonka.vm.ExtensionClassLoader.getInstance(urls, SystemClassLoader.getInstance());
extensionClassLoader.systemDefined = true;
installExtensionClassLoader(extensionClassLoader);
}
private static native void installExtensionClassLoader(ClassLoader cl);
private static native void installApplicationClassLoader(ClassLoader cl);
private static URL[] getApplicationClasspath(String classpath) {
Vector v;
int i;
int j;
int sz;
URL[] urls;
v = new Vector();
i = classpath.indexOf(':');
while (i != -1) {
v.add(classpath.substring(0,i));
classpath = classpath.substring(i+1);
i = classpath.indexOf(':');
}
v.add(classpath);
sz = v.size();
urls = new URL[sz];
j = 0;
for (i=0 ; i < sz ; i++) {
String urlname = (String)v.get(i);
if (urlname.indexOf(':') < 0) {
if (new File(urlname).isDirectory() && !urlname.endsWith("/")) {
urlname = urlname + "/";
}
urlname = "file:" + urlname;
}
try {
urls[j++] = new URL(urlname);
}
catch(java.net.MalformedURLException mue) {
mue.printStackTrace();
}
}
return urls;
}
static void createApplicationClassLoader() {
String extdirs = System.systemProperties.getProperty("java.ext.dirs");
String classpath = getCommandLineClasspath();
if (extdirs != null && extdirs.trim().length() != 0) {
useExtDirs(extdirs);
}
else {
extensionClassLoader = null;
}
URL[] urls = getApplicationClasspath(classpath);
ClassLoader parent = extensionClassLoader != null ? extensionClassLoader : SystemClassLoader.getInstance();
applicationClassLoader = wonka.vm.ApplicationClassLoader.getInstance(urls, parent);
applicationClassLoader.systemDefined = true;
installApplicationClassLoader(applicationClassLoader);
if (System.systemProperties != null) {
System.systemProperties.put("java.class.path", classpath);
}
}
/**
** Clear the assertion status settings for all classes and packages,
** and the default.
*/
public void clearAssertionStatus() {
assertionStatus.clear();
}
/**
* Sets the default assertion status for this class loader. This setting
* determines whether classes loaded by this class loader and initialized
* in the future will have assertions enabled or disabled by default. This
* setting may be overridden on a per-package or per-class basis by invoking
* setPackageAssertionStatus(String, boolean) or
* setClassAssertionStatus(String, boolean).
*/
public void setDefaultAssertionStatus(boolean enabled) {
assertionStatus.setDefault(enabled);
}
/**
* Sets the package-default assertion status for the named
* package in this class loader. The package-default assertion status
* determines the assertion status for classes loaded by this class loader
* and initialized in the future which belong to the named package or any
* of its “subpackages”.
* <p>A subpackage of a package named p is any package whose name begins
* with "p.". For example, javax.swing.text is a subpackage of javax.swing,
* and both java.util and java.lang.reflect are subpackages of java.
* <p>In the event that multiple package defaults apply to a given class,
* the package default pertaining to the most specific package takes
* precedence over the others. For example, if javax.lang and
* javax.lang.reflect both have package defaults associated with them, the
* latter package default applies to classes in javax.lang.reflect.
* <p>Package defaults take precedence over the class loader's default
* assertion status, and may be overridden on a per-class basis by invoking
* the setClassAssertionStatus method.
*
* @param packageName the name of the package whose default assertion
* status is to be set.
* @param enabled true if classes loaded by this class loader
* and belonging to the named package and its subpackages will
* henceforth have assertions enabled by default, false if they will
* have assertions disabled by default.
*/
public void setPackageAssertionStatus(String packageName, boolean enabled) {
assertionStatus.setForPackage(packageName, enabled);
}
/**
* Sets the desired assertion status for the named top-level class in this
* class loader and any nested classes contained therein. This setting takes
* precedence over the class loader's default assertion status, and over any
* applicable per-package default. This method has no effect if the named
* class has already been initialized. (Once a class is initialized, its
* assertion status cannot change.)
* <p>
* If the named class is not a top-level class, this invocation will have
* no effect on the actual assertion status of any class.
*
* @param className the fully qualified class name of the top-level class
* whose default assertion status is to be set.
* @param enabled true if the named class will have assertions enabled
* when (and if) it is initialized, false if it will
* have assertions disabled by default.
*/
public void setClassAssertionStatus(String className, boolean enabled) {
assertionStatus.setForClass(className, enabled);
}
/**
** Package-local method called by Runtime when a library is loaded.
*/
void registerLibrary(NativeLibrary library) {
loadedLibraries.add(library);
}
/**
** Package-private method by which java.lang.SecurityManager can detect whether
** a class loader is a system class loader without triggering a security check.
*/
static boolean isSystemClassLoader(ClassLoader cl) {
return cl == null || cl.systemDefined;
}
public native void enableResourceMonitoring(boolean enable);
}