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