View Javadoc

1   package com.github.smokestack.ejb.internal;
2   
3   /*
4    * ClassFinder.java
5    */
6   
7   import java.io.*;
8   import java.net.URL;
9   import java.net.JarURLConnection;
10  import java.net.MalformedURLException;
11  import java.util.*;
12  import java.util.jar.*;
13  
14  /**
15   * This utility class was based originally on <a
16   * href="private.php?do=newpm&u=47838">Daniel Le Berre</a>'s <code>RTSI</code>
17   * class. This class can be called in different modes, but the principal use is
18   * to determine what subclasses/implementations of a given class/interface exist
19   * in the current runtime environment.
20   * 
21   * @author Daniel Le Berre, Elliott Wade
22   */
23  public class ClassFinder {
24  	private Class<?> searchClass = null;
25  	private Map<URL, String> classpathLocations = new HashMap<URL, String>();
26  	private Map<Class<?>, URL> results = new HashMap<Class<?>, URL>();
27  	private List<Throwable> errors = new ArrayList<Throwable>();
28  	private boolean working = false;
29  	private boolean debug=false;
30  
31  	public ClassFinder() {
32  		refreshLocations();
33  	}
34  
35  	public ClassFinder(boolean debug) {
36  		this();
37  		this.debug=debug;
38  	}
39  
40  	/**
41  	 * Rescan the classpath, cacheing all possible file locations.
42  	 */
43  	public final void refreshLocations() {
44  		synchronized (classpathLocations) {
45  			classpathLocations = getClasspathLocations();
46  		}
47  	}
48  
49  	/**
50  	 * @param fqcn name of superclass/interface on which to search
51  	 */
52  	public final Vector<Class<?>> findSubclasses(String fqcn) {
53  		synchronized (classpathLocations) {
54  			synchronized (results) {
55  				try {
56  					working = true;
57  					searchClass = null;
58  					errors = new ArrayList<Throwable>();
59  					results = new TreeMap<Class<?>, URL>(CLASS_COMPARATOR);
60  
61  					//
62  					// filter malformed FQCN
63  					//
64  					if (fqcn.startsWith(".") || fqcn.endsWith(".")) {
65  						return new Vector<Class<?>>();
66  					}
67  
68  					//
69  					// Determine search class from fqcn
70  					//
71  					try {
72  						searchClass = Class.forName(fqcn);
73  					} catch (ClassNotFoundException ex) {
74  						// if class not found, let empty vector return...
75  						errors.add(ex);
76  						return new Vector<Class<?>>();
77  					}
78  
79  					return findSubclasses(searchClass, classpathLocations);
80  				} finally {
81  					working = false;
82  				}
83  			}
84  		}
85  	}
86  
87  	public final List<Throwable> getErrors() {
88  		return new ArrayList<Throwable>(errors);
89  	}
90  
91  	/**
92  	 * The result of the last search is cached in this object, along with the
93  	 * URL that corresponds to each class returned. This method may be called to
94  	 * query the cache for the location at which the given class was found.
95  	 * <code>null</code> will be returned if the given class was not found
96  	 * during the last search, or if the result cache has been cleared.
97  	 */
98  	public final URL getLocationOf(Class<?> cls) {
99  		if (results != null)
100 			return results.get(cls);
101 		else
102 			return null;
103 	}
104 
105 	/**
106 	 * Determine every URL location defined by the current classpath, and it's
107 	 * associated package name.
108 	 */
109 	public final Map<URL, String> getClasspathLocations() {
110 		Map<URL, String> map = new TreeMap<URL, String>(URL_COMPARATOR);
111 		File file = null;
112 
113 		String pathSep = System.getProperty("path.separator");
114 		String classpath = System.getProperty("java.class.path");
115 		// System.out.println ("classpath=" + classpath);
116 
117 		StringTokenizer st = new StringTokenizer(classpath, pathSep);
118 		while (st.hasMoreTokens()) {
119 			String path = st.nextToken();
120 			file = new File(path);
121 			include(null, file, map);
122 		}
123 
124 		Iterator<URL> it = map.keySet().iterator();
125 		while (it.hasNext()) {
126 			URL url = it.next();
127 			// System.out.println (url + "-->" + map.get (url));
128 		}
129 
130 		return map;
131 	}
132 
133 	private final static FileFilter DIRECTORIES_ONLY = new FileFilter() {
134 		public boolean accept(File f) {
135 			if (f.exists() && f.isDirectory())
136 				return true;
137 			else
138 				return false;
139 		}
140 	};
141 
142 	private final static Comparator<URL> URL_COMPARATOR = new Comparator<URL>() {
143 		public int compare(URL u1, URL u2) {
144 			return String.valueOf(u1).compareTo(String.valueOf(u2));
145 		}
146 	};
147 
148 	private final static Comparator<Class<?>> CLASS_COMPARATOR = new Comparator<Class<?>>() {
149 		public int compare(Class<?> c1, Class<?> c2) {
150 			return String.valueOf(c1).compareTo(String.valueOf(c2));
151 		}
152 	};
153 
154 	private final void include(String name, File file, Map<URL, String> map) {
155 		if (!file.exists())
156 			return;
157 		if (!file.isDirectory()) {
158 			// could be a JAR file
159 			includeJar(file, map);
160 			return;
161 		}
162 
163 		if (name == null)
164 			name = "";
165 		else
166 			name += ".";
167 
168 		// add subpackages
169 		File[] dirs = file.listFiles(DIRECTORIES_ONLY);
170 		for (int i = 0; i < dirs.length; i++) {
171 			try {
172 				// add the present package
173 				map.put(new URL("file://" + dirs[i].getCanonicalPath()), name
174 						+ dirs[i].getName());
175 			} catch (IOException ioe) {
176 				return;
177 			}
178 
179 			include(name + dirs[i].getName(), dirs[i], map);
180 		}
181 	}
182 
183 	private void includeJar(File file, Map<URL, String> map) {
184 		if (file.isDirectory())
185 			return;
186 
187 		URL jarURL = null;
188 		JarFile jar = null;
189 		try {
190 			jarURL = new URL("file://" + file.getCanonicalPath());
191 			jarURL = new URL("jar:" + jarURL.toExternalForm() + "!/");
192 			JarURLConnection conn = (JarURLConnection) jarURL.openConnection();
193 			jar = conn.getJarFile();
194 		} catch (Exception e) {
195 			// not a JAR or disk I/O error
196 			// either way, just skip
197 			return;
198 		}
199 
200 		if (jar == null || jarURL == null)
201 			return;
202 
203 		// include the jar's "default" package (i.e. jar's root)
204 		map.put(jarURL, "");
205 
206 		Enumeration<JarEntry> e = jar.entries();
207 		while (e.hasMoreElements()) {
208 			JarEntry entry = e.nextElement();
209 
210 			if (entry.isDirectory()) {
211 				if (entry.getName().toUpperCase().equals("META-INF/"))
212 					continue;
213 
214 				try {
215 					map.put(new URL(jarURL.toExternalForm() + entry.getName()),
216 							packageNameFor(entry));
217 				} catch (MalformedURLException murl) {
218 					// whacky entry?
219 					continue;
220 				}
221 			}
222 		}
223 	}
224 
225 	private static String packageNameFor(JarEntry entry) {
226 		if (entry == null)
227 			return "";
228 		String s = entry.getName();
229 		if (s == null)
230 			return "";
231 		if (s.length() == 0)
232 			return s;
233 		if (s.startsWith("/"))
234 			s = s.substring(1, s.length());
235 		if (s.endsWith("/"))
236 			s = s.substring(0, s.length() - 1);
237 		return s.replace('/', '.');
238 	}
239 
240 	private final void includeResourceLocations(String packageName,	Map<URL, String> map) {
241 		try {
242 			Enumeration<URL> resourceLocations = ClassFinder.class
243 					.getClassLoader().getResources(getPackagePath(packageName));
244 
245 			while (resourceLocations.hasMoreElements()) {
246 				map.put(resourceLocations.nextElement(), packageName);
247 			}
248 		} catch (Exception e) {
249 			// well, we tried
250 			if (debug){
251 				errors.add(e);
252 			}
253 			return;
254 		}
255 	}
256 
257 	private final Vector<Class<?>> findSubclasses(Class<?> superClass,
258 			Map<URL, String> locations) {
259 		Vector<Class<?>> v = new Vector<Class<?>>();
260 
261 		Vector<Class<?>> w = null; // new Vector<Class<?>> ();
262 
263 		// Package [] packages = Package.getPackages ();
264 		// for (int i=0;i<packages.length;i++)
265 		// {
266 		// System.out.println ("package: " + packages[i]);
267 		// }
268 
269 		Iterator<URL> it = locations.keySet().iterator();
270 		while (it.hasNext()) {
271 			URL url = it.next();
272 			// System.out.println (url + "-->" + locations.get (url));
273 
274 			w = findSubclasses(url, locations.get(url), superClass);
275 			if (w != null && (w.size() > 0))
276 				v.addAll(w);
277 		}
278 
279 		return v;
280 	}
281 
282 	private final Vector<Class<?>> findSubclasses(URL location,
283 			String packageName, Class<?> superClass) {
284 		// System.out.println ("looking in package:" + packageName);
285 		// System.out.println ("looking for  class:" + superClass);
286 
287 		synchronized (results) {
288 
289 			// hash guarantees unique names...
290 			Map<Class<?>, URL> thisResult = new TreeMap<Class<?>, URL>(
291 					CLASS_COMPARATOR);
292 			Vector<Class<?>> v = new Vector<Class<?>>(); // ...but return a
293 															// vector
294 
295 			// TODO: double-check for null search class
296 			String fqcn = searchClass.getName();
297 
298 			List<URL> knownLocations = new ArrayList<URL>();
299 			knownLocations.add(location);
300 			// TODO: add getResourceLocations() to this list
301 
302 			// iterate matching package locations...
303 			for (int loc = 0; loc < knownLocations.size(); loc++) {
304 				URL url = knownLocations.get(loc);
305 
306 				// Get a File object for the package
307 				File directory = new File(url.getFile());
308 
309 				// System.out.println ("\tlooking in " + directory);
310 
311 				if (directory.exists()) {
312 					// Get the list of the files contained in the package
313 					String[] files = directory.list();
314 					for (int i = 0; i < files.length; i++) {
315 						// we are only interested in .class files
316 						if (files[i].endsWith(".class")) {
317 							// removes the .class extension
318 							String classname = files[i].substring(0, files[i]
319 									.length() - 6);
320 
321 							// System.out.println ("\t\tchecking file " +
322 							// classname);
323 
324 							try {
325 								Class<?> c = Class.forName(packageName + "."
326 										+ classname);
327 								if (superClass.isAssignableFrom(c)
328 										&& !fqcn.equals(packageName + "."
329 												+ classname)) {
330 									thisResult.put(c, url);
331 								}
332 							} catch (ClassNotFoundException cnfex) {
333 								if (debug){
334 									errors.add(cnfex);
335 								}
336 								// System.err.println(cnfex);
337 							} catch (Exception ex) {
338 								if (debug){
339 									errors.add(ex);
340 								}
341 								// System.err.println (ex);
342 							} catch (NoClassDefFoundError ncdfe){
343 								if (debug){
344 									errors.add(ncdfe);								
345 								}
346 							}
347 						}
348 					}
349 				} else {
350 					try {
351 						// It does not work with the filesystem: we must
352 						// be in the case of a package contained in a jar file.
353 						JarURLConnection conn = (JarURLConnection) url
354 								.openConnection();
355 						// String starts = conn.getEntryName();
356 						JarFile jarFile = conn.getJarFile();
357 
358 						// System.out.println ("starts=" + starts);
359 						// System.out.println ("JarFile=" + jarFile);
360 
361 						Enumeration<JarEntry> e = jarFile.entries();
362 						while (e.hasMoreElements()) {
363 							JarEntry entry = e.nextElement();
364 							String entryname = entry.getName();
365 
366 							// System.out.println ("\tconsidering entry: " +
367 							// entryname);
368 
369 							if (!entry.isDirectory()
370 									&& entryname.endsWith(".class")) {
371 								String classname = entryname.substring(0,
372 										entryname.length() - 6);
373 								if (classname.startsWith("/"))
374 									classname = classname.substring(1);
375 								classname = classname.replace('/', '.');
376 
377 								// System.out.println ("\t\ttesting classname: "
378 								// + classname);
379 
380 								try {
381 									// TODO: verify this block
382 									Class c = Class.forName(classname);
383 
384 									if (superClass.isAssignableFrom(c)
385 											&& !fqcn.equals(classname)) {
386 										thisResult.put(c, url);
387 									}
388 								} catch (ClassNotFoundException cnfex) {
389 									// that's strange since we're scanning
390 									// the same classpath the classloader's
391 									// using... oh, well
392 									errors.add(cnfex);
393 								} catch (NoClassDefFoundError ncdfe) {
394 									// dependency problem... class is
395 									// unusable anyway, so just ignore it
396 									errors.add(ncdfe);
397 								} catch (UnsatisfiedLinkError ule) {
398 									// another dependency problem... class is
399 									// unusable anyway, so just ignore it
400 									errors.add(ule);
401 								} catch (Exception exception) {
402 									// unexpected problem
403 									// System.err.println (ex);
404 									errors.add(exception);
405 								} catch (Error error) {
406 									// lots of things could go wrong
407 									// that we'll just ignore since
408 									// they're so rare...
409 									errors.add(error);
410 								}
411 							}
412 						}
413 					} catch (IOException ioex) {
414 						// System.err.println(ioex);
415 						errors.add(ioex);
416 					}
417 				}
418 			} // while
419 
420 			// System.out.println ("results = " + thisResult);
421 
422 			results.putAll(thisResult);
423 
424 			Iterator<Class<?>> it = thisResult.keySet().iterator();
425 			while (it.hasNext()) {
426 				v.add(it.next());
427 			}
428 			return v;
429 
430 		} // synch results
431 	}
432 
433 	private final static String getPackagePath(String packageName) {
434 		// Translate the package name into an "absolute" path
435 		String path = new String(packageName);
436 		if (!path.startsWith("/")) {
437 			path = "/" + path;
438 		}
439 		path = path.replace('.', '/');
440 
441 		// ending with "/" indicates a directory to the classloader
442 		if (!path.endsWith("/"))
443 			path += "/";
444 
445 		// for actual classloader interface (NOT Class.getResource() which
446 		// hacks up the request string!) a resource beginning with a "/"
447 		// will never be found!!! (unless it's at the root, maybe?)
448 		if (path.startsWith("/"))
449 			path = path.substring(1, path.length());
450 
451 		// System.out.println ("package path=" + path);
452 
453 		return path;
454 	}
455 
456 	public Vector<Class<?>> findSubclasses(Class<?> clazz) {
457 		return findSubclasses(clazz.getCanonicalName());
458 	}
459 }