1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
72 if (testClass.toLowerCase().endsWith(".class"))
73 testClass = testClass.substring(0, testClass.length() - 6);
74
75 testClass = testClass.replace('//', '.').replace('/', '.');
76
77 while (testClass.startsWith("."))
78 testClass = testClass.substring(1);
79
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
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
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
124 System.out.println("Unable to open jar " + filename);
125 }
126 }
127
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
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 }