1 /* 2 * Copyright (c) 2012, 2016, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.tools.jdeps; 27 28 import static com.sun.tools.jdeps.Module.trace; 29 import static java.util.stream.Collectors.*; 30 31 import com.sun.tools.classfile.Dependency; 32 33 import java.io.BufferedInputStream; 34 import java.io.File; 35 import java.io.FileNotFoundException; 36 import java.io.IOException; 37 import java.io.InputStream; 38 import java.io.UncheckedIOException; 39 import java.lang.module.Configuration; 40 import java.lang.module.ModuleDescriptor; 41 import java.lang.module.ModuleDescriptor.Exports; 42 import java.lang.module.ModuleDescriptor.Opens; 43 import java.lang.module.ModuleFinder; 44 import java.lang.module.ModuleReader; 45 import java.lang.module.ModuleReference; 46 import java.lang.module.ResolvedModule; 47 import java.net.URI; 48 import java.nio.file.DirectoryStream; 49 import java.nio.file.FileSystem; 50 import java.nio.file.FileSystems; 51 import java.nio.file.Files; 52 import java.nio.file.Path; 53 import java.nio.file.Paths; 54 import java.util.ArrayList; 55 import java.util.Collections; 56 import java.util.HashMap; 57 import java.util.HashSet; 58 import java.util.LinkedHashMap; 59 import java.util.LinkedHashSet; 60 import java.util.List; 61 import java.util.Map; 62 import java.util.Objects; 63 import java.util.Optional; 64 import java.util.Set; 65 import java.util.function.Function; 66 import java.util.function.Supplier; 67 import java.util.stream.Stream; 68 69 public class JdepsConfiguration implements AutoCloseable { 70 // the token for "all modules on the module path" 71 public static final String ALL_MODULE_PATH = "ALL-MODULE-PATH"; 72 public static final String ALL_DEFAULT = "ALL-DEFAULT"; 73 public static final String MODULE_INFO = "module-info.class"; 74 75 private final SystemModuleFinder system; 76 private final ModuleFinder finder; 77 78 private final Map<String, Module> nameToModule = new LinkedHashMap<>(); 79 private final Map<String, Module> packageToModule = new HashMap<>(); 80 private final Map<String, List<Archive>> packageToUnnamedModule = new HashMap<>(); 81 82 private final List<Archive> classpathArchives = new ArrayList<>(); 83 private final List<Archive> initialArchives = new ArrayList<>(); 84 private final Set<Module> rootModules = new HashSet<>(); 85 private final Configuration configuration; 86 private final Runtime.Version version; 87 88 private JdepsConfiguration(SystemModuleFinder systemModulePath, 89 ModuleFinder finder, 90 Set<String> roots, 91 List<Path> classpaths, 92 List<Archive> initialArchives, 93 boolean allDefaultModules, 94 boolean allSystemModules, 95 Runtime.Version version) 96 throws IOException 97 { 98 trace("root: %s%n", roots); 99 100 this.system = systemModulePath; 101 this.finder = finder; 102 this.version = version; 103 104 // build root set for resolution 105 Set<String> mods = new HashSet<>(roots); 106 107 // add default modules to the root set 108 // unnamed module 109 if (!initialArchives.isEmpty() || !classpaths.isEmpty() || 110 roots.isEmpty() || allDefaultModules) { 111 mods.addAll(systemModulePath.defaultSystemRoots()); 112 } 113 if (allSystemModules) { 114 systemModulePath.findAll().stream() 115 .map(mref -> mref.descriptor().name()) 116 .forEach(mods::add); 117 } 118 119 this.configuration = Configuration.empty() 120 .resolveRequires(finder, ModuleFinder.of(), mods); 121 122 this.configuration.modules().stream() 123 .map(ResolvedModule::reference) 124 .forEach(this::addModuleReference); 125 126 // packages in unnamed module 127 initialArchives.forEach(archive -> { 128 addPackagesInUnnamedModule(archive); 129 this.initialArchives.add(archive); 130 }); 131 132 // classpath archives 133 for (Path p : classpaths) { 134 if (Files.exists(p)) { 135 Archive archive = Archive.getInstance(p, version); 136 addPackagesInUnnamedModule(archive); 137 classpathArchives.add(archive); 138 } 139 } 140 141 // all roots specified in --add-modules or -m are included 142 // as the initial set for analysis. 143 roots.stream() 144 .map(nameToModule::get) 145 .forEach(this.rootModules::add); 146 147 initProfiles(); 148 149 trace("resolved modules: %s%n", nameToModule.keySet().stream() 150 .sorted().collect(joining("\n", "\n", ""))); 151 } 152 153 private void initProfiles() { 154 // other system modules are not observed and not added in nameToModule map 155 Map<String, Module> systemModules = 156 system.moduleNames() 157 .collect(toMap(Function.identity(), (mn) -> { 158 Module m = nameToModule.get(mn); 159 if (m == null) { 160 ModuleReference mref = finder.find(mn).get(); 161 m = toModule(mref); 162 } 163 return m; 164 })); 165 Profile.init(systemModules); 166 } 167 168 private void addModuleReference(ModuleReference mref) { 169 Module module = toModule(mref); 170 nameToModule.put(mref.descriptor().name(), module); 171 mref.descriptor().packages() 172 .forEach(pn -> packageToModule.putIfAbsent(pn, module)); 173 } 174 175 private void addPackagesInUnnamedModule(Archive archive) { 176 archive.reader().entries().stream() 177 .filter(e -> e.endsWith(".class") && !e.equals(MODULE_INFO)) 178 .map(this::toPackageName) 179 .distinct() 180 .forEach(pn -> packageToUnnamedModule 181 .computeIfAbsent(pn, _n -> new ArrayList<>()).add(archive)); 182 } 183 184 private String toPackageName(String name) { 185 int i = name.lastIndexOf('/'); 186 return i > 0 ? name.replace('/', '.').substring(0, i) : ""; 187 } 188 189 public Optional<Module> findModule(String name) { 190 Objects.requireNonNull(name); 191 Module m = nameToModule.get(name); 192 return m!= null ? Optional.of(m) : Optional.empty(); 193 194 } 195 196 public Optional<ModuleDescriptor> findModuleDescriptor(String name) { 197 Objects.requireNonNull(name); 198 Module m = nameToModule.get(name); 199 return m!= null ? Optional.of(m.descriptor()) : Optional.empty(); 200 } 201 202 boolean isSystem(Module m) { 203 return system.find(m.name()).isPresent(); 204 } 205 206 boolean isValidToken(String name) { 207 return ALL_MODULE_PATH.equals(name) || ALL_DEFAULT.equals(name); 208 } 209 210 /** 211 * Returns the modules that the given module can read 212 */ 213 public Stream<Module> reads(Module module) { 214 return configuration.findModule(module.name()).get() 215 .reads().stream() 216 .map(ResolvedModule::name) 217 .map(nameToModule::get); 218 } 219 220 /** 221 * Returns the list of packages that split between resolved module and 222 * unnamed module 223 */ 224 public Map<String, Set<String>> splitPackages() { 225 Set<String> splitPkgs = packageToModule.keySet().stream() 226 .filter(packageToUnnamedModule::containsKey) 227 .collect(toSet()); 228 if (splitPkgs.isEmpty()) 229 return Collections.emptyMap(); 230 231 return splitPkgs.stream().collect(toMap(Function.identity(), (pn) -> { 232 Set<String> sources = new LinkedHashSet<>(); 233 sources.add(packageToModule.get(pn).getModule().location().toString()); 234 packageToUnnamedModule.get(pn).stream() 235 .map(Archive::getPathName) 236 .forEach(sources::add); 237 return sources; 238 })); 239 } 240 241 /** 242 * Returns an optional archive containing the given Location 243 */ 244 public Optional<Archive> findClass(Dependency.Location location) { 245 String name = location.getName(); 246 int i = name.lastIndexOf('/'); 247 String pn = i > 0 ? name.substring(0, i).replace('/', '.') : ""; 248 Archive archive = packageToModule.get(pn); 249 if (archive != null) { 250 return archive.contains(name + ".class") 251 ? Optional.of(archive) 252 : Optional.empty(); 253 } 254 255 if (packageToUnnamedModule.containsKey(pn)) { 256 return packageToUnnamedModule.get(pn).stream() 257 .filter(a -> a.contains(name + ".class")) 258 .findFirst(); 259 } 260 return Optional.empty(); 261 } 262 263 /** 264 * Returns the list of Modules that can be found in the specified 265 * module paths. 266 */ 267 public Map<String, Module> getModules() { 268 return nameToModule; 269 } 270 271 public Stream<Module> resolve(Set<String> roots) { 272 if (roots.isEmpty()) { 273 return nameToModule.values().stream(); 274 } else { 275 return Configuration.empty() 276 .resolveRequires(finder, ModuleFinder.of(), roots) 277 .modules().stream() 278 .map(ResolvedModule::name) 279 .map(nameToModule::get); 280 } 281 } 282 283 public List<Archive> classPathArchives() { 284 return classpathArchives; 285 } 286 287 public List<Archive> initialArchives() { 288 return initialArchives; 289 } 290 291 public Set<Module> rootModules() { 292 return rootModules; 293 } 294 295 public Module toModule(ModuleReference mref) { 296 try { 297 String mn = mref.descriptor().name(); 298 URI location = mref.location().orElseThrow(FileNotFoundException::new); 299 ModuleDescriptor md = mref.descriptor(); 300 Module.Builder builder = new Module.Builder(md, system.find(mn).isPresent()); 301 302 final ClassFileReader reader; 303 if (location.getScheme().equals("jrt")) { 304 reader = system.getClassReader(mn); 305 } else { 306 reader = ClassFileReader.newInstance(Paths.get(location), version); 307 } 308 309 builder.classes(reader); 310 builder.location(location); 311 312 return builder.build(); 313 } catch (IOException e) { 314 throw new UncheckedIOException(e); 315 } 316 } 317 318 public Runtime.Version getVersion() { 319 return version; 320 } 321 322 /* 323 * Close all archives e.g. JarFile 324 */ 325 @Override 326 public void close() throws IOException { 327 for (Archive archive : initialArchives) 328 archive.close(); 329 for (Archive archive : classpathArchives) 330 archive.close(); 331 for (Module module : nameToModule.values()) 332 module.close(); 333 } 334 335 static class SystemModuleFinder implements ModuleFinder { 336 private static final String JAVA_HOME = System.getProperty("java.home"); 337 private static final String JAVA_SE = "java.se"; 338 339 private final FileSystem fileSystem; 340 private final Path root; 341 private final Map<String, ModuleReference> systemModules; 342 343 SystemModuleFinder() { 344 if (Files.isRegularFile(Paths.get(JAVA_HOME, "lib", "modules"))) { 345 // jrt file system 346 this.fileSystem = FileSystems.getFileSystem(URI.create("jrt:/")); 347 this.root = fileSystem.getPath("/modules"); 348 this.systemModules = walk(root); 349 } else { 350 // exploded image 351 this.fileSystem = FileSystems.getDefault(); 352 root = Paths.get(JAVA_HOME, "modules"); 353 this.systemModules = ModuleFinder.ofSystem().findAll().stream() 354 .collect(toMap(mref -> mref.descriptor().name(), Function.identity())); 355 } 356 } 357 358 SystemModuleFinder(String javaHome) throws IOException { 359 if (javaHome == null) { 360 // --system none 361 this.fileSystem = null; 362 this.root = null; 363 this.systemModules = Collections.emptyMap(); 364 } else { 365 if (Files.isRegularFile(Paths.get(javaHome, "lib", "modules"))) 366 throw new IllegalArgumentException("Invalid java.home: " + javaHome); 367 368 // alternate java.home 369 Map<String, String> env = new HashMap<>(); 370 env.put("java.home", javaHome); 371 // a remote run-time image 372 this.fileSystem = FileSystems.newFileSystem(URI.create("jrt:/"), env); 373 this.root = fileSystem.getPath("/modules"); 374 this.systemModules = walk(root); 375 } 376 } 377 378 private Map<String, ModuleReference> walk(Path root) { 379 try (Stream<Path> stream = Files.walk(root, 1)) { 380 return stream.filter(path -> !path.equals(root)) 381 .map(this::toModuleReference) 382 .collect(toMap(mref -> mref.descriptor().name(), 383 Function.identity())); 384 } catch (IOException e) { 385 throw new UncheckedIOException(e); 386 } 387 } 388 389 private ModuleReference toModuleReference(Path path) { 390 Path minfo = path.resolve(MODULE_INFO); 391 try (InputStream in = Files.newInputStream(minfo); 392 BufferedInputStream bin = new BufferedInputStream(in)) { 393 394 ModuleDescriptor descriptor = dropHashes(ModuleDescriptor.read(bin)); 395 String mn = descriptor.name(); 396 URI uri = URI.create("jrt:/" + path.getFileName().toString()); 397 Supplier<ModuleReader> readerSupplier = new Supplier<>() { 398 @Override 399 public ModuleReader get() { 400 return new ModuleReader() { 401 @Override 402 public Optional<URI> find(String name) throws IOException { 403 return name.equals(mn) 404 ? Optional.of(uri) : Optional.empty(); 405 } 406 407 @Override 408 public Stream<String> list() { 409 return Stream.empty(); 410 } 411 412 @Override 413 public void close() { 414 } 415 }; 416 } 417 }; 418 419 return new ModuleReference(descriptor, uri) { 420 @Override 421 public ModuleReader open() { 422 return readerSupplier.get(); 423 } 424 }; 425 } catch (IOException e) { 426 throw new UncheckedIOException(e); 427 } 428 } 429 430 private ModuleDescriptor dropHashes(ModuleDescriptor md) { 431 ModuleDescriptor.Builder builder = ModuleDescriptor.module(md.name()); 432 md.requires().forEach(builder::requires); 433 md.exports().forEach(builder::exports); 434 md.opens().forEach(builder::opens); 435 md.provides().stream().forEach(builder::provides); 436 md.uses().stream().forEach(builder::uses); 437 438 Set<String> concealed = new HashSet<>(md.packages()); 439 md.exports().stream().map(Exports::source).forEach(concealed::remove); 440 md.opens().stream().map(Opens::source).forEach(concealed::remove); 441 concealed.forEach(builder::contains); 442 443 return builder.build(); 444 } 445 446 @Override 447 public Set<ModuleReference> findAll() { 448 return systemModules.values().stream().collect(toSet()); 449 } 450 451 @Override 452 public Optional<ModuleReference> find(String mn) { 453 return systemModules.containsKey(mn) 454 ? Optional.of(systemModules.get(mn)) : Optional.empty(); 455 } 456 457 public Stream<String> moduleNames() { 458 return systemModules.values().stream() 459 .map(mref -> mref.descriptor().name()); 460 } 461 462 public ClassFileReader getClassReader(String modulename) throws IOException { 463 Path mp = root.resolve(modulename); 464 if (Files.exists(mp) && Files.isDirectory(mp)) { 465 return ClassFileReader.newInstance(fileSystem, mp); 466 } else { 467 throw new FileNotFoundException(mp.toString()); 468 } 469 } 470 471 public Set<String> defaultSystemRoots() { 472 Set<String> roots = new HashSet<>(); 473 boolean hasJava = false; 474 if (systemModules.containsKey(JAVA_SE)) { 475 // java.se is a system module 476 hasJava = true; 477 roots.add(JAVA_SE); 478 } 479 480 for (ModuleReference mref : systemModules.values()) { 481 String mn = mref.descriptor().name(); 482 if (hasJava && mn.startsWith("java.")) 483 continue; 484 485 // add as root if observable and exports at least one package 486 ModuleDescriptor descriptor = mref.descriptor(); 487 for (ModuleDescriptor.Exports e : descriptor.exports()) { 488 if (!e.isQualified()) { 489 roots.add(mn); 490 break; 491 } 492 } 493 } 494 return roots; 495 } 496 } 497 498 public static class Builder { 499 500 final SystemModuleFinder systemModulePath; 501 final Set<String> rootModules = new HashSet<>(); 502 final List<Archive> initialArchives = new ArrayList<>(); 503 final List<Path> paths = new ArrayList<>(); 504 final List<Path> classPaths = new ArrayList<>(); 505 506 ModuleFinder upgradeModulePath; 507 ModuleFinder appModulePath; 508 boolean addAllApplicationModules; 509 boolean addAllDefaultModules; 510 boolean addAllSystemModules; 511 Runtime.Version version; 512 513 public Builder() { 514 this.systemModulePath = new SystemModuleFinder(); 515 } 516 517 public Builder(String javaHome) throws IOException { 518 this.systemModulePath = SystemModuleFinder.JAVA_HOME.equals(javaHome) 519 ? new SystemModuleFinder() 520 : new SystemModuleFinder(javaHome); 521 } 522 523 public Builder upgradeModulePath(String upgradeModulePath) { 524 this.upgradeModulePath = createModulePathFinder(upgradeModulePath); 525 return this; 526 } 527 528 public Builder appModulePath(String modulePath) { 529 this.appModulePath = createModulePathFinder(modulePath); 530 return this; 531 } 532 533 public Builder addmods(Set<String> addmods) { 534 for (String mn : addmods) { 535 switch (mn) { 536 case ALL_MODULE_PATH: 537 this.addAllApplicationModules = true; 538 break; 539 case ALL_DEFAULT: 540 this.addAllDefaultModules = true; 541 break; 542 default: 543 this.rootModules.add(mn); 544 } 545 } 546 return this; 547 } 548 549 /* 550 * This method is for --check option to find all target modules specified 551 * in qualified exports. 552 * 553 * Include all system modules and modules found on modulepath 554 */ 555 public Builder allModules() { 556 this.addAllSystemModules = true; 557 this.addAllApplicationModules = true; 558 return this; 559 } 560 561 public Builder multiRelease(Runtime.Version version) { 562 this.version = version; 563 return this; 564 } 565 566 public Builder addRoot(Path path) { 567 Archive archive = Archive.getInstance(path, version); 568 if (archive.contains(MODULE_INFO)) { 569 paths.add(path); 570 } else { 571 initialArchives.add(archive); 572 } 573 return this; 574 } 575 576 public Builder addClassPath(String classPath) { 577 this.classPaths.addAll(getClassPaths(classPath)); 578 return this; 579 } 580 581 public JdepsConfiguration build() throws IOException { 582 ModuleFinder finder = systemModulePath; 583 if (upgradeModulePath != null) { 584 finder = ModuleFinder.compose(upgradeModulePath, systemModulePath); 585 } 586 if (appModulePath != null) { 587 finder = ModuleFinder.compose(finder, appModulePath); 588 } 589 if (!paths.isEmpty()) { 590 ModuleFinder otherModulePath = ModuleFinder.of(paths.toArray(new Path[0])); 591 592 finder = ModuleFinder.compose(finder, otherModulePath); 593 // add modules specified on command-line (convenience) as root set 594 otherModulePath.findAll().stream() 595 .map(mref -> mref.descriptor().name()) 596 .forEach(rootModules::add); 597 } 598 if (addAllApplicationModules && appModulePath != null) { 599 appModulePath.findAll().stream() 600 .map(mref -> mref.descriptor().name()) 601 .forEach(rootModules::add); 602 } 603 604 return new JdepsConfiguration(systemModulePath, 605 finder, 606 rootModules, 607 classPaths, 608 initialArchives, 609 addAllDefaultModules, 610 addAllSystemModules, 611 version); 612 } 613 614 private static ModuleFinder createModulePathFinder(String mpaths) { 615 if (mpaths == null) { 616 return null; 617 } else { 618 String[] dirs = mpaths.split(File.pathSeparator); 619 Path[] paths = new Path[dirs.length]; 620 int i = 0; 621 for (String dir : dirs) { 622 paths[i++] = Paths.get(dir); 623 } 624 return ModuleFinder.of(paths); 625 } 626 } 627 628 /* 629 * Returns the list of Archive specified in cpaths and not included 630 * initialArchives 631 */ 632 private List<Path> getClassPaths(String cpaths) { 633 if (cpaths.isEmpty()) { 634 return Collections.emptyList(); 635 } 636 List<Path> paths = new ArrayList<>(); 637 for (String p : cpaths.split(File.pathSeparator)) { 638 if (p.length() > 0) { 639 // wildcard to parse all JAR files e.g. -classpath dir/* 640 int i = p.lastIndexOf(".*"); 641 if (i > 0) { 642 Path dir = Paths.get(p.substring(0, i)); 643 try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.jar")) { 644 for (Path entry : stream) { 645 paths.add(entry); 646 } 647 } catch (IOException e) { 648 throw new UncheckedIOException(e); 649 } 650 } else { 651 paths.add(Paths.get(p)); 652 } 653 } 654 } 655 return paths; 656 } 657 } 658 659 }