Reflection Java

/*
 * 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;
  }
    }
}