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