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 }