import java.io.File;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.Vector;
/**
* A simple static API for listing classes loaded in a JVM. Based on
* {@link #getClassLocation(Class)} from
* http://www.javaworld.com/javaworld/javaqa/2003-07/01-qa-0711-classsrc.html
* @author (C) <a href="http://www.javaworld.com/columns/jw-qna-index.shtml">Vlad Roubtsov</a>, 2003
*/
public class ClassScope
{
private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0];
private static final Throwable CVF_FAILURE, CR_FAILURE; // set in <clinit>
static {
Throwable failure = null;
Field tempf = null;
try {
// this can fail if this is not a Sun-compatible JVM
// or if the security is too tight:
tempf = ClassLoader.class.getDeclaredField("classes");
if (tempf.getType() != Vector.class) {
throw new RuntimeException("not of type java.util.Vector: "
+ tempf.getType().getName());
}
tempf.setAccessible(true);
} catch (Throwable t) {
failure = t;
}
CLASSES_VECTOR_FIELD = tempf;
CVF_FAILURE = failure;
failure = null;
CallerResolver tempcr = null;
try {
// this can fail if the current SecurityManager does not allow
// RuntimePermission ("createSecurityManager"):
tempcr = new CallerResolver();
} catch (Throwable t) {
failure = t;
}
CALLER_RESOLVER = tempcr;
CR_FAILURE = failure;
}
/**
* Given a class loader instance, returns all classes currently loaded by
* that class loader.
*
* @param defining
* class loader to inspect [may not be null]
* @return Class array such that every Class has 'loader' as its defining
* class loader [never null, may be empty]
*
* @throws RuntimeException
* if the "classes" field hack is not possible in this JRE
*/
@SuppressWarnings("unchecked")
public static Class<?>[] getLoadedClasses(final ClassLoader loader) {
if (loader == null) {
throw new IllegalArgumentException("null input: loader");
}
if (CLASSES_VECTOR_FIELD == null) {
throw new RuntimeException(
"ClassScope::getLoadedClasses() cannot be used in this JRE",
CVF_FAILURE);
}
try {
final Vector<Class<?>> classes =
(Vector<Class<?>>) CLASSES_VECTOR_FIELD.get(loader);
if (classes == null)
return EMPTY_CLASS_ARRAY;
final Class<?>[] result;
// note: Vector is synchronized in Java 2, which helps us make
// the following into a safe critical section:
synchronized (classes) {
result = new Class<?>[classes.size()];
classes.toArray(result);
}
return result;
}
// this should not happen if <clinit> was successful:
catch (IllegalAccessException e) {
e.printStackTrace(System.out);
return EMPTY_CLASS_ARRAY;
}
}
/**
* A convenience multi-loader version of
* {@link #getLoadedClasses(ClassLoader)}.
*
* @param an
* array of defining class loaders to inspect [may not be null]
* @return Class<?> array [never null, may be empty]
*
* @throws RuntimeException
* if the "classes" field hack is not possible in this JRE
*/
public static Class<?>[] getLoadedClasses(final ClassLoader[] loaders) {
if (loaders == null)
throw new IllegalArgumentException("null input: loaders");
final List<Class<?>> resultList = new LinkedList<Class<?>>();
for (int l = 0; l < loaders.length; ++l) {
final ClassLoader loader = loaders[l];
if (loader != null) {
final Class<?>[] classes = getLoadedClasses(loaders[l]);
resultList.addAll(Arrays.asList(classes));
}
}
final Class<?>[] result = new Class<?>[resultList.size()];
resultList.toArray(result);
return result;
}
/**
* Returns the class loader set "relevant" to the calling class, as
* described in the article. Starting with the class that is the caller of
* this method, it collects all class loaders that are loaders for all
* classes on the call stack and their respective parent loaders.
*
* @return ClassLoader array [never null]
*
* @throws RuntimeException
* if the caller context resolver could not be instantiated
*/
public static ClassLoader[] getCallerClassLoaderTree() {
if (CALLER_RESOLVER == null)
throw new RuntimeException(
"Class<?>Scope::getCallerClassLoaderTree() cannot be used in this JRE",
CR_FAILURE);
final Class<?>[] callContext = CALLER_RESOLVER.getClassContext();
final Set<ClassLoader> resultSet = new HashSet<ClassLoader>();
for (int c = 2; c < callContext.length; ++c) {
getClassLoaderTree(callContext[c], resultSet);
}
final ClassLoader[] result = new ClassLoader[resultSet.size()];
resultSet.toArray(result);
return result;
}
/**
* Given a Class<?> object, attempts to find its .class location [returns
* null if no such definiton could be found].
*
* @return URL that points to the class definition [null if not found]
*/
public static URL getClassLocation(final Class<?> cls) {
if (cls == null)
throw new IllegalArgumentException("null input: cls");
URL result = null;
final String clsAsResource = cls.getName().replace('.', '/').concat(
".class");
final ProtectionDomain pd = cls.getProtectionDomain();
// java.lang.Class<?> contract does not specify if 'pd' can ever be
// null;
// it is not the case for Sun's implementations, but guard against null
// just in case:
if (pd != null) {
final CodeSource cs = pd.getCodeSource();
// 'cs' can be null depending on the classloader behavior:
if (cs != null)
result = cs.getLocation();
if (result != null) {
// convert a code source location into a full class file
// location
// for some common cases:
if ("file".equals(result.getProtocol())) {
try {
if (result.toExternalForm().endsWith(".jar")
|| result.toExternalForm().endsWith(".zip"))
result = new URL("jar:".concat(
result.toExternalForm()).concat("!/")
.concat(clsAsResource));
else if (new File(result.getFile()).isDirectory())
result = new URL(result, clsAsResource);
} catch (MalformedURLException ignore) {
}
}
}
}
if (result == null) {
// try to find 'cls' definition as a resource; this is not
// documented to be legal but Sun's implementations seem to allow
// this:
final ClassLoader clsLoader = cls.getClassLoader();
result = clsLoader != null ? clsLoader.getResource(clsAsResource)
: ClassLoader.getSystemResource(clsAsResource);
}
return result;
}
/**
* A helper class to get the call context. It subclasses SecurityManager to
* make getClassContext() accessible. An instance of CallerResolver only
* needs to be created, not installed as an actual security manager.
*/
private static final class CallerResolver extends SecurityManager {
protected Class<?>[] getClassContext() {
return super.getClassContext();
}
} // end of nested class
private ClassScope() {
} // this class is not extendible
private static void getClassLoaderTree(final Class<?> cls,
final Set<ClassLoader> resultSet) {
if ((cls != null) && (resultSet != null)) {
for (ClassLoader loader = cls.getClassLoader(); loader != null; loader = loader
.getParent()) {
resultSet.add(loader);
}
}
}
private static final Field CLASSES_VECTOR_FIELD; // set in <clinit> [can be
// null]
private static final CallerResolver CALLER_RESOLVER; // set in <clinit> [can
// be null]
}