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

Fri, 18 Jul 2014 10:43:41 -0700

author
mchung
date
Fri, 18 Jul 2014 10:43:41 -0700
changeset 2539
a51b7fd0543b
parent 2538
1e39ae45d8ac
child 2702
9ca8d8713094
child 2802
6b43535fb9f8
permissions
-rw-r--r--

8050804: (jdeps) Recommend supported API to replace use of JDK internal API
Reviewed-by: dfuchs

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

mercurial