/*
* SearchlistClassLoader: class loader which loads classes using a searchlist
*
* Copyright (C) 2007-2009 Nik Trevallyn-Jones, Sydney Austraila.
*
* Author: Nik Trevallyn-Jones, nik777@users.sourceforge.net
* $Id: Exp $
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* This program 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 version 2 of the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* with this program. If not, version 2 of the license is available
* from the GNU project, at http://www.gnu.org.
*/
/*
* History:
* V1.2 NTJ, Jan 2009.
* - Fixed NullPointerException when searchlist was empty.
* - Added support for findLibrary()
*
* V1.1 NTJ, Aug 2007.
* - Reworked the searchlist storage, and moved searchlist
* traversal into a new method: getLoader(int, byte).
* -- traversal should be somewhat faster; searchMode can be
* changed even after ClassLoaders have been added;
* - Reworked findClass(name) so that all classes located
* through shared loaders are associated with the shared
* loader, and all classes located through non-shared loaders
* are associated with the owning SearchlistClassLoader.
* - removed 'recent' loader code.
*
* V1.0 NTJ, April 2007.
* - Initial coding, based on a RemoteClassLoader used in AOS.
*/
import java.util.ArrayList;
import java.util.Vector;
import java.util.Hashtable;
import java.net.URL;
import java.net.URLClassLoader;
import java.io.*;
/**
* A class loader which loads classes using a searchlist of
* other classloaders.
*
*
The classloaders in the searchlist are of two types: shared and
* non-shared. A shared classloader may be in use by other code, and
* so no duplicates should be made of the classes in the loaders.
*
A non-shared classloader is private to this SearchlistClassLoader, and
* so there is no possibility that other code could be using them. To avoid
* problems of isolation, all classes loaded through non-shared loaders are
* defined as having been loaded by the SearchlistClassLoader itself. This
* ensures the JVM can find the correct loader when loading associated
* classes (including shared classes).
*
*
The SearchlistClassLoader API therefore makes a clear distinction
* between shared and non-shared classloaders.
*
The {@link #add(ClassLoader)} method adds an existing classloader
* which means the added classloader is treated as being shared.
*
The {@link #add(URL)} method adds a new internally created classloader
* which loads the content associated with the specified URL, which means the
* internally created classloader is non-shared.
*
*
*
SearchlistClassLoader therefore also allows control over the order in
* which classloaders are searched, through the {@link #setSearchMode(byte)}
* method.
*
*
The possible searchmodes are:
*
* - SHARED
*
added classloaders are searched before added URLs, which causes
* an existing class instance to be used (and SHARED) in preference to
* loading (or creating) a NON-SHARED duplicate.
*
* - NONSHARED
*
added URLs are searched before added classloaders. This will
* create a NON-SHARED copy of a class (ie a duplicate) in preference to
* using a SHARED one from another classloader.
*
* - ORDERED
*
added classloaders and URLs are searched in the order in which
* they were added to the searchlist.
*
*
*
There is also a method which retrieves a class without searching
* any added classloaders. This effectively retrieves the canonical
* instance of the requested class (see {@link #loadLocalClass(String)}
* and {@link #getLocalResource(String)}).
*
*Implementation notes:.
*
*
Because each class object is associated with the classloader which
* defined it (see ClassLoader.defineClass(...)), SearchlistClassLoader
* must associate itself with all class objects it loads
* through non-shared loaders, and similarly must not associate
* itself with class objects loaded through shared loaders.
* (See {@link #findClass(String)}.)
*
*
The structure of the internal ClassLoader methods is as per the
* instructions in {@link java.lang.ClassLoader}. While I don't think this is
* necessary any longer, it was quite easy to comply with the instructions.
*/
public class SearchlistClassLoader extends ClassLoader
{
protected Vector list, search;
protected Hashtable cache;
protected Loader content = null;
protected byte searchMode = SHARED;
protected int divide = 0;
/** search mode enums */
public static final byte SHARED = 0x1;
public static final byte NONSHARED = 0x2;
public static final byte ORDERED = 0x3;
protected static final URL EMPTY_URL[] = new URL[0];
/**
* create a SearchlistClassLoader.
*/
public SearchlistClassLoader()
{}
/**
* create a SearchlistClassLoader.
*/
public SearchlistClassLoader(ClassLoader parent)
{ super(parent); }
/**
* create a SearchlistClassLoader.
*/
public SearchlistClassLoader(URL url[])
{ content = new Loader(new URLClassLoader(url), false); }
/**
* create a SearchlistClassLoader.
*/
public SearchlistClassLoader(URL url[], ClassLoader parent)
{
super(parent);
content = new Loader(new URLClassLoader(url), false);
}
/**
* set the search mode.
*
* @param mode enum for the searchmode: SHARED, NONSHARED, ORDERED
*/
public void setSearchMode(byte mode)
{
byte prev = searchMode;
searchMode = mode;
if (searchMode <= 0 || searchMode > ORDERED) {
System.out.println("SearchlistClassLoader.setSearchMode: " +
"Invalid search mode: " + mode +
"; defaulting to SHARED.");
searchMode = SHARED;
}
}
/**
* add a (shared) classloader to the searchlist.
*
* The loader is added to the list as a shared loader.
*
* @param loader the ClassLoader to add to the searchlist.
*/
public void add(ClassLoader loader)
{
Loader ldr = new Loader(loader, true);
// store loaders in order in list
if (list == null) list = new Vector(16);
list.add(ldr);
// store shared loaders in front of non-shared loaders in search.
if (search == null) search = new Vector(16);
if (search.size() > divide) search.add(divide, ldr);
else search.add(ldr);
divide++;
}
/**
* add a (non-shared) URL to the searchlist.
*
* Creates a new URLClassLoader and adds it to the searchlist as a
* non-shared classloader.
*
* @param url the URL to add to the searchlist.
*/
public void add(URL url)
{
Loader ldr = new Loader(new URLClassLoader(new URL[] { url }), false);
// store loaders in order in list
if (list == null) list = new Vector(16);
list.add(ldr);
// store non-shared loaders after shared loaders in search
if (search == null) search = new Vector(16);
search.add(ldr);
}
/**
* return the array of URLs used locally by this class loader
*/
public URL[] getURLs()
{
return (content != null
? ((URLClassLoader) content.loader).getURLs()
: EMPTY_URL
);
}
/**
* return the list of URLs in the search list
*/
public URL[] getSearchPath()
{
Loader ldr;
URL[] url;
int j;
ArrayList path = new ArrayList(8);
for (int i = 0; (ldr = getLoader(i++, searchMode)) != null; i++) {
if (ldr.loader instanceof SearchlistClassLoader)
url = ((SearchlistClassLoader) ldr.loader).getSearchPath();
else if (ldr.loader instanceof URLClassLoader)
url = ((URLClassLoader) ldr.loader).getURLs();
else
url = null;
if (url != null) {
for (j = 0; j < url.length; j++)
path.add(url[j]);
}
}
return (path.size() > 0 ? (URL[]) path.toArray(EMPTY_URL) : EMPTY_URL);
}
/**
* Return the local class instance for name.
*
*
This does not search the searchlist. Only classes loaded
* directly by this loader or its parent are returned.
*
*
This method can be used to retrieve the canonical instance
* of a class.
* If this method is called on a set of SearchlistClassLoaders, then
* the only classloader which will return the class is the one which
* originally loaded it (assuming no duplicates have been created yet).
*
* @param name the fully-qualified name of the class
* @return the loaded class.
*
* @throws ClassNotFoundException if the class is not found.
*/
public Class loadLocalClass(String name)
throws ClassNotFoundException
{
ClassNotFoundException err = null;
if (getParent() != null) {
try {
return getParent().loadClass(name);
} catch (ClassNotFoundException e) {
err = e;
}
}
if (content != null) {
// try the cache first
Class result = (cache != null ? (Class) cache.get(name) : null);
if (result != null) return result;
// try loading the class data
byte[] data = loadClassData(content.loader, name);
if (data != null) {
// define the class
result = defineClass(name, data, 0, data.length);
if (result != null) {
//System.out.println("defined class: " + name);
// cache the result
if (cache == null) cache = new Hashtable(1024);
cache.put(name, result);
return result;
}
}
}
throw (err != null ? err : new ClassNotFoundException(name));
}
/**
* Return the URL for the local resource specified by name.
*
*
This does not search the searchlist. Only resources loaded
* directly by this loader or its parent are returned.
*
*
This method can be used to retrieve the canonical URL for a
* resource.
* If this method is called on a set of SearchlistClassLoaders, then
* the only classloader which will return the resource is the one which
* originally loaded it (assuming no duplicates have been created yet).
*
* @param name the fully-qualified name of the resource.
* @return the located URL, or null.
*/
public URL getLocalResource(String name)
{
URL result = null;
if (getParent() != null) {
result = getParent().getResource(name);
}
if (result == null && content != null) {
result = content.loader.getResource(name);
}
return result;
}
/**
* Return a Class object for the specified class name.
*
* @overloads java.lang.ClassLoader#findClass(String)
*
* Traverses the searchlist looking for a classloader which can return
* the specified class.
*
*
This method is called by inherited loadClass() methods whenever
* a class cannot be found in the parent classloader.
*
*
If the class is found using a shared loader, then it is
* returned directly. If the class is found using a non-shared
* loader, then the actual class object is defined by the containing
* SearchlistClassLoader, which causes Java to associate the new class
* object with the SearchlistClassLaoder, rather then the non-shared
* loader.
*
* @param name the fully-qualified name of the class
* @return the loaded class object
*
* @throws ClassNotFoundException if the class could not be loaded.
*/
public Class findClass(String name)
throws ClassNotFoundException
{
Loader ldr;
Throwable err = null;
Class result;
byte[] data;
for (int i = 0; (ldr = getLoader(i, searchMode)) != null; i++) {
try {
// for shared loaders - just try getting the class
if (ldr.shared)
return ldr.loader.loadClass(name);
// for non-shared loaders, we have to define the class manually
else {
// check the cache first
result = (cache != null ? (Class) cache.get(name) : null);
if (result != null) return result;
// try loading the class
data = loadClassData(ldr.loader, name);
if (data != null) {
// data loaded, define the class
result = defineClass(name, data, 0, data.length);
if (result != null) {
//System.out.println("defined class: " + name);
// cache the result
if (cache == null) cache = new Hashtable(1024);
cache.put(name, result);
return result;
}
}
}
}
catch (Throwable t) {
err = t;
}
}
throw (err != null
? new ClassNotFoundException(name, err)
: new ClassNotFoundException(name)
);
}
/**
* find a resource using the searchlist.
*
* @overloads ClassLoader#findResource(String)
*
* NoteInherited behaviour of loadClass() and getResource() means
* that this method is not called if our parent classloader has
* already found the data.
*
* @param path the fully-qualified name of the resource to retrieve
* @return the URL if the resource is found, and null otherwise.
*/
public URL findResource(String path)
{
System.out.println("findResource: looking in " + this + " for " +
path);
URL url = null;
Loader ldr;
for (int i = 0; (ldr = getLoader(i, searchMode)) != null; i++) {
url = ldr.loader.getResource(path);
if (url != null) {
System.out.println("found " + path + " in loader: " +
ldr.loader);
break;
}
}
return url;
}
/**
* return the pathname to the specified native library.
*
* If the library is not found on the searchpath, then null
* is returned, indicating to the Java machine that it should search
* java.library.path.
*
* @param libname - the String name of the library to find
* @return the full path to the found library file, or null.
*/
public String findLibrary(String libname)
{
String fileName = System.mapLibraryName(libname);
System.out.println("findLibrary: looking in " + this + " for " +
libname + " as " + fileName);
int i, j;
URL[] url;
File dir, file;
Loader ldr;
for (i = 0; (ldr = getLoader(i++, searchMode)) != null; i++) {
if (ldr.loader instanceof SearchlistClassLoader)
url = ((SearchlistClassLoader) ldr.loader).getSearchPath();
else if (ldr.loader instanceof URLClassLoader)
url = ((URLClassLoader) ldr.loader).getURLs();
else
url = null;
if (url != null) {
for (j = 0; j < url.length; j++) {
if (!url[j].getProtocol().equalsIgnoreCase("file"))
continue;
try {
dir = new File(url[j].toURI()).getParentFile();
file = new File(dir, fileName);
if (file.exists()) {
System.out.println("found: " +
file.getAbsolutePath());
return file.getAbsolutePath();
}
} catch (Exception e) {
System.out.println("Ignoring url: " + url[j] + ": "
+ e);
}
}
}
}
// nothing found, use java.library.path
return null;
}
/**
* return the correct classloader for the specified position in the
* search.
*
* @param index the position (step) in the search process
* @param mode the search mode to use
*
* @return The corresponding Loader or null
*/
protected Loader getLoader(int index, byte mode)
{
// content is always the first loader searched
if (content != null) {
if (index == 0) return content;
else index--;
}
if (index < 0 || list == null || index >= list.size()) return null;
Loader result;
switch (mode) {
case SHARED:
// return shared loaders before non-shared loaders
result = (Loader) search.get(index);
break;
case NONSHARED:
// return non-shared loaders before shared loaders
{
int pos = index + divide;
result = (Loader) (pos < search.size()
? search.get(pos)
: search.get(pos-divide)
);
}
break;
default:
// return loaders in the order in which they were added
result = (Loader) list.get(index);
}
return result;
}
/**
* load the byte data for a class definition.
*
* @param name the fully-qualified class name
* @return a byte[] containing the class bytecode or null
*/
protected byte[] loadClassData(ClassLoader cl, String name)
{
ByteArrayOutputStream barray;
byte buff[];
int len;
InputStream in = cl.getResourceAsStream(translate(name, ".", "/")
+ ".class");
if (in == null) return null;
try {
barray = new ByteArrayOutputStream(1024*16);
buff = new byte[1024];
do {
len = in.read(buff, 0, buff.length);
if (len > 0) barray.write(buff, 0, len);
} while (len >= 0);
return (barray.size() > 0 ? barray.toByteArray() : null);
} catch (Exception e) {
return null;
}
}
/**
* translate matching chars in a string.
*
* @param str the String to translate
* @param match the list of chars to match, in a string.
* @param replace the list of corresponding chars to replace matched chars
* with.
*
*
* Eg: translate("the dog.", "o.", "i")
* returns "the dig", because 'o' is replaced with 'i', and '.' is
* replaced with nothing (ie deleted).
*
*
* @return the result as a string.
*/
public static String translate(String str, String match, String replace)
{
StringBuffer b = new StringBuffer(str.length());
int pos = 0;
char c = 0;
if (match == null) match = "";
if (replace == null) replace = "";
boolean copy = (match.length() != 0 &&
match.length() >= replace.length());
// loop over the input string
int max = str.length();
for (int x = 0; x < max; x++) {
c = str.charAt(x);
pos = match.indexOf(c);
// if found c in 'match'
if (pos >= 0) {
// translate
if (pos < replace.length()) b.append(replace.charAt(pos));
}
// copy
else if (copy || replace.indexOf(c) >= match.length()) b.append(c);
// otherwise, effectively, delete...
}
return b.toString();
}
/**
* internal class to store the state of each searchable ClassLoader.
*
* The containing SearchlistClassLoader needs to know information about
* each loader in the list.
*/
protected static class Loader
{
ClassLoader loader = null; // the actual classloader
boolean shared = false; // shared flag
Loader(ClassLoader loader, boolean shared)
{
this.loader = loader;
this.shared = shared;
}
}
}