//$Header: /home/cvs/jakarta-jmeter/src/jorphan/org/apache/jorphan/reflect/ClassFinder.java,v 1.9 2004/02/11 23:57:23 sebb Exp $
/*
* Copyright 2001-2004 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.getopt.luke;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.zip.ZipFile;
//import org.apache.jorphan.logging.LoggingManager;
//import org.apache.log.Logger;
/**
* This class finds classes that implement one or more specified interfaces.
*
* @author Burt Beckwith
* @author Michael Stover (mstover1 at apache.org)
* @version $Revision: 1.9 $
*/
public final class ClassFinder {
// transient private static Logger log = LoggingManager.getLoggerForClass();
private ClassFinder() {
}
// static only
/**
* Convenience method for <code>findClassesThatExtend(Class[],
* boolean)</code> with the option to include inner classes in the search set
* to false.
*
* @return ArrayList containing discovered classes.
*/
public static List findClassesThatExtend(String[] paths, Class[] superClasses)
throws IOException, ClassNotFoundException {
return findClassesThatExtend(paths, superClasses, false);
}
/**
* Convenience method that finds classes on the standard java classpath
*
* @param superClasses
* @return
* @throws IOException
* @throws ClassNotFoundException
*/
public static List findClassesThatExtend(Class[] superClasses)
throws IOException, ClassNotFoundException {
String javaClassPath = System.getProperty("java.class.path");
String paths[] = javaClassPath.split(File.pathSeparator);
return ClassFinder.findClassesThatExtend(paths, superClasses);
}
/**
* Convenience method that finds classes on the standard java classpath
*
* @param superClass
* @return
* @throws IOException
* @throws ClassNotFoundException
*/
public static List findClassesThatExtend(Class superClass)
throws IOException, ClassNotFoundException {
String javaClassPath = System.getProperty("java.class.path");
String paths[] = javaClassPath.split("" + File.pathSeparatorChar);
Class superClasses[] = new Class[1];
superClasses[0] = superClass;
return ClassFinder.findClassesThatExtend(paths, superClasses);
}
/**
* Convenience method to get a list of classes that can be instantiated
*
* @param superclass
* an interface or base class
* @return
* @throws IOException
* @throws ClassNotFoundException
*/
public static Class[] getInstantiableSubclasses(Class superclass)
throws IOException, ClassNotFoundException {
ArrayList instantiableSubclasses = new ArrayList();
List classes = findClassesThatExtend(superclass);
Set<String> uniq = new HashSet<String>(classes);
classes.clear();
classes.addAll(uniq);
Collections.sort(classes);
for (Iterator iter = classes.iterator(); iter.hasNext();) {
String className = (String) iter.next();
try {
Class clazz = Class.forName(className);
int modifiers = clazz.getModifiers();
if (!Modifier.isAbstract(modifiers)) {
instantiableSubclasses.add(clazz);
}
} catch (Throwable ignore) {
}
}
return (Class[]) instantiableSubclasses.toArray(new Class[0]);
}
/**
* Find classes in the provided path(s)/jar(s) that extend the class(es).
*
* @return ArrayList containing discovered classes
*/
private static String[] addJarsInPath(String[] paths) {
Set fullList = new HashSet();
for (int i = 0; i < paths.length; i++) {
fullList.add(paths[i]);
if (!paths[i].endsWith(".jar")) {
File dir = new File(paths[i]);
if (dir.exists() && dir.isDirectory()) {
String[] jars = dir.list(new FilenameFilter() {
public boolean accept(File f, String name) {
if (name.endsWith(".jar")) {
return true;
}
return false;
}
});
for (int x = 0; x < jars.length; x++) {
fullList.add(jars[x]);
}
}
}
}
return (String[]) fullList.toArray(new String[0]);
}
public static List findClassesThatExtend(String[] strPathsOrJars,
Class[] superClasses, boolean innerClasses) throws IOException,
ClassNotFoundException {
List listPaths = null;
ArrayList listClasses = null;
List listSuperClasses = null;
strPathsOrJars = addJarsInPath(strPathsOrJars);
/*
* if (log.isDebugEnabled()) { for (int k = 0; k < strPathsOrJars.length;
* k++) { log.debug("strPathsOrJars : " + strPathsOrJars[k]); } }
*/
listPaths = getClasspathMatches(strPathsOrJars);
/*
* if (log.isDebugEnabled()) { Iterator tIter = listPaths.iterator(); for (;
* tIter.hasNext();) { log.debug("listPaths : " + tIter.next()); } }
*/listClasses = new ArrayList();
listSuperClasses = new ArrayList();
for (int i = 0; i < superClasses.length; i++) {
listSuperClasses.add(superClasses[i].getName());
}
// first get all the classes
findClassesInPaths(listPaths, listClasses);
/*
* if (log.isDebugEnabled()) { Iterator tIter = listClasses.iterator(); for
* (; tIter.hasNext();) { log.debug("listClasses : " + tIter.next()); } }
*/List subClassList = findAllSubclasses(listSuperClasses, listClasses,
innerClasses);
return subClassList;
}
private static List getClasspathMatches(String[] strPathsOrJars) {
ArrayList listPaths = null;
StringTokenizer stPaths = null;
String strPath = null;
int i;
listPaths = new ArrayList();
// log.debug("Classpath = " + System.getProperty("java.class.path"));
stPaths = new StringTokenizer(System.getProperty("java.class.path"),
System.getProperty("path.separator"));
if (strPathsOrJars != null) {
strPathsOrJars = fixDotDirs(strPathsOrJars);
strPathsOrJars = fixSlashes(strPathsOrJars);
strPathsOrJars = fixEndingSlashes(strPathsOrJars);
}
/*
* if (log.isDebugEnabled()) { for (i = 0; i < strPathsOrJars.length; i++) {
* log.debug("strPathsOrJars[" + i + "] : " + strPathsOrJars[i]); } }
*/
// find all jar files or paths that end with strPathOrJar
while (stPaths.hasMoreTokens()) {
strPath = fixDotDir((String) stPaths.nextToken());
strPath = fixSlashes(strPath);
strPath = fixEndingSlashes(strPath);
if (strPathsOrJars == null) {
// log.debug("Adding: " + strPath);
listPaths.add(strPath);
} else {
boolean found = false;
for (i = 0; i < strPathsOrJars.length; i++) {
if (strPath.endsWith(strPathsOrJars[i])) {
found = true;
// log.debug("Adding "+strPath+" found at "+i);
listPaths.add(strPath);
break;// no need to look further
}
}
if (!found) {
// log.debug("Did not find: "+strPath);
}
}
}
return listPaths;
}
/**
* Get all interfaces that the class implements, including parent interfaces.
* This keeps us from having to instantiate and check instanceof, which
* wouldn't work anyway since instanceof requires a hard-coded class or
* interface name.
*
* @param theClass
* the class to get interfaces for
* @param hInterfaces
* a Map to store the discovered interfaces in
*
* NOTUSED private static void getAllInterfaces(Class theClass, Map
* hInterfaces) { Class[] interfaces = theClass.getInterfaces(); for
* (int i = 0; i < interfaces.length; i++) {
* hInterfaces.put(interfaces[i].getName(), interfaces[i]);
* getAllInterfaces(interfaces[i], hInterfaces); } }
*/
private static String[] fixDotDirs(String[] paths) {
for (int i = 0; i < paths.length; i++) {
paths[i] = fixDotDir(paths[i]);
}
return paths;
}
private static String fixDotDir(String path) {
if (path == null)
return null;
if (path.equals(".")) {
return System.getProperty("user.dir");
} else {
return path.trim();
}
}
private static String[] fixEndingSlashes(String[] strings) {
String[] strNew = new String[strings.length];
for (int i = 0; i < strings.length; i++) {
strNew[i] = fixEndingSlashes(strings[i]);
}
return strNew;
}
private static String fixEndingSlashes(String string) {
if (string.endsWith("/") || string.endsWith("\\")) {
string = string.substring(0, string.length() - 1);
string = fixEndingSlashes(string);
}
return string;
}
private static String[] fixSlashes(String[] strings) {
String[] strNew = new String[strings.length];
for (int i = 0; i < strings.length; i++) {
strNew[i] = fixSlashes(strings[i]) /* .toLowerCase() */;
}
return strNew;
}
private static String fixSlashes(String str) {
// replace \ with /
str = str.replace('\\', '/');
// compress multiples into singles;
// do in 2 steps with dummy string
// to avoid infinte loop
str = replaceString(str, "//", "_____");
str = replaceString(str, "_____", "/");
return str;
}
private static String replaceString(String s, String strToFind,
String strToReplace) {
int index;
int currentPos;
StringBuffer buffer = null;
if (s.indexOf(strToFind) == -1) {
return s;
}
currentPos = 0;
buffer = new StringBuffer();
while (true) {
index = s.indexOf(strToFind, currentPos);
if (index == -1) {
break;
}
buffer.append(s.substring(currentPos, index));
buffer.append(strToReplace);
currentPos = index + strToFind.length();
}
buffer.append(s.substring(currentPos));
return buffer.toString();
}
/**
* NOTUSED * Determine if the class implements the interface.
*
* @param theClass
* the class to check
* @param theInterface
* the interface to look for
* @return boolean true if it implements
*
* private static boolean classImplementsInterface( Class theClass,
* Class theInterface) { HashMap mapInterfaces = new HashMap(); String
* strKey = null; // pass in the map by reference since the method is
* recursive getAllInterfaces(theClass, mapInterfaces); Iterator
* iterInterfaces = mapInterfaces.keySet().iterator(); while
* (iterInterfaces.hasNext()) { strKey = (String)
* iterInterfaces.next(); if (mapInterfaces.get(strKey) ==
* theInterface) { return true; } } return false; }
*/
/**
* Convenience method for <code>findAllSubclasses(List, List,
* boolean)</code> with the option to include inner classes in the search set
* to false.
*
* @param listSuperClasses
* the base classes to find subclasses for
* @param listAllClasses
* the collection of classes to search in
* @return ArrayList of the subclasses
*
* NOTUSED private static ArrayList findAllSubclasses( List
* listSuperClasses, List listAllClasses) { return
* findAllSubclasses(listSuperClasses, listAllClasses, false); }
*/
/**
* Finds all classes that extend the classes in the listSuperClasses
* ArrayList, searching in the listAllClasses ArrayList.
*
* @param listSuperClasses
* the base classes to find subclasses for
* @param listAllClasses
* the collection of classes to search in
* @param innerClasses
* indicate whether to include inner classes in the search
* @return ArrayList of the subclasses
*/
private static ArrayList findAllSubclasses(List listSuperClasses,
List listAllClasses, boolean innerClasses) {
Iterator iterClasses = null;
ArrayList listSubClasses = null;
String strClassName = null;
Class tempClass = null;
listSubClasses = new ArrayList();
iterClasses = listSuperClasses.iterator();
while (iterClasses.hasNext()) {
strClassName = (String) iterClasses.next();
// only check classes if they are not inner classes
// or we intend to check for inner classes
if ((strClassName.indexOf("$") == -1) || innerClasses) {
// might throw an exception, assume this is ignorable
try {
tempClass = Class.forName(strClassName, false, Thread.currentThread()
.getContextClassLoader());
findAllSubclassesOneClass(tempClass, listAllClasses, listSubClasses,
innerClasses);
// call by reference - recursive
} catch (Throwable ignored) {
}
}
}
return listSubClasses;
}
/**
* Convenience method for <code>findAllSubclassesOneClass(Class, List, List,
* boolean)</code> with option to include inner classes in the search set to
* false.
*
* @param theClass
* the parent class
* @param listAllClasses
* the collection of classes to search in
* @param listSubClasses
* the collection of discovered subclasses
*
* NOTUSED private static void findAllSubclassesOneClass( Class
* theClass, List listAllClasses, List listSubClasses) {
* findAllSubclassesOneClass( theClass, listAllClasses,
* listSubClasses, false); }
*/
/**
* Finds all classes that extend the class, searching in the listAllClasses
* ArrayList.
*
* @param theClass
* the parent class
* @param listAllClasses
* the collection of classes to search in
* @param listSubClasses
* the collection of discovered subclasses
* @param innerClasses
* indicates whether inners classes should be included in the search
*/
private static void findAllSubclassesOneClass(Class theClass,
List listAllClasses, List listSubClasses, boolean innerClasses) {
Iterator iterClasses = null;
String strClassName = null;
Class c = null;
boolean bIsSubclass = false;
iterClasses = listAllClasses.iterator();
while (iterClasses.hasNext()) {
strClassName = (String) iterClasses.next();
// only check classes if they are not inner classes
// or we intend to check for inner classes
if ((strClassName.indexOf("$") == -1) || innerClasses) {
// might throw an exception, assume this is ignorable
try {
c = Class.forName(strClassName, false, Thread.currentThread()
.getContextClassLoader());
if (!c.isInterface() && !Modifier.isAbstract(c.getModifiers())) {
bIsSubclass = theClass.isAssignableFrom(c);
} else {
bIsSubclass = false;
}
if (bIsSubclass) {
listSubClasses.add(strClassName);
}
} catch (Throwable ignored) {
}
}
}
}
/**
* Converts a class file from the text stored in a Jar file to a version that
* can be used in Class.forName().
*
* @param strClassName
* the class name from a Jar file
* @return String the Java-style dotted version of the name
*/
private static String fixClassName(String strClassName) {
strClassName = strClassName.replace('\\', '.');
strClassName = strClassName.replace('/', '.');
strClassName = strClassName.substring(0, strClassName.length() - 6);
// remove ".class"
return strClassName;
}
private static void findClassesInOnePath(String strPath, List listClasses)
throws IOException {
File file = null;
ZipFile zipFile = null;
Enumeration entries = null;
String strEntry = null;
file = new File(strPath);
if (file.isDirectory()) {
findClassesInPathsDir(strPath, file, listClasses);
} else if (file.exists()) {
zipFile = new ZipFile(file);
entries = zipFile.entries();
while (entries.hasMoreElements()) {
strEntry = entries.nextElement().toString();
if (strEntry.endsWith(".class")) {
listClasses.add(fixClassName(strEntry));
}
}
}
}
private static void findClassesInPaths(List listPaths, List listClasses)
throws IOException {
Iterator iterPaths = listPaths.iterator();
while (iterPaths.hasNext()) {
findClassesInOnePath((String) iterPaths.next(), listClasses);
}
}
private static void findClassesInPathsDir(String strPathElement, File dir,
List listClasses) throws IOException {
File file = null;
String[] list = dir.list();
for (int i = 0; i < list.length; i++) {
file = new File(dir, list[i]);
if (file.isDirectory()) {
findClassesInPathsDir(strPathElement, file, listClasses);
} else if (file.exists() && (file.length() != 0)
&& list[i].endsWith(".class")) {
listClasses.add(file
.getPath()
.substring(strPathElement.length() + 1,
file.getPath().lastIndexOf("."))
.replace(File.separator.charAt(0), '.'));
}
}
}
}