I was explaining to Brad (a fellow JIRA dev) the other day about some code I had written to find functional tests that were missing from our Test Suite and therefore not running automatically.
“Basically it uses reflection to find all the concrete classes under the ztests package that implement TestCase,” I told him.
“But that’s impossible!” he replied.
As software developers, we are asked to do the impossible all the time. The standard response is to look down our nose at the unsuspecting sales-type. Then, with as much disdain and disgust as can be mustered, grunt something along the lines of “Can’t be done”. The point is to not explain; just leave them hanging. If they dare ask why just pull out “Gödel’s theorem”, or some such. However, when it suits us, doing the impossible can be rather handy.
As it happens, Brad was right.
The Java reflection API does not provide any methods to find all the classes in a package. This seems like a bit of an oversight at first. There exists a class, java.lang.Package, that represents a java package, so wouldn’t it be useful if it provided a method like Package.getAllClasses()?
Yes it would. If the question was meaningful.
But, once you consider how class loaders actually work within the JVM, you realise that the question doesn’t quite make sense.
Firstly, a “package” is not really a physical thing. It is only a logical way to organise related classes. Different classes in the same package can be coming from different places on your classpath. (Indeed, the JVM will only know about a particular package once it has loaded at least one class from that package). This in itself does not really make the question unanswerable, but it does lead onto the following:
A JVM could conceivably tell you which classes it currently has loaded that belong to a particular package, but it cannot necessarily tell you about the classes in that package that haven’t loaded yet. The ClassLoader API only requires that a class be able to be loaded via its name, it does not require that the ClassLoader be able to tell you all the potentially loadable classes. Imagine an application with dynamic plug-ins. Even the list of potentially loadable classes will change over time.
However, there is a good reason why the question seemed to make sense at first.
The two most common ways of loading application classes are via a class file on the file system, or in a jar file. In both these cases we can conceivably get a list of all classes that could be loaded.
So, to solve my original problem, I just needed to redefine my question to something more like:
“Find all the class files implementing TestCase on the file system in the place on the classpath where I will load classes for the ztests package.”

My life was made a little easier because I knew that in my case:
1) I was only interested in class files, and not jar files.
2) I knew there was only one directory containing classes in this package.
However, I wanted to find not just classes in the given package, but in all sub-packages as well.
With that in mind, here is a solution to find all such classes in a given package:

/**
* Returns a collection of all Classes inside the given package and subpackages.
* Note that this method relies on the classes living in class files (not jars) in a single directory.
*
* @param packageName Fully qualified name of the package; eg "com.example.foo"
* @return a collection of all Classes inside the given package and subpackages.
* @throws FileNotFoundException if the package name is invalid.
*/
private static Collection getClassesInPackage(String packageName) throws
FileNotFoundException
{
// We expect to find the classes in a file system directory with a name
// corresponding to the package name.
String directoryName = packageName.replace('.', '/');
// Try to find this directory on the class path -
//  Note we only expect one such directory.
URL urlPackageDirectory = ClassLoader.getSystemClassLoader().getResource(directoryName);
if (urlPackageDirectory == null)
{
throw new FileNotFoundException("Could not find directory '" +
directoryName + "' on the classpath.");
}
File directory = new File(urlPackageDirectory.getFile());
ArrayList classes = new ArrayList();
// Find all class files in this directory and sub-directories.
getClassesRecursively(packageName, directory, classes);
return classes;
}
/**
* Updates the given class list with class files in the given file-system
* directory and sub directories.
*
* @param packageName The package name corresponding to the given directory.
* @param directory The directory to extract class files from.
* @param classList The list of classes found. This is appended to recursively.
*/
private static void getClassesRecursively(String packageName, File directory,
Collection classList)
{
// Loop over the contents of this directory:
File[] childFiles = directory.listFiles();
for (int i = 0; i < childFiles.length; i++)
{
File childFile = childFiles[i];
if (childFile.isFile())
{
String fileName = childFile.getName();
// We only want the .class files.
if (fileName.endsWith(CLASS_FILE_EXTENSION))
{
// Remove the .class extension to get the class name.
String className = fileName.substring(0, fileName.length() -
CLASS_FILE_EXTENSION.length());
try
{
classList.add(Class.forName(packageName + '.' + className));
}
catch (ClassNotFoundException ex)
{
// This shouldn't happen since we are finding classes in a
//  directory on our classpath.
throw new RuntimeException(ex);
}
}
}
else if (childFile.isDirectory())
{
// Recurse into this subdirectory
getClassesRecursively(packageName + '.' + childFile.getName(),
childFile, classList);
}
}
}
private static final String CLASS_FILE_EXTENSION = ".class";

Once I had all the classes under the package, it is a simple matter to filter out just the ones I was actually interested in:

/**
* Returns a collection of all concrete public classes inside the given package and
* subpackages that extend junit.framework.TestCase.
* Note that this method relies on the classes living in class files (not jars) in a single directory.
*
* @param packageName Fully qualified name of the package; eg "com.example.foo"
* @return a collection of all Classes inside the given package and subpackages.
* @throws FileNotFoundException if the package name is invalid.
*/
private static Collection getTestCases(String packageName) throws FileNotFoundException
{
// Create a list to collect the Classes
final Collection testCases = new ArrayList();
// Loop through all classes in the given package and filter out the ones we don't want:
for (Iterator iterator = getClassesInPackage(packageName).iterator(); iterator.hasNext();)
{
Class clazz = (Class) iterator.next();
// Check if this class is a public concrete instance of TestCase:
if (TEST_CASE_CLASS.isAssignableFrom(clazz)
&& !Modifier.isAbstract(clazz.getModifiers())
&& Modifier.isPublic(clazz.getModifiers()) )
{
testCases.add(clazz);
}
}
return testCases;
}
/** The junit.framework.TestCase class. */
private final static Class TEST_CASE_CLASS = junit.framework.TestCase.class;

And the moral to the story?
If you really need to do the impossible, make sure that you don’t realise it can’t be done. At least, not until after you are finished.

Fresh ideas, announcements, and inspiration for your team, delivered weekly.

Subscribe now

Fresh ideas, announcements, and inspiration for your team, delivered weekly.

Subscribe now