/* * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* * ClassDiscovery.java * Copyright (C) 2005-2012 University of Waikato, Hamilton, New Zealand * */ package weka.core; import java.io.File; import java.lang.reflect.Modifier; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Enumeration; import java.util.HashSet; import java.util.Hashtable; import java.util.StringTokenizer; import java.util.Vector; import java.util.jar.JarFile; /** * This class is used for discovering classes that implement a certain * interface or a derived from a certain class. * * @author FracPete (fracpete at waikato dot ac dot nz) * @version $Revision: 8034 $ * @see StringCompare */ public class ClassDiscovery implements RevisionHandler { /** whether to output some debug information. */ public final static boolean VERBOSE = false; /** for caching queries (classname+packagename <-> Vector with classnames). */ protected static Hashtable<String,Vector<String>> m_Cache; /** the overall class cache. */ protected static ClassCache m_ClassCache; /** notify if VERBOSE is still on */ static { if (VERBOSE) System.err.println(ClassDiscovery.class.getName() + ": VERBOSE ON"); } /** * Checks whether the "otherclass" is a subclass of the given "superclass". * * @param superclass the superclass to check against * @param otherclass this class is checked whether it is a subclass * of the the superclass * @return TRUE if "otherclass" is a true subclass */ public static boolean isSubclass(String superclass, String otherclass) { try { return isSubclass(Class.forName(superclass), Class.forName(otherclass)); } catch (Exception e) { return false; } } /** * Checks whether the "otherclass" is a subclass of the given "superclass". * * @param superclass the superclass to check against * @param otherclass this class is checked whether it is a subclass * of the the superclass * @return TRUE if "otherclass" is a true subclass */ public static boolean isSubclass(Class superclass, Class otherclass) { Class currentclass; boolean result; result = false; currentclass = otherclass; do { result = currentclass.equals(superclass); // topmost class reached? if (currentclass.equals(Object.class)) break; if (!result) currentclass = currentclass.getSuperclass(); } while (!result); return result; } /** * Checks whether the given class implements the given interface. * * @param intf the interface to look for in the given class * @param cls the class to check for the interface * @return TRUE if the class contains the interface */ public static boolean hasInterface(String intf, String cls) { try { return hasInterface(Class.forName(intf), Class.forName(cls)); } catch (Exception e) { return false; } } /** * Checks whether the given class implements the given interface. * * @param intf the interface to look for in the given class * @param cls the class to check for the interface * @return TRUE if the class contains the interface */ public static boolean hasInterface(Class intf, Class cls) { Class[] intfs; int i; boolean result; Class currentclass; result = false; currentclass = cls; do { // check all the interfaces, this class implements intfs = currentclass.getInterfaces(); for (i = 0; i < intfs.length; i++) { if (intfs[i].equals(intf)) { result = true; break; } } // get parent class if (!result) { currentclass = currentclass.getSuperclass(); // topmost class reached or no superclass? if ( (currentclass == null) || (currentclass.equals(Object.class)) ) break; } } while (!result); return result; } /** * If the given package can be found in this part of the classpath then * an URL object is returned, otherwise <code>null</code>. * * @param classpathPart the part of the classpath to look for the package * @param pkgname the package to look for * @return if found, the url as string, otherwise null */ protected static URL getURL(String classpathPart, String pkgname) { String urlStr; URL result; File classpathFile; File file; JarFile jarfile; Enumeration enm; String pkgnameTmp; result = null; urlStr = null; try { classpathFile = new File(classpathPart); // directory or jar? if (classpathFile.isDirectory()) { // does the package exist in this directory? file = new File(classpathPart + pkgname); if (file.exists()) urlStr = "file:" + classpathPart + pkgname; } else { // is package actually included in jar? jarfile = new JarFile(classpathPart); enm = jarfile.entries(); pkgnameTmp = pkgname.substring(1); // remove the leading "/" while (enm.hasMoreElements()) { if (enm.nextElement().toString().startsWith(pkgnameTmp)) { urlStr = "jar:file:" + classpathPart + "!" + pkgname; break; } } } } catch (Exception e) { // ignore } // try to generate URL from url string if (urlStr != null) { try { result = new URL(urlStr); } catch (Exception e) { System.err.println( "Trying to create URL from '" + urlStr + "' generates this exception:\n" + e); result = null; } } return result; } /** * Checks the given packages for classes that inherited from the given class, * in case it's a class, or implement this class, in case it's an interface. * * @param classname the class/interface to look for * @param pkgnames the packages to search in * @return a list with all the found classnames */ public static Vector<String> find(String classname, String[] pkgnames) { Vector<String> result; Class cls; result = new Vector<String>(); try { cls = Class.forName(classname); result = find(cls, pkgnames); } catch (Exception e) { e.printStackTrace(); } return result; } /** * Checks the given package for classes that inherited from the given class, * in case it's a class, or implement this class, in case it's an interface. * * @param classname the class/interface to look for * @param pkgname the package to search in * @return a list with all the found classnames */ public static Vector<String> find(String classname, String pkgname) { Vector<String> result; Class cls; result = new Vector<String>(); try { cls = Class.forName(classname); result = find(cls, pkgname); } catch (Exception e) { e.printStackTrace(); } return result; } /** * Checks the given packages for classes that inherited from the given class, * in case it's a class, or implement this class, in case it's an interface. * * @param cls the class/interface to look for * @param pkgnames the packages to search in * @return a list with all the found classnames */ public static Vector<String> find(Class cls, String[] pkgnames) { Vector<String> result; int i; HashSet<String> names; result = new Vector<String>(); names = new HashSet<String>(); for (i = 0; i < pkgnames.length; i++) names.addAll(find(cls, pkgnames[i])); // sort result result.addAll(names); Collections.sort(result, new StringCompare()); return result; } /** * Find all classes that have the supplied matchText String in * their suffix. * * @param matchText the text to match * @return an array list of matching fully qualified class names. */ public static ArrayList<String> find(String matchText) { return m_ClassCache.find(matchText); } /** * Checks the given package for classes that inherited from the given class, * in case it's a class, or implement this class, in case it's an interface. * * @param cls the class/interface to look for * @param pkgname the package to search in * @return a list with all the found classnames */ public static Vector<String> find(Class cls, String pkgname) { Vector<String> result; int i; Class clsNew; // already cached? result = getCache(cls, pkgname); if (result == null) { if (VERBOSE) System.out.println( "Searching for '" + cls.getName() + "' in '" + pkgname + "':"); result = new Vector<String>(); if (m_ClassCache.getClassnames(pkgname) != null) result.addAll(m_ClassCache.getClassnames(pkgname)); // check classes i = 0; while (i < result.size()) { try { clsNew = Class.forName((String) result.get(i)); // no abstract classes if (Modifier.isAbstract(clsNew.getModifiers())) { m_ClassCache.remove(result.get(i)); result.remove(i); } // must implement interface else if ( (cls.isInterface()) && (!hasInterface(cls, clsNew)) ) { result.remove(i); } // must be derived from class else if ( (!cls.isInterface()) && (!isSubclass(cls, clsNew)) ) { result.remove(i); } else { i++; } } catch (Exception e) { System.out.println("Accessing class '" + result.get(i) + "' resulted in error:"); e.printStackTrace(); } } // sort result Collections.sort(result, new StringCompare()); // add to cache addCache(cls, pkgname, result); } return result; } /** * adds all the sub-directories recursively to the list. * * @param prefix the path prefix * @param dir the directory to look in for sub-dirs * @param list the current list of sub-dirs * @return the new list of sub-dirs */ protected static HashSet<String> getSubDirectories(String prefix, File dir, HashSet<String> list) { File[] files; int i; String newPrefix; // add directory to the list if (prefix == null) newPrefix = ""; else if (prefix.length() == 0) newPrefix = dir.getName(); else newPrefix = prefix + "." + dir.getName(); if (newPrefix.length() != 0) list.add(newPrefix); // search for sub-directories files = dir.listFiles(); if (files != null) { for (i = 0; i < files.length; i++) { if (files[i].isDirectory()) list = getSubDirectories(newPrefix, files[i], list); } } return list; } /** * Lists all packages it can find in the classpath. * * @return a list with all the found packages */ public static Vector<String> findPackages() { Vector<String> result; Enumeration<String> packages; initCache(); result = new Vector<String>(); packages = m_ClassCache.packages(); while (packages.hasMoreElements()) result.add(packages.nextElement()); Collections.sort(result, new StringCompare()); return result; } /** * initializes the cache for the classnames. */ protected static void initCache() { if (m_Cache == null) m_Cache = new Hashtable<String,Vector<String>>(); if (m_ClassCache == null) m_ClassCache = new ClassCache(); } /** * adds the list of classnames to the cache. * * @param cls the class to cache the classnames for * @param pkgname the package name the classes were found in * @param classnames the list of classnames to cache */ protected static void addCache(Class cls, String pkgname, Vector<String> classnames) { initCache(); m_Cache.put(cls.getName() + "-" + pkgname, classnames); } /** * returns the list of classnames associated with this class and package, if * available, otherwise null. * * @param cls the class to get the classnames for * @param pkgname the package name for the classes * @return the classnames if found, otherwise null */ protected static Vector<String> getCache(Class cls, String pkgname) { initCache(); return m_Cache.get(cls.getName() + "-" + pkgname); } /** * clears the cache for class/classnames queries. */ public static void clearCache() { initCache(); m_Cache.clear(); } /** * Calls clearCache() and resets the cache of classes on the classpath * (i.e. forces a rescan of the classpath). */ public static void clearClassCache() { clearCache(); // make sure that any new classes are picked up m_ClassCache = new ClassCache(); } /** * Returns the revision string. * * @return the revision */ public String getRevision() { return RevisionUtils.extract("$Revision: 8034 $"); } /** * Possible calls: * <ul> * <li> * weka.core.ClassDiscovery <packages><br/> * Prints all the packages in the current classpath * </li> * <li> * weka.core.ClassDiscovery <classname> <packagename(s)><br/> * Prints the classes it found. * </li> * </ul> * * @param args the commandline arguments */ public static void main(String[] args) { Vector<String> list; Vector<String> packages; int i; StringTokenizer tok; if ((args.length == 1) && (args[0].equals("packages"))) { list = findPackages(); for (i = 0; i < list.size(); i++) System.out.println(list.get(i)); } else if (args.length == 2) { // packages packages = new Vector<String>(); tok = new StringTokenizer(args[1], ","); while (tok.hasMoreTokens()) packages.add(tok.nextToken()); // search list = ClassDiscovery.find( args[0], (String[]) packages.toArray(new String[packages.size()])); // print result, if any System.out.println( "Searching for '" + args[0] + "' in '" + args[1] + "':\n" + " " + list.size() + " found."); for (i = 0; i < list.size(); i++) System.out.println(" " + (i+1) + ". " + list.get(i)); } else { System.out.println("\nUsage:"); System.out.println( ClassDiscovery.class.getName() + " packages"); System.out.println("\tlists all packages in the classpath"); System.out.println( ClassDiscovery.class.getName() + " <classname> <packagename(s)>"); System.out.println("\tlists classes derived from/implementing 'classname' that"); System.out.println("\tcan be found in 'packagename(s)' (comma-separated list"); System.out.println(); System.exit(1); } } /** * compares two strings. The following order is used:<br/> * <ul> * <li>case insensitive</li> * <li>german umlauts (ä , ö etc.) or other non-ASCII letters * are treated as special chars</li> * <li>special chars < numbers < letters</li> * </ul> */ public static class StringCompare implements Comparator, RevisionHandler { /** * appends blanks to the string if its shorter than <code>len</code>. * * @param s the string to pad * @param len the minimum length for the string to have * @return the padded string */ private String fillUp(String s, int len) { while (s.length() < len) s += " "; return s; } /** * returns the group of the character: 0=special char, 1=number, 2=letter. * * @param c the character to check * @return the group */ private int charGroup(char c) { int result; result = 0; if ( (c >= 'a') && (c <= 'z') ) result = 2; else if ( (c >= '0') && (c <= '9') ) result = 1; return result; } /** * Compares its two arguments for order. * * @param o1 the first object * @param o2 the second object * @return -1 if o1<o2, 0 if o1=o2 and 1 if o1&;gt;o2 */ public int compare(Object o1, Object o2) { String s1; String s2; int i; int result; int v1; int v2; result = 0; // they're equal // get lower case string s1 = o1.toString().toLowerCase(); s2 = o2.toString().toLowerCase(); // same length s1 = fillUp(s1, s2.length()); s2 = fillUp(s2, s1.length()); for (i = 0; i < s1.length(); i++) { // same char? if (s1.charAt(i) == s2.charAt(i)) { result = 0; } else { v1 = charGroup(s1.charAt(i)); v2 = charGroup(s2.charAt(i)); // different type (special, number, letter)? if (v1 != v2) { if (v1 < v2) result = -1; else result = 1; } else { if (s1.charAt(i) < s2.charAt(i)) result = -1; else result = 1; } break; } } return result; } /** * Indicates whether some other object is "equal to" this Comparator. * * @param obj the object to compare with this Comparator * @return true if the object is a StringCompare object as well */ public boolean equals(Object obj) { return (obj instanceof StringCompare); } /** * Returns the revision string. * * @return the revision */ public String getRevision() { return RevisionUtils.extract("$Revision: 8034 $"); } } }