langtools/src/jdk.dev/share/classes/com/sun/tools/jdeps/JdepsTask.java
changeset 30848 c275389a3680
parent 30842 b02fa8bb730c
parent 30847 9385b9c8506b
child 30849 fcfa8eb95c23
equal deleted inserted replaced
30842:b02fa8bb730c 30848:c275389a3680
     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 package com.sun.tools.jdeps;
       
    26 
       
    27 import com.sun.tools.classfile.AccessFlags;
       
    28 import com.sun.tools.classfile.ClassFile;
       
    29 import com.sun.tools.classfile.ConstantPoolException;
       
    30 import com.sun.tools.classfile.Dependencies;
       
    31 import com.sun.tools.classfile.Dependencies.ClassFileError;
       
    32 import com.sun.tools.classfile.Dependency;
       
    33 import com.sun.tools.classfile.Dependency.Location;
       
    34 import static com.sun.tools.jdeps.Analyzer.Type.*;
       
    35 import java.io.*;
       
    36 import java.nio.file.DirectoryStream;
       
    37 import java.nio.file.Files;
       
    38 import java.nio.file.Path;
       
    39 import java.nio.file.Paths;
       
    40 import java.text.MessageFormat;
       
    41 import java.util.*;
       
    42 import java.util.regex.Pattern;
       
    43 
       
    44 /**
       
    45  * Implementation for the jdeps tool for static class dependency analysis.
       
    46  */
       
    47 class JdepsTask {
       
    48     static class BadArgs extends Exception {
       
    49         static final long serialVersionUID = 8765093759964640721L;
       
    50         BadArgs(String key, Object... args) {
       
    51             super(JdepsTask.getMessage(key, args));
       
    52             this.key = key;
       
    53             this.args = args;
       
    54         }
       
    55 
       
    56         BadArgs showUsage(boolean b) {
       
    57             showUsage = b;
       
    58             return this;
       
    59         }
       
    60         final String key;
       
    61         final Object[] args;
       
    62         boolean showUsage;
       
    63     }
       
    64 
       
    65     static abstract class Option {
       
    66         Option(boolean hasArg, String... aliases) {
       
    67             this.hasArg = hasArg;
       
    68             this.aliases = aliases;
       
    69         }
       
    70 
       
    71         boolean isHidden() {
       
    72             return false;
       
    73         }
       
    74 
       
    75         boolean matches(String opt) {
       
    76             for (String a : aliases) {
       
    77                 if (a.equals(opt))
       
    78                     return true;
       
    79                 if (hasArg && opt.startsWith(a + "="))
       
    80                     return true;
       
    81             }
       
    82             return false;
       
    83         }
       
    84 
       
    85         boolean ignoreRest() {
       
    86             return false;
       
    87         }
       
    88 
       
    89         abstract void process(JdepsTask task, String opt, String arg) throws BadArgs;
       
    90         final boolean hasArg;
       
    91         final String[] aliases;
       
    92     }
       
    93 
       
    94     static abstract class HiddenOption extends Option {
       
    95         HiddenOption(boolean hasArg, String... aliases) {
       
    96             super(hasArg, aliases);
       
    97         }
       
    98 
       
    99         boolean isHidden() {
       
   100             return true;
       
   101         }
       
   102     }
       
   103 
       
   104     static Option[] recognizedOptions = {
       
   105         new Option(false, "-h", "-?", "-help") {
       
   106             void process(JdepsTask task, String opt, String arg) {
       
   107                 task.options.help = true;
       
   108             }
       
   109         },
       
   110         new Option(true, "-dotoutput") {
       
   111             void process(JdepsTask task, String opt, String arg) throws BadArgs {
       
   112                 Path p = Paths.get(arg);
       
   113                 if (Files.exists(p) && (!Files.isDirectory(p) || !Files.isWritable(p))) {
       
   114                     throw new BadArgs("err.invalid.path", arg);
       
   115                 }
       
   116                 task.options.dotOutputDir = arg;
       
   117             }
       
   118         },
       
   119         new Option(false, "-s", "-summary") {
       
   120             void process(JdepsTask task, String opt, String arg) {
       
   121                 task.options.showSummary = true;
       
   122                 task.options.verbose = SUMMARY;
       
   123             }
       
   124         },
       
   125         new Option(false, "-v", "-verbose",
       
   126                           "-verbose:package",
       
   127                           "-verbose:class") {
       
   128             void process(JdepsTask task, String opt, String arg) throws BadArgs {
       
   129                 switch (opt) {
       
   130                     case "-v":
       
   131                     case "-verbose":
       
   132                         task.options.verbose = VERBOSE;
       
   133                         task.options.filterSameArchive = false;
       
   134                         task.options.filterSamePackage = false;
       
   135                         break;
       
   136                     case "-verbose:package":
       
   137                         task.options.verbose = PACKAGE;
       
   138                         break;
       
   139                     case "-verbose:class":
       
   140                         task.options.verbose = CLASS;
       
   141                         break;
       
   142                     default:
       
   143                         throw new BadArgs("err.invalid.arg.for.option", opt);
       
   144                 }
       
   145             }
       
   146         },
       
   147         new Option(true, "-cp", "-classpath") {
       
   148             void process(JdepsTask task, String opt, String arg) {
       
   149                 task.options.classpath = arg;
       
   150             }
       
   151         },
       
   152         new Option(true, "-p", "-package") {
       
   153             void process(JdepsTask task, String opt, String arg) {
       
   154                 task.options.packageNames.add(arg);
       
   155             }
       
   156         },
       
   157         new Option(true, "-e", "-regex") {
       
   158             void process(JdepsTask task, String opt, String arg) {
       
   159                 task.options.regex = arg;
       
   160             }
       
   161         },
       
   162 
       
   163         new Option(true, "-f", "-filter") {
       
   164             void process(JdepsTask task, String opt, String arg) {
       
   165                 task.options.filterRegex = arg;
       
   166             }
       
   167         },
       
   168         new Option(false, "-filter:package",
       
   169                           "-filter:archive",
       
   170                           "-filter:none") {
       
   171             void process(JdepsTask task, String opt, String arg) {
       
   172                 switch (opt) {
       
   173                     case "-filter:package":
       
   174                         task.options.filterSamePackage = true;
       
   175                         task.options.filterSameArchive = false;
       
   176                         break;
       
   177                     case "-filter:archive":
       
   178                         task.options.filterSameArchive = true;
       
   179                         task.options.filterSamePackage = false;
       
   180                         break;
       
   181                     case "-filter:none":
       
   182                         task.options.filterSameArchive = false;
       
   183                         task.options.filterSamePackage = false;
       
   184                         break;
       
   185                 }
       
   186             }
       
   187         },
       
   188         new Option(true, "-include") {
       
   189             void process(JdepsTask task, String opt, String arg) throws BadArgs {
       
   190                 task.options.includePattern = Pattern.compile(arg);
       
   191             }
       
   192         },
       
   193         new Option(false, "-P", "-profile") {
       
   194             void process(JdepsTask task, String opt, String arg) throws BadArgs {
       
   195                 task.options.showProfile = true;
       
   196                 task.options.showModule = false;
       
   197             }
       
   198         },
       
   199         new Option(false, "-M", "-module") {
       
   200             void process(JdepsTask task, String opt, String arg) throws BadArgs {
       
   201                 task.options.showModule = true;
       
   202                 task.options.showProfile = false;
       
   203             }
       
   204         },
       
   205         new Option(false, "-apionly") {
       
   206             void process(JdepsTask task, String opt, String arg) {
       
   207                 task.options.apiOnly = true;
       
   208             }
       
   209         },
       
   210         new Option(false, "-R", "-recursive") {
       
   211             void process(JdepsTask task, String opt, String arg) {
       
   212                 task.options.depth = 0;
       
   213                 // turn off filtering
       
   214                 task.options.filterSameArchive = false;
       
   215                 task.options.filterSamePackage = false;
       
   216             }
       
   217         },
       
   218         new Option(false, "-jdkinternals") {
       
   219             void process(JdepsTask task, String opt, String arg) {
       
   220                 task.options.findJDKInternals = true;
       
   221                 task.options.verbose = CLASS;
       
   222                 if (task.options.includePattern == null) {
       
   223                     task.options.includePattern = Pattern.compile(".*");
       
   224                 }
       
   225             }
       
   226         },
       
   227             new HiddenOption(false, "-verify:access") {
       
   228                 void process(JdepsTask task, String opt, String arg) {
       
   229                     task.options.verifyAccess = true;
       
   230                     task.options.verbose = VERBOSE;
       
   231                     task.options.filterSameArchive = false;
       
   232                     task.options.filterSamePackage = false;
       
   233                 }
       
   234             },
       
   235             new HiddenOption(true, "-mp") {
       
   236                 void process(JdepsTask task, String opt, String arg) throws BadArgs {
       
   237                     task.options.mpath = Paths.get(arg);
       
   238                     if (!Files.isDirectory(task.options.mpath)) {
       
   239                         throw new BadArgs("err.invalid.path", arg);
       
   240                     }
       
   241                     if (task.options.includePattern == null) {
       
   242                         task.options.includePattern = Pattern.compile(".*");
       
   243                     }
       
   244                 }
       
   245             },
       
   246         new Option(false, "-version") {
       
   247             void process(JdepsTask task, String opt, String arg) {
       
   248                 task.options.version = true;
       
   249             }
       
   250         },
       
   251         new HiddenOption(false, "-fullversion") {
       
   252             void process(JdepsTask task, String opt, String arg) {
       
   253                 task.options.fullVersion = true;
       
   254             }
       
   255         },
       
   256         new HiddenOption(false, "-showlabel") {
       
   257             void process(JdepsTask task, String opt, String arg) {
       
   258                 task.options.showLabel = true;
       
   259             }
       
   260         },
       
   261         new HiddenOption(false, "-q", "-quiet") {
       
   262             void process(JdepsTask task, String opt, String arg) {
       
   263                 task.options.nowarning = true;
       
   264             }
       
   265         },
       
   266         new HiddenOption(true, "-depth") {
       
   267             void process(JdepsTask task, String opt, String arg) throws BadArgs {
       
   268                 try {
       
   269                     task.options.depth = Integer.parseInt(arg);
       
   270                 } catch (NumberFormatException e) {
       
   271                     throw new BadArgs("err.invalid.arg.for.option", opt);
       
   272                 }
       
   273             }
       
   274         },
       
   275     };
       
   276 
       
   277     private static final String PROGNAME = "jdeps";
       
   278     private final Options options = new Options();
       
   279     private final List<String> classes = new ArrayList<>();
       
   280 
       
   281     private PrintWriter log;
       
   282     void setLog(PrintWriter out) {
       
   283         log = out;
       
   284     }
       
   285 
       
   286     /**
       
   287      * Result codes.
       
   288      */
       
   289     static final int EXIT_OK = 0, // Completed with no errors.
       
   290                      EXIT_ERROR = 1, // Completed but reported errors.
       
   291                      EXIT_CMDERR = 2, // Bad command-line arguments
       
   292                      EXIT_SYSERR = 3, // System error or resource exhaustion.
       
   293                      EXIT_ABNORMAL = 4;// terminated abnormally
       
   294 
       
   295     int run(String[] args) {
       
   296         if (log == null) {
       
   297             log = new PrintWriter(System.out);
       
   298         }
       
   299         try {
       
   300             handleOptions(args);
       
   301             if (options.help) {
       
   302                 showHelp();
       
   303             }
       
   304             if (options.version || options.fullVersion) {
       
   305                 showVersion(options.fullVersion);
       
   306             }
       
   307             if (classes.isEmpty() && options.includePattern == null) {
       
   308                 if (options.help || options.version || options.fullVersion) {
       
   309                     return EXIT_OK;
       
   310                 } else {
       
   311                     showHelp();
       
   312                     return EXIT_CMDERR;
       
   313                 }
       
   314             }
       
   315             if (options.regex != null && options.packageNames.size() > 0) {
       
   316                 showHelp();
       
   317                 return EXIT_CMDERR;
       
   318             }
       
   319             if ((options.findJDKInternals || options.verifyAccess) &&
       
   320                    (options.regex != null || options.packageNames.size() > 0 || options.showSummary)) {
       
   321                 showHelp();
       
   322                 return EXIT_CMDERR;
       
   323             }
       
   324             if (options.showSummary && options.verbose != SUMMARY) {
       
   325                 showHelp();
       
   326                 return EXIT_CMDERR;
       
   327             }
       
   328 
       
   329             boolean ok = run();
       
   330             return ok ? EXIT_OK : EXIT_ERROR;
       
   331         } catch (BadArgs e) {
       
   332             reportError(e.key, e.args);
       
   333             if (e.showUsage) {
       
   334                 log.println(getMessage("main.usage.summary", PROGNAME));
       
   335             }
       
   336             return EXIT_CMDERR;
       
   337         } catch (IOException e) {
       
   338             return EXIT_ABNORMAL;
       
   339         } finally {
       
   340             log.flush();
       
   341         }
       
   342     }
       
   343 
       
   344     private final List<Archive> sourceLocations = new ArrayList<>();
       
   345     private final List<Archive> classpaths = new ArrayList<>();
       
   346     private final List<Archive> initialArchives = new ArrayList<>();
       
   347     private boolean run() throws IOException {
       
   348         buildArchives();
       
   349 
       
   350         if (options.verifyAccess) {
       
   351             return verifyModuleAccess();
       
   352         } else {
       
   353             return analyzeDeps();
       
   354         }
       
   355     }
       
   356 
       
   357     private boolean analyzeDeps() throws IOException {
       
   358         Analyzer analyzer = new Analyzer(options.verbose, new Analyzer.Filter() {
       
   359             @Override
       
   360             public boolean accepts(Location origin, Archive originArchive,
       
   361                                    Location target, Archive targetArchive)
       
   362             {
       
   363                 if (options.findJDKInternals) {
       
   364                     // accepts target that is JDK class but not exported
       
   365                     return isJDKModule(targetArchive) &&
       
   366                               !((Module) targetArchive).isExported(target.getClassName());
       
   367                 } else if (options.filterSameArchive) {
       
   368                     // accepts origin and target that from different archive
       
   369                     return originArchive != targetArchive;
       
   370                 }
       
   371                 return true;
       
   372             }
       
   373         });
       
   374 
       
   375         // parse classfiles and find all dependencies
       
   376         findDependencies(options.apiOnly);
       
   377 
       
   378         // analyze the dependencies
       
   379         analyzer.run(sourceLocations);
       
   380 
       
   381         // output result
       
   382         if (options.dotOutputDir != null) {
       
   383             Path dir = Paths.get(options.dotOutputDir);
       
   384             Files.createDirectories(dir);
       
   385             generateDotFiles(dir, analyzer);
       
   386         } else {
       
   387             printRawOutput(log, analyzer);
       
   388         }
       
   389 
       
   390         if (options.findJDKInternals && !options.nowarning) {
       
   391             showReplacements(analyzer);
       
   392         }
       
   393         return true;
       
   394     }
       
   395 
       
   396     private boolean verifyModuleAccess() throws IOException {
       
   397         // two passes
       
   398         // 1. check API dependences where the types of dependences must be re-exported
       
   399         // 2. check all dependences where types must be accessible
       
   400 
       
   401         // pass 1
       
   402         findDependencies(true /* api only */);
       
   403         Analyzer analyzer = Analyzer.getExportedAPIsAnalyzer();
       
   404         boolean pass1 = analyzer.run(sourceLocations);
       
   405         if (!pass1) {
       
   406             System.out.println("ERROR: Failed API access verification");
       
   407         }
       
   408         // pass 2
       
   409         findDependencies(false);
       
   410         analyzer =  Analyzer.getModuleAccessAnalyzer();
       
   411         boolean pass2 = analyzer.run(sourceLocations);
       
   412         if (!pass2) {
       
   413             System.out.println("ERROR: Failed module access verification");
       
   414         }
       
   415         if (pass1 & pass2) {
       
   416             System.out.println("Access verification succeeded.");
       
   417         }
       
   418         return pass1 & pass2;
       
   419     }
       
   420 
       
   421     private void generateSummaryDotFile(Path dir, Analyzer analyzer) throws IOException {
       
   422         // If verbose mode (-v or -verbose option),
       
   423         // the summary.dot file shows package-level dependencies.
       
   424         Analyzer.Type summaryType =
       
   425             (options.verbose == PACKAGE || options.verbose == SUMMARY) ? SUMMARY : PACKAGE;
       
   426         Path summary = dir.resolve("summary.dot");
       
   427         try (PrintWriter sw = new PrintWriter(Files.newOutputStream(summary));
       
   428              SummaryDotFile dotfile = new SummaryDotFile(sw, summaryType)) {
       
   429             for (Archive archive : sourceLocations) {
       
   430                 if (!archive.isEmpty()) {
       
   431                     if (options.verbose == PACKAGE || options.verbose == SUMMARY) {
       
   432                         if (options.showLabel) {
       
   433                             // build labels listing package-level dependencies
       
   434                             analyzer.visitDependences(archive, dotfile.labelBuilder(), PACKAGE);
       
   435                         }
       
   436                     }
       
   437                     analyzer.visitDependences(archive, dotfile, summaryType);
       
   438                 }
       
   439             }
       
   440         }
       
   441     }
       
   442 
       
   443     private void generateDotFiles(Path dir, Analyzer analyzer) throws IOException {
       
   444         // output individual .dot file for each archive
       
   445         if (options.verbose != SUMMARY) {
       
   446             for (Archive archive : sourceLocations) {
       
   447                 if (analyzer.hasDependences(archive)) {
       
   448                     Path dotfile = dir.resolve(archive.getName() + ".dot");
       
   449                     try (PrintWriter pw = new PrintWriter(Files.newOutputStream(dotfile));
       
   450                          DotFileFormatter formatter = new DotFileFormatter(pw, archive)) {
       
   451                         analyzer.visitDependences(archive, formatter);
       
   452                     }
       
   453                 }
       
   454             }
       
   455         }
       
   456         // generate summary dot file
       
   457         generateSummaryDotFile(dir, analyzer);
       
   458     }
       
   459 
       
   460     private void printRawOutput(PrintWriter writer, Analyzer analyzer) {
       
   461         RawOutputFormatter depFormatter = new RawOutputFormatter(writer);
       
   462         RawSummaryFormatter summaryFormatter = new RawSummaryFormatter(writer);
       
   463         for (Archive archive : sourceLocations) {
       
   464             if (!archive.isEmpty()) {
       
   465                 analyzer.visitDependences(archive, summaryFormatter, SUMMARY);
       
   466                 if (analyzer.hasDependences(archive) && options.verbose != SUMMARY) {
       
   467                     analyzer.visitDependences(archive, depFormatter);
       
   468                 }
       
   469             }
       
   470         }
       
   471     }
       
   472 
       
   473     private boolean isValidClassName(String name) {
       
   474         if (!Character.isJavaIdentifierStart(name.charAt(0))) {
       
   475             return false;
       
   476         }
       
   477         for (int i=1; i < name.length(); i++) {
       
   478             char c = name.charAt(i);
       
   479             if (c != '.'  && !Character.isJavaIdentifierPart(c)) {
       
   480                 return false;
       
   481             }
       
   482         }
       
   483         return true;
       
   484     }
       
   485 
       
   486     /*
       
   487      * Dep Filter configured based on the input jdeps option
       
   488      * 1. -p and -regex to match target dependencies
       
   489      * 2. -filter:package to filter out same-package dependencies
       
   490      *
       
   491      * This filter is applied when jdeps parses the class files
       
   492      * and filtered dependencies are not stored in the Analyzer.
       
   493      *
       
   494      * -filter:archive is applied later in the Analyzer as the
       
   495      * containing archive of a target class may not be known until
       
   496      * the entire archive
       
   497      */
       
   498     class DependencyFilter implements Dependency.Filter {
       
   499         final Dependency.Filter filter;
       
   500         final Pattern filterPattern;
       
   501         DependencyFilter() {
       
   502             if (options.regex != null) {
       
   503                 this.filter = Dependencies.getRegexFilter(Pattern.compile(options.regex));
       
   504             } else if (options.packageNames.size() > 0) {
       
   505                 this.filter = Dependencies.getPackageFilter(options.packageNames, false);
       
   506             } else {
       
   507                 this.filter = null;
       
   508             }
       
   509 
       
   510             this.filterPattern =
       
   511                 options.filterRegex != null ? Pattern.compile(options.filterRegex) : null;
       
   512         }
       
   513         @Override
       
   514         public boolean accepts(Dependency d) {
       
   515             if (d.getOrigin().equals(d.getTarget())) {
       
   516                 return false;
       
   517             }
       
   518             String pn = d.getTarget().getPackageName();
       
   519             if (options.filterSamePackage && d.getOrigin().getPackageName().equals(pn)) {
       
   520                 return false;
       
   521             }
       
   522 
       
   523             if (filterPattern != null && filterPattern.matcher(pn).matches()) {
       
   524                 return false;
       
   525             }
       
   526             return filter != null ? filter.accepts(d) : true;
       
   527         }
       
   528     }
       
   529 
       
   530     /**
       
   531      * Tests if the given class matches the pattern given in the -include option
       
   532      */
       
   533     private boolean matches(String classname) {
       
   534         if (options.includePattern != null) {
       
   535             return options.includePattern.matcher(classname.replace('/', '.')).matches();
       
   536         } else {
       
   537             return true;
       
   538         }
       
   539     }
       
   540 
       
   541     private void buildArchives() throws IOException {
       
   542         for (String s : classes) {
       
   543             Path p = Paths.get(s);
       
   544             if (Files.exists(p)) {
       
   545                 initialArchives.add(Archive.getInstance(p));
       
   546             }
       
   547         }
       
   548         sourceLocations.addAll(initialArchives);
       
   549 
       
   550         classpaths.addAll(getClassPathArchives(options.classpath));
       
   551         if (options.includePattern != null) {
       
   552             initialArchives.addAll(classpaths);
       
   553         }
       
   554         classpaths.addAll(PlatformClassPath.getModules(options.mpath));
       
   555         if (options.mpath != null) {
       
   556             initialArchives.addAll(PlatformClassPath.getModules(options.mpath));
       
   557         } else {
       
   558             classpaths.addAll(PlatformClassPath.getJarFiles());
       
   559         }
       
   560         // add all classpath archives to the source locations for reporting
       
   561         sourceLocations.addAll(classpaths);
       
   562     }
       
   563 
       
   564     private void findDependencies(boolean apiOnly) throws IOException {
       
   565         Dependency.Finder finder =
       
   566             apiOnly ? Dependencies.getAPIFinder(AccessFlags.ACC_PROTECTED)
       
   567                     : Dependencies.getClassDependencyFinder();
       
   568         Dependency.Filter filter = new DependencyFilter();
       
   569 
       
   570         Deque<String> roots = new LinkedList<>();
       
   571         for (String s : classes) {
       
   572             Path p = Paths.get(s);
       
   573             if (!Files.exists(p)) {
       
   574                 if (isValidClassName(s)) {
       
   575                     roots.add(s);
       
   576                 } else {
       
   577                     warning("warn.invalid.arg", s);
       
   578                 }
       
   579             }
       
   580         }
       
   581 
       
   582         // Work queue of names of classfiles to be searched.
       
   583         // Entries will be unique, and for classes that do not yet have
       
   584         // dependencies in the results map.
       
   585         Deque<String> deque = new LinkedList<>();
       
   586         Set<String> doneClasses = new HashSet<>();
       
   587 
       
   588         // get the immediate dependencies of the input files
       
   589         for (Archive a : initialArchives) {
       
   590             for (ClassFile cf : a.reader().getClassFiles()) {
       
   591                 String classFileName;
       
   592                 try {
       
   593                     classFileName = cf.getName();
       
   594                 } catch (ConstantPoolException e) {
       
   595                     throw new ClassFileError(e);
       
   596                 }
       
   597 
       
   598                 // tests if this class matches the -include or -apiOnly option if specified
       
   599                 if (!matches(classFileName) || (apiOnly && !cf.access_flags.is(AccessFlags.ACC_PUBLIC))) {
       
   600                     continue;
       
   601                 }
       
   602 
       
   603                 if (!doneClasses.contains(classFileName)) {
       
   604                     doneClasses.add(classFileName);
       
   605                 }
       
   606 
       
   607                 for (Dependency d : finder.findDependencies(cf)) {
       
   608                     if (filter.accepts(d)) {
       
   609                         String cn = d.getTarget().getName();
       
   610                         if (!doneClasses.contains(cn) && !deque.contains(cn)) {
       
   611                             deque.add(cn);
       
   612                         }
       
   613                         a.addClass(d.getOrigin(), d.getTarget());
       
   614                     } else {
       
   615                         // ensure that the parsed class is added the archive
       
   616                         a.addClass(d.getOrigin());
       
   617                     }
       
   618                 }
       
   619                 for (String name : a.reader().skippedEntries()) {
       
   620                     warning("warn.skipped.entry", name, a.getPathName());
       
   621                 }
       
   622             }
       
   623         }
       
   624 
       
   625         // add Archive for looking up classes from the classpath
       
   626         // for transitive dependency analysis
       
   627         Deque<String> unresolved = roots;
       
   628         int depth = options.depth > 0 ? options.depth : Integer.MAX_VALUE;
       
   629         do {
       
   630             String name;
       
   631             while ((name = unresolved.poll()) != null) {
       
   632                 if (doneClasses.contains(name)) {
       
   633                     continue;
       
   634                 }
       
   635                 ClassFile cf = null;
       
   636                 for (Archive a : classpaths) {
       
   637                     cf = a.reader().getClassFile(name);
       
   638                     if (cf != null) {
       
   639                         String classFileName;
       
   640                         try {
       
   641                             classFileName = cf.getName();
       
   642                         } catch (ConstantPoolException e) {
       
   643                             throw new ClassFileError(e);
       
   644                         }
       
   645                         if (!doneClasses.contains(classFileName)) {
       
   646                             // if name is a fully-qualified class name specified
       
   647                             // from command-line, this class might already be parsed
       
   648                             doneClasses.add(classFileName);
       
   649 
       
   650                             for (Dependency d : finder.findDependencies(cf)) {
       
   651                                 if (depth == 0) {
       
   652                                     // ignore the dependency
       
   653                                     a.addClass(d.getOrigin());
       
   654                                     break;
       
   655                                 } else if (filter.accepts(d)) {
       
   656                                     a.addClass(d.getOrigin(), d.getTarget());
       
   657                                     String cn = d.getTarget().getName();
       
   658                                     if (!doneClasses.contains(cn) && !deque.contains(cn)) {
       
   659                                         deque.add(cn);
       
   660                                     }
       
   661                                 } else {
       
   662                                     // ensure that the parsed class is added the archive
       
   663                                     a.addClass(d.getOrigin());
       
   664                                 }
       
   665                             }
       
   666                         }
       
   667                         break;
       
   668                     }
       
   669                 }
       
   670                 if (cf == null) {
       
   671                     doneClasses.add(name);
       
   672                 }
       
   673             }
       
   674             unresolved = deque;
       
   675             deque = new LinkedList<>();
       
   676         } while (!unresolved.isEmpty() && depth-- > 0);
       
   677     }
       
   678 
       
   679     public void handleOptions(String[] args) throws BadArgs {
       
   680         // process options
       
   681         for (int i=0; i < args.length; i++) {
       
   682             if (args[i].charAt(0) == '-') {
       
   683                 String name = args[i];
       
   684                 Option option = getOption(name);
       
   685                 String param = null;
       
   686                 if (option.hasArg) {
       
   687                     if (name.startsWith("-") && name.indexOf('=') > 0) {
       
   688                         param = name.substring(name.indexOf('=') + 1, name.length());
       
   689                     } else if (i + 1 < args.length) {
       
   690                         param = args[++i];
       
   691                     }
       
   692                     if (param == null || param.isEmpty() || param.charAt(0) == '-') {
       
   693                         throw new BadArgs("err.missing.arg", name).showUsage(true);
       
   694                     }
       
   695                 }
       
   696                 option.process(this, name, param);
       
   697                 if (option.ignoreRest()) {
       
   698                     i = args.length;
       
   699                 }
       
   700             } else {
       
   701                 // process rest of the input arguments
       
   702                 for (; i < args.length; i++) {
       
   703                     String name = args[i];
       
   704                     if (name.charAt(0) == '-') {
       
   705                         throw new BadArgs("err.option.after.class", name).showUsage(true);
       
   706                     }
       
   707                     classes.add(name);
       
   708                 }
       
   709             }
       
   710         }
       
   711     }
       
   712 
       
   713     private Option getOption(String name) throws BadArgs {
       
   714         for (Option o : recognizedOptions) {
       
   715             if (o.matches(name)) {
       
   716                 return o;
       
   717             }
       
   718         }
       
   719         throw new BadArgs("err.unknown.option", name).showUsage(true);
       
   720     }
       
   721 
       
   722     private void reportError(String key, Object... args) {
       
   723         log.println(getMessage("error.prefix") + " " + getMessage(key, args));
       
   724     }
       
   725 
       
   726     private void warning(String key, Object... args) {
       
   727         log.println(getMessage("warn.prefix") + " " + getMessage(key, args));
       
   728     }
       
   729 
       
   730     private void showHelp() {
       
   731         log.println(getMessage("main.usage", PROGNAME));
       
   732         for (Option o : recognizedOptions) {
       
   733             String name = o.aliases[0].substring(1); // there must always be at least one name
       
   734             name = name.charAt(0) == '-' ? name.substring(1) : name;
       
   735             if (o.isHidden() || name.equals("h") || name.startsWith("filter:")) {
       
   736                 continue;
       
   737             }
       
   738             log.println(getMessage("main.opt." + name));
       
   739         }
       
   740     }
       
   741 
       
   742     private void showVersion(boolean full) {
       
   743         log.println(version(full ? "full" : "release"));
       
   744     }
       
   745 
       
   746     private String version(String key) {
       
   747         // key=version:  mm.nn.oo[-milestone]
       
   748         // key=full:     mm.mm.oo[-milestone]-build
       
   749         if (ResourceBundleHelper.versionRB == null) {
       
   750             return System.getProperty("java.version");
       
   751         }
       
   752         try {
       
   753             return ResourceBundleHelper.versionRB.getString(key);
       
   754         } catch (MissingResourceException e) {
       
   755             return getMessage("version.unknown", System.getProperty("java.version"));
       
   756         }
       
   757     }
       
   758 
       
   759     static String getMessage(String key, Object... args) {
       
   760         try {
       
   761             return MessageFormat.format(ResourceBundleHelper.bundle.getString(key), args);
       
   762         } catch (MissingResourceException e) {
       
   763             throw new InternalError("Missing message: " + key);
       
   764         }
       
   765     }
       
   766 
       
   767     private static class Options {
       
   768         boolean help;
       
   769         boolean version;
       
   770         boolean fullVersion;
       
   771         boolean showProfile;
       
   772         boolean showModule;
       
   773         boolean showSummary;
       
   774         boolean apiOnly;
       
   775         boolean showLabel;
       
   776         boolean findJDKInternals;
       
   777         boolean nowarning;
       
   778         // default is to show package-level dependencies
       
   779         // and filter references from same package
       
   780         Analyzer.Type verbose = PACKAGE;
       
   781         boolean filterSamePackage = true;
       
   782         boolean filterSameArchive = false;
       
   783         String filterRegex;
       
   784         String dotOutputDir;
       
   785         String classpath = "";
       
   786         int depth = 1;
       
   787         Set<String> packageNames = new HashSet<>();
       
   788         String regex;             // apply to the dependences
       
   789         Pattern includePattern;   // apply to classes
       
   790         // module boundary access check
       
   791         boolean verifyAccess;
       
   792         Path mpath;
       
   793     }
       
   794     private static class ResourceBundleHelper {
       
   795         static final ResourceBundle versionRB;
       
   796         static final ResourceBundle bundle;
       
   797         static final ResourceBundle jdkinternals;
       
   798 
       
   799         static {
       
   800             Locale locale = Locale.getDefault();
       
   801             try {
       
   802                 bundle = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.jdeps", locale);
       
   803             } catch (MissingResourceException e) {
       
   804                 throw new InternalError("Cannot find jdeps resource bundle for locale " + locale);
       
   805             }
       
   806             try {
       
   807                 versionRB = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.version");
       
   808             } catch (MissingResourceException e) {
       
   809                 throw new InternalError("version.resource.missing");
       
   810             }
       
   811             try {
       
   812                 jdkinternals = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.jdkinternals");
       
   813             } catch (MissingResourceException e) {
       
   814                 throw new InternalError("Cannot find jdkinternals resource bundle");
       
   815             }
       
   816         }
       
   817     }
       
   818 
       
   819     /*
       
   820      * Returns the list of Archive specified in cpaths and not included
       
   821      * initialArchives
       
   822      */
       
   823     private List<Archive> getClassPathArchives(String cpaths)
       
   824             throws IOException
       
   825     {
       
   826         List<Archive> result = new ArrayList<>();
       
   827         if (cpaths.isEmpty()) {
       
   828             return result;
       
   829         }
       
   830         List<Path> paths = new ArrayList<>();
       
   831         for (String p : cpaths.split(File.pathSeparator)) {
       
   832             if (p.length() > 0) {
       
   833                 // wildcard to parse all JAR files e.g. -classpath dir/*
       
   834                 int i = p.lastIndexOf(".*");
       
   835                 if (i > 0) {
       
   836                     Path dir = Paths.get(p.substring(0, i));
       
   837                     try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.jar")) {
       
   838                         for (Path entry : stream) {
       
   839                             paths.add(entry);
       
   840                         }
       
   841                     }
       
   842                 } else {
       
   843                     paths.add(Paths.get(p));
       
   844                 }
       
   845             }
       
   846         }
       
   847         for (Path path : paths) {
       
   848             boolean found = initialArchives.stream()
       
   849                                            .map(Archive::path)
       
   850                                            .anyMatch(p -> isSameFile(path, p));
       
   851             if (!found && Files.exists(path)) {
       
   852                 result.add(Archive.getInstance(path));
       
   853             }
       
   854         }
       
   855         return result;
       
   856     }
       
   857 
       
   858     private boolean isSameFile(Path p1, Path p2) {
       
   859         try {
       
   860             return Files.isSameFile(p1, p2);
       
   861         } catch (IOException e) {
       
   862             throw new UncheckedIOException(e);
       
   863         }
       
   864     }
       
   865 
       
   866     class RawOutputFormatter implements Analyzer.Visitor {
       
   867         private final PrintWriter writer;
       
   868         private String pkg = "";
       
   869         RawOutputFormatter(PrintWriter writer) {
       
   870             this.writer = writer;
       
   871         }
       
   872         @Override
       
   873         public void visitDependence(String origin, Archive originArchive,
       
   874                                     String target, Archive targetArchive) {
       
   875             String tag = toTag(target, targetArchive);
       
   876             if (options.verbose == VERBOSE) {
       
   877                 writer.format("   %-50s -> %-50s %s%n", origin, target, tag);
       
   878             } else {
       
   879                 if (!origin.equals(pkg)) {
       
   880                     pkg = origin;
       
   881                     writer.format("   %s (%s)%n", origin, originArchive.getName());
       
   882                 }
       
   883                 writer.format("      -> %-50s %s%n", target, tag);
       
   884             }
       
   885         }
       
   886     }
       
   887 
       
   888     class RawSummaryFormatter implements Analyzer.Visitor {
       
   889         private final PrintWriter writer;
       
   890         RawSummaryFormatter(PrintWriter writer) {
       
   891             this.writer = writer;
       
   892         }
       
   893         @Override
       
   894         public void visitDependence(String origin, Archive originArchive,
       
   895                                     String target, Archive targetArchive) {
       
   896             String targetName =  targetArchive.getPathName();
       
   897             if (options.showModule && isJDKModule(targetArchive)) {
       
   898                 targetName = ((Module)targetArchive).name();
       
   899             }
       
   900             writer.format("%s -> %s", originArchive.getName(), targetName);
       
   901             if (options.showProfile && isJDKModule(targetArchive)) {
       
   902                 writer.format(" (%s)", target);
       
   903             }
       
   904             writer.format("%n");
       
   905         }
       
   906     }
       
   907 
       
   908     class DotFileFormatter implements Analyzer.Visitor, AutoCloseable {
       
   909         private final PrintWriter writer;
       
   910         private final String name;
       
   911         DotFileFormatter(PrintWriter writer, Archive archive) {
       
   912             this.writer = writer;
       
   913             this.name = archive.getName();
       
   914             writer.format("digraph \"%s\" {%n", name);
       
   915             writer.format("    // Path: %s%n", archive.getPathName());
       
   916         }
       
   917 
       
   918         @Override
       
   919         public void close() {
       
   920             writer.println("}");
       
   921         }
       
   922 
       
   923         @Override
       
   924         public void visitDependence(String origin, Archive originArchive,
       
   925                                     String target, Archive targetArchive) {
       
   926             String tag = toTag(target, targetArchive);
       
   927             writer.format("   %-50s -> \"%s\";%n",
       
   928                           String.format("\"%s\"", origin),
       
   929                           tag.isEmpty() ? target
       
   930                                         : String.format("%s (%s)", target, tag));
       
   931         }
       
   932     }
       
   933 
       
   934     class SummaryDotFile implements Analyzer.Visitor, AutoCloseable {
       
   935         private final PrintWriter writer;
       
   936         private final Analyzer.Type type;
       
   937         private final Map<Archive, Map<Archive,StringBuilder>> edges = new HashMap<>();
       
   938         SummaryDotFile(PrintWriter writer, Analyzer.Type type) {
       
   939             this.writer = writer;
       
   940             this.type = type;
       
   941             writer.format("digraph \"summary\" {%n");
       
   942         }
       
   943 
       
   944         @Override
       
   945         public void close() {
       
   946             writer.println("}");
       
   947         }
       
   948 
       
   949         @Override
       
   950         public void visitDependence(String origin, Archive originArchive,
       
   951                                     String target, Archive targetArchive) {
       
   952             String targetName = type == PACKAGE ? target : targetArchive.getName();
       
   953             if (isJDKModule(targetArchive)) {
       
   954                 Module m = (Module)targetArchive;
       
   955                 String n = showProfileOrModule(m);
       
   956                 if (!n.isEmpty()) {
       
   957                     targetName += " (" + n + ")";
       
   958                 }
       
   959             } else if (type == PACKAGE) {
       
   960                 targetName += " (" + targetArchive.getName() + ")";
       
   961             }
       
   962             String label = getLabel(originArchive, targetArchive);
       
   963             writer.format("  %-50s -> \"%s\"%s;%n",
       
   964                           String.format("\"%s\"", origin), targetName, label);
       
   965         }
       
   966 
       
   967         String getLabel(Archive origin, Archive target) {
       
   968             if (edges.isEmpty())
       
   969                 return "";
       
   970 
       
   971             StringBuilder label = edges.get(origin).get(target);
       
   972             return label == null ? "" : String.format(" [label=\"%s\",fontsize=9]", label.toString());
       
   973         }
       
   974 
       
   975         Analyzer.Visitor labelBuilder() {
       
   976             // show the package-level dependencies as labels in the dot graph
       
   977             return new Analyzer.Visitor() {
       
   978                 @Override
       
   979                 public void visitDependence(String origin, Archive originArchive, String target, Archive targetArchive) {
       
   980                     edges.putIfAbsent(originArchive, new HashMap<>());
       
   981                     edges.get(originArchive).putIfAbsent(targetArchive, new StringBuilder());
       
   982                     StringBuilder sb = edges.get(originArchive).get(targetArchive);
       
   983                     String tag = toTag(target, targetArchive);
       
   984                     addLabel(sb, origin, target, tag);
       
   985                 }
       
   986 
       
   987                 void addLabel(StringBuilder label, String origin, String target, String tag) {
       
   988                     label.append(origin).append(" -> ").append(target);
       
   989                     if (!tag.isEmpty()) {
       
   990                         label.append(" (" + tag + ")");
       
   991                     }
       
   992                     label.append("\\n");
       
   993                 }
       
   994             };
       
   995         }
       
   996     }
       
   997 
       
   998     /**
       
   999      * Test if the given archive is part of the JDK
       
  1000      */
       
  1001     private boolean isJDKModule(Archive archive) {
       
  1002         return Module.class.isInstance(archive);
       
  1003     }
       
  1004 
       
  1005     /**
       
  1006      * If the given archive is JDK archive, this method returns the profile name
       
  1007      * only if -profile option is specified; it accesses a private JDK API and
       
  1008      * the returned value will have "JDK internal API" prefix
       
  1009      *
       
  1010      * For non-JDK archives, this method returns the file name of the archive.
       
  1011      */
       
  1012     private String toTag(String name, Archive source) {
       
  1013         if (!isJDKModule(source)) {
       
  1014             return source.getName();
       
  1015         }
       
  1016 
       
  1017         Module module = (Module)source;
       
  1018         boolean isExported = false;
       
  1019         if (options.verbose == CLASS || options.verbose == VERBOSE) {
       
  1020             isExported = module.isExported(name);
       
  1021         } else {
       
  1022             isExported = module.isExportedPackage(name);
       
  1023         }
       
  1024         if (isExported) {
       
  1025             // exported API
       
  1026             return showProfileOrModule(module);
       
  1027         } else {
       
  1028             return "JDK internal API (" + source.getName() + ")";
       
  1029         }
       
  1030     }
       
  1031 
       
  1032     private String showProfileOrModule(Module m) {
       
  1033         String tag = "";
       
  1034         if (options.showProfile) {
       
  1035             Profile p = Profile.getProfile(m);
       
  1036             if (p != null) {
       
  1037                 tag = p.profileName();
       
  1038             }
       
  1039         } else if (options.showModule) {
       
  1040             tag = m.name();
       
  1041         }
       
  1042         return tag;
       
  1043     }
       
  1044 
       
  1045     private Profile getProfile(String name) {
       
  1046         String pn = name;
       
  1047         if (options.verbose == CLASS || options.verbose == VERBOSE) {
       
  1048             int i = name.lastIndexOf('.');
       
  1049             pn = i > 0 ? name.substring(0, i) : "";
       
  1050         }
       
  1051         return Profile.getProfile(pn);
       
  1052     }
       
  1053 
       
  1054     /**
       
  1055      * Returns the recommended replacement API for the given classname;
       
  1056      * or return null if replacement API is not known.
       
  1057      */
       
  1058     private String replacementFor(String cn) {
       
  1059         String name = cn;
       
  1060         String value = null;
       
  1061         while (value == null && name != null) {
       
  1062             try {
       
  1063                 value = ResourceBundleHelper.jdkinternals.getString(name);
       
  1064             } catch (MissingResourceException e) {
       
  1065                 // go up one subpackage level
       
  1066                 int i = name.lastIndexOf('.');
       
  1067                 name = i > 0 ? name.substring(0, i) : null;
       
  1068             }
       
  1069         }
       
  1070         return value;
       
  1071     };
       
  1072 
       
  1073     private void showReplacements(Analyzer analyzer) {
       
  1074         Map<String,String> jdkinternals = new TreeMap<>();
       
  1075         boolean useInternals = false;
       
  1076         for (Archive source : sourceLocations) {
       
  1077             useInternals = useInternals || analyzer.hasDependences(source);
       
  1078             for (String cn : analyzer.dependences(source)) {
       
  1079                 String repl = replacementFor(cn);
       
  1080                 if (repl != null) {
       
  1081                     jdkinternals.putIfAbsent(cn, repl);
       
  1082                 }
       
  1083             }
       
  1084         }
       
  1085         if (useInternals) {
       
  1086             log.println();
       
  1087             warning("warn.replace.useJDKInternals", getMessage("jdeps.wiki.url"));
       
  1088         }
       
  1089         if (!jdkinternals.isEmpty()) {
       
  1090             log.println();
       
  1091             log.format("%-40s %s%n", "JDK Internal API", "Suggested Replacement");
       
  1092             log.format("%-40s %s%n", "----------------", "---------------------");
       
  1093             for (Map.Entry<String,String> e : jdkinternals.entrySet()) {
       
  1094                 log.format("%-40s %s%n", e.getKey(), e.getValue());
       
  1095             }
       
  1096         }
       
  1097 
       
  1098     }
       
  1099 }