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