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