Find Java classes in the classpath

by Pål Brattberg on May 11, 2009

My brother Oskar needed to read some Java classes from disk, so I whipped up some code to show a possible way. Then I figured you might like it as well.

Here it is (and find the latest version at GitHub where you can submit your improved fork)

Ain’t java short and sweet? ;)


package com.palbrattberg.classpathtools;

import java.io.File;
import java.io.FilenameFilter;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * Find classes in the classpath (reads JARs and classpath folders).
 *
 * @author Pål Brattberg, brattberg@gmail.com
 * @see http://gist.github.com/pal
 */
@SuppressWarnings("unchecked")
public class ClasspathInspector {
	static boolean DEBUG = false;

	public static List<Class> getAllKnownClasses() {
		List<Class> classFiles = new ArrayList<Class>();
		List<File> classLocations = getClassLocationsForCurrentClasspath();
		for (File file : classLocations) {
			classFiles.addAll(getClassesFromPath(file));
		}
		return classFiles;
	}

	public static List<Class> getMatchingClasses(Class interfaceOrSuperclass) {
		List<Class> matchingClasses = new ArrayList<Class>();
		List<Class> classes = getAllKnownClasses();
		log("checking %s classes", classes.size());
		for (Class clazz : classes) {
			if (interfaceOrSuperclass.isAssignableFrom(clazz)) {
				matchingClasses.add(clazz);
				log("class %s is assignable from %s", interfaceOrSuperclass, clazz);
			}
		}
		return matchingClasses;
	}

	public static List<Class> getMatchingClasses(String validPackagePrefix, Class interfaceOrSuperclass) {
		throw new IllegalStateException("Not yet implemented!");
	}

	public static List<Class> getMatchingClasses(String validPackagePrefix) {
		throw new IllegalStateException("Not yet implemented!");
	}

	private static Collection<? extends Class> getClassesFromPath(File path) {
		if (path.isDirectory()) {
			return getClassesFromDirectory(path);
		} else {
			return getClassesFromJarFile(path);
		}
	}

	private static String fromFileToClassName(final String fileName) {
		return fileName.substring(0, fileName.length() - 6).replaceAll("/|\\\\", "\\.");
	}

	private static List<Class> getClassesFromJarFile(File path) {
		List<Class> classes = new ArrayList<Class>();
		log("getClassesFromJarFile: Getting classes for %s", path);

		try {
			if (path.canRead()) {
				JarFile jar = new JarFile(path);
				Enumeration<JarEntry> en = jar.entries();
				while (en.hasMoreElements()) {
					JarEntry entry = en.nextElement();
					if (entry.getName().endsWith("class")) {
						String className = fromFileToClassName(entry.getName());
						log("\tgetClassesFromJarFile: found %s", className);
						loadClass(classes, className);
					}
				}
			}
		} catch (Exception e) {
			throw new RuntimeException("Failed to read classes from jar file: " + path, e);
		}

		return classes;
	}

	private static List<Class> getClassesFromDirectory(File path) {
		List<Class> classes = new ArrayList<Class>();
		log("getClassesFromDirectory: Getting classes for " + path);

		// get jar files from top-level directory
		List<File> jarFiles = listFiles(path, new FilenameFilter() {
			@Override
			public boolean accept(File dir, String name) {
				return name.endsWith(".jar");
			}
		}, false);
		for (File file : jarFiles) {
			classes.addAll(getClassesFromJarFile(file));
		}

		// get all class-files
		List<File> classFiles = listFiles(path, new FilenameFilter() {
			@Override
			public boolean accept(File dir, String name) {
				return name.endsWith(".class");
			}
		}, true);

		// List<URL> urlList = new ArrayList<URL>();
		// List<String> classNameList = new ArrayList<String>();
		int substringBeginIndex = path.getAbsolutePath().length() + 1;
		for (File classfile : classFiles) {
			String className = classfile.getAbsolutePath().substring(substringBeginIndex);
			className = fromFileToClassName(className);
			log("Found class %s in path %s: ", className, path);
			loadClass(classes, className);
		}

		return classes;
	}

	private static List<File> listFiles(File directory, FilenameFilter filter, boolean recurse) {
		List<File> files = new ArrayList<File>();
		File[] entries = directory.listFiles();

		// Go over entries
		for (File entry : entries) {
			// If there is no filter or the filter accepts the
			// file / directory, add it to the list
			if (filter == null || filter.accept(directory, entry.getName())) {
				files.add(entry);
			}

			// If the file is a directory and the recurse flag
			// is set, recurse into the directory
			if (recurse && entry.isDirectory()) {
				files.addAll(listFiles(entry, filter, recurse));
			}
		}

		// Return collection of files
		return files;
	}

	public static List<File> getClassLocationsForCurrentClasspath() {
		List<File> urls = new ArrayList<File>();
		String javaClassPath = System.getProperty("java.class.path");
		if (javaClassPath != null) {
			for (String path : javaClassPath.split(File.pathSeparator)) {
				urls.add(new File(path));
			}
		}
		return urls;
	}

	// todo: this is only partial, probably
	public static URL normalize(URL url) throws MalformedURLException {
		String spec = url.getFile();

		// get url base - remove everything after ".jar!/??" , if exists
		final int i = spec.indexOf("!/");
		if (i != -1) {
			spec = spec.substring(0, spec.indexOf("!/"));
		}

		// uppercase windows drive
		url = new URL(url, spec);
		final String file = url.getFile();
		final int i1 = file.indexOf(':');
		if (i1 != -1) {
			String drive = file.substring(i1 - 1, 2).toUpperCase();
			url = new URL(url, file.substring(0, i1 - 1) + drive + file.substring(i1));
		}

		return url;
	}

	private static void log(String pattern, final Object... args) {
		if (DEBUG)
			System.out.printf(pattern + "\n", args);
	}

	private static void loadClass(List<Class> classes, String className) {
		try {
			Class claz = Class.forName(className, false, ClassLoader.getSystemClassLoader());
			classes.add(claz);
		} catch (ClassNotFoundException cnfe) {
			log("ClassNotFoundException: Could not load class %s: %s", className, cnfe);
		} catch (NoClassDefFoundError e) {
			log("NoClassDefFoundError: Could not load class %s: %s", className, e);
		}
	}

	public static void main(String[] args) {
		// find all classes in classpath
		List<Class> allClasses = ClasspathInspector.getAllKnownClasses();
		System.out.printf("There are %s classes available in the classpath\n", allClasses.size());

		// find all classes that implement/subclass an interface/superclass
		List<Class> serializableClasses = ClasspathInspector.getMatchingClasses(Serializable.class);
		for (Class clazz : serializableClasses) {
			System.out.printf("%s is Serializable\n", clazz);
		}
		System.out.printf("There are %s Serializable classes available in the classpath\n", serializableClasses.size());
	}
}

Gimme closures baby!

4 comments

Closure on this:
I trust you did a heck of a coding job as always!

by Svante Flodén on Friday, May 15, 2009 at 11:06 pm #

Closures are Groovy baby! :)

…reading your post (or rather your final comment) on an otherwise boring train trip a couple of days ago, got me inspired to get my head into Groovy some more!

by Tobias Löfstrand on Saturday, May 23, 2009 at 9:21 pm #

Hi Can I use your code in my school assignment ?

by Peter on Sunday, July 25, 2010 at 9:17 pm #

Go ahead Peter, just let your teacher know you found it on the net.

by Pål Brattberg on Friday, August 6, 2010 at 10:45 am #

Leave your comment

Required.

Required. Not published.

If you have one.