/*
* 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:
*
* - The loader that loaded the class or instance referred to in the API
* that caused this class to be loaded (the context class).
* - The loader returned in the current context by
* Thread.getContextClassLoader().
* - The loader returned by Thread.getContextClassLoader() at
* the time this ClassFinder was constructed (which should equate
* to the time of PersistenceManagerFactory.getPersistenceManager()).
*
*
*
* @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);
}
}