2

How can I reflectively get all of the packages in the project? I started with Package.getPackages(), but that only got all the packages associated to the current package. Is there a way to do this?

ahodder
  • 11,353
  • 14
  • 71
  • 114

4 Answers4

2

@PhilippWendler's comment led me to a method of accomplishing what I needed. I tweaked the method a little to make it recursive.

    /**
     * Recursively fetches a list of all the classes in a given
     * directory (and sub-directories) that have the @UnitTestable
     * annotation.
     * @param packageName The top level package to search.
     * @param loader The class loader to use. May be null; we'll
     * just grab the current threads.
     * @return The list of all @UnitTestable classes.
     */
    public List<Class<?>> getTestableClasses(String packageName, ClassLoader loader) {
        // State what package we are exploring
        System.out.println("Exploring package: " + packageName);
        // Create the list that will hold the testable classes
        List<Class<?>> ret = new ArrayList<Class<?>>();
        // Create the list of immediately accessible directories
        List<File> directories = new ArrayList<File>();
        // If we don't have a class loader, get one.
        if (loader == null)
            loader = Thread.currentThread().getContextClassLoader();
        // Convert the package path to file path
        String path = packageName.replace('.', '/');
        // Try to get all of nested directories.
        try {
            // Get all of the resources for the given path
            Enumeration<URL> res = loader.getResources(path);
            // While we have directories to look at, recursively
            // get all their classes.
            while (res.hasMoreElements()) {
                // Get the file path the the directory
                String dirPath = URLDecoder.decode(res.nextElement()
                        .getPath(), "UTF-8");
                // Make a file handler for easy managing
                File dir = new File(dirPath);
                // Check every file in the directory, if it's a
                // directory, recursively add its viable files
                for (File file : dir.listFiles()) {
                    if (file.isDirectory()) 
                        ret.addAll(getTestableClasses(packageName + '.' + file.getName(), loader));
                }
            }
        } catch (IOException e) {
            // We failed to get any nested directories. State
            // so and continue; this directory may still have
            // some UnitTestable classes.
            System.out.println("Failed to load resources for [" + packageName + ']');
        }
        // We need access to our directory, so we can pull
        // all the classes.
        URL tmp = loader.getResource(path);
        System.out.println(tmp);
        if (tmp == null)
            return ret;
        File currDir = new File(tmp.getPath());
        // Now we iterate through all of the classes we find
        for (String classFile : currDir.list()) {
            // Ensure that we only find class files; can't load gif's!
            if (classFile.endsWith(".class")) {
                // Attempt to load the class or state the issue
                try {
                    // Try loading the class
                    Class<?> add = Class.forName(packageName + '.' +
                            classFile.substring(0, classFile.length() - 6));
                    // If the class has the correct annotation, add it
                    if (add.isAnnotationPresent(UnitTestable.class))
                        ret.add(add);
                    else 
                        System.out.println(add.getName() + " is not a UnitTestable class");
                } catch (NoClassDefFoundError e) {
                    // The class loader could not load the class
                    System.out.println("We have found class [" + classFile + "], and couldn't load it.");
                } catch (ClassNotFoundException e) {
                    // We couldn't even find the damn class
                    System.out.println("We could not find class [" + classFile + ']');
                }
            }
        }
        return ret;
    }
david
  • 3,225
  • 9
  • 30
  • 43
ahodder
  • 11,353
  • 14
  • 71
  • 114
0

This approach prints all packages only (at least a root "packageName" has to be given first).

It is derived from above.

package devTools;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

public class DevToolUtil {

