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