View Javadoc
1 //Copyright (C) 2004, Brian Enigma <enigma at netninja.com> 2 //This file is part of MagicCodes. 3 // 4 //MagicCodes is free software; you can redistribute it and/or modify 5 //it under the terms of the GNU General Public License as published by 6 //the Free Software Foundation; either version 2 of the License, or 7 //(at your option) any later version. 8 // 9 //MagicCodes is distributed in the hope that it will be useful, 10 //but WITHOUT ANY WARRANTY; without even the implied warranty of 11 //MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 //GNU General Public License for more details. 13 // 14 //You should have received a copy of the GNU General Public License 15 //along with Foobar; if not, write to the Free Software 16 //Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 package org.ninjasoft.magiccodes.logic; 18 19 import java.util.*; 20 import java.util.jar.*; 21 import java.io.*; 22 23 /*** 24 * This class will (slightly inefficiently) look for all classes that implement 25 * a particular interface. It is useful for plugins. This is done by looking 26 * through the contents of all jar files in the classpath, as well as performing 27 * a recursive search for *.class files in the classpath directories. 28 * 29 * This particular class may not work in restrictive ClassLoader environments 30 * such as Applets or WebStart. (It may...but unlikely and untested.) 31 * @author enigma 32 */ 33 public class PluginDiscoverer { 34 /*** Print debugging statements? */ 35 private static final boolean DEBUG = false; 36 /*** List of the folders in the classpath */ 37 private Vector classpathFolders = new Vector(); 38 /*** List of the jars in the classpath */ 39 private Vector classpathJars = new Vector(); 40 41 /*** 42 * Construct the object and parse apart the classpath into directories 43 * and jars. The constructor simple parses and does not search because 44 * the parsing is quick and easy. The actual search work should NOT be 45 * done in the constructor, but in the findMatchingPlugins class. 46 */ 47 public PluginDiscoverer() { 48 String classpath = System.getProperty("java.class.path"); 49 StringTokenizer st = new StringTokenizer(classpath, File.pathSeparator); 50 while (st.hasMoreTokens()) { 51 String item = st.nextToken(); 52 File f = new File(item); 53 if (item.toLowerCase().endsWith(".jar") && f.isFile()) { 54 classpathJars.add(item); 55 } else if (f.isDirectory()) { 56 classpathFolders.add(item); 57 } 58 } 59 } 60 61 /*** 62 * Given the class/interface we want to match and the name of a class 63 * (which may be a filename such as "org/ninjasoft/Blah.class" or an 64 * actual class name such as "org.ninjasoft.Blah"), determine if the 65 * class implements the interface. 66 * @param interfaceClass Class object representing the interface we are looking for 67 * @param testClass Name of class or class file we are testing 68 * @return corrected/normalized name of class if matches, otherwise null 69 */ 70 private String checkIfClassMatches(Class interfaceClass, String testClass) { 71 // Drop any trailing .class, if this is a filename 72 if (testClass.toLowerCase().endsWith(".class")) 73 testClass = testClass.substring(0, testClass.length() - 6); 74 // Normalize slashes to dots, if this is a filename 75 testClass = testClass.replace('//', '.').replace('/', '.'); 76 // Drop any leading dots (for instance, if this was a filename that started with a slash) 77 while (testClass.startsWith(".")) 78 testClass = testClass.substring(1); 79 // If it is an internal class, forget about it 80 if (testClass.indexOf('$') != -1) 81 return null; 82 if (DEBUG) 83 System.out.println(" class:" + testClass); 84 try{ 85 Class testClassObj = Class.forName(testClass); 86 if (interfaceClass.isAssignableFrom(testClassObj)) { 87 if (DEBUG) 88 System.out.println("MATCH!"); 89 return testClass; 90 } 91 }catch(ClassNotFoundException e) { 92 //e.printStackTrace(); 93 System.out.println("Unable to load class " + testClass); 94 } 95 return null; 96 } 97 98 /*** 99 * Find all classes in the class path that implement the interface defined 100 * in the Class object parameter. 101 * @param c a Class object representing the interface we are looking for 102 * @return an array of all the class names that implement that interface 103 */ 104 public String[] findMatchingPlugins(Class c) { 105 Vector results = new Vector(); 106 // Check the jar files 107 for (Iterator i = classpathJars.iterator(); i.hasNext(); ) { 108 String filename = (String) i.next(); 109 if (DEBUG) 110 System.out.println("jar: " + filename); 111 try{ 112 JarFile jar = new JarFile(filename); 113 for (Enumeration item = jar.entries(); item.hasMoreElements(); ) { 114 JarEntry entry = (JarEntry) item.nextElement(); 115 String name = entry.getName(); 116 if (name.toLowerCase().endsWith(".class")) { 117 String classname = checkIfClassMatches(c, name); 118 if (classname != null) 119 results.add(classname); 120 } 121 } 122 }catch(IOException e){ 123 //e.printStackTrace(); 124 System.out.println("Unable to open jar " + filename); 125 } 126 } 127 // Check the classpath folders 128 for (Iterator i = classpathFolders.iterator(); i.hasNext(); ) { 129 String folder = (String) i.next(); 130 if (DEBUG) 131 System.out.println("classfolder:" + folder); 132 recursePath(c, results, folder, ""); 133 } 134 // Flatten the output 135 String[] resultArray = new String[results.size()]; 136 for (int i=0; i<results.size(); i++) 137 resultArray[i] = (String) results.get(i); 138 return resultArray; 139 } 140 141 /*** 142 * Recurse a path, looking for class files 143 * @param c the interface we are looking for 144 * @param results where to put matching classes 145 * @param base base directory 146 * @param path current location in the traversal 147 */ 148 private void recursePath(Class c, Vector results, String base, String path) { 149 File f = new File(base + File.separator + path); 150 if (!f.isDirectory()) 151 return; 152 File[] matches = f.listFiles(new classFilter()); 153 for (int i=0; i<matches.length; i++) { 154 String classname = path + File.separator + matches[i].getName(); 155 classname = checkIfClassMatches(c, classname); 156 if (classname != null) 157 results.add(classname); 158 } 159 matches = f.listFiles(new directoryFilter()); 160 for (int i=0; i<matches.length; i++) { 161 String folder = path + File.separator + matches[i].getName(); 162 recursePath(c, results, base, folder); 163 } 164 } 165 166 /*** 167 * File.listFiles filter that only matches class files 168 */ 169 public class classFilter implements FilenameFilter { 170 public boolean accept(File dir, String name) { 171 if (name.toLowerCase().endsWith(".class")) 172 return true; 173 return false; 174 } 175 } 176 177 /*** 178 * File.listFiles filter that only matches subdirectories 179 */ 180 public class directoryFilter implements FilenameFilter { 181 public boolean accept(File dir, String name) { 182 if (new File(dir.getPath() + File.separator + name).isDirectory()) 183 return true; 184 return false; 185 } 186 } 187 188 /*** 189 * Simple main function for testing 190 * @param argv unused 191 * @throws Exception if there was a problem 192 */ 193 public static void main(String[] argv) throws Exception { 194 PluginDiscoverer pd = new PluginDiscoverer(); 195 Class pluginInterface = Class.forName("org.ninjasoft.magiccodes.plugins.Plugin"); 196 String[] plugins = pd.findMatchingPlugins(pluginInterface); 197 System.out.println(); 198 System.out.println("" + plugins.length + " plugin" + (plugins.length == 1 ? "" : "s") + " discovered" + (plugins.length == 0 ? "" : ":")); 199 for (int i=0; i<plugins.length; i++) 200 System.out.println(plugins[i]); 201 } 202 }

This page was automatically generated by Maven