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 26 package com.sun.tools.jdeps; 27 28 import static com.sun.tools.jdeps.Analyzer.Type.*; 29 import static com.sun.tools.jdeps.JdepsWriter.*; 30 import static com.sun.tools.jdeps.ModuleAnalyzer.Graph; 31 32 import java.io.File; 33 import java.io.IOException; 34 import java.io.PrintWriter; 35 import java.lang.module.ResolutionException; 36 import java.nio.file.DirectoryStream; 37 import java.nio.file.Files; 38 import java.nio.file.Path; 39 import java.nio.file.Paths; 40 import java.text.MessageFormat; 41 import java.util.ArrayList; 42 import java.util.Arrays; 43 import java.util.Collections; 44 import java.util.HashSet; 45 import java.util.List; 46 import java.util.Locale; 47 import java.util.Map; 48 import java.util.MissingResourceException; 49 import java.util.Optional; 50 import java.util.ResourceBundle; 51 import java.util.Set; 52 import java.util.TreeMap; 53 import java.util.regex.Pattern; 54 import java.util.stream.Collectors; 55 import java.util.stream.Stream; 56 57 /** 58 * Implementation for the jdeps tool for static class dependency analysis. 59 */ 60 class JdepsTask { 61 static class BadArgs extends Exception { 62 static final long serialVersionUID = 8765093759964640721L; 63 BadArgs(String key, Object... args) { 64 super(JdepsTask.getMessage(key, args)); 65 this.key = key; 66 this.args = args; 67 } 68 69 BadArgs showUsage(boolean b) { 70 showUsage = b; 71 return this; 72 } 73 final String key; 74 final Object[] args; 75 boolean showUsage; 76 } 77 78 static abstract class Option { 79 Option(boolean hasArg, String... aliases) { 80 this.hasArg = hasArg; 81 this.aliases = aliases; 82 } 83 84 boolean isHidden() { 85 return false; 86 } 87 88 boolean matches(String opt) { 89 for (String a : aliases) { 90 if (a.equals(opt)) 91 return true; 92 if (hasArg && opt.startsWith(a + "=")) 93 return true; 94 } 95 return false; 96 } 97 98 boolean ignoreRest() { 99 return false; 100 } 101 102 abstract void process(JdepsTask task, String opt, String arg) throws BadArgs; 103 final boolean hasArg; 104 final String[] aliases; 105 } 106 107 static abstract class HiddenOption extends Option { 108 HiddenOption(boolean hasArg, String... aliases) { 109 super(hasArg, aliases); 110 } 111 112 boolean isHidden() { 113 return true; 114 } 115 } 116 117 static Option[] recognizedOptions = { 118 new Option(false, "-h", "-?", "-help") { 119 void process(JdepsTask task, String opt, String arg) { 120 task.options.help = true; 121 } 122 }, 123 new Option(true, "-dotoutput") { 124 void process(JdepsTask task, String opt, String arg) throws BadArgs { 125 Path p = Paths.get(arg); 126 if (Files.exists(p) && (!Files.isDirectory(p) || !Files.isWritable(p))) { 127 throw new BadArgs("err.invalid.path", arg); 128 } 129 task.options.dotOutputDir = arg; 130 } 131 }, 132 new Option(false, "-s", "-summary") { 133 void process(JdepsTask task, String opt, String arg) { 134 task.options.showSummary = true; 135 task.options.verbose = SUMMARY; 136 } 137 }, 138 new Option(false, "-v", "-verbose", 139 "-verbose:package", 140 "-verbose:class") { 141 void process(JdepsTask task, String opt, String arg) throws BadArgs { 142 switch (opt) { 143 case "-v": 144 case "-verbose": 145 task.options.verbose = VERBOSE; 146 task.options.filterSameArchive = false; 147 task.options.filterSamePackage = false; 148 break; 149 case "-verbose:package": 150 task.options.verbose = PACKAGE; 151 break; 152 case "-verbose:class": 153 task.options.verbose = CLASS; 154 break; 155 default: 156 throw new BadArgs("err.invalid.arg.for.option", opt); 157 } 158 } 159 }, 160 new Option(true, "-p", "-package") { 161 void process(JdepsTask task, String opt, String arg) { 162 task.options.packageNames.add(arg); 163 } 164 }, 165 new Option(true, "-e", "-regex") { 166 void process(JdepsTask task, String opt, String arg) { 167 task.options.regex = Pattern.compile(arg); 168 } 169 }, 170 new Option(true, "-module") { 171 void process(JdepsTask task, String opt, String arg) { 172 task.options.requires.add(arg); 173 } 174 }, 175 new Option(true, "-f", "-filter") { 176 void process(JdepsTask task, String opt, String arg) { 177 task.options.filterRegex = Pattern.compile(arg); 178 } 179 }, 180 new Option(false, "-filter:package", 181 "-filter:archive", "-filter:module", 182 "-filter:none") { 183 void process(JdepsTask task, String opt, String arg) { 184 switch (opt) { 185 case "-filter:package": 186 task.options.filterSamePackage = true; 187 task.options.filterSameArchive = false; 188 break; 189 case "-filter:archive": 190 case "-filter:module": 191 task.options.filterSameArchive = true; 192 task.options.filterSamePackage = false; 193 break; 194 case "-filter:none": 195 task.options.filterSameArchive = false; 196 task.options.filterSamePackage = false; 197 break; 198 } 199 } 200 }, 201 new Option(true, "-include") { 202 void process(JdepsTask task, String opt, String arg) throws BadArgs { 203 task.options.includePattern = Pattern.compile(arg); 204 } 205 }, 206 new Option(false, "-P", "-profile") { 207 void process(JdepsTask task, String opt, String arg) throws BadArgs { 208 task.options.showProfile = true; 209 task.options.showModule = false; 210 } 211 }, 212 new Option(false, "-M") { 213 void process(JdepsTask task, String opt, String arg) throws BadArgs { 214 task.options.showModule = true; 215 task.options.showProfile = false; 216 } 217 }, 218 new Option(false, "-apionly") { 219 void process(JdepsTask task, String opt, String arg) { 220 task.options.apiOnly = true; 221 } 222 }, 223 new Option(false, "-R", "-recursive") { 224 void process(JdepsTask task, String opt, String arg) { 225 task.options.depth = 0; 226 // turn off filtering 227 task.options.filterSameArchive = false; 228 task.options.filterSamePackage = false; 229 } 230 }, 231 new Option(true, "-genmoduleinfo") { 232 void process(JdepsTask task, String opt, String arg) throws BadArgs { 233 Path p = Paths.get(arg); 234 if (Files.exists(p) && (!Files.isDirectory(p) || !Files.isWritable(p))) { 235 throw new BadArgs("err.invalid.path", arg); 236 } 237 task.options.genModuleInfo = arg; 238 } 239 }, 240 new Option(false, "-ct", "-compile-time") { 241 void process(JdepsTask task, String opt, String arg) { 242 task.options.compileTimeView = true; 243 task.options.filterSamePackage = true; 244 task.options.filterSameArchive = true; 245 task.options.depth = 0; 246 } 247 }, 248 new Option(false, "-jdkinternals") { 249 void process(JdepsTask task, String opt, String arg) { 250 task.options.findJDKInternals = true; 251 task.options.verbose = CLASS; 252 if (task.options.includePattern == null) { 253 task.options.includePattern = Pattern.compile(".*"); 254 } 255 } 256 }, 257 new Option(true, "-cp", "-classpath") { 258 void process(JdepsTask task, String opt, String arg) { 259 task.options.classpath = arg; 260 } 261 }, 262 new Option(true, "-mp", "-modulepath") { 263 void process(JdepsTask task, String opt, String arg) throws BadArgs { 264 task.options.modulePath = arg; 265 task.options.showModule = true; 266 } 267 }, 268 new Option(true, "-upgrademodulepath") { 269 void process(JdepsTask task, String opt, String arg) throws BadArgs { 270 task.options.upgradeModulePath = arg; 271 task.options.showModule = true; 272 } 273 }, 274 new Option(true, "-m") { 275 void process(JdepsTask task, String opt, String arg) throws BadArgs { 276 task.options.rootModule = arg; 277 task.options.includes.add(arg); 278 task.options.showModule = true; 279 } 280 }, 281 new Option(false, "-check") { 282 void process(JdepsTask task, String opt, String arg) throws BadArgs { 283 task.options.checkModuleDeps = true; 284 } 285 }, 286 new HiddenOption(true, "-include-modules") { 287 void process(JdepsTask task, String opt, String arg) throws BadArgs { 288 Arrays.stream(arg.split(",")) 289 .forEach(task.options.includes::add); 290 task.options.showModule = true; 291 } 292 }, 293 new HiddenOption(true, "-exclude-modules") { 294 void process(JdepsTask task, String opt, String arg) throws BadArgs { 295 Arrays.stream(arg.split(",")) 296 .forEach(task.options.excludes::add); 297 task.options.showModule = true; 298 } 299 }, 300 new Option(false, "-q", "-quiet") { 301 void process(JdepsTask task, String opt, String arg) { 302 task.options.nowarning = true; 303 } 304 }, 305 306 new Option(false, "-version") { 307 void process(JdepsTask task, String opt, String arg) { 308 task.options.version = true; 309 } 310 }, 311 new HiddenOption(false, "-fullversion") { 312 void process(JdepsTask task, String opt, String arg) { 313 task.options.fullVersion = true; 314 } 315 }, 316 new HiddenOption(false, "-showlabel") { 317 void process(JdepsTask task, String opt, String arg) { 318 task.options.showLabel = true; 319 } 320 }, 321 322 new HiddenOption(true, "-depth") { 323 void process(JdepsTask task, String opt, String arg) throws BadArgs { 324 try { 325 task.options.depth = Integer.parseInt(arg); 326 } catch (NumberFormatException e) { 327 throw new BadArgs("err.invalid.arg.for.option", opt); 328 } 329 } 330 }, 331 }; 332 333 private static final String PROGNAME = "jdeps"; 334 private final Options options = new Options(); 335 private final List<String> classes = new ArrayList<>(); 336 337 private PrintWriter log; 338 void setLog(PrintWriter out) { 339 log = out; 340 } 341 342 /** 343 * Result codes. 344 */ 345 static final int EXIT_OK = 0, // Completed with no errors. 346 EXIT_ERROR = 1, // Completed but reported errors. 347 EXIT_CMDERR = 2, // Bad command-line arguments 348 EXIT_SYSERR = 3, // System error or resource exhaustion. 349 EXIT_ABNORMAL = 4;// terminated abnormally 350 351 int run(String[] args) { 352 if (log == null) { 353 log = new PrintWriter(System.out); 354 } 355 try { 356 handleOptions(args); 357 if (options.help) { 358 showHelp(); 359 } 360 if (options.version || options.fullVersion) { 361 showVersion(options.fullVersion); 362 } 363 if (options.rootModule != null && !classes.isEmpty()) { 364 reportError("err.invalid.module.option", options.rootModule, classes); 365 return EXIT_CMDERR; 366 } 367 if (options.checkModuleDeps && options.rootModule == null) { 368 reportError("err.root.module.not.set"); 369 return EXIT_CMDERR; 370 } 371 if (classes.isEmpty() && options.rootModule == null && options.includePattern == null) { 372 if (options.help || options.version || options.fullVersion) { 373 return EXIT_OK; 374 } else { 375 showHelp(); 376 return EXIT_CMDERR; 377 } 378 } 379 if (options.genModuleInfo != null) { 380 if (options.dotOutputDir != null || !options.classpath.isEmpty() || options.hasFilter()) { 381 showHelp(); 382 return EXIT_CMDERR; 383 } 384 // default to compile time view analysis 385 options.compileTimeView = true; 386 for (String fn : classes) { 387 Path p = Paths.get(fn); 388 if (!Files.exists(p) || !fn.endsWith(".jar")) { 389 reportError("err.genmoduleinfo.not.jarfile", fn); 390 return EXIT_CMDERR; 391 } 392 } 393 } 394 395 if (options.numFilters() > 1) { 396 reportError("err.invalid.filters"); 397 return EXIT_CMDERR; 398 } 399 400 if ((options.findJDKInternals) && (options.hasFilter() || options.showSummary)) { 401 showHelp(); 402 return EXIT_CMDERR; 403 } 404 if (options.showSummary && options.verbose != SUMMARY) { 405 showHelp(); 406 return EXIT_CMDERR; 407 } 408 409 boolean ok = run(); 410 return ok ? EXIT_OK : EXIT_ERROR; 411 } catch (BadArgs e) { 412 reportError(e.key, e.args); 413 if (e.showUsage) { 414 log.println(getMessage("main.usage.summary", PROGNAME)); 415 } 416 return EXIT_CMDERR; 417 } catch (ResolutionException e) { 418 reportError("err.exception.message", e.getMessage()); 419 return EXIT_CMDERR; 420 } catch (IOException e) { 421 e.printStackTrace(); 422 return EXIT_ABNORMAL; 423 } finally { 424 log.flush(); 425 } 426 } 427 428 private ModulePaths modulePaths; 429 private boolean run() throws BadArgs, IOException { 430 DependencyFinder dependencyFinder = 431 new DependencyFinder(options.compileTimeView); 432 433 buildArchive(dependencyFinder); 434 435 if (options.rootModule != null && 436 (options.checkModuleDeps || (options.dotOutputDir != null && 437 options.verbose == SUMMARY))) { 438 // -dotfile -s prints the configuration of the given root 439 // -checkModuleDeps prints the suggested module-info.java 440 return analyzeModules(dependencyFinder); 441 } 442 443 // otherwise analyze the dependencies 444 if (options.genModuleInfo != null) { 445 return genModuleInfo(dependencyFinder); 446 } else { 447 return analyzeDeps(dependencyFinder); 448 } 449 } 450 451 private void buildArchive(DependencyFinder dependencyFinder) 452 throws BadArgs, IOException 453 { 454 // If -genmoduleinfo is specified, the input arguments must be JAR files 455 // Treat them as automatic modules for analysis 456 List<Path> jarfiles = options.genModuleInfo != null 457 ? classes.stream().map(Paths::get) 458 .collect(Collectors.toList()) 459 : Collections.emptyList(); 460 // Set module paths 461 this.modulePaths = new ModulePaths(options.upgradeModulePath, options.modulePath, jarfiles); 462 463 // add modules to dependency finder for analysis 464 Map<String, Module> modules = modulePaths.getModules(); 465 modules.values().stream() 466 .forEach(dependencyFinder::addModule); 467 468 // If -m option is set, add the specified module and its transitive dependences 469 // to the root set 470 if (options.rootModule != null) { 471 modulePaths.dependences(options.rootModule) 472 .forEach(dependencyFinder::addRoot); 473 } 474 475 // check if any module specified in -requires is missing 476 Optional<String> req = options.requires.stream() 477 .filter(mn -> !modules.containsKey(mn)) 478 .findFirst(); 479 if (req.isPresent()) { 480 throw new BadArgs("err.module.not.found", req.get()); 481 } 482 483 // classpath 484 for (Path p : getClassPaths(options.classpath)) { 485 if (Files.exists(p)) { 486 dependencyFinder.addClassPathArchive(p); 487 } 488 } 489 490 // if -genmoduleinfo is not set, the input arguments are considered as 491 // unnamed module. Add them to the root set 492 if (options.genModuleInfo == null) { 493 // add root set 494 for (String s : classes) { 495 Path p = Paths.get(s); 496 if (Files.exists(p)) { 497 // add to the initial root set 498 dependencyFinder.addRoot(p); 499 } else { 500 if (isValidClassName(s)) { 501 dependencyFinder.addClassName(s); 502 } else { 503 warning("warn.invalid.arg", s); 504 } 505 } 506 } 507 } 508 } 509 510 private boolean analyzeDeps(DependencyFinder dependencyFinder) throws IOException { 511 JdepsFilter filter = dependencyFilter(); 512 513 // parse classfiles and find all dependencies 514 findDependencies(dependencyFinder, filter, options.apiOnly); 515 516 // analyze the dependencies collected 517 Analyzer analyzer = new Analyzer(options.verbose, filter); 518 analyzer.run(dependencyFinder.archives()); 519 520 // output result 521 final JdepsWriter writer; 522 if (options.dotOutputDir != null) { 523 Path dir = Paths.get(options.dotOutputDir); 524 Files.createDirectories(dir); 525 writer = new DotFileWriter(dir, options.verbose, 526 options.showProfile, 527 options.showModule, 528 options.showLabel); 529 } else { 530 writer = new SimpleWriter(log, options.verbose, 531 options.showProfile, 532 options.showModule); 533 } 534 535 // Targets for reporting - include the root sets and other analyzed archives 536 final List<Archive> targets; 537 if (options.rootModule == null) { 538 // no module as the root set 539 targets = dependencyFinder.archives() 540 .filter(filter::accept) 541 .filter(a -> !a.getModule().isNamed()) 542 .collect(Collectors.toList()); 543 } else { 544 // named modules in topological order 545 Stream<Module> modules = dependencyFinder.archives() 546 .filter(a -> a.getModule().isNamed()) 547 .map(Archive::getModule); 548 Graph<Module> graph = ModuleAnalyzer.graph(modulePaths, modules.toArray(Module[]::new)); 549 // then add unnamed module 550 targets = graph.orderedNodes() 551 .filter(filter::accept) 552 .collect(Collectors.toList()); 553 554 // in case any reference not found 555 dependencyFinder.archives() 556 .filter(a -> !a.getModule().isNamed()) 557 .forEach(targets::add); 558 } 559 560 writer.generateOutput(targets, analyzer); 561 if (options.findJDKInternals && !options.nowarning) { 562 showReplacements(targets, analyzer); 563 } 564 return true; 565 } 566 567 private JdepsFilter dependencyFilter() { 568 // Filter specified by -filter, -package, -regex, and -module options 569 JdepsFilter.Builder builder = new JdepsFilter.Builder(); 570 571 // Exclude JDK modules from analysis and reporting if -m specified. 572 modulePaths.getModules().values().stream() 573 .filter(m -> m.isJDK()) 574 .map(Module::name) 575 .forEach(options.excludes::add); 576 577 // source filters 578 builder.includePattern(options.includePattern); 579 builder.includeModules(options.includes); 580 builder.excludeModules(options.excludes); 581 582 builder.filter(options.filterSamePackage, options.filterSameArchive); 583 builder.findJDKInternals(options.findJDKInternals); 584 585 // -module 586 if (!options.requires.isEmpty()) { 587 Map<String, Module> modules = modulePaths.getModules(); 588 builder.packages(options.requires.stream() 589 .map(modules::get) 590 .flatMap(m -> m.packages().stream()) 591 .collect(Collectors.toSet())); 592 } 593 // -regex 594 if (options.regex != null) 595 builder.regex(options.regex); 596 // -package 597 if (!options.packageNames.isEmpty()) 598 builder.packages(options.packageNames); 599 // -filter 600 if (options.filterRegex != null) 601 builder.filter(options.filterRegex); 602 603 return builder.build(); 604 } 605 606 private void findDependencies(DependencyFinder dependencyFinder, 607 JdepsFilter filter, 608 boolean apiOnly) 609 throws IOException 610 { 611 dependencyFinder.findDependencies(filter, apiOnly, options.depth); 612 613 // print skipped entries, if any 614 for (Archive a : dependencyFinder.roots()) { 615 for (String name : a.reader().skippedEntries()) { 616 warning("warn.skipped.entry", name, a.getPathName()); 617 } 618 } 619 } 620 621 private boolean genModuleInfo(DependencyFinder dependencyFinder) throws IOException { 622 ModuleInfoBuilder builder = new ModuleInfoBuilder(modulePaths, dependencyFinder); 623 boolean result = builder.run(options.verbose, options.nowarning); 624 builder.build(Paths.get(options.genModuleInfo)); 625 return result; 626 } 627 628 private boolean analyzeModules(DependencyFinder dependencyFinder) 629 throws IOException 630 { 631 ModuleAnalyzer analyzer = new ModuleAnalyzer(modulePaths, 632 dependencyFinder, 633 options.rootModule); 634 if (options.checkModuleDeps) { 635 return analyzer.run(); 636 } 637 if (options.dotOutputDir != null && options.verbose == SUMMARY) { 638 Path dir = Paths.get(options.dotOutputDir); 639 Files.createDirectories(dir); 640 analyzer.genDotFile(dir); 641 return true; 642 } 643 return false; 644 } 645 646 private boolean isValidClassName(String name) { 647 if (!Character.isJavaIdentifierStart(name.charAt(0))) { 648 return false; 649 } 650 for (int i=1; i < name.length(); i++) { 651 char c = name.charAt(i); 652 if (c != '.' && !Character.isJavaIdentifierPart(c)) { 653 return false; 654 } 655 } 656 return true; 657 } 658 659 public void handleOptions(String[] args) throws BadArgs { 660 // process options 661 for (int i=0; i < args.length; i++) { 662 if (args[i].charAt(0) == '-') { 663 String name = args[i]; 664 Option option = getOption(name); 665 String param = null; 666 if (option.hasArg) { 667 if (name.startsWith("-") && name.indexOf('=') > 0) { 668 param = name.substring(name.indexOf('=') + 1, name.length()); 669 } else if (i + 1 < args.length) { 670 param = args[++i]; 671 } 672 if (param == null || param.isEmpty() || param.charAt(0) == '-') { 673 throw new BadArgs("err.missing.arg", name).showUsage(true); 674 } 675 } 676 option.process(this, name, param); 677 if (option.ignoreRest()) { 678 i = args.length; 679 } 680 } else { 681 // process rest of the input arguments 682 for (; i < args.length; i++) { 683 String name = args[i]; 684 if (name.charAt(0) == '-') { 685 throw new BadArgs("err.option.after.class", name).showUsage(true); 686 } 687 classes.add(name); 688 } 689 } 690 } 691 } 692 693 private Option getOption(String name) throws BadArgs { 694 for (Option o : recognizedOptions) { 695 if (o.matches(name)) { 696 return o; 697 } 698 } 699 throw new BadArgs("err.unknown.option", name).showUsage(true); 700 } 701 702 private void reportError(String key, Object... args) { 703 log.println(getMessage("error.prefix") + " " + getMessage(key, args)); 704 } 705 706 private void warning(String key, Object... args) { 707 log.println(getMessage("warn.prefix") + " " + getMessage(key, args)); 708 } 709 710 private void showHelp() { 711 log.println(getMessage("main.usage", PROGNAME)); 712 for (Option o : recognizedOptions) { 713 String name = o.aliases[0].substring(1); // there must always be at least one name 714 name = name.charAt(0) == '-' ? name.substring(1) : name; 715 if (o.isHidden() || name.equals("h") || name.startsWith("filter:")) { 716 continue; 717 } 718 log.println(getMessage("main.opt." + name)); 719 } 720 } 721 722 private void showVersion(boolean full) { 723 log.println(version(full ? "full" : "release")); 724 } 725 726 private String version(String key) { 727 // key=version: mm.nn.oo[-milestone] 728 // key=full: mm.mm.oo[-milestone]-build 729 if (ResourceBundleHelper.versionRB == null) { 730 return System.getProperty("java.version"); 731 } 732 try { 733 return ResourceBundleHelper.versionRB.getString(key); 734 } catch (MissingResourceException e) { 735 return getMessage("version.unknown", System.getProperty("java.version")); 736 } 737 } 738 739 static String getMessage(String key, Object... args) { 740 try { 741 return MessageFormat.format(ResourceBundleHelper.bundle.getString(key), args); 742 } catch (MissingResourceException e) { 743 throw new InternalError("Missing message: " + key); 744 } 745 } 746 747 private static class Options { 748 boolean help; 749 boolean version; 750 boolean fullVersion; 751 boolean showProfile; 752 boolean showModule; 753 boolean showSummary; 754 boolean apiOnly; 755 boolean showLabel; 756 boolean findJDKInternals; 757 boolean nowarning = false; 758 // default is to show package-level dependencies 759 // and filter references from same package 760 Analyzer.Type verbose = PACKAGE; 761 boolean filterSamePackage = true; 762 boolean filterSameArchive = false; 763 Pattern filterRegex; 764 String dotOutputDir; 765 String genModuleInfo; 766 String classpath = ""; 767 int depth = 1; 768 Set<String> requires = new HashSet<>(); 769 Set<String> packageNames = new HashSet<>(); 770 Pattern regex; // apply to the dependences 771 Pattern includePattern; // apply to classes 772 boolean compileTimeView = false; 773 boolean checkModuleDeps = false; 774 String upgradeModulePath; 775 String modulePath; 776 String rootModule; 777 // modules to be included or excluded 778 Set<String> includes = new HashSet<>(); 779 Set<String> excludes = new HashSet<>(); 780 781 boolean hasFilter() { 782 return numFilters() > 0; 783 } 784 785 int numFilters() { 786 int count = 0; 787 if (requires.size() > 0) count++; 788 if (regex != null) count++; 789 if (packageNames.size() > 0) count++; 790 return count; 791 } 792 793 boolean isRootModule() { 794 return rootModule != null; 795 } 796 } 797 private static class ResourceBundleHelper { 798 static final ResourceBundle versionRB; 799 static final ResourceBundle bundle; 800 static final ResourceBundle jdkinternals; 801 802 static { 803 Locale locale = Locale.getDefault(); 804 try { 805 bundle = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.jdeps", locale); 806 } catch (MissingResourceException e) { 807 throw new InternalError("Cannot find jdeps resource bundle for locale " + locale); 808 } 809 try { 810 versionRB = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.version"); 811 } catch (MissingResourceException e) { 812 throw new InternalError("version.resource.missing"); 813 } 814 try { 815 jdkinternals = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.jdkinternals"); 816 } catch (MissingResourceException e) { 817 throw new InternalError("Cannot find jdkinternals resource bundle"); 818 } 819 } 820 } 821 822 /* 823 * Returns the list of Archive specified in cpaths and not included 824 * initialArchives 825 */ 826 private List<Path> getClassPaths(String cpaths) throws IOException 827 { 828 if (cpaths.isEmpty()) { 829 return Collections.emptyList(); 830 } 831 List<Path> paths = new ArrayList<>(); 832 for (String p : cpaths.split(File.pathSeparator)) { 833 if (p.length() > 0) { 834 // wildcard to parse all JAR files e.g. -classpath dir/* 835 int i = p.lastIndexOf(".*"); 836 if (i > 0) { 837 Path dir = Paths.get(p.substring(0, i)); 838 try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.jar")) { 839 for (Path entry : stream) { 840 paths.add(entry); 841 } 842 } 843 } else { 844 paths.add(Paths.get(p)); 845 } 846 } 847 } 848 return paths; 849 } 850 851 /** 852 * Returns the recommended replacement API for the given classname; 853 * or return null if replacement API is not known. 854 */ 855 private String replacementFor(String cn) { 856 String name = cn; 857 String value = null; 858 while (value == null && name != null) { 859 try { 860 value = ResourceBundleHelper.jdkinternals.getString(name); 861 } catch (MissingResourceException e) { 862 // go up one subpackage level 863 int i = name.lastIndexOf('.'); 864 name = i > 0 ? name.substring(0, i) : null; 865 } 866 } 867 return value; 868 }; 869 870 private void showReplacements(List<Archive> archives, Analyzer analyzer) { 871 Map<String,String> jdkinternals = new TreeMap<>(); 872 boolean useInternals = false; 873 for (Archive source : archives) { 874 useInternals = useInternals || analyzer.hasDependences(source); 875 for (String cn : analyzer.dependences(source)) { 876 String repl = replacementFor(cn); 877 if (repl != null) { 878 jdkinternals.putIfAbsent(cn, repl); 879 } 880 } 881 } 882 if (useInternals) { 883 log.println(); 884 warning("warn.replace.useJDKInternals", getMessage("jdeps.wiki.url")); 885 } 886 if (!jdkinternals.isEmpty()) { 887 log.println(); 888 log.format("%-40s %s%n", "JDK Internal API", "Suggested Replacement"); 889 log.format("%-40s %s%n", "----------------", "---------------------"); 890 for (Map.Entry<String,String> e : jdkinternals.entrySet()) { 891 log.format("%-40s %s%n", e.getKey(), e.getValue()); 892 } 893 } 894 } 895 896 }