src/share/classes/com/sun/tools/jdeps/JdepsTask.java

Thu, 24 May 2018 18:02:46 +0800

author
aoqi
date
Thu, 24 May 2018 18:02:46 +0800
changeset 3446
e468915bad3a
parent 3368
f206126308bc
parent 2893
ca5783d9a597
permissions
-rw-r--r--

Merge

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

mercurial