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