Reflection Java

/*
 * Copyright 2007 (C) TJDO.
 * All rights reserved.
 *
 * This software is distributed under the terms of the TJDO License version 1.0.
 * See the terms of the TJDO License in the documentation provided with this software.
 *
 * $Id: ClassFinder.java,v 1.1 2007/10/03 01:20:37 jackknifebarber Exp $
 */
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
/**
 * Used to load classes according to the rules defined in the JDO spec.
 * See section 12.5 of the JDO 1.0.1 spec.
 * 


 * This class also provides a {@link #classForName(String,boolean,ClassLoader)
 * static method} for loading classes that utilizes a cache.
 * The cache contains class names that were previously not found,
 * organized by the loader that failed to find them.
 * This speeds up cases where attempts to load the same non-existent class are
 * repeated many times.
 *
 * @author Mike Martin
 * @version $Revision: 1.1 $
 */
public class ClassFinder
{
    private static final Map findersByLoader = new WeakHashMap();
    private static final Map nonExistentClassNamesByLoader = new WeakHashMap();
    /**
     * Returns the class finder instance associated with the context class
     * loader of the calling thread.
     */
    public static ClassFinder getInstance()
    {
        return getInstance(getContextClassLoaderPrivileged());
    }
    /**
     * Returns the class finder instance associated with the specified class
     * loader.
     * The specified loader becomes the finder's "original context class loader"
     * for the algorithm implemented by
     * {@link #classForName(String,boolean,Class)}.
     *
     * @param ctxLoader
     *      the context class loader
     */
    public static synchronized ClassFinder getInstance(ClassLoader ctxLoader)
    {
        ClassFinder cf = (ClassFinder)findersByLoader.get(ctxLoader);
        if (cf == null)
            findersByLoader.put(ctxLoader, cf = new ClassFinder(ctxLoader));
        return cf;
    }
    /**
     * Calls getContextClassLoader() for the current thread in a doPrivileged
     * block.
     *
     * @return
     *      The context class loader of the current thread.
     * @exception SecurityException
     *      If getContextClassLoader() fails.
     */
    private static ClassLoader getContextClassLoaderPrivileged() throws SecurityException
    {
        return (ClassLoader)AccessController.doPrivileged(
            new PrivilegedAction()
            {
                public Object run()
                {
                    return Thread.currentThread().getContextClassLoader();
                }
            }
        );
    }
    private final ClassLoader origContextClassLoader;
    private ClassFinder(ClassLoader origContextClassLoader)
    {
        this.origContextClassLoader = origContextClassLoader;
    }
    /**
     * Returns the Class object associated with the class or interface
     * with the given string name.
     * 


     * This method implements the algorithm described in section 12.5 of the JDO
     * 1.0.1 spec.
     * It attempts to load the class using up to three loaders:
     * 


         *  
  1. The loader that loaded the class or instance referred to in the API
         *  that caused this class to be loaded (the context class).

  2.      *  
  3. The loader returned in the current context by
         *  Thread.getContextClassLoader().

  4.      *  
  5. The loader returned by Thread.getContextClassLoader() at
         *  the time this ClassFinder was constructed (which should equate
         *  to the time of PersistenceManagerFactory.getPersistenceManager()).
         *  

  6.      * 

     *
     * @param className
     *      fully qualified name of the desired class
     * @param initialize
     *      whether the class must be initialized
     * @param contextClass
     *      another class to serve as context for the loading of the named
     *      class, or null if there is no such other class
     *
     * @return
     *      class object representing the desired class 
     * @exception ClassNotFoundException
     *      if the class cannot be located
     */
    public Class classForName(String className, boolean initialize, Class contextClass) throws ClassNotFoundException
    {
        if (contextClass != null)
        {
            try
            {
                return classForName(className, initialize, contextClass.getClassLoader());
            }
            catch (ClassNotFoundException e)
            {
            }
        }
        return classForName(className, initialize);
    }
    /**
     * Returns the Class object associated with the class or interface
     * with the given string name.
     * 


     * This method is equivalent to:
     * 


     * classForName(className, initialize, null)
     * 

     *
     * @param className
     *      fully qualified name of the desired class
     * @param initialize
     *      whether the class must be initialized
     *
     * @return
     *      class object representing the desired class 
     * @exception ClassNotFoundException
     *      if the class cannot be located
     */
    public Class classForName(String className, boolean initialize) throws ClassNotFoundException
    {
        try
        {
            return classForName(className, initialize, getContextClassLoaderPrivileged());
        }
        catch (ClassNotFoundException e)
        {
            return classForName(className, initialize, origContextClassLoader);
        }
    }
    /**
     * Returns the Class object associated with the class or interface
     * with the given string name.
     * 


     * This method is functionally equivalent to
     *{@link Class#forName(String,boolean,ClassLoader)}
     * except that it maintains a cache of class names that are not
     * found.
     * In many cases, especially in an app server environment, searching for
     * non-existent classes can be a slow process.
     * It's important because some things in TJDO, notably the macro mechanism
     * used in e.g. view definitions, will search for class names that don't
     * exist.
     *
     * @param className
     *      fully qualified name of the desired class
     * @param initialize
     *      whether the class must be initialized
     * @param loader
     *      class loader from which the class must be loaded 
     *
     * @return
     *      class object representing the desired class 
     * @exception ClassNotFoundException
     *      if the class cannot be located
     */
    public static Class classForName(String className, boolean initialize, ClassLoader loader) throws ClassNotFoundException
    {
        Set names;
        synchronized (nonExistentClassNamesByLoader)
        {
            names = (Set)nonExistentClassNamesByLoader.get(loader);
            if (names == null)
                nonExistentClassNamesByLoader.put(loader, names = new HashSet());
            else
            {
                if (names.contains(className))
                    throw new ClassNotFoundException(className + " (cached from previous lookup attempt)");
            }
        }
        try
        {
            return Class.forName(className, initialize, loader);
        }
        catch (ClassNotFoundException e)
        {
           
            synchronized (nonExistentClassNamesByLoader) { names.add(className); }
            throw e;
        }
    }
    /**
     * Returns a hash code value for this object.
     */
    public int hashCode()
    {
        return origContextClassLoader.hashCode();
    }
    /**
     * Indicates whether some object is "equal to" this one.
     * Two ClassFinder objects are considered equal if their original
     * context class loaders are all equal.
     *
     * @param obj
     *      the reference object with which to compare
     *
     * @return
     *      true if this object is equal to the obj argument;
     *      false otherwise.
     */
    public boolean equals(Object obj)
    {
        if (obj == this)
            return true;
        if (!(obj instanceof ClassFinder))
            return false;
        ClassFinder cf = (ClassFinder)obj;
        return origContextClassLoader.equals(cf.origContextClassLoader);
    }
}