source: src/main/java/weka/core/ClassDiscovery.java @ 26

Last change on this file since 26 was 4, checked in by gnappo, 14 years ago

Import di weka.

File size: 21.7 KB
Line 
1/*
2 *    This program is free software; you can redistribute it and/or modify
3 *    it under the terms of the GNU General Public License as published by
4 *    the Free Software Foundation; either version 2 of the License, or
5 *    (at your option) any later version.
6 *
7 *    This program is distributed in the hope that it will be useful,
8 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
9 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10 *    GNU General Public License for more details.
11 *
12 *    You should have received a copy of the GNU General Public License
13 *    along with this program; if not, write to the Free Software
14 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
15 */
16
17/*
18 * ClassDiscovery.java
19 * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
20 *
21 */
22
23package weka.core;
24
25import java.io.File;
26import java.lang.reflect.Modifier;
27import java.net.URISyntaxException;
28import java.net.URL;
29import java.net.URLClassLoader;
30import java.util.Collections;
31import java.util.Comparator;
32import java.util.Enumeration;
33import java.util.HashSet;
34import java.util.Hashtable;
35import java.util.StringTokenizer;
36import java.util.Vector;
37import java.util.jar.JarEntry;
38import java.util.jar.JarFile;
39
40/**
41 * This class is used for discovering classes that implement a certain
42 * interface or a derived from a certain class.
43 *
44 * @author FracPete (fracpete at waikato dot ac dot nz)
45 * @version $Revision: 6074 $
46 * @see StringCompare
47 */
48public class ClassDiscovery
49  implements RevisionHandler {
50
51  /** whether to output some debug information. */
52  public final static boolean VERBOSE = false;
53 
54  /** for caching queries (classname+packagename <-> Vector with classnames). */
55  protected static Hashtable<String,Vector<String>> m_Cache;
56 
57  /** notify if VERBOSE is still on */
58  static {
59    if (VERBOSE)
60      System.err.println(ClassDiscovery.class.getName() + ": VERBOSE ON");
61  }
62 
63  /**
64   * Checks whether the "otherclass" is a subclass of the given "superclass".
65   *
66   * @param superclass      the superclass to check against
67   * @param otherclass      this class is checked whether it is a subclass
68   *                        of the the superclass
69   * @return                TRUE if "otherclass" is a true subclass
70   */
71  public static boolean isSubclass(String superclass, String otherclass) {
72    try {
73      return isSubclass(Class.forName(superclass), Class.forName(otherclass));
74    }
75    catch (Exception e) {
76      return false;
77    }
78  }
79 
80  /**
81   * Checks whether the "otherclass" is a subclass of the given "superclass".
82   *
83   * @param superclass      the superclass to check against
84   * @param otherclass      this class is checked whether it is a subclass
85   *                        of the the superclass
86   * @return                TRUE if "otherclass" is a true subclass
87   */
88  public static boolean isSubclass(Class superclass, Class otherclass) {
89    Class       currentclass;
90    boolean     result;
91   
92    result       = false;
93    currentclass = otherclass;
94    do {
95      result = currentclass.equals(superclass);
96     
97      // topmost class reached?
98      if (currentclass.equals(Object.class))
99        break;
100     
101      if (!result)
102        currentclass = currentclass.getSuperclass(); 
103    } 
104    while (!result);
105   
106    return result;
107  }
108 
109  /**
110   * Checks whether the given class implements the given interface.
111   *
112   * @param intf      the interface to look for in the given class
113   * @param cls       the class to check for the interface
114   * @return          TRUE if the class contains the interface
115   */
116  public static boolean hasInterface(String intf, String cls) {
117    try {
118      return hasInterface(Class.forName(intf), Class.forName(cls));
119    }
120    catch (Exception e) {
121      return false;
122    }
123  }
124 
125  /**
126   * Checks whether the given class implements the given interface.
127   *
128   * @param intf      the interface to look for in the given class
129   * @param cls       the class to check for the interface
130   * @return          TRUE if the class contains the interface
131   */
132  public static boolean hasInterface(Class intf, Class cls) {
133    Class[]       intfs;
134    int           i;
135    boolean       result;
136    Class         currentclass;
137   
138    result       = false;
139    currentclass = cls;
140    do {
141      // check all the interfaces, this class implements
142      intfs = currentclass.getInterfaces();
143      for (i = 0; i < intfs.length; i++) {
144        if (intfs[i].equals(intf)) {
145          result = true;
146          break;
147        }
148      }
149
150      // get parent class
151      if (!result) {
152        currentclass = currentclass.getSuperclass();
153       
154        // topmost class reached or no superclass?
155        if ( (currentclass == null) || (currentclass.equals(Object.class)) )
156          break;
157      }
158    } 
159    while (!result);
160     
161    return result;
162  }
163 
164  /**
165   * If the given package can be found in this part of the classpath then
166   * an URL object is returned, otherwise <code>null</code>.
167   *
168   * @param classpathPart     the part of the classpath to look for the package
169   * @param pkgname           the package to look for
170   * @return                  if found, the url as string, otherwise null
171   */
172  protected static URL getURL(String classpathPart, String pkgname) {
173    String              urlStr;
174    URL                 result;
175    File                classpathFile;
176    File                file;
177    JarFile             jarfile;
178    Enumeration         enm;
179    String              pkgnameTmp;
180   
181    result = null;
182    urlStr = null;
183
184    try {
185      classpathFile = new File(classpathPart);
186     
187      // directory or jar?
188      if (classpathFile.isDirectory()) {
189        // does the package exist in this directory?
190        file = new File(classpathPart + pkgname);
191        if (file.exists())
192          urlStr = "file:" + classpathPart + pkgname;
193      }
194      else {
195        // is package actually included in jar?
196        jarfile    = new JarFile(classpathPart);
197        enm        = jarfile.entries();
198        pkgnameTmp = pkgname.substring(1);   // remove the leading "/"
199        while (enm.hasMoreElements()) {
200          if (enm.nextElement().toString().startsWith(pkgnameTmp)) {
201            urlStr = "jar:file:" + classpathPart + "!" + pkgname;
202            break;
203          }
204        }
205      }
206    }
207    catch (Exception e) {
208      // ignore
209    }
210   
211    // try to generate URL from url string
212    if (urlStr != null) {
213      try {
214        result = new URL(urlStr);
215      }
216      catch (Exception e) {
217        System.err.println(
218            "Trying to create URL from '" + urlStr
219            + "' generates this exception:\n" + e);
220        result = null;
221      }
222    }
223
224    return result;
225  }
226
227  /**
228   * Checks the given packages for classes that inherited from the given class,
229   * in case it's a class, or implement this class, in case it's an interface.
230   *
231   * @param classname       the class/interface to look for
232   * @param pkgnames        the packages to search in
233   * @return                a list with all the found classnames
234   */
235  public static Vector<String> find(String classname, String[] pkgnames) {
236    Vector<String>      result;
237    Class       cls;
238
239    result = new Vector<String>();
240
241    try {
242      cls    = Class.forName(classname);
243      result = find(cls, pkgnames);
244    }
245    catch (Exception e) {
246      e.printStackTrace();
247    }
248
249    return result;
250  }
251
252  /**
253   * Checks the given package for classes that inherited from the given class,
254   * in case it's a class, or implement this class, in case it's an interface.
255   *
256   * @param classname       the class/interface to look for
257   * @param pkgname         the package to search in
258   * @return                a list with all the found classnames
259   */
260  public static Vector<String> find(String classname, String pkgname) {
261    Vector<String>      result;
262    Class       cls;
263
264    result = new Vector<String>();
265
266    try {
267      cls    = Class.forName(classname);
268      result = find(cls, pkgname);
269    }
270    catch (Exception e) {
271      e.printStackTrace();
272    }
273
274    return result;
275  }
276
277  /**
278   * Checks the given packages for classes that inherited from the given class,
279   * in case it's a class, or implement this class, in case it's an interface.
280   *
281   * @param cls             the class/interface to look for
282   * @param pkgnames        the packages to search in
283   * @return                a list with all the found classnames
284   */
285  public static Vector<String> find(Class cls, String[] pkgnames) {
286    Vector<String>      result;
287    int         i;
288    HashSet<String>     names;
289
290    result = new Vector<String>();
291
292    names = new HashSet<String>();
293    for (i = 0; i < pkgnames.length; i++)
294      names.addAll(find(cls, pkgnames[i]));
295
296    // sort result
297    result.addAll(names);
298    Collections.sort(result, new StringCompare());
299
300    return result;
301  }
302
303  /**
304   * Checks the given package for classes that inherited from the given class,
305   * in case it's a class, or implement this class, in case it's an interface.
306   *
307   * @param cls             the class/interface to look for
308   * @param pkgname         the package to search in
309   * @return                a list with all the found classnames
310   */
311  public static Vector<String> find(Class cls, String pkgname) {
312    Vector<String>                result;
313    StringTokenizer       tok;
314    String                part;
315    String                pkgpath;
316    File                  dir;
317    File[]                files;
318    URL                   url;
319    int                   i;
320    Class                 clsNew;
321    String                classname;
322    JarFile               jar;
323    JarEntry              entry;
324    Enumeration           enm;
325
326
327    // already cached?
328    result = getCache(cls, pkgname);
329   
330    if (result == null) {
331      result = new Vector<String>();
332
333      if (VERBOSE)
334        System.out.println(
335            "Searching for '" + cls.getName() + "' in '" + pkgname + "':");
336
337      // turn package into path
338      pkgpath = pkgname.replaceAll("\\.", "/");
339
340      // check all parts of the classpath, to include additional classes from
341      // "parallel" directories/jars, not just the first occurence
342      /*tok = new StringTokenizer(
343          System.getProperty("java.class.path"),
344          System.getProperty("path.separator")); */
345     
346      ClassloaderUtil clu = new ClassloaderUtil();
347      URLClassLoader sysLoader = (URLClassLoader)clu.getClass().getClassLoader();
348      URL[] cl_urls = sysLoader.getURLs();
349
350      //while (tok.hasMoreTokens()) {
351      for (i = 0; i < cl_urls.length; i++) {
352        //part = tok.nextToken();
353        part = cl_urls[i].toString();
354        if (part.startsWith("file:")) {
355          part.replace(" ", "%20");
356          try {
357            File temp = new File(new java.net.URI(part));
358            part = temp.getAbsolutePath();
359          } catch (URISyntaxException e) {           
360            e.printStackTrace();
361          }
362        }
363        if (VERBOSE)
364          System.out.println("Classpath-part: " + part);
365
366        // does package exist in this part of the classpath?
367        url = getURL(part, "/" + pkgpath);
368        if (VERBOSE) {
369          if (url == null)
370            System.out.println("   " + pkgpath + " NOT FOUND");
371          else
372            System.out.println("   " + pkgpath + " FOUND");
373        }
374        if (url == null)
375          continue;
376
377        // find classes
378        dir = new File(part + "/" + pkgpath);
379        if (dir.exists()) {
380          files = dir.listFiles();
381          for (int j = 0; j < files.length; j++) {
382            // only class files
383            if (    (!files[j].isFile()) 
384                || (!files[j].getName().endsWith(".class")) )
385              continue;
386
387            try {
388              classname =   pkgname + "." 
389              + files[j].getName().replaceAll(".*/", "")
390              .replaceAll("\\.class", "");
391              result.add(classname);
392            }
393            catch (Exception e) {
394              e.printStackTrace();
395            }
396          }
397        }
398        else {
399          try {
400            jar = new JarFile(part);
401            enm = jar.entries();
402            while (enm.hasMoreElements()) {
403              entry = (JarEntry) enm.nextElement();
404
405              // only class files
406              if (    (entry.isDirectory())
407                  || (!entry.getName().endsWith(".class")) )
408                continue;
409
410              classname = entry.getName().replaceAll("\\.class", "");
411
412              // only classes in the particular package
413              if (!classname.startsWith(pkgpath))
414                continue;
415
416              // no sub-package
417              if (classname.substring(pkgpath.length() + 1).indexOf("/") > -1)
418                continue;
419
420              result.add(classname.replaceAll("/", "."));
421            }
422          }
423          catch (Exception e) {
424            e.printStackTrace();
425          }
426        }
427      }
428
429      // check classes
430      i = 0;
431      while (i < result.size()) {
432        try {
433          clsNew = Class.forName((String) result.get(i));
434
435          // no abstract classes
436          if (Modifier.isAbstract(clsNew.getModifiers()))
437            result.remove(i);
438          // must implement interface
439          else if ( (cls.isInterface()) && (!hasInterface(cls, clsNew)) )
440            result.remove(i);
441          // must be derived from class
442          else if ( (!cls.isInterface()) && (!isSubclass(cls, clsNew)) )
443            result.remove(i);
444          else
445            i++;
446        }
447        catch (Throwable e) {
448          System.err.println("Checking class: " + result.get(i));
449          e.printStackTrace();
450          result.remove(i);
451        }
452      }
453
454      // sort result
455      Collections.sort(result, new StringCompare());
456
457      // add to cache
458      addCache(cls, pkgname, result);
459    }
460
461    return result;
462  }
463
464  /**
465   * adds all the sub-directories recursively to the list.
466   *
467   * @param prefix      the path prefix
468   * @param dir         the directory to look in for sub-dirs
469   * @param list        the current list of sub-dirs
470   * @return            the new list of sub-dirs
471   */
472  protected static HashSet<String> getSubDirectories(String prefix, File dir, HashSet<String> list) {
473    File[]      files;
474    int         i;
475    String      newPrefix;
476   
477    // add directory to the list
478    if (prefix == null)
479      newPrefix = "";
480    else if (prefix.length() == 0)
481      newPrefix = dir.getName();
482    else
483      newPrefix = prefix + "." + dir.getName();
484
485    if (newPrefix.length() != 0)
486      list.add(newPrefix);
487   
488    // search for sub-directories
489    files = dir.listFiles();
490    if (files != null) {
491      for (i = 0; i < files.length; i++) {
492        if (files[i].isDirectory())
493          list = getSubDirectories(newPrefix, files[i], list);
494      }
495    }
496     
497    return list;
498  }
499 
500  /**
501   * Lists all packages it can find in the classpath.
502   *
503   * @return                a list with all the found packages
504   */
505  public static Vector<String> findPackages() {
506    Vector<String>              result;
507    StringTokenizer     tok;
508    String              part;
509    File                file;
510    JarFile             jar;
511    JarEntry            entry;
512    Enumeration<JarEntry>               enm;
513    HashSet<String>             set;
514
515    result = new Vector<String>();
516    set    = new HashSet<String>();
517   
518    // check all parts of the classpath, to include additional classes from
519    // "parallel" directories/jars, not just the first occurence
520    tok = new StringTokenizer(
521        System.getProperty("java.class.path"), 
522        System.getProperty("path.separator"));
523
524    while (tok.hasMoreTokens()) {
525      part = tok.nextToken();
526      if (VERBOSE)
527        System.out.println("Classpath-part: " + part);
528     
529      // find classes
530      file = new File(part);
531      if (file.isDirectory()) {
532        set = getSubDirectories(null, file, set);
533      }
534      else if (file.exists()) {
535        try {
536          jar = new JarFile(part);
537          enm = jar.entries();
538          while (enm.hasMoreElements()) {
539            entry = (JarEntry) enm.nextElement();
540           
541            // only directories
542            if (entry.isDirectory())
543              set.add(entry.getName().replaceAll("/", ".").replaceAll("\\.$", ""));
544          }
545        }
546        catch (Exception e) {
547          e.printStackTrace();
548        }
549      }
550    }
551
552    // sort result
553    set.remove("META-INF");
554    result.addAll(set);
555    Collections.sort(result, new StringCompare());
556
557    return result;
558  }
559
560  /**
561   * initializes the cache for the classnames.
562   */
563  protected static void initCache() {
564    if (m_Cache == null)
565      m_Cache = new Hashtable<String,Vector<String>>();
566  }
567 
568  /**
569   * adds the list of classnames to the cache.
570   *
571   * @param cls         the class to cache the classnames for
572   * @param pkgname     the package name the classes were found in
573   * @param classnames  the list of classnames to cache
574   */
575  protected static void addCache(Class cls, String pkgname, Vector<String> classnames) {
576    initCache();
577    m_Cache.put(cls.getName() + "-" + pkgname, classnames);
578  }
579 
580  /**
581   * returns the list of classnames associated with this class and package, if
582   * available, otherwise null.
583   *
584   * @param cls         the class to get the classnames for
585   * @param pkgname     the package name for the classes
586   * @return            the classnames if found, otherwise null
587   */
588  protected static Vector<String> getCache(Class cls, String pkgname) {
589    initCache();
590    return m_Cache.get(cls.getName() + "-" + pkgname);
591  }
592 
593  /**
594   * clears the cache for class/classnames relation.
595   */
596  public static void clearCache() {
597    initCache();
598    m_Cache.clear();
599  }
600 
601  /**
602   * Returns the revision string.
603   *
604   * @return            the revision
605   */
606  public String getRevision() {
607    return RevisionUtils.extract("$Revision: 6074 $");
608  }
609
610  /**
611   * Possible calls:
612   * <ul>
613   *    <li>
614   *      weka.core.ClassDiscovery &lt;packages&gt;<br/>
615   *      Prints all the packages in the current classpath
616   *    </li>
617   *    <li>
618   *      weka.core.ClassDiscovery &lt;classname&gt; &lt;packagename(s)&gt;<br/>
619   *      Prints the classes it found.
620   *    </li>
621   * </ul>
622   *
623   * @param args        the commandline arguments
624   */
625  public static void main(String[] args) {
626    Vector<String>              list;
627    Vector<String>              packages;
628    int                 i;
629    StringTokenizer     tok;
630   
631    if ((args.length == 1) && (args[0].equals("packages"))) {
632      list = findPackages();
633      for (i = 0; i < list.size(); i++)
634        System.out.println(list.get(i));
635    }
636    else if (args.length == 2) {
637      // packages
638      packages = new Vector<String>();
639      tok = new StringTokenizer(args[1], ",");
640      while (tok.hasMoreTokens())
641        packages.add(tok.nextToken());
642     
643      // search
644      list = ClassDiscovery.find(
645                args[0], 
646                (String[]) packages.toArray(new String[packages.size()]));
647
648      // print result, if any
649      System.out.println(
650          "Searching for '" + args[0] + "' in '" + args[1] + "':\n" 
651          + "  " + list.size() + " found.");
652      for (i = 0; i < list.size(); i++)
653        System.out.println("  " + (i+1) + ". " + list.get(i));
654    }
655    else {
656      System.out.println("\nUsage:");
657      System.out.println(
658          ClassDiscovery.class.getName() + " packages");
659      System.out.println("\tlists all packages in the classpath");
660      System.out.println(
661          ClassDiscovery.class.getName() + " <classname> <packagename(s)>");
662      System.out.println("\tlists classes derived from/implementing 'classname' that");
663      System.out.println("\tcan be found in 'packagename(s)' (comma-separated list");
664      System.out.println();
665      System.exit(1);
666    }
667  }
668 
669  /**
670   * compares two strings. The following order is used:<br/>
671   * <ul>
672   *    <li>case insensitive</li>
673   *    <li>german umlauts (&auml; , &ouml; etc.) or other non-ASCII letters
674   *    are treated as special chars</li>
675   *    <li>special chars &lt; numbers &lt; letters</li>
676   * </ul>
677   */
678  public static class StringCompare 
679    implements Comparator, RevisionHandler {
680
681    /**
682     * appends blanks to the string if its shorter than <code>len</code>.
683     *
684     * @param s         the string to pad
685     * @param len       the minimum length for the string to have
686     * @return          the padded string
687     */
688    private String fillUp(String s, int len) {
689      while (s.length() < len)
690        s += " ";
691      return s;
692    }
693   
694    /**
695     * returns the group of the character: 0=special char, 1=number, 2=letter.
696     *
697     * @param c         the character to check
698     * @return          the group
699     */
700    private int charGroup(char c) {
701      int         result;
702     
703      result = 0;
704     
705      if ( (c >= 'a') && (c <= 'z') )
706        result = 2;
707      else if ( (c >= '0') && (c <= '9') )
708        result = 1;
709     
710      return result;
711    }
712   
713    /**
714     * Compares its two arguments for order.
715     *
716     * @param o1        the first object
717     * @param o2        the second object
718     * @return          -1 if o1&lt;o2, 0 if o1=o2 and 1 if o1&;gt;o2
719     */   
720    public int compare(Object o1, Object o2) {
721      String        s1;
722      String        s2;
723      int           i;
724      int           result;
725      int           v1;
726      int           v2;
727     
728      result = 0;   // they're equal
729     
730      // get lower case string
731      s1 = o1.toString().toLowerCase();
732      s2 = o2.toString().toLowerCase();
733     
734      // same length
735      s1 = fillUp(s1, s2.length());
736      s2 = fillUp(s2, s1.length());
737     
738      for (i = 0; i < s1.length(); i++) {
739        // same char?
740        if (s1.charAt(i) == s2.charAt(i)) {
741          result = 0;
742        }
743        else {
744          v1 = charGroup(s1.charAt(i));
745          v2 = charGroup(s2.charAt(i));
746         
747          // different type (special, number, letter)?
748          if (v1 != v2) {
749            if (v1 < v2)
750              result = -1;
751            else
752              result = 1;
753          }
754          else {
755            if (s1.charAt(i) < s2.charAt(i))
756              result = -1;
757            else
758              result = 1;
759          }
760         
761          break;
762        }
763      }
764     
765      return result;
766    }
767   
768    /**
769     * Indicates whether some other object is "equal to" this Comparator.
770     *
771     * @param obj       the object to compare with this Comparator
772     * @return          true if the object is a StringCompare object as well
773     */
774    public boolean equals(Object obj) {
775      return (obj instanceof StringCompare);
776    }
777   
778    /**
779     * Returns the revision string.
780     *
781     * @return          the revision
782     */
783    public String getRevision() {
784      return RevisionUtils.extract("$Revision: 6074 $");
785    }
786  }
787}
Note: See TracBrowser for help on using the repository browser.