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

Mon, 16 Oct 2017 16:07:48 +0800

author
aoqi
date
Mon, 16 Oct 2017 16:07:48 +0800
changeset 2893
ca5783d9a597
parent 2802
6b43535fb9f8
parent 2702
9ca8d8713094
child 3446
e468915bad3a
permissions
-rw-r--r--

merge

     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;
    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         // Work queue of names of classfiles to be searched.
   519         // Entries will be unique, and for classes that do not yet have
   520         // dependencies in the results map.
   521         Deque<String> deque = new LinkedList<>();
   522         Set<String> doneClasses = new HashSet<>();
   524         // get the immediate dependencies of the input files
   525         for (Archive a : archives) {
   526             for (ClassFile cf : a.reader().getClassFiles()) {
   527                 String classFileName;
   528                 try {
   529                     classFileName = cf.getName();
   530                 } catch (ConstantPoolException e) {
   531                     throw new ClassFileError(e);
   532                 }
   534                 // tests if this class matches the -include or -apiOnly option if specified
   535                 if (!matches(classFileName, cf.access_flags)) {
   536                     continue;
   537                 }
   539                 if (!doneClasses.contains(classFileName)) {
   540                     doneClasses.add(classFileName);
   541                 }
   543                 for (Dependency d : finder.findDependencies(cf)) {
   544                     if (filter.accepts(d)) {
   545                         String cn = d.getTarget().getName();
   546                         if (!doneClasses.contains(cn) && !deque.contains(cn)) {
   547                             deque.add(cn);
   548                         }
   549                         a.addClass(d.getOrigin(), d.getTarget());
   550                     } else {
   551                         // ensure that the parsed class is added the archive
   552                         a.addClass(d.getOrigin());
   553                     }
   554                 }
   555                 for (String name : a.reader().skippedEntries()) {
   556                     warning("warn.skipped.entry", name, a.getPathName());
   557                 }
   558             }
   559         }
   561         // add Archive for looking up classes from the classpath
   562         // for transitive dependency analysis
   563         Deque<String> unresolved = roots;
   564         int depth = options.depth > 0 ? options.depth : Integer.MAX_VALUE;
   565         do {
   566             String name;
   567             while ((name = unresolved.poll()) != null) {
   568                 if (doneClasses.contains(name)) {
   569                     continue;
   570                 }
   571                 ClassFile cf = null;
   572                 for (Archive a : classpaths) {
   573                     cf = a.reader().getClassFile(name);
   574                     if (cf != null) {
   575                         String classFileName;
   576                         try {
   577                             classFileName = cf.getName();
   578                         } catch (ConstantPoolException e) {
   579                             throw new ClassFileError(e);
   580                         }
   581                         if (!doneClasses.contains(classFileName)) {
   582                             // if name is a fully-qualified class name specified
   583                             // from command-line, this class might already be parsed
   584                             doneClasses.add(classFileName);
   585                             // process @jdk.Exported for JDK classes
   586                             if (isJDKArchive(a)) {
   587                                 ((JDKArchive)a).processJdkExported(cf);
   588                             }
   589                             for (Dependency d : finder.findDependencies(cf)) {
   590                                 if (depth == 0) {
   591                                     // ignore the dependency
   592                                     a.addClass(d.getOrigin());
   593                                     break;
   594                                 } else if (filter.accepts(d)) {
   595                                     a.addClass(d.getOrigin(), d.getTarget());
   596                                     String cn = d.getTarget().getName();
   597                                     if (!doneClasses.contains(cn) && !deque.contains(cn)) {
   598                                         deque.add(cn);
   599                                     }
   600                                 } else {
   601                                     // ensure that the parsed class is added the archive
   602                                     a.addClass(d.getOrigin());
   603                                 }
   604                             }
   605                         }
   606                         break;
   607                     }
   608                 }
   609                 if (cf == null) {
   610                     doneClasses.add(name);
   611                 }
   612             }
   613             unresolved = deque;
   614             deque = new LinkedList<>();
   615         } while (!unresolved.isEmpty() && depth-- > 0);
   616     }
   618     public void handleOptions(String[] args) throws BadArgs {
   619         // process options
   620         for (int i=0; i < args.length; i++) {
   621             if (args[i].charAt(0) == '-') {
   622                 String name = args[i];
   623                 Option option = getOption(name);
   624                 String param = null;
   625                 if (option.hasArg) {
   626                     if (name.startsWith("-") && name.indexOf('=') > 0) {
   627                         param = name.substring(name.indexOf('=') + 1, name.length());
   628                     } else if (i + 1 < args.length) {
   629                         param = args[++i];
   630                     }
   631                     if (param == null || param.isEmpty() || param.charAt(0) == '-') {
   632                         throw new BadArgs("err.missing.arg", name).showUsage(true);
   633                     }
   634                 }
   635                 option.process(this, name, param);
   636                 if (option.ignoreRest()) {
   637                     i = args.length;
   638                 }
   639             } else {
   640                 // process rest of the input arguments
   641                 for (; i < args.length; i++) {
   642                     String name = args[i];
   643                     if (name.charAt(0) == '-') {
   644                         throw new BadArgs("err.option.after.class", name).showUsage(true);
   645                     }
   646                     classes.add(name);
   647                 }
   648             }
   649         }
   650     }
   652     private Option getOption(String name) throws BadArgs {
   653         for (Option o : recognizedOptions) {
   654             if (o.matches(name)) {
   655                 return o;
   656             }
   657         }
   658         throw new BadArgs("err.unknown.option", name).showUsage(true);
   659     }
   661     private void reportError(String key, Object... args) {
   662         log.println(getMessage("error.prefix") + " " + getMessage(key, args));
   663     }
   665     private void warning(String key, Object... args) {
   666         log.println(getMessage("warn.prefix") + " " + getMessage(key, args));
   667     }
   669     private void showHelp() {
   670         log.println(getMessage("main.usage", PROGNAME));
   671         for (Option o : recognizedOptions) {
   672             String name = o.aliases[0].substring(1); // there must always be at least one name
   673             name = name.charAt(0) == '-' ? name.substring(1) : name;
   674             if (o.isHidden() || name.equals("h") || name.startsWith("filter:")) {
   675                 continue;
   676             }
   677             log.println(getMessage("main.opt." + name));
   678         }
   679     }
   681     private void showVersion(boolean full) {
   682         log.println(version(full ? "full" : "release"));
   683     }
   685     private String version(String key) {
   686         // key=version:  mm.nn.oo[-milestone]
   687         // key=full:     mm.mm.oo[-milestone]-build
   688         if (ResourceBundleHelper.versionRB == null) {
   689             return System.getProperty("java.version");
   690         }
   691         try {
   692             return ResourceBundleHelper.versionRB.getString(key);
   693         } catch (MissingResourceException e) {
   694             return getMessage("version.unknown", System.getProperty("java.version"));
   695         }
   696     }
   698     static String getMessage(String key, Object... args) {
   699         try {
   700             return MessageFormat.format(ResourceBundleHelper.bundle.getString(key), args);
   701         } catch (MissingResourceException e) {
   702             throw new InternalError("Missing message: " + key);
   703         }
   704     }
   706     private static class Options {
   707         boolean help;
   708         boolean version;
   709         boolean fullVersion;
   710         boolean showProfile;
   711         boolean showSummary;
   712         boolean apiOnly;
   713         boolean showLabel;
   714         boolean findJDKInternals;
   715         boolean nowarning;
   716         // default is to show package-level dependencies
   717         // and filter references from same package
   718         Analyzer.Type verbose = PACKAGE;
   719         boolean filterSamePackage = true;
   720         boolean filterSameArchive = false;
   721         String filterRegex;
   722         String dotOutputDir;
   723         String classpath = "";
   724         int depth = 1;
   725         Set<String> packageNames = new HashSet<>();
   726         String regex;             // apply to the dependences
   727         Pattern includePattern;   // apply to classes
   728     }
   729     private static class ResourceBundleHelper {
   730         static final ResourceBundle versionRB;
   731         static final ResourceBundle bundle;
   732         static final ResourceBundle jdkinternals;
   734         static {
   735             Locale locale = Locale.getDefault();
   736             try {
   737                 bundle = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.jdeps", locale);
   738             } catch (MissingResourceException e) {
   739                 throw new InternalError("Cannot find jdeps resource bundle for locale " + locale);
   740             }
   741             try {
   742                 versionRB = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.version");
   743             } catch (MissingResourceException e) {
   744                 throw new InternalError("version.resource.missing");
   745             }
   746             try {
   747                 jdkinternals = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.jdkinternals");
   748             } catch (MissingResourceException e) {
   749                 throw new InternalError("Cannot find jdkinternals resource bundle");
   750             }
   751         }
   752     }
   754     /*
   755      * Returns the list of Archive specified in cpaths and not included
   756      * initialArchives
   757      */
   758     private List<Archive> getClassPathArchives(String cpaths, List<Path> initialArchives)
   759             throws IOException
   760     {
   761         List<Archive> result = new ArrayList<>();
   762         if (cpaths.isEmpty()) {
   763             return result;
   764         }
   766         List<Path> paths = new ArrayList<>();
   767         for (String p : cpaths.split(File.pathSeparator)) {
   768             if (p.length() > 0) {
   769                 // wildcard to parse all JAR files e.g. -classpath dir/*
   770                 int i = p.lastIndexOf(".*");
   771                 if (i > 0) {
   772                     Path dir = Paths.get(p.substring(0, i));
   773                     try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.jar")) {
   774                         for (Path entry : stream) {
   775                             paths.add(entry);
   776                         }
   777                     }
   778                 } else {
   779                     paths.add(Paths.get(p));
   780                 }
   781             }
   782         }
   783         for (Path p : paths) {
   784             if (Files.exists(p) && !hasSameFile(initialArchives, p)) {
   785                 result.add(Archive.getInstance(p));
   786             }
   787         }
   788         return result;
   789     }
   791     private boolean hasSameFile(List<Path> paths, Path p2) throws IOException {
   792         for (Path p1 : paths) {
   793             if (Files.isSameFile(p1, p2)) {
   794                 return true;
   795             }
   796         }
   797         return false;
   798     }
   800     class RawOutputFormatter implements Analyzer.Visitor {
   801         private final PrintWriter writer;
   802         private String pkg = "";
   803         RawOutputFormatter(PrintWriter writer) {
   804             this.writer = writer;
   805         }
   806         @Override
   807         public void visitDependence(String origin, Archive originArchive,
   808                                     String target, Archive targetArchive) {
   809             String tag = toTag(target, targetArchive);
   810             if (options.verbose == VERBOSE) {
   811                 writer.format("   %-50s -> %-50s %s%n", origin, target, tag);
   812             } else {
   813                 if (!origin.equals(pkg)) {
   814                     pkg = origin;
   815                     writer.format("   %s (%s)%n", origin, originArchive.getName());
   816                 }
   817                 writer.format("      -> %-50s %s%n", target, tag);
   818             }
   819         }
   820     }
   822     class RawSummaryFormatter implements Analyzer.Visitor {
   823         private final PrintWriter writer;
   824         RawSummaryFormatter(PrintWriter writer) {
   825             this.writer = writer;
   826         }
   827         @Override
   828         public void visitDependence(String origin, Archive originArchive,
   829                                     String target, Archive targetArchive) {
   830             writer.format("%s -> %s", originArchive.getName(), targetArchive.getPathName());
   831             if (options.showProfile && JDKArchive.isProfileArchive(targetArchive)) {
   832                 writer.format(" (%s)", target);
   833             }
   834             writer.format("%n");
   835         }
   836     }
   838     class DotFileFormatter implements Analyzer.Visitor, AutoCloseable {
   839         private final PrintWriter writer;
   840         private final String name;
   841         DotFileFormatter(PrintWriter writer, Archive archive) {
   842             this.writer = writer;
   843             this.name = archive.getName();
   844             writer.format("digraph \"%s\" {%n", name);
   845             writer.format("    // Path: %s%n", archive.getPathName());
   846         }
   848         @Override
   849         public void close() {
   850             writer.println("}");
   851         }
   853         @Override
   854         public void visitDependence(String origin, Archive originArchive,
   855                                     String target, Archive targetArchive) {
   856             String tag = toTag(target, targetArchive);
   857             writer.format("   %-50s -> \"%s\";%n",
   858                           String.format("\"%s\"", origin),
   859                           tag.isEmpty() ? target
   860                                         : String.format("%s (%s)", target, tag));
   861         }
   862     }
   864     class SummaryDotFile implements Analyzer.Visitor, AutoCloseable {
   865         private final PrintWriter writer;
   866         private final Analyzer.Type type;
   867         private final Map<Archive, Map<Archive,StringBuilder>> edges = new HashMap<>();
   868         SummaryDotFile(PrintWriter writer, Analyzer.Type type) {
   869             this.writer = writer;
   870             this.type = type;
   871             writer.format("digraph \"summary\" {%n");
   872         }
   874         @Override
   875         public void close() {
   876             writer.println("}");
   877         }
   879         @Override
   880         public void visitDependence(String origin, Archive originArchive,
   881                                     String target, Archive targetArchive) {
   882             String targetName = type == PACKAGE ? target : targetArchive.getName();
   883             if (type == PACKAGE) {
   884                 String tag = toTag(target, targetArchive, type);
   885                 if (!tag.isEmpty())
   886                     targetName += " (" + tag + ")";
   887             } else if (options.showProfile && JDKArchive.isProfileArchive(targetArchive)) {
   888                 targetName += " (" + target + ")";
   889             }
   890             String label = getLabel(originArchive, targetArchive);
   891             writer.format("  %-50s -> \"%s\"%s;%n",
   892                           String.format("\"%s\"", origin), targetName, label);
   893         }
   895         String getLabel(Archive origin, Archive target) {
   896             if (edges.isEmpty())
   897                 return "";
   899             StringBuilder label = edges.get(origin).get(target);
   900             return label == null ? "" : String.format(" [label=\"%s\",fontsize=9]", label.toString());
   901         }
   903         Analyzer.Visitor labelBuilder() {
   904             // show the package-level dependencies as labels in the dot graph
   905             return new Analyzer.Visitor() {
   906                 @Override
   907                 public void visitDependence(String origin, Archive originArchive,
   908                                             String target, Archive targetArchive)
   909                 {
   910                     Map<Archive,StringBuilder> labels = edges.get(originArchive);
   911                     if (!edges.containsKey(originArchive)) {
   912                         edges.put(originArchive, labels = new HashMap<>());
   913                     }
   914                     StringBuilder sb = labels.get(targetArchive);
   915                     if (sb == null) {
   916                         labels.put(targetArchive, sb = new StringBuilder());
   917                     }
   918                     String tag = toTag(target, targetArchive, PACKAGE);
   919                     addLabel(sb, origin, target, tag);
   920                 }
   922                 void addLabel(StringBuilder label, String origin, String target, String tag) {
   923                     label.append(origin).append(" -> ").append(target);
   924                     if (!tag.isEmpty()) {
   925                         label.append(" (" + tag + ")");
   926                     }
   927                     label.append("\\n");
   928                 }
   929             };
   930         }
   931     }
   933     /**
   934      * Test if the given archive is part of the JDK
   935      */
   936     private boolean isJDKArchive(Archive archive) {
   937         return JDKArchive.class.isInstance(archive);
   938     }
   940     /**
   941      * If the given archive is JDK archive, this method returns the profile name
   942      * only if -profile option is specified; it accesses a private JDK API and
   943      * the returned value will have "JDK internal API" prefix
   944      *
   945      * For non-JDK archives, this method returns the file name of the archive.
   946      */
   947     private String toTag(String name, Archive source, Analyzer.Type type) {
   948         if (!isJDKArchive(source)) {
   949             return source.getName();
   950         }
   952         JDKArchive jdk = (JDKArchive)source;
   953         boolean isExported = false;
   954         if (type == CLASS || type == VERBOSE) {
   955             isExported = jdk.isExported(name);
   956         } else {
   957             isExported = jdk.isExportedPackage(name);
   958         }
   959         Profile p = getProfile(name, type);
   960         if (isExported) {
   961             // exported API
   962             return options.showProfile && p != null ? p.profileName() : "";
   963         } else {
   964             return "JDK internal API (" + source.getName() + ")";
   965         }
   966     }
   968     private String toTag(String name, Archive source) {
   969         return toTag(name, source, options.verbose);
   970     }
   972     private Profile getProfile(String name, Analyzer.Type type) {
   973         String pn = name;
   974         if (type == CLASS || type == VERBOSE) {
   975             int i = name.lastIndexOf('.');
   976             pn = i > 0 ? name.substring(0, i) : "";
   977         }
   978         return Profile.getProfile(pn);
   979     }
   981     /**
   982      * Returns the recommended replacement API for the given classname;
   983      * or return null if replacement API is not known.
   984      */
   985     private String replacementFor(String cn) {
   986         String name = cn;
   987         String value = null;
   988         while (value == null && name != null) {
   989             try {
   990                 value = ResourceBundleHelper.jdkinternals.getString(name);
   991             } catch (MissingResourceException e) {
   992                 // go up one subpackage level
   993                 int i = name.lastIndexOf('.');
   994                 name = i > 0 ? name.substring(0, i) : null;
   995             }
   996         }
   997         return value;
   998     };
  1000     private void showReplacements(Analyzer analyzer) {
  1001         Map<String,String> jdkinternals = new TreeMap<>();
  1002         boolean useInternals = false;
  1003         for (Archive source : sourceLocations) {
  1004             useInternals = useInternals || analyzer.hasDependences(source);
  1005             for (String cn : analyzer.dependences(source)) {
  1006                 String repl = replacementFor(cn);
  1007                 if (repl != null && !jdkinternals.containsKey(cn)) {
  1008                     jdkinternals.put(cn, repl);
  1012         if (useInternals) {
  1013             log.println();
  1014             warning("warn.replace.useJDKInternals", getMessage("jdeps.wiki.url"));
  1016         if (!jdkinternals.isEmpty()) {
  1017             log.println();
  1018             log.format("%-40s %s%n", "JDK Internal API", "Suggested Replacement");
  1019             log.format("%-40s %s%n", "----------------", "---------------------");
  1020             for (Map.Entry<String,String> e : jdkinternals.entrySet()) {
  1021                 log.format("%-40s %s%n", e.getKey(), e.getValue());

mercurial