package com.gotdns.lappy;
import java.io.*;
import java.util.*;
import java.util.jar.*;
import java.net.*;
/**
* This abstract class can be used to obtain a list of all classes in a classpath.
*
* Caveat: When used in environments which utilize multiple class loaders--such as
* a J2EE Container like Tomcat--it is important to select the correct classloader
* otherwise the classes returned, if any, will be incompatible with those declared
* in the code employing this class lister.
* to get a reference to your classloader within an instance method use:
* this.getClass().getClassLoader() or
* Thread.currentThread().getContextClassLoader() anywhere else
*
* @author Kris Dover
* @version 0.2.0
* @since 0.1.0
*/
public abstract class ClassList{
/**
* Searches the classpath for all classes matching a specified search criteria,
* returning them in a map keyed with the interfaces they implement or null if they
* have no interfaces. The search criteria can be specified via interface, package
* and jar name filter arguments
*
* @param classLoader The classloader whose classpath will be traversed
* @param interfaceFilter A Set of fully qualified interface names to search for
* or null to return classes implementing all interfaces
* @param packageFilter A Set of fully qualified package names to search for or
* or null to return classes in all packages
* @param jarFilter A Set of jar file names to search for or null to return
* classes from all jars
* @return A Map of a Set of Classes keyed to their interface names
*
* @throws ClassNotFoundException if the current thread's classloader cannot load
* a requested class for any reason
*/
public static Map> findClasses(ClassLoader classLoader,
Set interfaceFilter,
Set packageFilter,
Set jarFilter)
throws ClassNotFoundException {
Map> classTable = new HashMap();
Object[] classPaths;
try{
// get a list of all classpaths
classPaths = ((java.net.URLClassLoader) classLoader).getURLs();
}catch(ClassCastException cce){
// or cast failed; tokenize the system classpath
classPaths = System.getProperty("java.class.path", "").split(File.pathSeparator);
}
for(int h = 0; h < classPaths.length; h++){
Enumeration files = null;
JarFile module = null;
// for each classpath ...
File classPath = new File( (URL.class).isInstance(classPaths[h]) ?
((URL)classPaths[h]).getFile() : classPaths[h].toString() );
if( classPath.isDirectory() && jarFilter == null){ // is our classpath a directory and jar filters are not active?
List dirListing = new ArrayList();
// get a recursive listing of this classpath
recursivelyListDir(dirListing, classPath, new StringBuffer() );
// an enumeration wrapping our list of files
files = Collections.enumeration( dirListing );
}else if( classPath.getName().endsWith(".jar") ){ // is our classpath a jar?
// skip any jars not list in the filter
if( jarFilter != null && !jarFilter.contains( classPath.getName() ) ){
continue;
}
try{
// if our resource is a jar, instantiate a jarfile using the full path to resource
module = new JarFile( classPath );
}catch (MalformedURLException mue){
throw new ClassNotFoundException("Bad classpath. Error: " + mue.getMessage());
}catch (IOException io){
throw new ClassNotFoundException("jar file '" + classPath.getName() +
"' could not be instantiate from file path. Error: " + io.getMessage());
}
// get an enumeration of the files in this jar
files = module.entries();
}
// for each file path in our directory or jar
while( files != null && files.hasMoreElements() ){
// get each fileName
String fileName = files.nextElement().toString();
// we only want the class files
if( fileName.endsWith(".class") ){
// convert our full filename to a fully qualified class name
String className = fileName.replaceAll("/", ".").substring(0, fileName.length() - 6);
// debug class list
//System.out.println(className);
// skip any classes in packages not explicitly requested in our package filter
if( packageFilter != null && !packageFilter.contains(className.substring( 0, className.lastIndexOf(".") )) ){
continue;
}
// get the class for our class name
Class theClass = null;
try{
theClass = Class.forName(className, false, classLoader);
}catch(NoClassDefFoundError e){
System.out.println("Skipping class '" + className + "' for reason " + e.getMessage());
continue;
}
// skip interfaces
if( theClass.isInterface() ){
continue;
}
//then get an array of all the interfaces in our class
Class [] classInterfaces = theClass.getInterfaces();
// for each interface in this class, add both class and interface into the map
String interfaceName = null;
for (int i = 0; i < classInterfaces.length || (i == 0 && interfaceFilter == null); i++) {
if(i < classInterfaces.length){
interfaceName = classInterfaces[i].getName();
// was this interface requested?
if( interfaceFilter != null && !interfaceFilter.contains(interfaceName) ){
continue;
}
}
// is this interface already in the map?
if( classTable.containsKey( interfaceName ) ){
// if so then just add this class to the end of the list of classes implementing this interface
classTable.get(interfaceName).add( theClass );
}else{
// else create a new list initialised with our first class and put the list into the map
Set allClasses = new HashSet();
allClasses.add( theClass );
classTable.put(interfaceName, allClasses);
}
}
}
}
// close the jar if it was used
if(module != null){
try{
module.close();
}catch(IOException ioe){
throw new ClassNotFoundException("The module jar file '" + classPath.getName() +
"' could not be closed. Error: " + ioe.getMessage());
}
}
} // end for loop
return classTable;
} // end method
/**
* Recursively lists a directory while generating relative paths. This is a helper function for findClasses.
* Note: Uses a StringBuffer to avoid the excessive overhead of multiple String concatentation
*
* @param dirListing A list variable for storing the directory listing as a list of Strings
* @param dir A File for the directory to be listed
* @param relativePath A StringBuffer used for building the relative paths
*/
private static void recursivelyListDir(List dirListing, File dir, StringBuffer relativePath){
int prevLen; // used to undo append operations to the StringBuffer
// if the dir is really a directory
if( dir.isDirectory() ){
// get a list of the files in this directory
File[] files = dir.listFiles();
// for each file in the present dir
for(int i = 0; i < files.length; i++){
// store our original relative path string length
prevLen = relativePath.length();
// call this function recursively with file list from present
// dir and relateveto appended with present dir
recursivelyListDir(dirListing, files[i], relativePath.append( prevLen == 0 ? "" : "/" ).append( files[i].getName() ) );
// delete subdirectory previously appended to our relative path
relativePath.delete(prevLen, relativePath.length());
}
}else{
// this dir is a file; append it to the relativeto path and add it to the directory listing
dirListing.add( relativePath.toString() );
}
}
}