views:

929

answers:

2

I have a simple marker annotation for methods (similar to the first example in Item 35 in Effective Java (2nd ed)):

/**
 * Marker annotation for methods that are called from installer's 
 * validation scripts etc. 
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface InstallerMethod {
}

Then, in a given package (say com.acme.installer), which has a few subpackages containing some 20 classes, I'd like to find all methods that are annotated with it. (Because I'd like to do some checks regarding all the annotated methods in a unit test.)

What (if any) is the easiest way to do this? Preferably without adding new 3rd party libraries or frameworks.

Edit: to clarify, obviously method.isAnnotationPresent(InstallerMethod.class) will be the way to check if a method has the annotation - but this problem includes finding all the methods.

+2  A: 

If you want to implement it yourself, these methods will find all the classes in a given package:

/**
 * Scans all classes accessible from the context class loader which belong
 * to the given package and subpackages.
 * 
 * @param packageName
 *            The base package
 * @return The classes
 * @throws ClassNotFoundException
 * @throws IOException
 */
private Iterable<Class> getClasses(String packageName) throws ClassNotFoundException, IOException
{
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    String path = packageName.replace('.', '/');
    Enumeration<URL> resources = classLoader.getResources(path);
    List<File> dirs = new ArrayList<File>();
    while (resources.hasMoreElements())
    {
        URL resource = resources.nextElement();
        dirs.add(new File(resource.getFile()));
    }
    List<Class> classes = new ArrayList<Class>();
    for (File directory : dirs)
    {
        classes.addAll(findClasses(directory, packageName));
    }

    return classes;
}

/**
 * Recursive method used to find all classes in a given directory and
 * subdirs.
 * 
 * @param directory
 *            The base directory
 * @param packageName
 *            The package name for classes found inside the base directory
 * @return The classes
 * @throws ClassNotFoundException
 */
private List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException
{
    List<Class> classes = new ArrayList<Class>();
    if (!directory.exists())
    {
        return classes;
    }
    File[] files = directory.listFiles();
    for (File file : files)
    {
        if (file.isDirectory())
        {
            classes.addAll(findClasses(file, packageName + "." + file.getName()));
        }
        else if (file.getName().endsWith(".class"))
        {
            classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
        }
    }
    return classes;
}

Then you can just filter on those classes with the given annotation:

for (Method method : testClass.getMethods())
{
    if (method.isAnnotationPresent(InstallerMethod.class))
    {
        // do something
    }
}
parkr
Thanks! Using this I was able to do what I wanted. It's pretty much code, but perhaps there just isn't a simpler way (without external libs), with classloader working the way it does.
Jonik
A: 

If you're happy to use Spring, then that does something along these lines using it's context:component-scan functionality, where Spring scans for annotated classes in a given package. Under the covers, it's pretty gruesome, and involves grubbing about on the filesystem and in JAR files looking for classes in the package.

Even if you can't use Spring directly, having a look through its source code might give you some ideas.

Certainly, the Java reflection APi is no use here, it specifically does not provide a means to obtain all classes in a package.

skaffman
Thanks. Hmm, you say "Spring scans for annotated classes" - can you use it in this case where we're looking for annotated *methods*, even in classes with no annotations? Or, alternatively, if you would implement a method like getClasses(String packageName) from parkr's answer making use of Spring, could it be made much simpler?
Jonik
The Spring code looks for all classes in that package, and then looks for class and method level annotations. You may be able to make it simpler in certain environments, but not hugely so.
skaffman