Mega Code Archive

 
Categories / Java / Reflection
 

A class loader which loads classes using a searchlist of other classloaders

/*  * 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.  *  *<br>The classloaders in the searchlist are of two types: <b>shared</b> and  *  <b>non-shared</b>. A shared classloader may be in use by other code, and  *  so <i>no</i> duplicates should be made of the classes in the loaders.  *<br>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).  *  *<br>The SearchlistClassLoader API therefore makes a clear distinction  *  between <b>shared</b> and <b>non-shared</b> classloaders.  *<br>The {@link #add(ClassLoader)} method adds an <i>existing</i> classloader  *  which means the added classloader is treated as being <i>shared</i>.  *<br>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 <i>non-shared</i>.  *<br>  *  *<br>SearchlistClassLoader therefore also allows control over the order in  *  which classloaders are searched, through the {@link #setSearchMode(byte)}  *  method.  *  *<br>The possible <i>searchmodes</i> are:  *<ul>  *   <li>SHARED  *  <br>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.  *  *   <li>NONSHARED  *  <br>added URLs are searched <i>before</i> 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.  *  *   <li>ORDERED  *  <br>added classloaders and URLs are searched in the order in which  *  they were added to the searchlist.  *</ul>  *  *<br>There is also a method which retrieves a class <i>without</i> searching  *  any added classloaders. This effectively retrieves the <i>canonical</i>  *  instance of the requested class (see {@link #loadLocalClass(String)}  *  and {@link #getLocalResource(String)}).  *  *<p><i>Implementation notes:</i>.  *  *<br>Because each class object is associated with the classloader which  *  defined it (see ClassLoader.defineClass(...)), SearchlistClassLoader  *  must associate <i>itself</i> with <i>all</i> class objects it loads  *  through <i>non-shared</i> loaders, and similarly <i>must not</i> associate  *  itself with class objects loaded through shared loaders.  *  (See {@link #findClass(String)}.)  *  *<br>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 <i>name</i>.      *      *<br>This does <i>not</i> search the searchlist. Only classes loaded      *  directly by this loader or its parent are returned.      *      *<br>This method can be used to retrieve the <i>canonical</i> 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 <i>name</i>.      *      *<br>This does <i>not</i> search the searchlist. Only resources loaded      *  directly by this loader or its parent are returned.      *      *<br>This method can be used to retrieve the <i>canonical</i> 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 <i>null</i>.      */     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.      *      *<br>This method is called by inherited loadClass() methods whenever      *  a class cannot be found in the parent classloader.      *      *<br>If the class is found using a <i>shared</i> loader, then it is      *  returned directly. If the class is found using a <i>non-shared</i>      *  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)      *      *  <em>Note</em>Inherited behaviour of loadClass() and getResource() means      *  that this method is <em>not</em> 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 <i>null</i> 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 <i>null</i>      *  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 <i>null</i>.      */     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 <i>null</i>      */     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 <i>null</i>      */     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.      *      *<pre>      *  Eg: translate("the dog.", "o.", "i")      *  returns "the dig", because 'o' is replaced with 'i', and '.' is      *  replaced with nothing (ie deleted).      *</pre>      *      *  @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;   }     } }