JdepsTask.java revision 3294:9adfb22ff08f
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.*;
30import static com.sun.tools.jdeps.ModuleAnalyzer.Graph;
31
32import java.io.File;
33import java.io.IOException;
34import java.io.PrintWriter;
35import java.lang.module.ResolutionException;
36import java.nio.file.DirectoryStream;
37import java.nio.file.Files;
38import java.nio.file.Path;
39import java.nio.file.Paths;
40import java.text.MessageFormat;
41import java.util.ArrayList;
42import java.util.Arrays;
43import java.util.Collections;
44import java.util.HashSet;
45import java.util.List;
46import java.util.Locale;
47import java.util.Map;
48import java.util.MissingResourceException;
49import java.util.Optional;
50import java.util.ResourceBundle;
51import java.util.Set;
52import java.util.TreeMap;
53import java.util.regex.Pattern;
54import java.util.stream.Collectors;
55import java.util.stream.Stream;
56
57/**
58 * Implementation for the jdeps tool for static class dependency analysis.
59 */
60class 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}
897