    /**
     * Method prints all packages (at least a root "packageName" has to be given first).
     *
     * @see http://stackoverflow.com/questions/9316726/reflectively-get-all-packages-in-a-project
     * @since 2016-12-05
     * @param packageName
     * @param loader
     * @return List of classes.
     */
    public List<Class<?>> getTestableClasses(final String packageName, ClassLoader loader) {
        System.out.println("Exploring package: " + packageName);

        final List<Class<?>> ret = new ArrayList<Class<?>>();

        if (loader == null) {
            loader = Thread.currentThread().getContextClassLoader();
        }

        final String path = packageName.replace('.', '/');

        try {
            final Enumeration<URL> res = loader.getResources(path);

            while (res.hasMoreElements()) {
                final String dirPath = URLDecoder.decode(res.nextElement().getPath(), "UTF-8");
                final File dir = new File(dirPath);

                if (dir.listFiles() != null) {
                    for (final File file : dir.listFiles()) {
                        if (file.isDirectory()) {
                            final String packageNameAndFile = packageName + '.' + file.getName();
                            ret.addAll(getTestableClasses(packageNameAndFile, loader));
                        }
                    }
                }
            }
        } catch (final IOException e) {
            System.out.println("Failed to load resources for [" + packageName + ']');
        }

        return ret;
    }

    public static void main(final String[] args) {
        new DevToolUtil().getTestableClasses("at", null);
    }
}
0

It's possible but tricky and expensive, since you need to walk the classpath yourself. Here is how TestNG does it, you can probably extract the important part for yourself:

https://github.com/cbeust/testng/blob/master/src/main/java/org/testng/internal/PackageUtils.java

Cedric Beust
  • 15,480
  • 2
  • 55
  • 55
  • brief look at the link -> Downvote for usage of Vector and "m_" prefix in class members names.seems like this code is little out-dated – aviad Feb 18 '12 at 12:52
  • lol... Payback for me telling you that your other answer was off-topic? Classy. – Cedric Beust Feb 19 '12 at 03:53
  • I am glad that I made you "lol". The (partial) reason for the downvote is explained in my 1st comment to your answer. Nothing personal, really, but I believe that your solution is incorrect and the code is ugly. Sometimes, it is good to think out-of-the-box and use the technology that best fits the purpose. – aviad Feb 19 '12 at 07:24
-2

May be off-topic (because it is not exactly in terms of java "reflection") ... However, how about the following solution:

Java packages can be treated as folders (or directories on Linux\UNIX). Assuming that you have a root package and its absolute path is known, you can recursively print all sub-folders that have *.java or *.class files using the following batch as the basis:

@echo off
rem List all the subfolders under current dir

FOR /R "." %%G in (.) DO (
 Pushd %%G
 Echo now in %%G
 Popd )
Echo "back home" 

You can wrap this batch in java or if you run on Linux\Unix rewrite it in some shell script.

Good Luck!

Aviad.

aviad
  • 8,229
  • 9
  • 50
  • 98
  • 1
    That's not Java and even if it were, all it does is list the subdirectories of the current directory, which has nothing to do with the question asked. – Cedric Beust Feb 18 '12 at 07:49
  • @Cedric Beust, where in the question have you found that the solution has to be in java?(the project is in java but the author did not mention any limitations regarding the solution).Regarding what this batch does, well, it mainly illustrates the idea of how tasks that involve too much unreadable coding in java can be implemented in other "technologies" that better fit the purpose.Regarding what the batch does or does not, again, it prints a list of subdies of current directory and this was what the question was about. Did my comment make you feel "strange"?(you can always voteup back:) – aviad Feb 18 '12 at 08:14
  • No, the question asks how to find the "packages" in a project. "packages" is a Java concept and the question is tagged with "java", so it's pretty obvious that the person is asking a purely Java question. And again, even if this were not the case, your code shows how find directories, not packages. – Cedric Beust Feb 19 '12 at 03:51
  • @Cedric Beust, "java packages" can be treated as "directories" in terms of java project. Please refer to http://docs.oracle.com/javase/tutorial/java/concepts/package.html. Moreover, I believe that my solution is more elegant (less unreadable java coding than in your link) and more correct since your solution retrieves fully-qualified-classpaths of all the classes used by running project (along with the 3rd parties dependencies) and the question was about getting all packages of the project. – aviad Feb 19 '12 at 07:20