1 /******************************************************************************* 2 * Copyright (c) 2015, 2020 IBM Corporation. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 *******************************************************************************/ 14 package org.eclipse.jdt.internal.compiler.util; 15 16 import java.io.ByteArrayInputStream; 17 import java.io.File; 18 import java.io.IOException; 19 import java.io.InputStream; 20 import java.net.URI; 21 import java.net.URL; 22 import java.net.URLClassLoader; 23 import java.nio.channels.ClosedByInterruptException; 24 import java.nio.file.DirectoryStream; 25 import java.nio.file.FileSystem; 26 import java.nio.file.FileSystems; 27 import java.nio.file.FileVisitResult; 28 import java.nio.file.FileVisitor; 29 import java.nio.file.Files; 30 import java.nio.file.NoSuchFileException; 31 import java.nio.file.Path; 32 import java.nio.file.Paths; 33 import java.nio.file.attribute.BasicFileAttributes; 34 import java.util.ArrayList; 35 import java.util.Collections; 36 import java.util.HashMap; 37 import java.util.List; 38 import java.util.Map; 39 import java.util.Optional; 40 import java.util.concurrent.ConcurrentHashMap; 41 import java.util.function.Predicate; 42 43 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader; 44 import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException; 45 import org.eclipse.jdt.internal.compiler.env.IModule; 46 47 public class JRTUtil { 48 49 public static final boolean DISABLE_CACHE = Boolean.getBoolean("org.eclipse.jdt.disable_JRT_cache"); //$NON-NLS-1$ 50 51 public static final String JAVA_BASE = "java.base"; //$NON-NLS-1$ 52 public static final char[] JAVA_BASE_CHAR = JAVA_BASE.toCharArray(); 53 static final String MODULES_SUBDIR = "/modules"; //$NON-NLS-1$ 54 static final String[] DEFAULT_MODULE = new String[]{JAVA_BASE}; 55 static final String[] NO_MODULE = new String[0]; 56 static final String MULTIPLE = "MU"; //$NON-NLS-1$ 57 static final String DEFAULT_PACKAGE = ""; //$NON-NLS-1$ 58 static String MODULE_TO_LOAD; 59 public static final String JRT_FS_JAR = "jrt-fs.jar"; //$NON-NLS-1$ 60 static URI JRT_URI = URI.create("jrt:/"); //$NON-NLS-1$ 61 public static final int NOTIFY_FILES = 0x0001; 62 public static final int NOTIFY_PACKAGES = 0x0002; 63 public static final int NOTIFY_MODULES = 0x0004; 64 public static final int NOTIFY_ALL = NOTIFY_FILES | NOTIFY_PACKAGES | NOTIFY_MODULES; 65 66 // TODO: Java 9 Think about clearing the cache too. 67 private static Map<String, Optional<JrtFileSystem>> images = new ConcurrentHashMap<>(); 68 69 /** 70 * Map from JDK home path to ct.sym file (located in /lib in the JDK) 71 */ 72 private static final Map<Path, CtSym> ctSymFiles = new ConcurrentHashMap<>(); 73 74 public interface JrtFileVisitor<T> { 75 visitPackage(T dir, T mod, BasicFileAttributes attrs)76 public FileVisitResult visitPackage(T dir, T mod, BasicFileAttributes attrs) throws IOException; 77 visitFile(T file, T mod, BasicFileAttributes attrs)78 public FileVisitResult visitFile(T file, T mod, BasicFileAttributes attrs) throws IOException; 79 /** 80 * Invoked when a root directory of a module being visited. The element returned 81 * contains only the module name segment - e.g. "java.base". Clients can use this to control 82 * how the JRT needs to be processed, for e.g., clients can skip a particular module 83 * by returning FileVisitResult.SKIP_SUBTREE 84 */ visitModule(T path, String name)85 public FileVisitResult visitModule(T path, String name) throws IOException; 86 } 87 88 static abstract class AbstractFileVisitor<T> implements FileVisitor<T> { 89 @Override preVisitDirectory(T dir, BasicFileAttributes attrs)90 public FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs) throws IOException { 91 return FileVisitResult.CONTINUE; 92 } 93 94 @Override visitFile(T file, BasicFileAttributes attrs)95 public FileVisitResult visitFile(T file, BasicFileAttributes attrs) throws IOException { 96 return FileVisitResult.CONTINUE; 97 } 98 99 @Override visitFileFailed(T file, IOException exc)100 public FileVisitResult visitFileFailed(T file, IOException exc) throws IOException { 101 return FileVisitResult.CONTINUE; 102 } 103 104 @Override postVisitDirectory(T dir, IOException exc)105 public FileVisitResult postVisitDirectory(T dir, IOException exc) throws IOException { 106 return FileVisitResult.CONTINUE; 107 } 108 } 109 getJrtSystem(File image)110 public static JrtFileSystem getJrtSystem(File image) { 111 return getJrtSystem(image, null); 112 } 113 getJrtSystem(File image, String release)114 public static JrtFileSystem getJrtSystem(File image, String release) { 115 String key = image.toString(); 116 if (release != null) key = key + "|" + release; //$NON-NLS-1$ 117 Optional<JrtFileSystem> system = images.computeIfAbsent(key, x -> { 118 try { 119 return Optional.ofNullable(JrtFileSystem.getNewJrtFileSystem(image, release)); 120 } catch (IOException e) { 121 // Needs better error handling downstream? But for now, make sure 122 // a dummy JrtFileSystem is not created. 123 e.printStackTrace(); 124 return Optional.empty(); 125 } 126 }); 127 return system.orElse(null); 128 } 129 getCtSym(Path jdkHome)130 public static CtSym getCtSym(Path jdkHome) throws IOException { 131 CtSym ctSym; 132 try { 133 ctSym = ctSymFiles.compute(jdkHome, (Path x, CtSym current) -> { 134 if (current == null || !current.getFs().isOpen()) { 135 try { 136 return new CtSym(x); 137 } catch (IOException e) { 138 throw new RuntimeIOException(e); 139 } 140 } 141 return current; 142 }); 143 } catch (RuntimeIOException rio) { 144 throw rio.getCause(); 145 } 146 return ctSym; 147 } 148 149 /** TEST ONLY (use when changing the "modules.to.load" property). */ reset()150 public static void reset() { 151 images.clear(); 152 MODULE_TO_LOAD = System.getProperty("modules.to.load"); //$NON-NLS-1$ 153 } 154 155 /** 156 * Given the path of a modular image file, this method walks the archive content and 157 * notifies the supplied visitor about packages and files visited. 158 * 159 * The file system contains the following top level directories: 160 * /modules/$MODULE/$PATH 161 * /packages/$PACKAGE/$MODULE 162 * The latter provides quick look up of the module that contains a particular package. However, 163 * this method only notifies its clients of the entries within the modules (latter) sub-directory. 164 * Clients can decide which notifications they want to receive. See {@link JRTUtil#NOTIFY_ALL}, 165 * {@link JRTUtil#NOTIFY_FILES}, {@link JRTUtil#NOTIFY_PACKAGES} and {@link JRTUtil#NOTIFY_MODULES}. 166 * 167 * @param image a java.io.File handle to the JRT image. 168 * @param visitor an instance of JrtFileVisitor to be notified of the entries in the JRT image. 169 * @param notify flag indicating the notifications the client is interested in. 170 * @throws IOException 171 */ walkModuleImage(File image, final JRTUtil.JrtFileVisitor<java.nio.file.Path> visitor, int notify)172 public static void walkModuleImage(File image, final JRTUtil.JrtFileVisitor<java.nio.file.Path> visitor, int notify) throws IOException { 173 getJrtSystem(image, null).walkModuleImage(visitor, notify); 174 } 175 walkModuleImage(File image, String release, final JRTUtil.JrtFileVisitor<java.nio.file.Path> visitor, int notify)176 public static void walkModuleImage(File image, String release, final JRTUtil.JrtFileVisitor<java.nio.file.Path> visitor, int notify) throws IOException { 177 getJrtSystem(image, release).walkModuleImage(visitor, notify); 178 } 179 getContentFromJrt(File jrt, String fileName, String module)180 public static InputStream getContentFromJrt(File jrt, String fileName, String module) throws IOException { 181 return getJrtSystem(jrt).getContentFromJrt(fileName, module); 182 } 183 getClassfileContent(File jrt, String fileName, String module)184 public static byte[] getClassfileContent(File jrt, String fileName, String module) throws IOException { 185 return getJrtSystem(jrt).getClassfileContent(fileName, module); 186 } 187 getClassfile(File jrt, String fileName, IModule module)188 public static ClassFileReader getClassfile(File jrt, String fileName, IModule module) throws IOException, ClassFormatException { 189 return getJrtSystem(jrt).getClassfile(fileName, module); 190 } 191 getClassfile(File jrt, String fileName, String module, Predicate<String> moduleNameFilter)192 public static ClassFileReader getClassfile(File jrt, String fileName, String module, Predicate<String> moduleNameFilter) throws IOException, ClassFormatException { 193 return getJrtSystem(jrt).getClassfile(fileName, module, moduleNameFilter); 194 } 195 getModulesDeclaringPackage(File jrt, String qName, String moduleName)196 public static List<String> getModulesDeclaringPackage(File jrt, String qName, String moduleName) { 197 return getJrtSystem(jrt).getModulesDeclaringPackage(qName, moduleName); 198 } 199 hasCompilationUnit(File jrt, String qualifiedPackageName, String moduleName)200 public static boolean hasCompilationUnit(File jrt, String qualifiedPackageName, String moduleName) { 201 return getJrtSystem(jrt).hasClassFile(qualifiedPackageName, moduleName); 202 } 203 204 /* 205 * Returns only the file name after removing trailing '/' if any for folders 206 */ sanitizedFileName(Path path)207 public static String sanitizedFileName(Path path) { 208 String p = path.getFileName().toString(); 209 if (p.length() > 1 && p.charAt(p.length() - 1) == '/') { 210 return p.substring(0, p.length() - 1); 211 } 212 return p; 213 } 214 215 /** 216 * Tries to read all bytes of the file denoted by path, 217 * returns null if the file could not be found or if the read was interrupted. 218 * @param path 219 * @return bytes or null 220 * @throws IOException any IO exception other than NoSuchFileException 221 */ safeReadBytes(Path path)222 public static byte[] safeReadBytes(Path path) throws IOException { 223 try { 224 return Files.readAllBytes(path); 225 } catch (ClosedByInterruptException | NoSuchFileException e) { 226 return null; 227 } 228 } 229 } 230 231 class JrtFileSystemWithOlderRelease extends JrtFileSystem { 232 233 final String release; 234 String releaseInHex; 235 private List<Path> releaseRoots = Collections.emptyList(); 236 protected Path modulePath; 237 private CtSym ctSym; 238 239 /** 240 * The jrt file system is based on the location of the JRE home whose libraries 241 * need to be loaded. 242 * 243 * @param jrt the path to the root of the JRE whose libraries we are interested in. 244 * @param release the older release where classes and modules should be searched for. 245 * @throws IOException 246 */ JrtFileSystemWithOlderRelease(File jrt, String release)247 JrtFileSystemWithOlderRelease(File jrt, String release) throws IOException { 248 super(jrt); 249 this.release = release; 250 initialize(jrt, release); 251 } 252 253 @Override initialize(File jdk)254 void initialize(File jdk) throws IOException { 255 // Just to make sure we don't do anything in super.initialize() 256 // before setting this.release 257 } 258 initialize(File jdk, String rel)259 private void initialize(File jdk, String rel) throws IOException { 260 super.initialize(jdk); 261 this.fs = null;// reset and proceed, TODO: this is crude and need to be removed. 262 this.releaseInHex = Integer.toHexString(Integer.parseInt(this.release)).toUpperCase(); 263 this.ctSym = JRTUtil.getCtSym(Paths.get(this.jdkHome)); 264 this.fs = this.ctSym.getFs(); 265 if (!Files.exists(this.fs.getPath(this.releaseInHex)) 266 || Files.exists(this.fs.getPath(this.releaseInHex, "system-modules"))) { //$NON-NLS-1$ 267 this.fs = null; 268 } 269 this.releaseRoots = this.ctSym.releaseRoots(this.releaseInHex); 270 } 271 272 @Override walkModuleImage(final JRTUtil.JrtFileVisitor<java.nio.file.Path> visitor, final int notify)273 void walkModuleImage(final JRTUtil.JrtFileVisitor<java.nio.file.Path> visitor, final int notify) throws IOException { 274 for (Path p : this.releaseRoots) { 275 Files.walkFileTree(p, new JRTUtil.AbstractFileVisitor<java.nio.file.Path>() { 276 @Override 277 public FileVisitResult preVisitDirectory(java.nio.file.Path dir, BasicFileAttributes attrs) 278 throws IOException { 279 int count = dir.getNameCount(); 280 if (count == 1) { 281 return FileVisitResult.CONTINUE; 282 } 283 if (count == 2) { 284 // e.g. /9A/java.base 285 java.nio.file.Path mod = dir.getName(1); 286 if ((JRTUtil.MODULE_TO_LOAD != null && JRTUtil.MODULE_TO_LOAD.length() > 0 287 && JRTUtil.MODULE_TO_LOAD.indexOf(mod.toString()) == -1)) { 288 return FileVisitResult.SKIP_SUBTREE; 289 } 290 return ((notify & JRTUtil.NOTIFY_MODULES) == 0) ? FileVisitResult.CONTINUE 291 : visitor.visitModule(dir, JRTUtil.sanitizedFileName(mod)); 292 } 293 if ((notify & JRTUtil.NOTIFY_PACKAGES) == 0) { 294 // client is not interested in packages 295 return FileVisitResult.CONTINUE; 296 } 297 return visitor.visitPackage(dir.subpath(2, count), dir.getName(1), attrs); 298 } 299 300 @Override 301 public FileVisitResult visitFile(java.nio.file.Path file, BasicFileAttributes attrs) 302 throws IOException { 303 if ((notify & JRTUtil.NOTIFY_FILES) == 0) { 304 return FileVisitResult.CONTINUE; 305 } 306 // This happens when a file in a default package is present. E.g. /modules/some.module/file.name 307 if (file.getNameCount() == 3) { 308 cachePackage(JRTUtil.DEFAULT_PACKAGE, file.getName(1).toString()); 309 } 310 return visitor.visitFile(file.subpath(2, file.getNameCount()), file.getName(1), attrs); 311 } 312 }); 313 } 314 } 315 316 } 317 318 final class RuntimeIOException extends RuntimeException { 319 private static final long serialVersionUID = 1L; 320 RuntimeIOException(IOException cause)321 public RuntimeIOException(IOException cause) { 322 super(cause); 323 } 324 325 @Override getCause()326 public synchronized IOException getCause() { 327 return (IOException) super.getCause(); 328 } 329 } 330 331 class JrtFileSystem { 332 333 private final Map<String, String> packageToModule = new HashMap<String, String>(); 334 335 private final Map<String, List<String>> packageToModules = new HashMap<String, List<String>>(); 336 337 338 private final Map<Path, Optional<byte[]>> classCache = new ConcurrentHashMap<>(10007); 339 340 FileSystem fs; 341 Path modRoot; 342 String jdkHome; 343 getNewJrtFileSystem(File jrt, String release)344 public static JrtFileSystem getNewJrtFileSystem(File jrt, String release) throws IOException { 345 return (release == null) ? new JrtFileSystem(jrt) : 346 new JrtFileSystemWithOlderRelease(jrt, release); 347 348 } 349 350 /** 351 * The jrt file system is based on the location of the JRE home whose libraries 352 * need to be loaded. 353 * 354 * @param jrt the path to the root of the JRE whose libraries we are interested in. 355 * @throws IOException 356 */ JrtFileSystem(File jrt)357 JrtFileSystem(File jrt) throws IOException { 358 initialize(jrt); 359 } 360 initialize(File jrt)361 void initialize(File jrt) throws IOException { 362 URL jrtPath = null; 363 this.jdkHome = null; 364 if (jrt.toString().endsWith(JRTUtil.JRT_FS_JAR)) { 365 jrtPath = jrt.toPath().toUri().toURL(); 366 this.jdkHome = jrt.getParentFile().getParent(); 367 } else { 368 this.jdkHome = jrt.toPath().toString(); 369 jrtPath = Paths.get(this.jdkHome, "lib", JRTUtil.JRT_FS_JAR).toUri().toURL(); //$NON-NLS-1$ 370 371 } 372 JRTUtil.MODULE_TO_LOAD = System.getProperty("modules.to.load"); //$NON-NLS-1$ 373 String javaVersion = System.getProperty("java.version"); //$NON-NLS-1$ 374 if (javaVersion != null && javaVersion.startsWith("1.8")) { //$NON-NLS-1$ 375 URLClassLoader loader = new URLClassLoader(new URL[] { jrtPath }); 376 HashMap<String, ?> env = new HashMap<>(); 377 this.fs = FileSystems.newFileSystem(JRTUtil.JRT_URI, env, loader); 378 } else { 379 HashMap<String, String> env = new HashMap<>(); 380 env.put("java.home", this.jdkHome); //$NON-NLS-1$ 381 this.fs = FileSystems.newFileSystem(JRTUtil.JRT_URI, env); 382 } 383 this.modRoot = this.fs.getPath(JRTUtil.MODULES_SUBDIR); 384 // Set up the root directory wherere modules are located 385 walkJrtForModules(); 386 } 387 getModulesDeclaringPackage(String qualifiedPackageName, String moduleName)388 public List<String> getModulesDeclaringPackage(String qualifiedPackageName, String moduleName) { 389 qualifiedPackageName = qualifiedPackageName.replace('.', '/'); 390 String module = this.packageToModule.get(qualifiedPackageName); 391 if (moduleName == null) { 392 // wildcard search: 393 if (module == null) 394 return null; 395 if (module == JRTUtil.MULTIPLE) 396 return this.packageToModules.get(qualifiedPackageName); 397 return Collections.singletonList(module); 398 } 399 if (module != null) { 400 // specific search: 401 if (module == JRTUtil.MULTIPLE) { 402 List<String> list = this.packageToModules.get(qualifiedPackageName); 403 if (list.contains(moduleName)) 404 return Collections.singletonList(moduleName); 405 } else { 406 if (module.equals(moduleName)) 407 return Collections.singletonList(moduleName); 408 } 409 } 410 return null; 411 } 412 getModules(String fileName)413 public String[] getModules(String fileName) { 414 int idx = fileName.lastIndexOf('/'); 415 String pack = null; 416 if (idx != -1) { 417 pack = fileName.substring(0, idx); 418 } else { 419 pack = JRTUtil.DEFAULT_PACKAGE; 420 } 421 String module = this.packageToModule.get(pack); 422 if (module != null) { 423 if (module == JRTUtil.MULTIPLE) { 424 List<String> list = this.packageToModules.get(pack); 425 return list.toArray(new String[0]); 426 } else { 427 return new String[]{module}; 428 } 429 } 430 return JRTUtil.DEFAULT_MODULE; 431 } 432 hasClassFile(String qualifiedPackageName, String module)433 public boolean hasClassFile(String qualifiedPackageName, String module) { 434 if (module == null) 435 return false; 436 // easy checks first: 437 String knownModule = this.packageToModule.get(qualifiedPackageName); 438 if (knownModule == null || (knownModule != JRTUtil.MULTIPLE && !knownModule.equals(module))) 439 return false; 440 Path packagePath = this.fs.getPath(JRTUtil.MODULES_SUBDIR, module, qualifiedPackageName); 441 if (!Files.exists(packagePath)) 442 return false; 443 // iterate files: 444 try { 445 return Files.list(packagePath) 446 .anyMatch(filePath -> filePath.toString().endsWith(SuffixConstants.SUFFIX_STRING_class) 447 || filePath.toString().endsWith(SuffixConstants.SUFFIX_STRING_CLASS)); 448 } catch (IOException e) { 449 return false; 450 } 451 } 452 getContentFromJrt(String fileName, String module)453 public InputStream getContentFromJrt(String fileName, String module) throws IOException { 454 if (module != null) { 455 byte[] fileBytes = getFileBytes(fileName, module); 456 if(fileBytes == null) { 457 return null; 458 } 459 return new ByteArrayInputStream(fileBytes); 460 } 461 String[] modules = getModules(fileName); 462 for (String mod : modules) { 463 byte[] fileBytes = getFileBytes(fileName, mod); 464 if(fileBytes != null) { 465 return new ByteArrayInputStream(fileBytes); 466 } 467 } 468 return null; 469 } 470 getClassfile(String fileName, Predicate<String> moduleNameFilter)471 private ClassFileReader getClassfile(String fileName, Predicate<String> moduleNameFilter) throws IOException, ClassFormatException { 472 String[] modules = getModules(fileName); 473 byte[] content = null; 474 String module = null; 475 for (String mod : modules) { 476 if (moduleNameFilter != null && !moduleNameFilter.test(mod)) { 477 continue; 478 } 479 content = getFileBytes(fileName, mod); 480 if (content != null) { 481 module = mod; 482 break; 483 } 484 } 485 if (content != null) { 486 ClassFileReader reader = new ClassFileReader(content, fileName.toCharArray()); 487 reader.moduleName = module.toCharArray(); 488 return reader; 489 } 490 return null; 491 } 492 getClassfileContent(String fileName, String module)493 byte[] getClassfileContent(String fileName, String module) throws IOException { 494 byte[] content = null; 495 if (module != null) { 496 content = getFileBytes(fileName, module); 497 } else { 498 String[] modules = getModules(fileName); 499 for (String mod : modules) { 500 content = getFileBytes(fileName, mod); 501 if (content != null) { 502 break; 503 } 504 } 505 } 506 return content; 507 } 508 getFileBytes(String fileName, String module)509 private byte[] getFileBytes(String fileName, String module) throws IOException { 510 Path path = this.fs.getPath(JRTUtil.MODULES_SUBDIR, module, fileName); 511 if(JRTUtil.DISABLE_CACHE) { 512 return JRTUtil.safeReadBytes(path); 513 } else { 514 try { 515 Optional<byte[]> bytes = this.classCache.computeIfAbsent(path, key -> { 516 try { 517 return Optional.ofNullable(JRTUtil.safeReadBytes(key)); 518 } catch (IOException e) { 519 throw new RuntimeIOException(e); 520 } 521 }); 522 return bytes.orElse(null); 523 } catch (RuntimeIOException rio) { 524 throw rio.getCause(); 525 } 526 } 527 } 528 getClassfile(String fileName, String module, Predicate<String> moduleNameFilter)529 public ClassFileReader getClassfile(String fileName, String module, Predicate<String> moduleNameFilter) throws IOException, ClassFormatException { 530 ClassFileReader reader = null; 531 if (module == null) { 532 reader = getClassfile(fileName, moduleNameFilter); 533 } else { 534 byte[] content = getFileBytes(fileName, module); 535 if (content != null) { 536 reader = new ClassFileReader(content, fileName.toCharArray()); 537 reader.moduleName = module.toCharArray(); 538 } 539 } 540 return reader; 541 } 542 getClassfile(String fileName, IModule module)543 public ClassFileReader getClassfile(String fileName, IModule module) throws IOException, ClassFormatException { 544 ClassFileReader reader = null; 545 if (module == null) { 546 reader = getClassfile(fileName, (Predicate<String>)null); 547 } else { 548 byte[] content = getFileBytes(fileName, new String(module.name())); 549 if (content != null) { 550 reader = new ClassFileReader(content, fileName.toCharArray()); 551 } 552 } 553 return reader; 554 } 555 walkJrtForModules()556 void walkJrtForModules() throws IOException { 557 Iterable<java.nio.file.Path> roots = this.fs.getRootDirectories(); 558 for (java.nio.file.Path path : roots) { 559 try (DirectoryStream<java.nio.file.Path> stream = Files.newDirectoryStream(path)) { 560 for (final java.nio.file.Path subdir: stream) { 561 if (!subdir.toString().equals(JRTUtil.MODULES_SUBDIR)) { 562 Files.walkFileTree(subdir, new JRTUtil.AbstractFileVisitor<java.nio.file.Path>() { 563 @Override 564 public FileVisitResult visitFile(java.nio.file.Path file, BasicFileAttributes attrs) throws IOException { 565 // e.g. /modules/java.base 566 java.nio.file.Path relative = subdir.relativize(file); 567 cachePackage(relative.getParent().toString(), relative.getFileName().toString()); 568 return FileVisitResult.CONTINUE; 569 } 570 }); 571 } 572 } 573 } catch (Exception e) { 574 throw new IOException(e.getMessage(), e); 575 } 576 } 577 } 578 walkModuleImage(final JRTUtil.JrtFileVisitor<java.nio.file.Path> visitor, final int notify)579 void walkModuleImage(final JRTUtil.JrtFileVisitor<java.nio.file.Path> visitor, final int notify) throws IOException { 580 Files.walkFileTree(this.modRoot, new JRTUtil.AbstractFileVisitor<java.nio.file.Path>() { 581 @Override 582 public FileVisitResult preVisitDirectory(java.nio.file.Path dir, BasicFileAttributes attrs) throws IOException { 583 int count = dir.getNameCount(); 584 if (count == 1) return FileVisitResult.CONTINUE; 585 if (count == 2) { 586 // e.g. /modules/java.base 587 java.nio.file.Path mod = dir.getName(1); 588 if ((JRTUtil.MODULE_TO_LOAD != null && JRTUtil.MODULE_TO_LOAD.length() > 0 && 589 JRTUtil.MODULE_TO_LOAD.indexOf(mod.toString()) == -1)) { 590 return FileVisitResult.SKIP_SUBTREE; 591 } 592 return ((notify & JRTUtil.NOTIFY_MODULES) == 0) ? 593 FileVisitResult.CONTINUE : visitor.visitModule(dir, JRTUtil.sanitizedFileName(mod)); 594 } 595 if ((notify & JRTUtil.NOTIFY_PACKAGES) == 0) { 596 // We are dealing with a module or not client is not interested in packages 597 return FileVisitResult.CONTINUE; 598 } 599 return visitor.visitPackage(dir.subpath(2, count), dir.getName(1), attrs); 600 } 601 602 @Override 603 public FileVisitResult visitFile(java.nio.file.Path file, BasicFileAttributes attrs) throws IOException { 604 if ((notify & JRTUtil.NOTIFY_FILES) == 0) 605 return FileVisitResult.CONTINUE; 606 int count = file.getNameCount(); 607 // This happens when a file in a default package is present. E.g. /modules/some.module/file.name 608 if (count == 3) { 609 cachePackage(JRTUtil.DEFAULT_PACKAGE, file.getName(1).toString()); 610 } 611 return visitor.visitFile(file.subpath(2, count), file.getName(1), attrs); 612 } 613 }); 614 } 615 cachePackage(String packageName, String module)616 void cachePackage(String packageName, String module) { 617 packageName = packageName.intern(); 618 module = module.intern(); 619 packageName = packageName.replace('.', '/'); 620 Object current = this.packageToModule.get(packageName); 621 if (current == null) { 622 this.packageToModule.put(packageName, module); 623 } else if(current == module || current.equals(module)) { 624 return; 625 } else if (current == JRTUtil.MULTIPLE) { 626 List<String> list = this.packageToModules.get(packageName); 627 if (!list.contains(module)) { 628 if (JRTUtil.JAVA_BASE == module || JRTUtil.JAVA_BASE.equals(module)) { 629 list.add(0, JRTUtil.JAVA_BASE); 630 } else { 631 list.add(module); 632 } 633 } 634 } else { 635 String first = (String) current; 636 this.packageToModule.put(packageName, JRTUtil.MULTIPLE); 637 List<String> list = new ArrayList<String>(); 638 // Just do this as comparator might be overkill 639 if (JRTUtil.JAVA_BASE == current || JRTUtil.JAVA_BASE.equals(current)) { 640 list.add(first); 641 list.add(module); 642 } else { 643 list.add(module); 644 list.add(first); 645 } 646 this.packageToModules.put(packageName, list); 647 } 648 } 649 }