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 com.sun.tools.jdeps.Analyzer.Type; 29 import static com.sun.tools.jdeps.Analyzer.Type.*; 30 import static com.sun.tools.jdeps.JdepsWriter.*; 31 import static java.util.stream.Collectors.*; 32 33 import java.io.IOException; 34 import java.io.PrintWriter; 35 import java.lang.module.ResolutionException; 36 import java.nio.file.Files; 37 import java.nio.file.Path; 38 import java.nio.file.Paths; 39 import java.text.MessageFormat; 40 import java.util.*; 41 import java.util.jar.JarFile; 42 import java.util.regex.Pattern; 43 44 /** 45 * Implementation for the jdeps tool for static class dependency analysis. 46 */ 47 class JdepsTask { 48 static interface BadArguments { 49 String getKey(); 50 Object[] getArgs(); 51 boolean showUsage(); 52 } 53 static class BadArgs extends Exception implements BadArguments { 54 static final long serialVersionUID = 8765093759964640721L; 55 BadArgs(String key, Object... args) { 56 super(JdepsTask.getMessage(key, args)); 57 this.key = key; 58 this.args = args; 59 } 60 61 BadArgs showUsage(boolean b) { 62 showUsage = b; 63 return this; 64 } 65 final String key; 66 final Object[] args; 67 boolean showUsage; 68 69 @Override 70 public String getKey() { 71 return key; 72 } 73 74 @Override 75 public Object[] getArgs() { 76 return args; 77 } 78 79 @Override 80 public boolean showUsage() { 81 return showUsage; 82 } 83 } 84 85 static class UncheckedBadArgs extends RuntimeException implements BadArguments { 86 static final long serialVersionUID = -1L; 87 final BadArgs cause; 88 UncheckedBadArgs(BadArgs cause) { 89 super(cause); 90 this.cause = cause; 91 } 92 @Override 93 public String getKey() { 94 return cause.key; 95 } 96 97 @Override 98 public Object[] getArgs() { 99 return cause.args; 100 } 101 102 @Override 103 public boolean showUsage() { 104 return cause.showUsage; 105 } 106 } 107 108 static abstract class Option { 109 Option(boolean hasArg, String... aliases) { 110 this.hasArg = hasArg; 111 this.aliases = aliases; 112 } 113 114 Option(boolean hasArg, CommandOption cmd) { 115 this(hasArg, cmd.names()); 116 } 117 118 boolean isHidden() { 119 return false; 120 } 121 122 boolean matches(String opt) { 123 for (String a : aliases) { 124 if (a.equals(opt)) 125 return true; 126 if (hasArg && opt.startsWith(a + "=")) 127 return true; 128 } 129 return false; 130 } 131 132 boolean ignoreRest() { 133 return false; 134 } 135 136 abstract void process(JdepsTask task, String opt, String arg) throws BadArgs; 137 final boolean hasArg; 138 final String[] aliases; 139 } 140 141 static abstract class HiddenOption extends Option { 142 HiddenOption(boolean hasArg, String... aliases) { 143 super(hasArg, aliases); 144 } 145 146 boolean isHidden() { 147 return true; 148 } 149 } 150 151 enum CommandOption { 152 ANALYZE_DEPS(""), 153 GENERATE_DOT_FILE("-dotoutput", "--dot-output"), 154 GENERATE_MODULE_INFO("--generate-module-info"), 155 GENERATE_OPEN_MODULE("--generate-open-module"), 156 LIST_DEPS("--list-deps"), 157 LIST_REDUCED_DEPS("--list-reduced-deps"), 158 PRINT_MODULE_DEPS("--print-module-deps"), 159 CHECK_MODULES("--check"); 160 161 private final String[] names; 162 CommandOption(String... names) { 163 this.names = names; 164 } 165 166 String[] names() { 167 return names; 168 } 169 170 @Override 171 public String toString() { 172 return names[0]; 173 } 174 } 175 176 static Option[] recognizedOptions = { 177 new Option(false, "-h", "-?", "-help", "--help") { 178 void process(JdepsTask task, String opt, String arg) { 179 task.options.help = true; 180 } 181 }, 182 new Option(true, CommandOption.GENERATE_DOT_FILE) { 183 void process(JdepsTask task, String opt, String arg) throws BadArgs { 184 if (task.command != null) { 185 throw new BadArgs("err.command.set", task.command, opt); 186 } 187 task.command = task.genDotFile(Paths.get(arg)); 188 } 189 }, 190 new Option(false, "-s", "-summary") { 191 void process(JdepsTask task, String opt, String arg) { 192 task.options.showSummary = true; 193 } 194 }, 195 new Option(false, "-v", "-verbose", 196 "-verbose:module", 197 "-verbose:package", 198 "-verbose:class") { 199 void process(JdepsTask task, String opt, String arg) throws BadArgs { 200 switch (opt) { 201 case "-v": 202 case "-verbose": 203 task.options.verbose = VERBOSE; 204 task.options.filterSameArchive = false; 205 task.options.filterSamePackage = false; 206 break; 207 case "-verbose:module": 208 task.options.verbose = MODULE; 209 break; 210 case "-verbose:package": 211 task.options.verbose = PACKAGE; 212 break; 213 case "-verbose:class": 214 task.options.verbose = CLASS; 215 break; 216 default: 217 throw new BadArgs("err.invalid.arg.for.option", opt); 218 } 219 } 220 }, 221 new Option(false, "-apionly", "--api-only") { 222 void process(JdepsTask task, String opt, String arg) { 223 task.options.apiOnly = true; 224 } 225 }, 226 227 new Option(false, "-jdkinternals", "--jdk-internals") { 228 void process(JdepsTask task, String opt, String arg) { 229 task.options.findJDKInternals = true; 230 if (task.options.includePattern == null) { 231 task.options.includePattern = Pattern.compile(".*"); 232 } 233 } 234 }, 235 236 // ---- paths option ---- 237 new Option(true, "-cp", "-classpath", "--class-path") { 238 void process(JdepsTask task, String opt, String arg) { 239 task.options.classpath = arg; 240 } 241 }, 242 new Option(true, "--module-path") { 243 void process(JdepsTask task, String opt, String arg) throws BadArgs { 244 task.options.modulePath = arg; 245 } 246 }, 247 new Option(true, "--upgrade-module-path") { 248 void process(JdepsTask task, String opt, String arg) throws BadArgs { 249 task.options.upgradeModulePath = arg; 250 } 251 }, 252 new Option(true, "--system") { 253 void process(JdepsTask task, String opt, String arg) throws BadArgs { 254 if (arg.equals("none")) { 255 task.options.systemModulePath = null; 256 } else { 257 Path path = Paths.get(arg); 258 if (Files.isRegularFile(path.resolve("lib").resolve("modules"))) 259 task.options.systemModulePath = arg; 260 else 261 throw new BadArgs("err.invalid.path", arg); 262 } 263 } 264 }, 265 new Option(true, "--add-modules") { 266 void process(JdepsTask task, String opt, String arg) throws BadArgs { 267 Set<String> mods = Set.of(arg.split(",")); 268 task.options.addmods.addAll(mods); 269 } 270 }, 271 new Option(true, "--multi-release") { 272 void process(JdepsTask task, String opt, String arg) throws BadArgs { 273 if (arg.equalsIgnoreCase("base")) { 274 task.options.multiRelease = JarFile.baseVersion(); 275 } else { 276 try { 277 int v = Integer.parseInt(arg); 278 if (v < 9) { 279 throw new BadArgs("err.invalid.arg.for.option", arg); 280 } 281 } catch (NumberFormatException x) { 282 throw new BadArgs("err.invalid.arg.for.option", arg); 283 } 284 task.options.multiRelease = Runtime.Version.parse(arg); 285 } 286 } 287 }, 288 new Option(false, "-q", "-quiet") { 289 void process(JdepsTask task, String opt, String arg) { 290 task.options.nowarning = true; 291 } 292 }, 293 new Option(false, "-version", "--version") { 294 void process(JdepsTask task, String opt, String arg) { 295 task.options.version = true; 296 } 297 }, 298 299 // ---- module-specific options ---- 300 301 new Option(true, "-m", "--module") { 302 void process(JdepsTask task, String opt, String arg) throws BadArgs { 303 if (!task.options.rootModules.isEmpty()) { 304 throw new BadArgs("err.option.already.specified", opt); 305 } 306 task.options.rootModules.add(arg); 307 task.options.addmods.add(arg); 308 } 309 }, 310 new Option(true, CommandOption.GENERATE_MODULE_INFO) { 311 void process(JdepsTask task, String opt, String arg) throws BadArgs { 312 if (task.command != null) { 313 throw new BadArgs("err.command.set", task.command, opt); 314 } 315 task.command = task.genModuleInfo(Paths.get(arg), false); 316 } 317 }, 318 new Option(true, CommandOption.GENERATE_OPEN_MODULE) { 319 void process(JdepsTask task, String opt, String arg) throws BadArgs { 320 if (task.command != null) { 321 throw new BadArgs("err.command.set", task.command, opt); 322 } 323 task.command = task.genModuleInfo(Paths.get(arg), true); 324 } 325 }, 326 new Option(true, CommandOption.CHECK_MODULES) { 327 void process(JdepsTask task, String opt, String arg) throws BadArgs { 328 if (task.command != null) { 329 throw new BadArgs("err.command.set", task.command, opt); 330 } 331 Set<String> mods = Set.of(arg.split(",")); 332 task.options.addmods.addAll(mods); 333 task.command = task.checkModuleDeps(mods); 334 } 335 }, 336 new Option(false, CommandOption.LIST_DEPS) { 337 void process(JdepsTask task, String opt, String arg) throws BadArgs { 338 if (task.command != null) { 339 throw new BadArgs("err.command.set", task.command, opt); 340 } 341 task.command = task.listModuleDeps(CommandOption.LIST_DEPS); 342 } 343 }, 344 new Option(false, CommandOption.LIST_REDUCED_DEPS) { 345 void process(JdepsTask task, String opt, String arg) throws BadArgs { 346 if (task.command != null) { 347 throw new BadArgs("err.command.set", task.command, opt); 348 } 349 task.command = task.listModuleDeps(CommandOption.LIST_REDUCED_DEPS); 350 } 351 }, 352 new Option(false, CommandOption.PRINT_MODULE_DEPS) { 353 void process(JdepsTask task, String opt, String arg) throws BadArgs { 354 if (task.command != null) { 355 throw new BadArgs("err.command.set", task.command, opt); 356 } 357 task.command = task.listModuleDeps(CommandOption.PRINT_MODULE_DEPS); 358 } 359 }, 360 new Option(false, "--ignore-missing-deps") { 361 void process(JdepsTask task, String opt, String arg) { 362 task.options.ignoreMissingDeps = true; 363 } 364 }, 365 366 // ---- Target filtering options ---- 367 new Option(true, "-p", "-package", "--package") { 368 void process(JdepsTask task, String opt, String arg) { 369 task.options.packageNames.add(arg); 370 } 371 }, 372 new Option(true, "-e", "-regex", "--regex") { 373 void process(JdepsTask task, String opt, String arg) { 374 task.options.regex = Pattern.compile(arg); 375 } 376 }, 377 new Option(true, "--require") { 378 void process(JdepsTask task, String opt, String arg) { 379 task.options.requires.add(arg); 380 task.options.addmods.add(arg); 381 } 382 }, 383 new Option(true, "-f", "-filter") { 384 void process(JdepsTask task, String opt, String arg) { 385 task.options.filterRegex = Pattern.compile(arg); 386 } 387 }, 388 new Option(false, "-filter:package", 389 "-filter:archive", "-filter:module", 390 "-filter:none") { 391 void process(JdepsTask task, String opt, String arg) { 392 switch (opt) { 393 case "-filter:package": 394 task.options.filterSamePackage = true; 395 task.options.filterSameArchive = false; 396 break; 397 case "-filter:archive": 398 case "-filter:module": 399 task.options.filterSameArchive = true; 400 task.options.filterSamePackage = false; 401 break; 402 case "-filter:none": 403 task.options.filterSameArchive = false; 404 task.options.filterSamePackage = false; 405 break; 406 } 407 } 408 }, 409 new Option(false, "--missing-deps") { 410 void process(JdepsTask task, String opt, String arg) { 411 task.options.findMissingDeps = true; 412 } 413 }, 414 415 // ---- Source filtering options ---- 416 new Option(true, "-include") { 417 void process(JdepsTask task, String opt, String arg) throws BadArgs { 418 task.options.includePattern = Pattern.compile(arg); 419 } 420 }, 421 422 new Option(false, "-P", "-profile") { 423 void process(JdepsTask task, String opt, String arg) throws BadArgs { 424 task.options.showProfile = true; 425 } 426 }, 427 428 new Option(false, "-R", "-recursive") { 429 void process(JdepsTask task, String opt, String arg) { 430 task.options.depth = 0; 431 // turn off filtering 432 task.options.filterSameArchive = false; 433 task.options.filterSamePackage = false; 434 } 435 }, 436 437 new Option(false, "-I", "--inverse") { 438 void process(JdepsTask task, String opt, String arg) { 439 task.options.inverse = true; 440 // equivalent to the inverse of compile-time view analysis 441 task.options.compileTimeView = true; 442 task.options.filterSamePackage = true; 443 task.options.filterSameArchive = true; 444 } 445 }, 446 447 new Option(false, "--compile-time") { 448 void process(JdepsTask task, String opt, String arg) { 449 task.options.compileTimeView = true; 450 task.options.filterSamePackage = true; 451 task.options.filterSameArchive = true; 452 task.options.depth = 0; 453 } 454 }, 455 456 new HiddenOption(false, "-fullversion") { 457 void process(JdepsTask task, String opt, String arg) { 458 task.options.fullVersion = true; 459 } 460 }, 461 new HiddenOption(false, "-showlabel") { 462 void process(JdepsTask task, String opt, String arg) { 463 task.options.showLabel = true; 464 } 465 }, 466 new HiddenOption(false, "--hide-show-module") { 467 void process(JdepsTask task, String opt, String arg) { 468 task.options.showModule = false; 469 } 470 }, 471 new HiddenOption(true, "-depth") { 472 void process(JdepsTask task, String opt, String arg) throws BadArgs { 473 try { 474 task.options.depth = Integer.parseInt(arg); 475 } catch (NumberFormatException e) { 476 throw new BadArgs("err.invalid.arg.for.option", opt); 477 } 478 } 479 }, 480 }; 481 482 private static final String PROGNAME = "jdeps"; 483 private final Options options = new Options(); 484 private final List<String> inputArgs = new ArrayList<>(); 485 486 private Command command; 487 private PrintWriter log; 488 void setLog(PrintWriter out) { 489 log = out; 490 } 491 492 /** 493 * Result codes. 494 */ 495 static final int EXIT_OK = 0, // Completed with no errors. 496 EXIT_ERROR = 1, // Completed but reported errors. 497 EXIT_CMDERR = 2, // Bad command-line arguments 498 EXIT_SYSERR = 3, // System error or resource exhaustion. 499 EXIT_ABNORMAL = 4; // terminated abnormally 500 501 int run(String... args) { 502 if (log == null) { 503 log = new PrintWriter(System.out); 504 } 505 try { 506 handleOptions(args); 507 if (options.help) { 508 showHelp(); 509 } 510 if (options.version || options.fullVersion) { 511 showVersion(options.fullVersion); 512 } 513 if (options.help || options.version || options.fullVersion) { 514 return EXIT_OK; 515 } 516 if (options.numFilters() > 1) { 517 reportError("err.invalid.filters"); 518 return EXIT_CMDERR; 519 } 520 521 // default command to analyze dependences 522 if (command == null) { 523 command = analyzeDeps(); 524 } 525 if (!command.checkOptions()) { 526 return EXIT_CMDERR; 527 } 528 529 boolean ok = run(); 530 return ok ? EXIT_OK : EXIT_ERROR; 531 532 } catch (BadArgs|UncheckedBadArgs e) { 533 reportError(e.getKey(), e.getArgs()); 534 if (e.showUsage()) { 535 log.println(getMessage("main.usage.summary", PROGNAME)); 536 } 537 return EXIT_CMDERR; 538 } catch (ResolutionException e) { 539 reportError("err.exception.message", e.getMessage()); 540 return EXIT_CMDERR; 541 } catch (IOException e) { 542 e.printStackTrace(); 543 return EXIT_CMDERR; 544 } catch (MultiReleaseException e) { 545 reportError(e.getKey(), e.getParams()); 546 return EXIT_CMDERR; // could be EXIT_ABNORMAL sometimes 547 } finally { 548 log.flush(); 549 } 550 } 551 552 boolean run() throws IOException { 553 try (JdepsConfiguration config = buildConfig()) { 554 if (!options.nowarning) { 555 // detect split packages 556 config.splitPackages().entrySet() 557 .stream() 558 .sorted(Map.Entry.comparingByKey()) 559 .forEach(e -> warning("warn.split.package", 560 e.getKey(), 561 e.getValue().stream().collect(joining(" ")))); 562 } 563 564 // check if any module specified in --add-modules, --require, and -m is missing 565 options.addmods.stream() 566 .filter(mn -> !JdepsConfiguration.isToken(mn)) 567 .forEach(mn -> config.findModule(mn).orElseThrow(() -> 568 new UncheckedBadArgs(new BadArgs("err.module.not.found", mn)))); 569 570 return command.run(config); 571 } 572 } 573 574 private JdepsConfiguration buildConfig() throws IOException { 575 JdepsConfiguration.Builder builder = 576 new JdepsConfiguration.Builder(options.systemModulePath); 577 578 builder.upgradeModulePath(options.upgradeModulePath) 579 .appModulePath(options.modulePath) 580 .addmods(options.addmods) 581 .addmods(command.addModules()); 582 583 if (options.classpath != null) 584 builder.addClassPath(options.classpath); 585 586 if (options.multiRelease != null) 587 builder.multiRelease(options.multiRelease); 588 589 // build the root set of archives to be analyzed 590 for (String s : inputArgs) { 591 Path p = Paths.get(s); 592 if (Files.exists(p)) { 593 builder.addRoot(p); 594 } else { 595 warning("warn.invalid.arg", s); 596 } 597 } 598 599 return builder.build(); 600 } 601 602 // ---- factory methods to create a Command 603 604 private AnalyzeDeps analyzeDeps() throws BadArgs { 605 return options.inverse ? new InverseAnalyzeDeps() 606 : new AnalyzeDeps(); 607 } 608 609 private GenDotFile genDotFile(Path dir) throws BadArgs { 610 if (Files.exists(dir) && (!Files.isDirectory(dir) || !Files.isWritable(dir))) { 611 throw new BadArgs("err.invalid.path", dir.toString()); 612 } 613 return new GenDotFile(dir); 614 } 615 616 private GenModuleInfo genModuleInfo(Path dir, boolean openModule) throws BadArgs { 617 if (Files.exists(dir) && (!Files.isDirectory(dir) || !Files.isWritable(dir))) { 618 throw new BadArgs("err.invalid.path", dir.toString()); 619 } 620 return new GenModuleInfo(dir, openModule); 621 } 622 623 private ListModuleDeps listModuleDeps(CommandOption option) throws BadArgs { 624 // no need to record the dependences on the same archive or same package 625 options.filterSameArchive = true; 626 options.filterSamePackage = true; 627 // do transitive dependence analysis 628 options.depth = 0; 629 switch (option) { 630 case LIST_DEPS: 631 return new ListModuleDeps(option, true, false); 632 case LIST_REDUCED_DEPS: 633 return new ListModuleDeps(option, true, true); 634 case PRINT_MODULE_DEPS: 635 return new ListModuleDeps(option, false, true, ","); 636 default: 637 throw new IllegalArgumentException(option.toString()); 638 } 639 } 640 641 private CheckModuleDeps checkModuleDeps(Set<String> mods) throws BadArgs { 642 return new CheckModuleDeps(mods); 643 } 644 645 abstract class Command { 646 final CommandOption option; 647 protected Command(CommandOption option) { 648 this.option = option; 649 } 650 651 /** 652 * Returns true if the command-line options are all valid; 653 * otherwise, returns false. 654 */ 655 abstract boolean checkOptions(); 656 657 /** 658 * Do analysis 659 */ 660 abstract boolean run(JdepsConfiguration config) throws IOException; 661 662 /** 663 * Includes all modules on system module path and application module path 664 * 665 * When a named module is analyzed, it will analyze the dependences 666 * only. The method should be overridden when this command should 667 * analyze all modules instead. 668 */ 669 Set<String> addModules() { 670 return Set.of(); 671 } 672 673 @Override 674 public String toString() { 675 return option.toString(); 676 } 677 } 678 679 680 /** 681 * Analyze dependences 682 */ 683 class AnalyzeDeps extends Command { 684 JdepsWriter writer; 685 AnalyzeDeps() { 686 this(CommandOption.ANALYZE_DEPS); 687 } 688 689 AnalyzeDeps(CommandOption option) { 690 super(option); 691 } 692 693 @Override 694 boolean checkOptions() { 695 if (options.findJDKInternals || options.findMissingDeps) { 696 // cannot set any filter, -verbose and -summary option 697 if (options.showSummary || options.verbose != null) { 698 reportError("err.invalid.options", "-summary or -verbose", 699 options.findJDKInternals ? "-jdkinternals" : "--missing-deps"); 700 return false; 701 } 702 if (options.hasFilter()) { 703 reportError("err.invalid.options", "--package, --regex, --require", 704 options.findJDKInternals ? "-jdkinternals" : "--missing-deps"); 705 return false; 706 } 707 } 708 if (options.showSummary) { 709 // -summary cannot use with -verbose option 710 if (options.verbose != null) { 711 reportError("err.invalid.options", "-v, -verbose", "-s, -summary"); 712 return false; 713 } 714 } 715 716 if (!inputArgs.isEmpty() && !options.rootModules.isEmpty()) { 717 reportError("err.invalid.arg.for.option", "-m"); 718 } 719 if (inputArgs.isEmpty() && !options.hasSourcePath()) { 720 showHelp(); 721 return false; 722 } 723 return true; 724 } 725 726 /* 727 * Default is to show package-level dependencies 728 */ 729 Type getAnalyzerType() { 730 if (options.showSummary) 731 return Type.SUMMARY; 732 733 if (options.findJDKInternals || options.findMissingDeps) 734 return Type.CLASS; 735 736 // default to package-level verbose 737 return options.verbose != null ? options.verbose : PACKAGE; 738 } 739 740 @Override 741 boolean run(JdepsConfiguration config) throws IOException { 742 Type type = getAnalyzerType(); 743 // default to package-level verbose 744 JdepsWriter writer = new SimpleWriter(log, 745 type, 746 options.showProfile, 747 options.showModule); 748 749 return run(config, writer, type); 750 } 751 752 boolean run(JdepsConfiguration config, JdepsWriter writer, Type type) 753 throws IOException 754 { 755 // analyze the dependencies 756 DepsAnalyzer analyzer = new DepsAnalyzer(config, 757 dependencyFilter(config), 758 writer, 759 type, 760 options.apiOnly); 761 762 boolean ok = analyzer.run(options.compileTimeView, options.depth); 763 764 // print skipped entries, if any 765 if (!options.nowarning) { 766 analyzer.archives() 767 .forEach(archive -> archive.reader() 768 .skippedEntries().stream() 769 .forEach(name -> warning("warn.skipped.entry", name))); 770 } 771 772 if (options.findJDKInternals && !options.nowarning) { 773 Map<String, String> jdkInternals = new TreeMap<>(); 774 Set<String> deps = analyzer.dependences(); 775 // find the ones with replacement 776 deps.forEach(cn -> replacementFor(cn).ifPresent( 777 repl -> jdkInternals.put(cn, repl)) 778 ); 779 780 if (!deps.isEmpty()) { 781 log.println(); 782 warning("warn.replace.useJDKInternals", getMessage("jdeps.wiki.url")); 783 } 784 785 if (!jdkInternals.isEmpty()) { 786 log.println(); 787 String internalApiTitle = getMessage("internal.api.column.header"); 788 String replacementApiTitle = getMessage("public.api.replacement.column.header"); 789 log.format("%-40s %s%n", internalApiTitle, replacementApiTitle); 790 log.format("%-40s %s%n", 791 internalApiTitle.replaceAll(".", "-"), 792 replacementApiTitle.replaceAll(".", "-")); 793 jdkInternals.entrySet().stream() 794 .forEach(e -> { 795 String key = e.getKey(); 796 String[] lines = e.getValue().split("\\n"); 797 for (String s : lines) { 798 log.format("%-40s %s%n", key, s); 799 key = ""; 800 } 801 }); 802 } 803 } 804 return ok; 805 } 806 } 807 808 809 class InverseAnalyzeDeps extends AnalyzeDeps { 810 InverseAnalyzeDeps() { 811 } 812 813 @Override 814 boolean checkOptions() { 815 if (options.depth != 1) { 816 reportError("err.invalid.options", "-R", "--inverse"); 817 return false; 818 } 819 820 if (options.numFilters() == 0) { 821 reportError("err.filter.not.specified"); 822 return false; 823 } 824 825 if (!super.checkOptions()) { 826 return false; 827 } 828 829 return true; 830 } 831 832 @Override 833 boolean run(JdepsConfiguration config) throws IOException { 834 Type type = getAnalyzerType(); 835 836 InverseDepsAnalyzer analyzer = 837 new InverseDepsAnalyzer(config, 838 dependencyFilter(config), 839 writer, 840 type, 841 options.apiOnly); 842 boolean ok = analyzer.run(); 843 844 log.println(); 845 if (!options.requires.isEmpty()) 846 log.println(getMessage("inverse.transitive.dependencies.on", 847 options.requires)); 848 else 849 log.println(getMessage("inverse.transitive.dependencies.matching", 850 options.regex != null 851 ? options.regex.toString() 852 : "packages " + options.packageNames)); 853 854 analyzer.inverseDependences() 855 .stream() 856 .sorted(comparator()) 857 .map(this::toInversePath) 858 .forEach(log::println); 859 return ok; 860 } 861 862 private String toInversePath(Deque<Archive> path) { 863 return path.stream() 864 .map(Archive::getName) 865 .collect(joining(" <- ")); 866 } 867 868 /* 869 * Returns a comparator for sorting the inversed path, grouped by 870 * the first module name, then the shortest path and then sort by 871 * the module names of each path 872 */ 873 private Comparator<Deque<Archive>> comparator() { 874 return Comparator.<Deque<Archive>, String> 875 comparing(deque -> deque.peekFirst().getName()) 876 .thenComparingInt(Deque::size) 877 .thenComparing(this::toInversePath); 878 } 879 880 /* 881 * Returns true if --require is specified so that all modules are 882 * analyzed to find all modules that depend on the modules specified in the 883 * --require option directly and indirectly 884 */ 885 Set<String> addModules() { 886 return options.requires.size() > 0 ? Set.of("ALL-SYSTEM") : Set.of(); 887 } 888 } 889 890 891 class GenModuleInfo extends Command { 892 final Path dir; 893 final boolean openModule; 894 GenModuleInfo(Path dir, boolean openModule) { 895 super(CommandOption.GENERATE_MODULE_INFO); 896 this.dir = dir; 897 this.openModule = openModule; 898 } 899 900 @Override 901 boolean checkOptions() { 902 if (options.classpath != null) { 903 reportError("err.invalid.options", "-classpath", 904 option); 905 return false; 906 } 907 if (options.hasFilter()) { 908 reportError("err.invalid.options", "--package, --regex, --require", 909 option); 910 return false; 911 } 912 return true; 913 } 914 915 @Override 916 boolean run(JdepsConfiguration config) throws IOException { 917 // check if any JAR file contains unnamed package 918 for (String arg : inputArgs) { 919 try (ClassFileReader reader = ClassFileReader.newInstance(Paths.get(arg))) { 920 Optional<String> classInUnnamedPackage = 921 reader.entries().stream() 922 .filter(n -> n.endsWith(".class")) 923 .filter(cn -> toPackageName(cn).isEmpty()) 924 .findFirst(); 925 926 if (classInUnnamedPackage.isPresent()) { 927 if (classInUnnamedPackage.get().equals("module-info.class")) { 928 reportError("err.genmoduleinfo.not.jarfile", arg); 929 } else { 930 reportError("err.genmoduleinfo.unnamed.package", arg); 931 } 932 return false; 933 } 934 } 935 } 936 937 ModuleInfoBuilder builder 938 = new ModuleInfoBuilder(config, inputArgs, dir, openModule); 939 boolean ok = builder.run(); 940 941 if (!ok && !options.nowarning) { 942 reportError("err.missing.dependences"); 943 builder.visitMissingDeps(new SimpleDepVisitor()); 944 } 945 return ok; 946 } 947 948 private String toPackageName(String name) { 949 int i = name.lastIndexOf('/'); 950 return i > 0 ? name.replace('/', '.').substring(0, i) : ""; 951 } 952 } 953 954 class CheckModuleDeps extends Command { 955 final Set<String> modules; 956 CheckModuleDeps(Set<String> mods) { 957 super(CommandOption.CHECK_MODULES); 958 this.modules = mods; 959 } 960 961 @Override 962 boolean checkOptions() { 963 if (!inputArgs.isEmpty()) { 964 reportError("err.invalid.options", inputArgs, "--check"); 965 return false; 966 } 967 return true; 968 } 969 970 @Override 971 boolean run(JdepsConfiguration config) throws IOException { 972 if (!config.initialArchives().isEmpty()) { 973 String list = config.initialArchives().stream() 974 .map(Archive::getPathName).collect(joining(" ")); 975 throw new UncheckedBadArgs(new BadArgs("err.invalid.options", 976 list, "--check")); 977 } 978 return new ModuleAnalyzer(config, log, modules).run(); 979 } 980 981 /* 982 * Returns true to analyze all modules 983 */ 984 Set<String> addModules() { 985 return Set.of("ALL-SYSTEM", "ALL-MODULE-PATH"); 986 } 987 } 988 989 class ListModuleDeps extends Command { 990 final boolean jdkinternals; 991 final boolean reduced; 992 final String separator; 993 ListModuleDeps(CommandOption option, boolean jdkinternals, boolean reduced) { 994 this(option, jdkinternals, reduced, System.getProperty("line.separator")); 995 } 996 ListModuleDeps(CommandOption option, boolean jdkinternals, boolean reduced, String sep) { 997 super(option); 998 this.jdkinternals = jdkinternals; 999 this.reduced = reduced; 1000 this.separator = sep; 1001 } 1002 1003 @Override 1004 boolean checkOptions() { 1005 if (options.showSummary || options.verbose != null) { 1006 reportError("err.invalid.options", "-summary or -verbose", option); 1007 return false; 1008 } 1009 if (options.findJDKInternals) { 1010 reportError("err.invalid.options", "-jdkinternals", option); 1011 return false; 1012 } 1013 if (options.findMissingDeps) { 1014 reportError("err.invalid.options", "--missing-deps", option); 1015 return false; 1016 } 1017 1018 if (!inputArgs.isEmpty() && !options.rootModules.isEmpty()) { 1019 reportError("err.invalid.arg.for.option", "-m"); 1020 } 1021 if (inputArgs.isEmpty() && !options.hasSourcePath()) { 1022 showHelp(); 1023 return false; 1024 } 1025 return true; 1026 } 1027 1028 @Override 1029 boolean run(JdepsConfiguration config) throws IOException { 1030 ModuleExportsAnalyzer analyzer = new ModuleExportsAnalyzer(config, 1031 dependencyFilter(config), 1032 jdkinternals, 1033 reduced, 1034 log, 1035 separator); 1036 boolean ok = analyzer.run(options.depth, options.ignoreMissingDeps); 1037 if (!ok) { 1038 reportError("err.cant.list.module.deps"); 1039 log.println(); 1040 analyzer.visitMissingDeps(new SimpleDepVisitor()); 1041 } 1042 return ok; 1043 } 1044 } 1045 1046 class GenDotFile extends AnalyzeDeps { 1047 final Path dotOutputDir; 1048 GenDotFile(Path dotOutputDir) { 1049 super(CommandOption.GENERATE_DOT_FILE); 1050 1051 this.dotOutputDir = dotOutputDir; 1052 } 1053 1054 @Override 1055 boolean run(JdepsConfiguration config) throws IOException { 1056 if ((options.showSummary || options.verbose == MODULE) && 1057 !options.addmods.isEmpty() && inputArgs.isEmpty()) { 1058 // generate dot graph from the resolved graph from module 1059 // resolution. No class dependency analysis is performed. 1060 return new ModuleDotGraph(config, options.apiOnly) 1061 .genDotFiles(dotOutputDir); 1062 } 1063 1064 Type type = getAnalyzerType(); 1065 JdepsWriter writer = new DotFileWriter(dotOutputDir, 1066 type, 1067 options.showProfile, 1068 options.showModule, 1069 options.showLabel); 1070 return run(config, writer, type); 1071 } 1072 } 1073 1074 class SimpleDepVisitor implements Analyzer.Visitor { 1075 private Archive source; 1076 @Override 1077 public void visitDependence(String origin, Archive originArchive, String target, Archive targetArchive) { 1078 if (source != originArchive) { 1079 source = originArchive; 1080 log.format("%s%n", originArchive); 1081 } 1082 log.format(" %-50s -> %-50s %s%n", origin, target, targetArchive.getName()); 1083 } 1084 } 1085 1086 /** 1087 * Returns a filter used during dependency analysis 1088 */ 1089 private JdepsFilter dependencyFilter(JdepsConfiguration config) { 1090 // Filter specified by -filter, -package, -regex, and --require options 1091 JdepsFilter.Builder builder = new JdepsFilter.Builder(); 1092 1093 // source filters 1094 builder.includePattern(options.includePattern); 1095 1096 // target filters 1097 builder.filter(options.filterSamePackage, options.filterSameArchive); 1098 builder.findJDKInternals(options.findJDKInternals); 1099 builder.findMissingDeps(options.findMissingDeps); 1100 1101 // --require 1102 if (!options.requires.isEmpty()) { 1103 options.requires.stream() 1104 .forEach(mn -> { 1105 Module m = config.findModule(mn).get(); 1106 builder.requires(mn, m.packages()); 1107 }); 1108 } 1109 // -regex 1110 if (options.regex != null) 1111 builder.regex(options.regex); 1112 // -package 1113 if (!options.packageNames.isEmpty()) 1114 builder.packages(options.packageNames); 1115 // -filter 1116 if (options.filterRegex != null) 1117 builder.filter(options.filterRegex); 1118 1119 return builder.build(); 1120 } 1121 1122 public void handleOptions(String[] args) throws BadArgs { 1123 // process options 1124 for (int i=0; i < args.length; i++) { 1125 if (args[i].charAt(0) == '-') { 1126 String name = args[i]; 1127 Option option = getOption(name); 1128 String param = null; 1129 if (option.hasArg) { 1130 if (name.startsWith("-") && name.indexOf('=') > 0) { 1131 param = name.substring(name.indexOf('=') + 1, name.length()); 1132 } else if (i + 1 < args.length) { 1133 param = args[++i]; 1134 } 1135 if (param == null || param.isEmpty() || param.charAt(0) == '-') { 1136 throw new BadArgs("err.missing.arg", name).showUsage(true); 1137 } 1138 } 1139 option.process(this, name, param); 1140 if (option.ignoreRest()) { 1141 i = args.length; 1142 } 1143 } else { 1144 // process rest of the input arguments 1145 for (; i < args.length; i++) { 1146 String name = args[i]; 1147 if (name.charAt(0) == '-') { 1148 throw new BadArgs("err.option.after.class", name).showUsage(true); 1149 } 1150 inputArgs.add(name); 1151 } 1152 } 1153 } 1154 } 1155 1156 private Option getOption(String name) throws BadArgs { 1157 for (Option o : recognizedOptions) { 1158 if (o.matches(name)) { 1159 return o; 1160 } 1161 } 1162 throw new BadArgs("err.unknown.option", name).showUsage(true); 1163 } 1164 1165 private void reportError(String key, Object... args) { 1166 log.println(getMessage("error.prefix") + " " + getMessage(key, args)); 1167 } 1168 1169 void warning(String key, Object... args) { 1170 log.println(getMessage("warn.prefix") + " " + getMessage(key, args)); 1171 } 1172 1173 private void showHelp() { 1174 log.println(getMessage("main.usage", PROGNAME)); 1175 for (Option o : recognizedOptions) { 1176 String name = o.aliases[0].substring(1); // there must always be at least one name 1177 name = name.charAt(0) == '-' ? name.substring(1) : name; 1178 if (o.isHidden() || name.startsWith("filter:")) { 1179 continue; 1180 } 1181 log.println(getMessage("main.opt." + name)); 1182 } 1183 } 1184 1185 private void showVersion(boolean full) { 1186 log.println(version(full ? "full" : "release")); 1187 } 1188 1189 private String version(String key) { 1190 // key=version: mm.nn.oo[-milestone] 1191 // key=full: mm.mm.oo[-milestone]-build 1192 try { 1193 return ResourceBundleHelper.getVersion(key); 1194 } catch (MissingResourceException e) { 1195 return getMessage("version.unknown", System.getProperty("java.version")); 1196 } 1197 } 1198 1199 static String getMessage(String key, Object... args) { 1200 try { 1201 return MessageFormat.format(ResourceBundleHelper.getMessage(key), args); 1202 } catch (MissingResourceException e) { 1203 throw new InternalError("Missing message: " + key); 1204 } 1205 } 1206 1207 private static class Options { 1208 boolean help; 1209 boolean version; 1210 boolean fullVersion; 1211 boolean showProfile; 1212 boolean showModule = true; 1213 boolean showSummary; 1214 boolean apiOnly; 1215 boolean showLabel; 1216 boolean findJDKInternals; 1217 boolean findMissingDeps; 1218 boolean ignoreMissingDeps; 1219 boolean nowarning = false; 1220 Analyzer.Type verbose; 1221 // default filter references from same package 1222 boolean filterSamePackage = true; 1223 boolean filterSameArchive = false; 1224 Pattern filterRegex; 1225 String classpath; 1226 int depth = 1; 1227 Set<String> requires = new HashSet<>(); 1228 Set<String> packageNames = new HashSet<>(); 1229 Pattern regex; // apply to the dependences 1230 Pattern includePattern; 1231 boolean inverse = false; 1232 boolean compileTimeView = false; 1233 String systemModulePath = System.getProperty("java.home"); 1234 String upgradeModulePath; 1235 String modulePath; 1236 Set<String> rootModules = new HashSet<>(); 1237 Set<String> addmods = new HashSet<>(); 1238 Runtime.Version multiRelease; 1239 1240 boolean hasSourcePath() { 1241 return !addmods.isEmpty() || includePattern != null; 1242 } 1243 1244 boolean hasFilter() { 1245 return numFilters() > 0; 1246 } 1247 1248 int numFilters() { 1249 int count = 0; 1250 if (requires.size() > 0) count++; 1251 if (regex != null) count++; 1252 if (packageNames.size() > 0) count++; 1253 return count; 1254 } 1255 } 1256 1257 private static class ResourceBundleHelper { 1258 static final String LS = System.lineSeparator(); 1259 static final ResourceBundle versionRB; 1260 static final ResourceBundle bundle; 1261 static final ResourceBundle jdkinternals; 1262 1263 static { 1264 Locale locale = Locale.getDefault(); 1265 try { 1266 bundle = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.jdeps", locale); 1267 } catch (MissingResourceException e) { 1268 throw new InternalError("Cannot find jdeps resource bundle for locale " + locale); 1269 } 1270 try { 1271 versionRB = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.version"); 1272 } catch (MissingResourceException e) { 1273 throw new InternalError("version.resource.missing"); 1274 } 1275 try { 1276 jdkinternals = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.jdkinternals"); 1277 } catch (MissingResourceException e) { 1278 throw new InternalError("Cannot find jdkinternals resource bundle"); 1279 } 1280 } 1281 1282 static String getMessage(String key) { 1283 return bundle.getString(key).replace("\n", LS); 1284 } 1285 1286 static String getVersion(String key) { 1287 if (ResourceBundleHelper.versionRB == null) { 1288 return System.getProperty("java.version"); 1289 } 1290 return versionRB.getString(key).replace("\n", LS); 1291 } 1292 1293 static String getSuggestedReplacement(String key) { 1294 return ResourceBundleHelper.jdkinternals.getString(key).replace("\n", LS); 1295 } 1296 } 1297 1298 /** 1299 * Returns the recommended replacement API for the given classname; 1300 * or return null if replacement API is not known. 1301 */ 1302 private Optional<String> replacementFor(String cn) { 1303 String name = cn; 1304 String value = null; 1305 while (value == null && name != null) { 1306 try { 1307 value = ResourceBundleHelper.getSuggestedReplacement(name); 1308 } catch (MissingResourceException e) { 1309 // go up one subpackage level 1310 int i = name.lastIndexOf('.'); 1311 name = i > 0 ? name.substring(0, i) : null; 1312 } 1313 } 1314 return Optional.ofNullable(value); 1315 } 1316 }