package com.hackerdude.lib; import java.io.File; import java.io.FileFilter; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.Iterator; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; /** * This class allows you to query the classpath for the * existence of classes that implement specified interfaces * or inherit from specific classes. You can use this to keep * your application logic and your "drivers" for specific * functionality decoupled. * * <P>For example, you might have a series of SQL classes that * implement the functionality you want using different database * syntaxes. With this you can have them all implement a specific * interface and instruct the people who write these drivers to * put them in a specific package (maybe the same package as your * interface). Then you can simply ask the introspector to "find them". * If they are in any jar in the classpath, they will be added the list. */ public class ClassPathIntrospector { // A cached classloader. protected ClassLoader cLoader; /** * Creates a new classpath introspector using the * system classloader. */ public ClassPathIntrospector() { this(ClassLoader.getSystemClassLoader()); } /** * Creates a new classpath introspector using the specified classloader. */ public ClassPathIntrospector(ClassLoader cLoader) { this.cLoader = cLoader; } /** * Retrieves all the classnames found along the classpath which are * assignment compatible with the specified class and live in the * specified package. * <P class=Note>Note: It only reports regular, instantiatable classes (no interfaces, inner * classes or such). */ public Collection getClassNamesFor(Class compatibleClass, Package pkg) { return getClassNamesFor(System.getProperty("java.class.path", ""),compatibleClass, pkg.getName()); } /** * Retrieves all the classnames found along the classpath which are * assignment compatible with the specified class and live in the * specified package. * <P class=Note>Note: It only reports regular, instantiatable classes (no interfaces, inner * classes or such). */ public Collection getClassNamesFor(Class compatibleClass, String pkg) { return getClassNamesFor(System.getProperty("java.class.path", ""), compatibleClass, pkg); } /** * Retrieves all the classnames found along the specified classpath classpath which are * assignment compatible with the specified class and live in the * specified package. * <P class=Note>Note: It only reports regular, instantiatable classes (no interfaces, inner * classes or such). */ public Collection getClassNamesFor(String classPath, Class compatibleClass, Package pkg) { return getClassNamesFor(classPath, compatibleClass, pkg.getName()); } /** * This method retrieves all the class names compatible (assignable from) * the class names for a classpath, inside the specified package. * <P class=Note>Note: It only reports regular, instantiatable classes (no interfaces, inner * classes or such). */ public Collection getClassNamesFor(String classPath, Class compatibleClass, String pkg) { String[] paths = initializePath(classPath); ArrayList allClasses = new ArrayList(); for ( int i=0; i<paths.length; i++ ) { allClasses.addAll(getClassNamesMatching(paths[i], pkg)); } // Now that we have all classes, filter them so only unique concrete instances of the requested compatibleclass can be used. Collection filtered = filterClasses(allClasses, compatibleClass); return filtered; } /** * This method loads all the classes expressed as Strings inside the * allClasses collection, and makes sure they are compatible (assignable * from) the specified class. It also filters out abstracts or interfaces. */ public Collection filterClasses(Collection allClasses, Class compatibleClass) { Iterator iter = allClasses.iterator(); Collection result = new ArrayList(); while ( iter.hasNext() ) { String thisClass = (String)iter.next(); // Don't add duplicates. if ( ! result.contains(thisClass) ) { try { Class theClass = Class.forName(thisClass, false, cLoader); if ( compatibleClass.isAssignableFrom(theClass) && // Don't add abstracts or interfaces ! java.lang.reflect.Modifier.isAbstract(theClass.getModifiers()) && ! java.lang.reflect.Modifier.isInterface(theClass.getModifiers()) ) { result.add(thisClass); } } catch ( ClassNotFoundException exc ) {} // Ignore. Just don't add it. } } return result; } /** * This method simply replaces package names in the format xxx.yyy.zzz for * xxx/yyy/zzz or xxx\yyy\zzz (depending on the OS). */ private String initializePackage(String pkg) { return pkg.replace('.', File.separatorChar); } /** * This method returns all the classes in a specified package from a * spacified path (a JAR, Zip or a directory) as an array of Strings. */ private Collection getClassNamesMatching(String path, String packageName) { String FSPackageName = initializePackage(packageName); String packageName4Jar = packageName.replace('.', '/'); ArrayList al = new ArrayList(); File theFile = new File(path); FileFilter classFileFilter = new ClassFileFilter(); if ( theFile.isDirectory() ) { // Deal with File File newDir = new File(theFile.getPath()+File.separator+FSPackageName); if ( newDir.exists() ) { // System.out.println("Oh, looky there - we found the package in "+newDir.getName()+" Let's see..."); File[] theFiles = newDir.listFiles(classFileFilter); for ( int i=0; i<theFiles.length; i++ ) { String theClassName = packageName+"."+theFiles[i].getName(); // Remove the .class theClassName = theClassName.substring(0, theClassName.length()-6); // length of .class al.add(theClassName); } } } else { // System.out.println("Can't deal with jar yet - "+path); try { ZipFile theZipFile = new ZipFile(theFile); Enumeration entries = theZipFile.entries(); while ( entries.hasMoreElements() ) { ZipEntry current = (ZipEntry)entries.nextElement(); String currentName = current.getName(); if ( currentName.startsWith(packageName4Jar) && currentName.endsWith(".class" ) ) { // System.out.println("Jar "+theFile+" - Found! "+currentName); String theClassName = currentName; // Remove the .class theClassName = theClassName.substring(0, theClassName.length()-6); // length of .class theClassName = theClassName.replace('/','.'); al.add(theClassName); } } ZipEntry entry = theZipFile.getEntry(FSPackageName); if ( entry != null ) { System.out.println("[ClassPathIntrospector] We have ignition "); } // Ignore errors. Just don't verify that one. } catch ( java.io.IOException exc ) {} } return al; } /** * This method parses a series of path elements. */ private String[] initializePath(String ldpath) { String ps = File.pathSeparator; int ldlen = ldpath.length(); int i, j, n; // Count the separators in the path i = ldpath.indexOf(ps); n = 0; while (i >= 0) { n++; i = ldpath.indexOf(ps, i+1); } // allocate the array of paths - n :'s = n + 1 path elements String[] paths = new String[n + 1]; // Fill the array with paths from the ldpath n = i = 0; j = ldpath.indexOf(ps); while (j >= 0) { if (j - i > 0) { paths[n++] = ldpath.substring(i, j); } else if (j - i == 0) { paths[n++] = "."; } i = j + 1; j = ldpath.indexOf(ps, i); } paths[n] = ldpath.substring(i, ldlen); return paths; } /** * A little classFile filter that allows in files ending with .class. */ class ClassFileFilter implements FileFilter { public boolean accept(File theFile) { return theFile.getName().endsWith(".class"); } } }