1 /* 2 * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 package jdk.tools.jaotc.collect; 25 26 import jdk.tools.jaotc.LogPrinter; 27 import jdk.tools.jaotc.Main; 28 29 import java.io.File; 30 import java.io.IOException; 31 import java.net.*; 32 import java.nio.file.*; 33 import java.nio.file.attribute.BasicFileAttributes; 34 import java.util.*; 35 36 import static java.nio.file.FileVisitResult.CONTINUE; 37 38 public class ClassCollector { 39 private final Main.Options options; 40 private final LogPrinter log; 41 42 public ClassCollector(Main.Options options, LogPrinter log) { 43 this.options = options; 44 this.log = log; 45 } 46 47 /** 48 * Collect all class names passed by the user. 49 * 50 * @return array list of classes 51 */ 52 public Set<Class<?>> collectClassesToCompile() { 53 Set<Class<?>> classes = new HashSet<>(); 54 List<String> filesToScan = new LinkedList<>(options.files); 55 56 if (options.module != null) { 57 classes.addAll(scanModule(filesToScan)); 58 } 59 60 classes.addAll(scanFiles(filesToScan)); 61 return classes; 62 } 63 64 private Set<Class<?>> scanModule(List<String> filesToScan) { 65 String module = options.module; 66 // Search module in standard JDK installation. 67 Path dir = getModuleDirectory(options.modulepath, module); 68 69 if (Files.isDirectory(dir)) { 70 return loadFromModuleDirectory(dir); 71 } else { 72 findFilesToScan(filesToScan, module); 73 return new HashSet<>(); 74 } 75 } 76 77 private Set<Class<?>> loadFromModuleDirectory(Path dir) { 78 log.printInfo("Scanning module: " + dir + " ..."); 79 log.printlnVerbose(" "); // Break line 80 81 FileSystemFinder finder = new FileSystemFinder(dir, pathname -> entryIsClassFile(pathname.toString())); 82 Set<Class<?>> cls = loadWithClassLoader(() -> ClassLoader.getSystemClassLoader(), dir, finder); 83 log.printlnInfo(" " + cls.size() + " classes loaded."); 84 return cls; 85 } 86 87 private void findFilesToScan(List<String> filesToScan, String module) { 88 // Try to search regular directory, .jar or .class files 89 Path path = Paths.get(options.modulepath, module); 90 91 if (Files.isDirectory(path)) { 92 filesToScan.add("."); 93 options.classpath = path.toString(); 94 } else if (path.endsWith(".jar") || path.endsWith(".class")) { 95 filesToScan.add(path.toString()); 96 } else { 97 path = Paths.get(options.modulepath, module + ".jar"); 98 if (Files.exists(path)) { 99 filesToScan.add(path.toString()); 100 } else { 101 path = Paths.get(options.modulepath, module + ".class"); 102 if (Files.exists(path)) { 103 filesToScan.add(path.toString()); 104 } else { 105 throw new InternalError("Expecting a .class, .jar or directory: " + path); 106 } 107 } 108 } 109 } 110 111 private boolean entryIsClassFile(String entry) { 112 return entry.endsWith(".class") && !entry.endsWith("module-info.class"); 113 } 114 115 private Set<Class<?>> scanFiles(List<String> filesToScan) { 116 Set<Class<?>> classes = new HashSet<>(); 117 for (String fileName : filesToScan) { 118 Set<Class<?>> loaded = scanFile(fileName); 119 log.printlnInfo(" " + loaded.size() + " classes loaded."); 120 classes.addAll(loaded); 121 } 122 return classes; 123 } 124 125 interface ClassLoaderFactory { 126 ClassLoader create() throws IOException; 127 } 128 129 private Set<Class<?>> loadWithClassLoader(ClassLoaderFactory factory, Path root, FileSystemFinder finder) { 130 ClassLoader loader = null; 131 try { 132 loader = factory.create(); 133 return loadClassFiles(root, finder, loader); 134 } catch (IOException e) { 135 throw new InternalError(e); 136 } finally { 137 if (loader instanceof AutoCloseable) { 138 try { 139 ((AutoCloseable) loader).close(); 140 } catch (Exception e) { 141 throw new InternalError(e); 142 } 143 } 144 } 145 } 146 147 private Set<Class<?>> scanFile(String fileName) { 148 log.printInfo("Scanning: " + fileName + " ..."); 149 log.printlnVerbose(" "); // Break line 150 151 if (fileName.endsWith(".jar")) { 152 return loadFromJarFile(fileName); 153 } else if (fileName.endsWith(".class")) { 154 Set<Class<?>> classes = new HashSet<>(); 155 loadFromClassFile(fileName, classes); 156 return classes; 157 } else { 158 return scanClassPath(fileName); 159 } 160 } 161 162 private Set<Class<?>> loadFromJarFile(String fileName) { 163 FileSystem fs = makeFileSystem(fileName); 164 FileSystemFinder finder = new FileSystemFinder(fs.getPath("/"), pathname -> entryIsClassFile(pathname.toString())); 165 return loadWithClassLoader(() -> URLClassLoader.newInstance(buildUrls(fileName)), fs.getPath("/"), finder); 166 } 167 168 private void loadFromClassFile(String fileName, Set<Class<?>> classes) { 169 Class<?> result; 170 File file = new File(options.classpath); 171 try (URLClassLoader loader = URLClassLoader.newInstance(buildUrls(file))) { 172 result = loadClassFile(loader, fileName); 173 } catch (IOException e) { 174 throw new InternalError(e); 175 } 176 Class<?> c = result; 177 addClass(classes, fileName, c); 178 } 179 180 private Set<Class<?>> scanClassPath(String fileName) { 181 Path classPath = Paths.get(options.classpath); 182 if (!Files.exists(classPath)) { 183 throw new InternalError("Path does not exist: " + classPath); 184 } 185 if (!Files.isDirectory(classPath)) { 186 throw new InternalError("Path must be a directory: " + classPath); 187 } 188 189 // Combine class path and file name and see what it is. 190 Path combinedPath = Paths.get(options.classpath + File.separator + fileName); 191 if (combinedPath.endsWith(".class")) { 192 throw new InternalError("unimplemented"); 193 } else if (Files.isDirectory(combinedPath)) { 194 return scanDirectory(classPath, combinedPath); 195 } else { 196 throw new InternalError("Expecting a .class, .jar or directory: " + fileName); 197 } 198 } 199 200 private FileSystem makeFileSystem(String fileName) { 201 try { 202 return FileSystems.newFileSystem(makeJarFileURI(fileName), new HashMap<>()); 203 } catch (IOException e) { 204 throw new InternalError(e); 205 } 206 } 207 208 private URI makeJarFileURI(String fileName) { 209 String name = Paths.get(fileName).toAbsolutePath().toString(); 210 name = name.replace('\\', '/'); 211 try { 212 return new URI("jar:file:///" + name + "!/"); 213 } catch (URISyntaxException e) { 214 throw new InternalError(e); 215 } 216 } 217 218 private PathMatcher combine(PathMatcher m1, PathMatcher m2) { 219 return path -> m1.matches(path) && m2.matches(path); 220 } 221 222 private Set<Class<?>> scanDirectory(Path classPath, Path combinedPath) { 223 String dir = options.classpath; 224 225 FileSystem fileSystem = FileSystems.getDefault(); 226 PathMatcher matcher = fileSystem.getPathMatcher("glob:" + "*.class"); 227 FileSystemFinder finder = new FileSystemFinder(combinedPath, 228 combine(matcher, pathname -> entryIsClassFile(pathname.toString()))); 229 230 File file = new File(dir); 231 try (URLClassLoader loader = URLClassLoader.newInstance(buildUrls(file))) { 232 return loadClassFiles(classPath, finder, loader); 233 } catch (IOException e) { 234 throw new InternalError(e); 235 } 236 } 237 238 private Set<Class<?>> loadClassFiles(Path root, FileSystemFinder finder, ClassLoader loader) { 239 Set<Class<?>> classes = new HashSet<>(); 240 for (Path name : finder.done()) { 241 // Now relativize to the class path so we get the actual class names. 242 String entry = root.relativize(name).normalize().toString(); 243 Class<?> c = loadClassFile(loader, entry); 244 addClass(classes, entry, c); 245 } 246 return classes; 247 } 248 249 private void addClass(Set<Class<?>> classes, String name, Class<?> c) { 250 if (c != null) { 251 classes.add(c); 252 log.printlnVerbose(" loaded " + name); 253 } 254 } 255 256 private URL[] buildUrls(String fileName) throws MalformedURLException { 257 String name = Paths.get(fileName).toAbsolutePath().toString(); 258 name = name.replace('\\', '/'); 259 return new URL[]{ new URL("jar:file:///" + name + "!/") }; 260 } 261 262 private URL[] buildUrls(File file) throws MalformedURLException { 263 return new URL[] {file.toURI().toURL() }; 264 } 265 266 private Path getModuleDirectory(String modulepath, String module) { 267 FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/")); 268 return fs.getPath(modulepath, module); 269 } 270 271 /** 272 * Loads a class with the given file name from the specified {@link URLClassLoader}. 273 */ 274 private Class<?> loadClassFile(final ClassLoader loader, final String fileName) { 275 int start = 0; 276 if (fileName.startsWith("/")) { 277 start = 1; 278 } 279 String className = fileName.substring(start, fileName.length() - ".class".length()); 280 className = className.replace('/', '.'); 281 className = className.replace('\\', '.'); 282 try { 283 return loader.loadClass(className); 284 } catch (Throwable e) { 285 // If we are running in JCK mode we ignore all exceptions. 286 if (options.ignoreClassLoadingErrors) { 287 log.printError(className + ": " + e); 288 return null; 289 } 290 throw new InternalError(e); 291 } 292 } 293 294 /** 295 * {@link FileVisitor} implementation to find class files recursively. 296 */ 297 private static class FileSystemFinder extends SimpleFileVisitor<Path> { 298 private final ArrayList<Path> fileNames = new ArrayList<>(); 299 private final PathMatcher filter; 300 301 FileSystemFinder(Path combinedPath, PathMatcher filter) { 302 this.filter = filter; 303 try { 304 Files.walkFileTree(combinedPath, this); 305 } catch (IOException e) { 306 throw new InternalError(e); 307 } 308 } 309 310 /** 311 * Compares the glob pattern against the file name. 312 */ 313 void find(Path file) { 314 Path name = file.getFileName(); 315 if (name != null && filter.matches(name)) { 316 fileNames.add(file); 317 } 318 } 319 320 List<Path> done() { 321 return fileNames; 322 } 323 324 @Override 325 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { 326 find(file); 327 return CONTINUE; 328 } 329 330 @Override 331 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { 332 find(dir); 333 return CONTINUE; 334 } 335 336 } 337 }