/* * (c) Copyright 2010-2011 AgileBirds * * This file is part of OpenFlexo. * * OpenFlexo is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OpenFlexo 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 the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenFlexo. If not, see <http://www.gnu.org/licenses/>. * */ package org.openflexo.toolbox; 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 { @Override 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] }