weka.core.ClassDiscovery Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of weka-stable Show documentation
Show all versions of weka-stable Show documentation
The Waikato Environment for Knowledge Analysis (WEKA), a machine
learning workbench. This is the stable version. Apart from bugfixes, this version
does not receive any other updates.
/*
* 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 .
*/
/*
* 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.JarEntry;
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: 10203 $
* @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> 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 null
.
*
* @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;
}
}
jarfile.close();
}
} 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 find(String classname, String[] pkgnames) {
Vector result;
Class> cls;
result = new Vector();
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 find(String classname, String pkgname) {
Vector result;
Class> cls;
result = new Vector();
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 find(Class> cls, String[] pkgnames) {
Vector result;
int i;
HashSet names;
result = new Vector();
names = new HashSet();
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 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 find(Class> cls, String pkgname) {
Vector 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();
if (m_ClassCache.getClassnames(pkgname) != null) {
result.addAll(m_ClassCache.getClassnames(pkgname));
}
// check classes
i = 0;
while (i < result.size()) {
try {
clsNew = Class.forName(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 getSubDirectories(String prefix, File dir,
HashSet 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 findPackages() {
Vector result;
Enumeration packages;
initCache();
result = new Vector();
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>();
}
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 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 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
*/
@Override
public String getRevision() {
return RevisionUtils.extract("$Revision: 10203 $");
}
/**
* Possible calls:
*
* -
* weka.core.ClassDiscovery <packages>
* Prints all the packages in the current classpath
* -
* weka.core.ClassDiscovery <classname> <packagename(s)>
* Prints the classes it found.
*
*
* @param args the commandline arguments
*/
public static void main(String[] args) {
Vector list;
Vector 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();
tok = new StringTokenizer(args[1], ",");
while (tok.hasMoreTokens()) {
packages.add(tok.nextToken());
}
// search
list = ClassDiscovery.find(args[0],
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()
+ " ");
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:
*
* - case insensitive
* - german umlauts (ä , ö etc.) or other non-ASCII letters are
* treated as special chars
* - special chars < numbers < letters
*
*/
public static class StringCompare implements Comparator,
RevisionHandler {
/**
* appends blanks to the string if its shorter than len
.
*
* @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
*/
@Override
public int compare(String o1, String 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
*/
@Override
public boolean equals(Object obj) {
return (obj instanceof StringCompare);
}
/**
* Returns the revision string.
*
* @return the revision
*/
@Override
public String getRevision() {
return RevisionUtils.extract("$Revision: 10203 $");
}
}
}