/* * This file is part of JGAP. * * JGAP offers a dual license model containing the LGPL as well as the MPL. * * For licensing information please see the file license.txt included with JGAP * or have a look at the top of class org.jgap.Chromosome which representatively * includes the JGAP license policy applicable for any file delivered with JGAP. */ package org.jgap.util; import java.io.*; import java.util.*; import java.util.jar.*; /** * This class will (slightly inefficiently) look for all classes that implement * a particular interface. It is useful for plugins. This is done by looking * through the contents of all jar files in the classpath, as well as * performing a recursive search for *.class files in the classpath directories * * This particular class may not work in restrictive ClassLoader environments * such as Applets or WebStart. (It may...but unlikely and untested.) * * @author Klaus Meffert * @since 2.3 */ public class PluginDiscoverer { /** String containing the CVS revision. Read out via reflection!*/ private final static String CVS_REVISION = "$Revision: 1.9 $"; private static final boolean DEBUG = false; //list of folders in the classpath private List m_classpathFolders; //list of jars in the classpath private List m_classpathJars; private String m_jarFile; /** * Reads the list of jars and classpath folders into instance variables * for later (cached) access. * * @author Klaus Meffert * @since 2.3 */ public PluginDiscoverer() { init(); String classpath = System.getProperty("java.class.path"); StringTokenizer st = new StringTokenizer(classpath, File.pathSeparator); while (st.hasMoreTokens()) { String item = st.nextToken(); File f = new File(item); if (item.toLowerCase().endsWith(".jar") && f.isFile()) { m_classpathJars.add(item); } else if (f.isDirectory()) { m_classpathFolders.add(item); } } } /** * Prepares the discoverer for a single jar file * @param a_jarFile String * * @author Klaus Meffert * @since 3.2 */ public PluginDiscoverer(String a_jarFile) { init(); m_jarFile = a_jarFile; m_classpathJars.add(m_jarFile); } private void init() { m_classpathFolders = new Vector(); m_classpathJars = new Vector(); } /** * Checks if a given class matches a given interface * @param interfaceClass The interface we are looking for * @param testClass class under test against the interface * @return corrected name of matched class, or null if not matching * * @author Klaus Meffert * @since 2.3 */ private String checkIfClassMatches(String a_jarFilename, final Class a_interfaceClass, String a_testClass) { // remove trailing dots if (a_testClass.toLowerCase().endsWith(".class")) { a_testClass = a_testClass.substring(0, a_testClass.length() - 6); } // replace slashes with dots a_testClass = a_testClass.replace('\\', '.').replace('/', '.'); // remove leading dots while (a_testClass.startsWith(".")) { a_testClass = a_testClass.substring(1); } if (a_testClass.indexOf('$') != -1) { // don't handle inner/internal classes return null; } try { ClassLoader cl; if (a_jarFilename == null) { cl = getClass().getClassLoader(); } else { cl = new JarClassLoader(a_jarFilename); } Class testClassObj = Class.forName(a_testClass, false, cl); if (a_interfaceClass.isAssignableFrom(testClassObj)) { if (testClassObj.isInterface()) { // no interfaces wanted as result return null; } if ( (testClassObj.getModifiers() & java.lang.reflect.Modifier.ABSTRACT) > 0) { // no abstract classes wanted as result return null; } return a_testClass; } } catch (UnsatisfiedLinkError ule) { if (DEBUG) { System.out.println("Unsatisfied link error for class: " + a_testClass); } } catch (IllegalAccessError e) { if (DEBUG) { System.out.println("Unable to load class: " + a_testClass); } } catch (ClassNotFoundException cnfe) { if (DEBUG) { System.out.println("Class not found: " + a_testClass); } } catch (NoClassDefFoundError nex) { if (DEBUG) { System.out.println("No class definition found: " + a_testClass); } } return null; } /** * Finds all classes implementing the given interface * @param a_fullInterfaceName name of the interface (inclusive package name) * to find implementing classes (not abstract) for * @return list of class names that implement the given interface * * @throws ClassNotFoundException * * @author Klaus Meffert * @since 2.4 */ public List findImplementingClasses(final String a_fullInterfaceName) throws ClassNotFoundException { Class interfaceToLookFor = Class.forName(a_fullInterfaceName); return findImplementingClasses(interfaceToLookFor); } /** * Finds all classes implementing the given interface * @param a_intrface the interface to check against * @return list of class names that implement the given interface * * @author Klaus Meffert * @since 2.3 */ public List findImplementingClasses(final Class a_intrface) { List result = new Vector(); // Check the jar files String s = null; try { // determine current directory File f = new File("."); s = f.getCanonicalPath(); s = FileKit.getConformPath(s, true); } catch (IOException iex) { throw new RuntimeException("Unable to determine current directory",iex); } Iterator i = m_classpathJars.iterator(); while (i.hasNext()) { String filename = (String) i.next(); filename = FileKit.getConformPath(filename, true); // only search for jars in current dir or subdir (otherwise we would scan // the whole bunch of system and external library jars, too, and that // would be really inperformant) if (filename.startsWith(s)) { try { JarFile jar = new JarFile(filename); Enumeration item = jar.entries(); while (item.hasMoreElements()) { JarEntry entry = (JarEntry) item.nextElement(); String name = entry.getName(); if (name.toLowerCase().endsWith(".class")) { String classname = checkIfClassMatches(filename, a_intrface, name); if (classname != null) { result.add(classname); } } } } catch (IOException e) { System.out.println("Unable to open jar " + filename); } } } // Iterate over the classpath folders i = m_classpathFolders.iterator(); while (i.hasNext()) { String folder = (String) i.next(); System.err.println(folder); findImplementingClasses0(a_intrface, result, folder, ""); } return result; } /** * Recursive helper method, searching a path recursively for class files * conforming to a given interface * @param intrface the interface we are looking for * @param result container for storing the results * @param base base directory * @param path current location in the traversal * * @author Klaus Meffert * @since 2.3 */ private void findImplementingClasses0(final Class a_intrface, final List a_result, final String a_base, final String a_path) { a_result.addAll(findImplementingClasses(a_intrface, a_base, a_path)); File f = new File(a_base + File.separator + a_path); if (!f.isDirectory()) { return; } // File[] matches = f.listFiles(new ClassFilter()); // for (int i = 0; i < matches.length; i++) { // String classname = a_path + File.separator + matches[i].getName(); // classname = checkIfClassMatches(a_intrface, classname); // if (classname != null) { // a_result.add(classname); // } // } File[] matches = f.listFiles(new DirectoryFilter()); for (int i = 0; i < matches.length; i++) { String folder = a_path + File.separator + matches[i].getName(); findImplementingClasses0(a_intrface, a_result, a_base, folder); } } /** * Finds all classes implementing the given interface within a given * directory. * * @param a_intrface Class * @param a_base String * @param a_path String * @return List */ public List findImplementingClasses(final Class a_intrface, final String a_base, final String a_path) { List result = new Vector(); File f = new File(a_base + File.separator + a_path); if (!f.isDirectory()) { return result; } File[] matches = f.listFiles(new ClassFilter()); for (int i = 0; i < matches.length; i++) { String classname = a_path + File.separator + matches[i].getName(); classname = checkIfClassMatches(null, a_intrface, classname); if (classname != null) { result.add(classname); } } return result; } /** * Filter that only matches class files */ public class ClassFilter implements FilenameFilter { public boolean accept(final File a_dir, final String a_name) { return (a_name != null && a_name.toLowerCase().endsWith(".class")); } } /** * Filter that only matches subdirectories */ public class DirectoryFilter implements FilenameFilter { public boolean accept(final File a_dir, final String a_name) { return (a_dir != null && new File(a_dir.getPath() + File.separator + a_name).isDirectory()); } } /** * For testing purpose * @param args not used * @throws Exception in case of any problem * * @author Klaus Meffert * @since 2.3 */ public static void main(String[] args) throws Exception { PluginDiscoverer discoverer = new PluginDiscoverer(); // Manifest mf = discoverer.getManifestOfJar("C:/temp/jgap/jgap.jar!/"); // String version = discoverer.getJGAPVersion(mf); // List plugins = discoverer.findImplementingClasses( "org.jgap.INaturalSelector"); System.out.println(); int size = plugins.size(); System.out.println("" + size + " plugin" + (size == 1 ? "" : "s") + " discovered" + (size == 0 ? "" : ":")); for (int i = 0; i < size; i++) { System.out.println(plugins.get(i)); } // System.out.println("\n\n"); // plugins = discoverer.findImplementingClasses(IGridConfiguration.class, // "c:/JavaProjekte/JGAP_CVS/classes", "examples/grid/fitnessDistributed"); // System.out.println(); // size = plugins.size(); // System.out.println("" + size + " plugin" // + (size == 1 ? "" : "s") + " discovered" // + (size == 0 ? "" : ":")); // for (int i = 0; i < size; i++) { // System.out.println(plugins.get(i)); // } System.exit(0); } }