/*
* ClassFinder.java
*/
import java.io.*;
import java.net.URL;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.util.*;
import java.util.jar.*;
import java.util.zip.*;
/**
* This utility class was based originally on <a href="mailto:[email protected]">Daniel Le Berre</a>'s
* <code>RTSI</code> class. This class can be called in different modes, but the principal use
* is to determine what subclasses/implementations of a given class/interface exist in the current
* runtime environment.
* @author Daniel Le Berre, Elliott Wade
*/
public class ClassFinder
{
private Class<?> searchClass = null;
private Map<URL, String> classpathLocations = new HashMap<URL, String> ();
private Map<Class<?>, URL> results = new HashMap<Class<?>, URL> ();
private List<Throwable> errors = new ArrayList<Throwable> ();
private boolean working = false;
public ClassFinder ()
{
refreshLocations ();
}
/**
* Rescan the classpath, cacheing all possible file locations.
*/
public final void refreshLocations ()
{
synchronized (classpathLocations)
{
classpathLocations = getClasspathLocations ();
}
}
/**
* @param fqcn Name of superclass/interface on which to search
*/
public final Vector<Class<?>> findSubclasses (String fqcn)
{
synchronized (classpathLocations)
{
synchronized (results)
{
try
{
working = true;
searchClass = null;
errors = new ArrayList<Throwable> ();
results = new TreeMap<Class<?>, URL> (CLASS_COMPARATOR);
//
// filter malformed FQCN
//
if (fqcn.startsWith (".") || fqcn.endsWith ("."))
{
return new Vector<Class<?>> ();
}
//
// Determine search class from fqcn
//
try
{
searchClass = Class.forName (fqcn);
}
catch (ClassNotFoundException ex)
{
// if class not found, let empty vector return...
errors.add (ex);
return new Vector<Class<?>> ();
}
return findSubclasses (searchClass, classpathLocations);
}
finally
{
working = false;
}
}
}
}
public final List<Throwable> getErrors ()
{
return new ArrayList<Throwable> (errors);
}
/**
* The result of the last search is cached in this object, along
* with the URL that corresponds to each class returned. This
* method may be called to query the cache for the location at
* which the given class was found. <code>null</code> will be
* returned if the given class was not found during the last
* search, or if the result cache has been cleared.
*/
public final URL getLocationOf (Class<?> cls)
{
if (results != null) return results.get (cls);
else return null;
}
/**
* Determine every URL location defined by the current classpath, and
* it's associated package name.
*/
public final Map<URL, String> getClasspathLocations ()
{
Map<URL, String> map = new TreeMap<URL, String> (URL_COMPARATOR);
File file = null;
String pathSep = System.getProperty ("path.separator");
String classpath = System.getProperty ("java.class.path");
//System.out.println ("classpath=" + classpath);
StringTokenizer st = new StringTokenizer (classpath, pathSep);
while (st.hasMoreTokens ())
{
String path = st.nextToken ();
file = new File (path);
include (null, file, map);
}
Iterator<URL> it = map.keySet ().iterator ();
while (it.hasNext ())
{
URL url = it.next ();
//System.out.println (url + "-->" + map.get (url));
}
return map;
}
private final static FileFilter DIRECTORIES_ONLY = new FileFilter ()
{
public boolean accept (File f)
{
if (f.exists () && f.isDirectory ()) return true;
else return false;
}
};
private final static Comparator<URL> URL_COMPARATOR = new Comparator<URL> ()
{
public int compare (URL u1, URL u2)
{
return String.valueOf (u1).compareTo (String.valueOf (u2));
}
};
private final static Comparator<Class<?>> CLASS_COMPARATOR = new Comparator<Class<?>> ()
{
public int compare (Class<?> c1, Class<?> c2)
{
return String.valueOf (c1).compareTo (String.valueOf (c2));
}
};
private final void include (String name, File file, Map<URL, String> map)
{
if (!file.exists ()) return;
if (!file.isDirectory ())
{
// could be a JAR file
includeJar (file, map);
return;
}
if (name == null)
name = "";
else
name += ".";
// add subpackages
File [] dirs = file.listFiles (DIRECTORIES_ONLY);
for (int i=0; i<dirs.length; i++)
{
try
{
// add the present package
map.put
(
new URL ("file://" + dirs[i].getCanonicalPath ()),
name + dirs[i].getName ()
);
}
catch (IOException ioe)
{
return;
}
include (name + dirs[i].getName (), dirs[i], map);
}
}
private void includeJar (File file, Map<URL, String> map)
{
if (file.isDirectory ()) return;
URL jarURL = null;
JarFile jar = null;
try
{
jarURL = new URL ("file:/" + file.getCanonicalPath ());
jarURL = new URL ("jar:" + jarURL.toExternalForm () + "!/");
JarURLConnection conn = (JarURLConnection) jarURL.openConnection ();
jar = conn.getJarFile();
}
catch (Exception e)
{
// not a JAR or disk I/O error
// either way, just skip
return;
}
if (jar == null || jarURL == null) return;
// include the jar's "default" package (i.e. jar's root)
map.put (jarURL, "");
Enumeration<JarEntry> e = jar.entries();
while (e.hasMoreElements())
{
JarEntry entry = e.nextElement ();
if (entry.isDirectory ())
{
if (entry.getName ().toUpperCase ().equals ("META-INF/")) continue;
try
{
map.put
(
new URL (jarURL.toExternalForm () + entry.getName ()),
packageNameFor (entry)
);
}
catch (MalformedURLException murl)
{
// whacky entry?
continue;
}
}
}
}
private static String packageNameFor (JarEntry entry)
{
if (entry == null) return "";
String s = entry.getName ();
if (s == null) return "";
if (s.length () == 0) return s;
if (s.startsWith ("/")) s = s.substring (1, s.length ());
if (s.endsWith ("/")) s = s.substring (0, s.length ()-1);
return s.replace ('/', '.');
}
private final void includeResourceLocations (String packageName, Map<URL, String> map)
{
try
{
Enumeration<URL> resourceLocations =
ClassFinder.class.getClassLoader ().getResources (getPackagePath (packageName));
while (resourceLocations.hasMoreElements ())
{
map.put
(
resourceLocations.nextElement (),
packageName
);
}
}
catch (Exception e)
{
// well, we tried
errors.add (e);
return;
}
}