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