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 }