Find all implementing classes in classpath?

M

Martha Goys

Sorry if this is a "Classloader 101"-type question, but is there a way
to programatically find all classes (in the current classpath) that
implement an interface?

I.e. I'm looking for some way to perform the following:

public Class[] getImplementingClasses(Class someInterface) {
...
}

or alternately:

public Class[] getSubclasses(Class someClass) {
...
}


Make sense? Is this possible?


Many thanks,

M
 
T

Tor Iver Wilhelmsen

Martha Goys said:
Sorry if this is a "Classloader 101"-type question, but is there a way
to programatically find all classes (in the current classpath) that
implement an interface?

Short answer: No.

Longer answer:

A ClassLoader does not give out details about its implementation.
Also, ClassLoaders work passively: They are requested to load a
specific class, and tries to find it.

J2SE has AFAIK two ClassLoader implementations it uses, the
URLClassLoader (for instance used by applets) and the one that uses
the classpath. For the latter, you could split up
System.getProperty("java.class.path"), and analyze the components as
either archives or directories. Then traverse the sub-entries (using
java.io.File and the mechanisms of the java.util.zip package), looking
for resources ending in ".class".
 
A

Andrew Thompson

Sorry if this is a "Classloader 101"-type question, but is there a way
to programatically find all classes (in the current classpath)
No.

..that implement an interface?

Yes.

There is no way to find all the classes on the current classpath,
but if you have a list of classes it is possible to determine if
any implement a particular interface.

Why do you feel you need to do this? What is the end result
you wish to achieve?

--
Andrew Thompson
http://www.PhySci.org/codes/ Web & IT Help
http://www.PhySci.org/ Open-source software suite
http://www.1point1C.org/ Science & Technology
http://www.lensescapes.com/ Images that escape the mundane
 
J

John McGrath

J2SE has AFAIK two ClassLoader implementations it uses, the
URLClassLoader (for instance used by applets) and the one that uses
the classpath.

Just a note: the ClassLoader that uses the classpath is provided by the
Sun Java launcher (java.exe). It is known as the System ClassLoader, and
it is also a URLClassLoader. In particular, it is of an instance of the
class sun.misc.Launcher.AppClassLoader, a subclass of URLClassLoader.

I think pretty much all ClassLoaders used by Java are subclasses of
URLClassLoader, although that is not necessary. The ClassLoader used by
applets depends on the container that sets up the Java Virtual Machine.
You may have different ClassLoaders, depending on whether you are running
your applet in the AppletViewer, a browser, etc. In the latter case, it
will depend on which browser you are using.
 
M

Martha Goys

Andrew said:
Yes.

There is no way to find all the classes on the current classpath,
but if you have a list of classes it is possible to determine if
any implement a particular interface.

Why do you feel you need to do this? What is the end result
you wish to achieve?

I'd like to list all classes implementing a certain interface in a UI,
so that users can choose which to instantiate and use. The interface is
used to define a simple plugin architecture, and I felt it would be
easiest if there was a programmatic way to 'search' for classes that
implement this interface, versus having to manually add the class names
to a properties file, etc. I.e. if they're in the classpath, they'd
'automagically' be available and listed in the UI. Minor, but thought
it would be nice touch.

I'm assuming then that a properties file or similar route is the only
way I can go about this?


Thanks,

M
 
A

Andrew Thompson

I'd like to list all classes implementing a certain interface in a UI,
so that users can choose which to instantiate and use. The interface is
used to define a simple plugin architecture, and I felt it would be
easiest if there was a programmatic way to 'search' for classes that
implement this interface, versus having to manually add the class names
to a properties file, etc.

The properties file is probably the way to go, if
your app. is JWS'd, the properties file will be
updated when changed. But..
..I.e. if they're in the classpath, they'd
'automagically' be available and listed in the UI. Minor, but thought
it would be nice touch.

Exactly how are thes plug-ins stored, as classes
on the file-system? As classes in a jar file?
Are they delivered as required over the net?

