github.phpd.cn/thought-machine/please@v12.2.0+incompatible/tools/junit_runner/src/build/please/test/ClassFinder.java (about)

     1  package build.please.test;
     2  
     3  import java.io.File;
     4  import java.io.IOException;
     5  import java.net.URISyntaxException;
     6  import java.net.URL;
     7  import java.net.URLClassLoader;
     8  import java.nio.file.Paths;
     9  import java.util.Enumeration;
    10  import java.util.HashMap;
    11  import java.util.HashSet;
    12  import java.util.LinkedHashSet;
    13  import java.util.Map;
    14  import java.util.Set;
    15  import java.util.jar.JarEntry;
    16  import java.util.jar.JarFile;
    17  
    18  /**
    19   * Used to find any classes under the test package.
    20   * Based off parts of Google's Guava library, but is much more specific to our needs.
    21   * We prefer not to bring in Guava as a third-party dependency because it can cause
    22   * issues if the user's tests depend on a different version of it (see #164).
    23   */
    24  class ClassFinder {
    25  
    26      private final String prefix;
    27      private final ClassLoader loader;
    28      private final Set<Class> classes = new LinkedHashSet<Class>();
    29  
    30      public ClassFinder(ClassLoader loader) throws IOException {
    31          this.prefix = "";
    32          this.loader = loader;
    33      }
    34  
    35      public ClassFinder(ClassLoader loader, String prefix) throws IOException {
    36          this.prefix = prefix;
    37          this.loader = loader;
    38          scan(getClassPathEntries(loader));
    39      }
    40  
    41      public Set<Class> getClasses() {
    42          return classes;
    43      }
    44  
    45      /**
    46       * Returns all the classpath entries from a class loader.
    47       */
    48      private Map<File, ClassLoader> getClassPathEntries(ClassLoader loader) {
    49          Map<File, ClassLoader> entries = new HashMap<File, ClassLoader>();
    50          ClassLoader parent = loader.getParent();
    51          if (parent != null) {
    52              entries.putAll(getClassPathEntries(parent));
    53          }
    54          if (loader instanceof URLClassLoader) {
    55              for (URL entry : ((URLClassLoader) loader).getURLs()) {
    56                  if (entry.getProtocol().equals("file")) {
    57                      try {
    58                          File file = Paths.get(entry.toURI()).toFile();
    59                          if (!entries.containsKey(file)) {
    60                              entries.put(file, loader);
    61                          }
    62                      } catch (URISyntaxException ex) {
    63                          // This shouldn't really happen because Please doesn't execute tests in
    64                          // a way where this would happen. It might be technically possible if the
    65                          // user manipulates JVM flags though.
    66                          throw new IllegalStateException(ex);
    67                      }
    68                  }
    69              }
    70          }
    71          return entries;
    72      }
    73  
    74      /**
    75       * Scans a series of class loaders and produces all the appropriate classes.
    76       */
    77      private void scan(Map<File, ClassLoader> loaders) throws IOException {
    78          for (Map.Entry<File, ClassLoader> entry : loaders.entrySet()) {
    79              scan(entry.getKey(), entry.getValue());
    80          }
    81      }
    82  
    83      /**
    84       * Scans a single file for classes to load.
    85       */
    86      private void scan(File file, ClassLoader loader) throws IOException {
    87        try {
    88          if (!file.exists()) {
    89            return;
    90          }
    91        } catch (SecurityException e) {
    92            System.err.println("Cannot access " + file + ": " + e);
    93            return;
    94        }
    95        if (file.isDirectory()) {
    96            // Please only ever runs tests from uberjars so we don't need to support this.
    97            System.err.println("Directory scanning not supported for " + file);
    98        } else {
    99            scanJar(file, loader);
   100        }
   101      }
   102  
   103      /**
   104       * Scans a single jar for classes to load.
   105       */
   106      private void scanJar(File file, ClassLoader loader) throws IOException {
   107          JarFile jarFile = new JarFile(file);
   108          Enumeration<JarEntry> entries = jarFile.entries();
   109          while (entries.hasMoreElements()) {
   110              JarEntry entry = entries.nextElement();
   111              if (entry.isDirectory() || entry.getName().equals(JarFile.MANIFEST_NAME)) {
   112                  continue;
   113              }
   114              loadClass(loader, entry.getName());
   115          }
   116          jarFile.close();
   117      }
   118  
   119      /**
   120       * Loads a single class if it matches our prefix.
   121       */
   122      private void loadClass(ClassLoader loader, String filename) {
   123          if (!filename.endsWith(".class")) {
   124              return;
   125          }
   126          int classNameEnd = filename.length() - ".class".length();
   127          String className = filename.substring(0, classNameEnd).replace('/', '.');
   128          if (className.startsWith(prefix)) {
   129              try {
   130                  classes.add(loader.loadClass(className));
   131              } catch (NoClassDefFoundError ex) {
   132                  // This happens sometimes with some classes. For now we just skip it.
   133              } catch (ClassNotFoundException ex) {
   134                  // Theoretically this shouldn't happen, because we've already found it
   135                  // on the classpath.
   136                  throw new IllegalStateException(ex);
   137              }
   138          }
   139      }
   140  
   141      /**
   142       * Loads a single class by filename into our internal set.
   143       */
   144      public void loadClass(String filename) {
   145          loadClass(loader, filename);
   146      }
   147  }