views:

699

answers:

6

Is there a way to iterate over all the classes in the classpath ?

I want to make some reflective checks on some classes implementing a certain interface, but I want to do it completely dynamically, without any input on which classes to check, just browsing the classpath.

+5  A: 

You can't do this elegantly.

Basically a classloader can be asked to load a specific class name, but can't be asked for all the classes it could load. (In the case of something loading classes over the web, it may not be feasible to do so - you can't reliably ask the web server to tell you all the files under a particular directory.)

If your classpath only deals with the file system, you could painfully find all the jar files in extension directories, recurse down normal classpath directories, and look inside all explicitly specified jar files - but it'll be tricky and probably fragile too.

Jon Skeet
A: 

you can't without a custom classloader.

think for example about a case where you load your classes from a remote server that does not even offer directory listing. how can you possibly iterate over all the classes there?

the classloader interface does not provide such a function. but if you use your own classloader (or extend an existing classloader) you could add this function. in any case, it's not pretty.

Omry
A: 

I think you'll have to inspect it manually:

String classpath = System.getProperty("java.class.path");
String[] locations = classpath.split(System.getProperty("path.separator"));
// inspect all jar's and class files in these locations, which is a pain in the b%tt

Or use some third party library that does this (I don't know of any).

Bart Kiers
and even that may not give you every single class loadable at that point in time, since you do not know if any custom classloaders exist (unless you specifically look for classloaders, and analyze them to see if they load new classes...but thats like solving the halting problem).
Chii
Apache VFS lets you iterate over the contents of JAR files.
Rob H
A: 

I'm not aware of a library that does this, but there are open source projects that do this for their own purposes. For example, Spring can iterate over classes in the classpath to find ones that have a certain annotation. You could take a look at how they do it to get some ideas. I'd start with the classes in the org.springframework.context.annotation package such as ClassPathScanningCandidateComponentProvider and CommonAnnotationBeanPostProcessor.

Rob H
+4  A: 

The Reflections library helps deal with this problem. As others have stated, it isn't fully possible in all class loading situations, but if all you have are jars and files, this will do it reliably.

Yishai
Really nice find... I was looking for the same thing a while back... wish I had just asked here :)
Gregory Mostizky
Great mate, thanks a lot.
subtenante
+1  A: 

I have solved this problem for a single class loader. I needed reflection to write code to inspect JUnit tests and report on ignored tests.

  /**
   * Attempts to list all the classes in the specified package as determined
   * by the context class loader
   * 
   * @param pckgname
   *            the package name to search
   * @return a list of classes that exist within that package
   * @throws ClassNotFoundException
   *             if something went wrong
   */
  private static List getClassesForPackage(String pckgname) throws ClassNotFoundException {
      // This will hold a list of directories matching the pckgname. There may be more than one if a package is split over multiple jars/paths
      ArrayList directories = new ArrayList();
      try {
          ClassLoader cld = Thread.currentThread().getContextClassLoader();
          if (cld == null) {
              throw new ClassNotFoundException("Can't get class loader.");
          }
          String path = pckgname.replace('.', '/');
          // Ask for all resources for the path
          Enumeration resources = cld.getResources(path);
          while (resources.hasMoreElements()) {
              directories.add(new File(URLDecoder.decode(resources.nextElement().getPath(), "UTF-8")));
          }
      } catch (NullPointerException x) {
          throw new ClassNotFoundException(pckgname + " does not appear to be a valid package (Null pointer exception)");
      } catch (UnsupportedEncodingException encex) {
          throw new ClassNotFoundException(pckgname + " does not appear to be a valid package (Unsupported encoding)");
      } catch (IOException ioex) {
          throw new ClassNotFoundException("IOException was thrown when trying to get all resources for " + pckgname);
      }

      ArrayList classes = new ArrayList();
      // For every directory identified capture all the .class files
      for (File directory : directories) {
          if (directory.exists()) {
              // Get the list of the files contained in the package
              String[] files = directory.list();
              for (String file : files) {
                  // we are only interested in .class files
                  if (file.endsWith(".class")) {
                      // removes the .class extension
                    try
                    {
                      classes.add(Class.forName(pckgname + '.' + file.substring(0, file.length() - 6)));                      
                    }
                    catch (NoClassDefFoundError e)
                    {
                      // do nothing. this class hasn't been found by the loader, and we don't care.
                    }
                  }
              }
          } else {
              throw new ClassNotFoundException(pckgname + " (" + directory.getPath() + ") does not appear to be a valid package");
          }
      }
      return classes;
  }  

I based my solution on the code in this thread:

ckovacs