(In the first two situations, you *can* find the
names of classes that are in a particular directory/package
on the file-system, or in a jar file)
 
Joined
Jul 25, 2008
Messages
3
Reaction score
0
Find all classes extending/implementing given class/interface

I hacked up this solution to the problem a while ago based on someone else's (see comments in code) original idea. Happened upon this thread whilst searching for something else, but can't resist posting. As usual, user assumes all risk, no promises, etc, but if anyone out there makes improvements, I'd love to know :)

Since this site has a characters/post limit, you'll have to glue my next two posts together...

-Elliott
 
Joined
Jul 25, 2008
Messages
3
Reaction score
0
ClassFinder part 1

HTML:
/*
 * 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;
		}
	}
 
Joined
Jul 25, 2008
Messages
3
Reaction score
0
ClassFinder part 2

Append this code to that from ClassFinder part 1...

Code:
    private final Vector<Class<?>> findSubclasses
	(
		Class<?> superClass, Map<URL, String> locations
	)
    {
    	Vector<Class<?>> v = new Vector<Class<?>> ();
		
		Vector<Class<?>> w = null; //new Vector<Class<?>> ();
		
		//Package [] packages = Package.getPackages ();
		//for (int i=0;i<packages.length;i++)
		//{
		//	System.out.println ("package: " + packages[i]);
		//}
		
		Iterator<URL> it = locations.keySet ().iterator ();
		while (it.hasNext ())
		{
			URL url = it.next ();
			//System.out.println (url + "-->" + locations.get (url));
			
			w = findSubclasses (url, locations.get (url), superClass);
			if (w != null && (w.size () > 0)) v.addAll (w);
		}
    	
    	return v;
    }

    private final Vector<Class<?>> findSubclasses
	(
		URL location, String packageName, Class<?> superClass
	)
    {
		//System.out.println ("looking in package:" + packageName);
		//System.out.println ("looking for  class:" + superClass);
		
		synchronized (results)
		{
		
		// hash guarantees unique names...
    	Map<Class<?>, URL> thisResult = new TreeMap<Class<?>, URL> (CLASS_COMPARATOR);
    	Vector<Class<?>> v = new Vector<Class<?>> (); // ...but return a vector
    	
		// TODO: double-check for null search class
		String fqcn = searchClass.getName ();
		
		List<URL> knownLocations = new ArrayList<URL> ();
		knownLocations.add(location);
		// TODO: add getResourceLocations() to this list
		
		// iterate matching package locations...
		for (int loc=0; loc<knownLocations.size (); loc++)
	  	{
	  		URL url = knownLocations.get (loc);
			
			// Get a File object for the package
			File directory = new File (url.getFile ());
			
			//System.out.println ("\tlooking in " + directory);
			
			if (directory.exists ())
			{
			    // Get the list of the files contained in the package
			    String [] files = directory.list();
			    for (int i=0;i<files.length;i++)
			    {
					// we are only interested in .class files
					if (files[i].endsWith(".class"))
					{
					    // removes the .class extension
					    String classname = files[i].substring(0,files[i].length()-6);
						
						//System.out.println ("\t\tchecking file " + classname);
						
					    try
					    {
						    Class<?> c = Class.forName (packageName + "." + classname);
							if (superClass.isAssignableFrom (c) &&
								!fqcn.equals (packageName + "." + classname))
							{
								thisResult.put (c, url);
							}
					    }
					    catch (ClassNotFoundException cnfex)
					    {
							errors.add (cnfex);
							//System.err.println(cnfex);
					    }
					    catch (Exception ex)
						{
							errors.add (ex);
							//System.err.println (ex);
						}
					}
			    }
			}
			else
			{	
			    try
			    {
					// It does not work with the filesystem: we must
					// be in the case of a package contained in a jar file.
					JarURLConnection conn = (JarURLConnection)url.openConnection();
					//String starts = conn.getEntryName();
					JarFile jarFile = conn.getJarFile();
					
					//System.out.println ("starts=" + starts);
					//System.out.println ("JarFile=" + jarFile);
					
					Enumeration<JarEntry> e = jarFile.entries();
					while (e.hasMoreElements())
					{
					    JarEntry entry = e.nextElement();
					    String entryname = entry.getName();
						
						//System.out.println ("\tconsidering entry: " + entryname);
						
					    if (!entry.isDirectory () && entryname.endsWith(".class"))
						{
							String classname = entryname.substring(0,entryname.length()-6);
							if (classname.startsWith("/")) 
							    classname = classname.substring(1);
							classname = classname.replace('/','.');
							
							//System.out.println ("\t\ttesting classname: " + classname);
							
							try
							{
								// TODO: verify this block
								Class c = Class.forName (classname);
								
							    if (superClass.isAssignableFrom (c) &&
							    	!fqcn.equals (classname))
							    {
									thisResult.put (c, url);
							    }
							}
							catch (ClassNotFoundException cnfex)
							{
							    // that's strange since we're scanning
								// the same classpath the classloader's
								// using... oh, well
								errors.add (cnfex);
							}
							catch (NoClassDefFoundError ncdfe)
							{
								// dependency problem... class is
								// unusable anyway, so just ignore it
								errors.add (ncdfe);
							}
							catch (UnsatisfiedLinkError ule)
							{
								// another dependency problem... class is
								// unusable anyway, so just ignore it
								errors.add (ule);
							}
							catch (Exception exception)
							{
								// unexpected problem
								//System.err.println (ex);
								errors.add (exception);
							}
							catch (Error error)
							{
								// lots of things could go wrong
								// that we'll just ignore since
								// they're so rare...
								errors.add (error);
							}
					    }
					}
			    }
			    catch (IOException ioex)
			    {
					//System.err.println(ioex);
					errors.add (ioex);
			    }	
			}
	  	} // while
	  	
	  	//System.out.println ("results = " + thisResult);
	  	
		results.putAll (thisResult);
		
		Iterator<Class<?>> it = thisResult.keySet ().iterator ();
	  	while (it.hasNext ())
	  	{
	  		v.add (it.next ());
	  	}
	  	return v;
		
		} // synch results
    }
	
	private final static String getPackagePath (String packageName)
	{
		// Translate the package name into an "absolute" path
		String path = new String (packageName);
		if (!path.startsWith ("/"))
		{
		    path = "/" + path;
		}	
		path = path.replace ('.','/');
		
		// ending with "/" indicates a directory to the classloader
		if (!path.endsWith ("/")) path += "/";
		
		// for actual classloader interface (NOT Class.getResource() which
		//  hacks up the request string!) a resource beginning with a "/"
		//  will never be found!!! (unless it's at the root, maybe?)
		if (path.startsWith ("/")) path = path.substring (1, path.length ());
		
		//System.out.println ("package path=" + path);
		
		return path;
	}
	
    public static void main(String []args)
    {
		//args = new String[] {"rcm.core.Any_String"};
		
		ClassFinder finder = null;
    	Vector<Class<?>> v = null;
    	List<Throwable> errors = null;
		
		if (args.length==1)
		{
			finder = new ClassFinder ();
			v = finder.findSubclasses (args[0]);
			errors = finder.getErrors ();
		}
		else
		{
			System.out.println("Usage: java ClassFinder <fully.qualified.superclass.name>");
			return;
		}
		
		System.out.println ("RESULTS:");
		if (v != null && v.size () > 0)
		{
			for (Class<?> cls : v)
			{
				System.out.println
				(
					cls + " in " + 
					((finder != null) ? String.valueOf (finder.getLocationOf (cls)) : "?")
				);
			}
		}
		else
		{
			System.out.println ("No subclasses of " + args[0] + " found.");
		}
		
		// TODO: verbose mode
//		if (errors != null && errors.size () > 0)
//		{
//			System.out.println ("ERRORS:");
//			for (Throwable t : errors) System.out.println (t);
//		}
    }
}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,774
Messages
2,569,598
Members
45,152
Latest member
LorettaGur
Top