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

Wed, 30 Oct 2013 08:35:52 -0700

author
mchung
date
Wed, 30 Oct 2013 08:35:52 -0700
changeset 2172
aa91bc6e8480
parent 2139
defadd528513
child 2214
4a2ed1900428
permissions
-rw-r--r--

8027481: jdeps to handle classes with the same package name and correct profile for javax.crypto.*
Reviewed-by: alanb, dfuchs

     1 /*
     2  * Copyright (c) 2012, 2013, 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.jdeps.PlatformClassPath.JDKArchive;
    34 import java.io.*;
    35 import java.nio.file.DirectoryStream;
    36 import java.nio.file.Files;
    37 import java.nio.file.Path;
    38 import java.nio.file.Paths;
    39 import java.text.MessageFormat;
    40 import java.util.*;
    41 import java.util.regex.Pattern;
    43 /**
    44  * Implementation for the jdeps tool for static class dependency analysis.
    45  */
    46 class JdepsTask {
    47     static class BadArgs extends Exception {
    48         static final long serialVersionUID = 8765093759964640721L;
    49         BadArgs(String key, Object... args) {
    50             super(JdepsTask.getMessage(key, args));
    51             this.key = key;
    52             this.args = args;
    53         }
    55         BadArgs showUsage(boolean b) {
    56             showUsage = b;
    57             return this;
    58         }
    59         final String key;
    60         final Object[] args;
    61         boolean showUsage;
    62     }
    64     static abstract class Option {
    65         Option(boolean hasArg, String... aliases) {
    66             this.hasArg = hasArg;
    67             this.aliases = aliases;
    68         }
    70         boolean isHidden() {
    71             return false;
    72         }
    74         boolean matches(String opt) {
    75             for (String a : aliases) {
    76                 if (a.equals(opt))
    77                     return true;
    78                 if (hasArg && opt.startsWith(a + "="))
    79                     return true;
    80             }
    81             return false;
    82         }
    84         boolean ignoreRest() {
    85             return false;
    86         }
    88         abstract void process(JdepsTask task, String opt, String arg) throws BadArgs;
    89         final boolean hasArg;
    90         final String[] aliases;
    91     }
    93     static abstract class HiddenOption extends Option {
    94         HiddenOption(boolean hasArg, String... aliases) {
    95             super(hasArg, aliases);
    96         }
    98         boolean isHidden() {
    99             return true;
   100         }
   101     }
   103     static Option[] recognizedOptions = {
   104         new Option(false, "-h", "-?", "-help") {
   105             void process(JdepsTask task, String opt, String arg) {
   106                 task.options.help = true;
   107             }
   108         },
   109         new Option(true, "-dotoutput") {
   110             void process(JdepsTask task, String opt, String arg) throws BadArgs {
   111                 Path p = Paths.get(arg);
   112                 if (Files.exists(p) && (!Files.isDirectory(p) || !Files.isWritable(p))) {
   113                     throw new BadArgs("err.dot.output.path", arg);
   114                 }
   115                 task.options.dotOutputDir = arg;
   116             }
   117         },
   118         new Option(false, "-s", "-summary") {
   119             void process(JdepsTask task, String opt, String arg) {
   120                 task.options.showSummary = true;
   121                 task.options.verbose = Analyzer.Type.SUMMARY;
   122             }
   123         },
   124         new Option(false, "-v", "-verbose",
   125                           "-verbose:package",
   126                           "-verbose:class")
   127         {
   128             void process(JdepsTask task, String opt, String arg) throws BadArgs {
   129                 switch (opt) {
   130                     case "-v":
   131                     case "-verbose":
   132                         task.options.verbose = Analyzer.Type.VERBOSE;
   133                         break;
   134                     case "-verbose:package":
   135                             task.options.verbose = Analyzer.Type.PACKAGE;
   136                             break;
   137                     case "-verbose:class":
   138                             task.options.verbose = Analyzer.Type.CLASS;
   139                             break;
   140                     default:
   141                         throw new BadArgs("err.invalid.arg.for.option", opt);
   142                 }
   143             }
   144         },
   145         new Option(true, "-cp", "-classpath") {
   146             void process(JdepsTask task, String opt, String arg) {
   147                 task.options.classpath = arg;
   148             }
   149         },
   150         new Option(true, "-p", "-package") {
   151             void process(JdepsTask task, String opt, String arg) {
   152                 task.options.packageNames.add(arg);
   153             }
   154         },
   155         new Option(true, "-e", "-regex") {
   156             void process(JdepsTask task, String opt, String arg) {
   157                 task.options.regex = arg;
   158             }
   159         },
   160         new Option(true, "-include") {
   161             void process(JdepsTask task, String opt, String arg) throws BadArgs {
   162                 task.options.includePattern = Pattern.compile(arg);
   163             }
   164         },
   165         new Option(false, "-P", "-profile") {
   166             void process(JdepsTask task, String opt, String arg) throws BadArgs {
   167                 task.options.showProfile = true;
   168                 if (Profile.getProfileCount() == 0) {
   169                     throw new BadArgs("err.option.unsupported", opt, getMessage("err.profiles.msg"));
   170                 }
   171             }
   172         },
   173         new Option(false, "-apionly") {
   174             void process(JdepsTask task, String opt, String arg) {
   175                 task.options.apiOnly = true;
   176             }
   177         },
   178         new Option(false, "-R", "-recursive") {
   179             void process(JdepsTask task, String opt, String arg) {
   180                 task.options.depth = 0;
   181             }
   182         },
   183         new Option(false, "-version") {
   184             void process(JdepsTask task, String opt, String arg) {
   185                 task.options.version = true;
   186             }
   187         },
   188         new HiddenOption(false, "-fullversion") {
   189             void process(JdepsTask task, String opt, String arg) {
   190                 task.options.fullVersion = true;
   191             }
   192         },
   193         new HiddenOption(false, "-showlabel") {
   194             void process(JdepsTask task, String opt, String arg) {
   195                 task.options.showLabel = true;
   196             }
   197         },
   198         new HiddenOption(true, "-depth") {
   199             void process(JdepsTask task, String opt, String arg) throws BadArgs {
   200                 try {
   201                     task.options.depth = Integer.parseInt(arg);
   202                 } catch (NumberFormatException e) {
   203                     throw new BadArgs("err.invalid.arg.for.option", opt);
   204                 }
   205             }
   206         },
   207     };
   209     private static final String PROGNAME = "jdeps";
   210     private final Options options = new Options();
   211     private final List<String> classes = new ArrayList<String>();
   213     private PrintWriter log;
   214     void setLog(PrintWriter out) {
   215         log = out;
   216     }
   218     /**
   219      * Result codes.
   220      */
   221     static final int EXIT_OK = 0, // Completed with no errors.
   222                      EXIT_ERROR = 1, // Completed but reported errors.
   223                      EXIT_CMDERR = 2, // Bad command-line arguments
   224                      EXIT_SYSERR = 3, // System error or resource exhaustion.
   225                      EXIT_ABNORMAL = 4;// terminated abnormally
   227     int run(String[] args) {
   228         if (log == null) {
   229             log = new PrintWriter(System.out);
   230         }
   231         try {
   232             handleOptions(args);
   233             if (options.help) {
   234                 showHelp();
   235             }
   236             if (options.version || options.fullVersion) {
   237                 showVersion(options.fullVersion);
   238             }
   239             if (classes.isEmpty() && options.includePattern == null) {
   240                 if (options.help || options.version || options.fullVersion) {
   241                     return EXIT_OK;
   242                 } else {
   243                     showHelp();
   244                     return EXIT_CMDERR;
   245                 }
   246             }
   247             if (options.regex != null && options.packageNames.size() > 0) {
   248                 showHelp();
   249                 return EXIT_CMDERR;
   250             }
   251             if (options.showSummary && options.verbose != Analyzer.Type.SUMMARY) {
   252                 showHelp();
   253                 return EXIT_CMDERR;
   254             }
   255             boolean ok = run();
   256             return ok ? EXIT_OK : EXIT_ERROR;
   257         } catch (BadArgs e) {
   258             reportError(e.key, e.args);
   259             if (e.showUsage) {
   260                 log.println(getMessage("main.usage.summary", PROGNAME));
   261             }
   262             return EXIT_CMDERR;
   263         } catch (IOException e) {
   264             return EXIT_ABNORMAL;
   265         } finally {
   266             log.flush();
   267         }
   268     }
   270     private final List<Archive> sourceLocations = new ArrayList<>();
   271     private boolean run() throws IOException {
   272         findDependencies();
   273         Analyzer analyzer = new Analyzer(options.verbose);
   274         analyzer.run(sourceLocations);
   275         if (options.dotOutputDir != null) {
   276             Path dir = Paths.get(options.dotOutputDir);
   277             Files.createDirectories(dir);
   278             generateDotFiles(dir, analyzer);
   279         } else {
   280             printRawOutput(log, analyzer);
   281         }
   282         return true;
   283     }
   285     private void generateDotFiles(Path dir, Analyzer analyzer) throws IOException {
   286         Path summary = dir.resolve("summary.dot");
   287         boolean verbose = options.verbose == Analyzer.Type.VERBOSE;
   288         DotGraph<?> graph = verbose ? new DotSummaryForPackage()
   289                                     : new DotSummaryForArchive();
   290         for (Archive archive : sourceLocations) {
   291             analyzer.visitArchiveDependences(archive, graph);
   292             if (verbose || options.showLabel) {
   293                 // traverse detailed dependences to generate package-level
   294                 // summary or build labels for edges
   295                 analyzer.visitDependences(archive, graph);
   296             }
   297         }
   298         try (PrintWriter sw = new PrintWriter(Files.newOutputStream(summary))) {
   299             graph.writeTo(sw);
   300         }
   301         // output individual .dot file for each archive
   302         if (options.verbose != Analyzer.Type.SUMMARY) {
   303             for (Archive archive : sourceLocations) {
   304                 if (analyzer.hasDependences(archive)) {
   305                     Path dotfile = dir.resolve(archive.getFileName() + ".dot");
   306                     try (PrintWriter pw = new PrintWriter(Files.newOutputStream(dotfile));
   307                          DotFileFormatter formatter = new DotFileFormatter(pw, archive)) {
   308                         analyzer.visitDependences(archive, formatter);
   309                     }
   310                 }
   311             }
   312         }
   313     }
   315     private void printRawOutput(PrintWriter writer, Analyzer analyzer) {
   316         for (Archive archive : sourceLocations) {
   317             RawOutputFormatter formatter = new RawOutputFormatter(writer);
   318             analyzer.visitArchiveDependences(archive, formatter);
   319             if (options.verbose != Analyzer.Type.SUMMARY) {
   320                 analyzer.visitDependences(archive, formatter);
   321             }
   322         }
   323     }
   324     private boolean isValidClassName(String name) {
   325         if (!Character.isJavaIdentifierStart(name.charAt(0))) {
   326             return false;
   327         }
   328         for (int i=1; i < name.length(); i++) {
   329             char c = name.charAt(i);
   330             if (c != '.'  && !Character.isJavaIdentifierPart(c)) {
   331                 return false;
   332             }
   333         }
   334         return true;
   335     }
   337     private Dependency.Filter getDependencyFilter() {
   338          if (options.regex != null) {
   339             return Dependencies.getRegexFilter(Pattern.compile(options.regex));
   340         } else if (options.packageNames.size() > 0) {
   341             return Dependencies.getPackageFilter(options.packageNames, false);
   342         } else {
   343             return new Dependency.Filter() {
   344                 @Override
   345                 public boolean accepts(Dependency dependency) {
   346                     return !dependency.getOrigin().equals(dependency.getTarget());
   347                 }
   348             };
   349         }
   350     }
   352     private boolean matches(String classname, AccessFlags flags) {
   353         if (options.apiOnly && !flags.is(AccessFlags.ACC_PUBLIC)) {
   354             return false;
   355         } else if (options.includePattern != null) {
   356             return options.includePattern.matcher(classname.replace('/', '.')).matches();
   357         } else {
   358             return true;
   359         }
   360     }
   362     private void findDependencies() throws IOException {
   363         Dependency.Finder finder =
   364             options.apiOnly ? Dependencies.getAPIFinder(AccessFlags.ACC_PROTECTED)
   365                             : Dependencies.getClassDependencyFinder();
   366         Dependency.Filter filter = getDependencyFilter();
   368         List<Archive> archives = new ArrayList<>();
   369         Deque<String> roots = new LinkedList<>();
   370         for (String s : classes) {
   371             Path p = Paths.get(s);
   372             if (Files.exists(p)) {
   373                 archives.add(new Archive(p, ClassFileReader.newInstance(p)));
   374             } else {
   375                 if (isValidClassName(s)) {
   376                     roots.add(s);
   377                 } else {
   378                     warning("warn.invalid.arg", s);
   379                 }
   380             }
   381         }
   382         sourceLocations.addAll(archives);
   384         List<Archive> classpaths = new ArrayList<>(); // for class file lookup
   385         classpaths.addAll(getClassPathArchives(options.classpath));
   386         if (options.includePattern != null) {
   387             archives.addAll(classpaths);
   388         }
   389         classpaths.addAll(PlatformClassPath.getArchives());
   391         // add all classpath archives to the source locations for reporting
   392         sourceLocations.addAll(classpaths);
   394         // Work queue of names of classfiles to be searched.
   395         // Entries will be unique, and for classes that do not yet have
   396         // dependencies in the results map.
   397         Deque<String> deque = new LinkedList<>();
   398         Set<String> doneClasses = new HashSet<>();
   400         // get the immediate dependencies of the input files
   401         for (Archive a : archives) {
   402             for (ClassFile cf : a.reader().getClassFiles()) {
   403                 String classFileName;
   404                 try {
   405                     classFileName = cf.getName();
   406                 } catch (ConstantPoolException e) {
   407                     throw new ClassFileError(e);
   408                 }
   410                 if (matches(classFileName, cf.access_flags)) {
   411                     if (!doneClasses.contains(classFileName)) {
   412                         doneClasses.add(classFileName);
   413                     }
   414                     for (Dependency d : finder.findDependencies(cf)) {
   415                         if (filter.accepts(d)) {
   416                             String cn = d.getTarget().getName();
   417                             if (!doneClasses.contains(cn) && !deque.contains(cn)) {
   418                                 deque.add(cn);
   419                             }
   420                             a.addClass(d.getOrigin(), d.getTarget());
   421                         }
   422                     }
   423                 }
   424             }
   425         }
   427         // add Archive for looking up classes from the classpath
   428         // for transitive dependency analysis
   429         Deque<String> unresolved = roots;
   430         int depth = options.depth > 0 ? options.depth : Integer.MAX_VALUE;
   431         do {
   432             String name;
   433             while ((name = unresolved.poll()) != null) {
   434                 if (doneClasses.contains(name)) {
   435                     continue;
   436                 }
   437                 ClassFile cf = null;
   438                 for (Archive a : classpaths) {
   439                     cf = a.reader().getClassFile(name);
   440                     if (cf != null) {
   441                         String classFileName;
   442                         try {
   443                             classFileName = cf.getName();
   444                         } catch (ConstantPoolException e) {
   445                             throw new ClassFileError(e);
   446                         }
   447                         if (!doneClasses.contains(classFileName)) {
   448                             // if name is a fully-qualified class name specified
   449                             // from command-line, this class might already be parsed
   450                             doneClasses.add(classFileName);
   451                             for (Dependency d : finder.findDependencies(cf)) {
   452                                 if (depth == 0) {
   453                                     // ignore the dependency
   454                                     a.addClass(d.getOrigin());
   455                                     break;
   456                                 } else if (filter.accepts(d)) {
   457                                     a.addClass(d.getOrigin(), d.getTarget());
   458                                     String cn = d.getTarget().getName();
   459                                     if (!doneClasses.contains(cn) && !deque.contains(cn)) {
   460                                         deque.add(cn);
   461                                     }
   462                                 }
   463                             }
   464                         }
   465                         break;
   466                     }
   467                 }
   468                 if (cf == null) {
   469                     doneClasses.add(name);
   470                 }
   471             }
   472             unresolved = deque;
   473             deque = new LinkedList<>();
   474         } while (!unresolved.isEmpty() && depth-- > 0);
   475     }
   477     public void handleOptions(String[] args) throws BadArgs {
   478         // process options
   479         for (int i=0; i < args.length; i++) {
   480             if (args[i].charAt(0) == '-') {
   481                 String name = args[i];
   482                 Option option = getOption(name);
   483                 String param = null;
   484                 if (option.hasArg) {
   485                     if (name.startsWith("-") && name.indexOf('=') > 0) {
   486                         param = name.substring(name.indexOf('=') + 1, name.length());
   487                     } else if (i + 1 < args.length) {
   488                         param = args[++i];
   489                     }
   490                     if (param == null || param.isEmpty() || param.charAt(0) == '-') {
   491                         throw new BadArgs("err.missing.arg", name).showUsage(true);
   492                     }
   493                 }
   494                 option.process(this, name, param);
   495                 if (option.ignoreRest()) {
   496                     i = args.length;
   497                 }
   498             } else {
   499                 // process rest of the input arguments
   500                 for (; i < args.length; i++) {
   501                     String name = args[i];
   502                     if (name.charAt(0) == '-') {
   503                         throw new BadArgs("err.option.after.class", name).showUsage(true);
   504                     }
   505                     classes.add(name);
   506                 }
   507             }
   508         }
   509     }
   511     private Option getOption(String name) throws BadArgs {
   512         for (Option o : recognizedOptions) {
   513             if (o.matches(name)) {
   514                 return o;
   515             }
   516         }
   517         throw new BadArgs("err.unknown.option", name).showUsage(true);
   518     }
   520     private void reportError(String key, Object... args) {
   521         log.println(getMessage("error.prefix") + " " + getMessage(key, args));
   522     }
   524     private void warning(String key, Object... args) {
   525         log.println(getMessage("warn.prefix") + " " + getMessage(key, args));
   526     }
   528     private void showHelp() {
   529         log.println(getMessage("main.usage", PROGNAME));
   530         for (Option o : recognizedOptions) {
   531             String name = o.aliases[0].substring(1); // there must always be at least one name
   532             name = name.charAt(0) == '-' ? name.substring(1) : name;
   533             if (o.isHidden() || name.equals("h")) {
   534                 continue;
   535             }
   536             log.println(getMessage("main.opt." + name));
   537         }
   538     }
   540     private void showVersion(boolean full) {
   541         log.println(version(full ? "full" : "release"));
   542     }
   544     private String version(String key) {
   545         // key=version:  mm.nn.oo[-milestone]
   546         // key=full:     mm.mm.oo[-milestone]-build
   547         if (ResourceBundleHelper.versionRB == null) {
   548             return System.getProperty("java.version");
   549         }
   550         try {
   551             return ResourceBundleHelper.versionRB.getString(key);
   552         } catch (MissingResourceException e) {
   553             return getMessage("version.unknown", System.getProperty("java.version"));
   554         }
   555     }
   557     static String getMessage(String key, Object... args) {
   558         try {
   559             return MessageFormat.format(ResourceBundleHelper.bundle.getString(key), args);
   560         } catch (MissingResourceException e) {
   561             throw new InternalError("Missing message: " + key);
   562         }
   563     }
   565     private static class Options {
   566         boolean help;
   567         boolean version;
   568         boolean fullVersion;
   569         boolean showProfile;
   570         boolean showSummary;
   571         boolean wildcard;
   572         boolean apiOnly;
   573         boolean showLabel;
   574         String dotOutputDir;
   575         String classpath = "";
   576         int depth = 1;
   577         Analyzer.Type verbose = Analyzer.Type.PACKAGE;
   578         Set<String> packageNames = new HashSet<>();
   579         String regex;             // apply to the dependences
   580         Pattern includePattern;   // apply to classes
   581     }
   582     private static class ResourceBundleHelper {
   583         static final ResourceBundle versionRB;
   584         static final ResourceBundle bundle;
   586         static {
   587             Locale locale = Locale.getDefault();
   588             try {
   589                 bundle = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.jdeps", locale);
   590             } catch (MissingResourceException e) {
   591                 throw new InternalError("Cannot find jdeps resource bundle for locale " + locale);
   592             }
   593             try {
   594                 versionRB = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.version");
   595             } catch (MissingResourceException e) {
   596                 throw new InternalError("version.resource.missing");
   597             }
   598         }
   599     }
   601     private List<Archive> getArchives(List<String> filenames) throws IOException {
   602         List<Archive> result = new ArrayList<Archive>();
   603         for (String s : filenames) {
   604             Path p = Paths.get(s);
   605             if (Files.exists(p)) {
   606                 result.add(new Archive(p, ClassFileReader.newInstance(p)));
   607             } else {
   608                 warning("warn.file.not.exist", s);
   609             }
   610         }
   611         return result;
   612     }
   614     private List<Archive> getClassPathArchives(String paths) throws IOException {
   615         List<Archive> result = new ArrayList<>();
   616         if (paths.isEmpty()) {
   617             return result;
   618         }
   619         for (String p : paths.split(File.pathSeparator)) {
   620             if (p.length() > 0) {
   621                 List<Path> files = new ArrayList<>();
   622                 // wildcard to parse all JAR files e.g. -classpath dir/*
   623                 int i = p.lastIndexOf(".*");
   624                 if (i > 0) {
   625                     Path dir = Paths.get(p.substring(0, i));
   626                     try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.jar")) {
   627                         for (Path entry : stream) {
   628                             files.add(entry);
   629                         }
   630                     }
   631                 } else {
   632                     files.add(Paths.get(p));
   633                 }
   634                 for (Path f : files) {
   635                     if (Files.exists(f)) {
   636                         result.add(new Archive(f, ClassFileReader.newInstance(f)));
   637                     }
   638                 }
   639             }
   640         }
   641         return result;
   642     }
   644     /**
   645      * If the given archive is JDK archive and non-null Profile,
   646      * this method returns the profile name only if -profile option is specified;
   647      * a null profile indicates it accesses a private JDK API and this method
   648      * will return "JDK internal API".
   649      *
   650      * For non-JDK archives, this method returns the file name of the archive.
   651      */
   652     private String getProfileArchiveInfo(Archive source, Profile profile) {
   653         if (options.showProfile && profile != null)
   654             return profile.toString();
   656         if (source instanceof JDKArchive) {
   657             return profile == null ? "JDK internal API (" + source.getFileName() + ")" : "";
   658         }
   659         return source.getFileName();
   660     }
   662     /**
   663      * Returns the profile name or "JDK internal API" for JDK archive;
   664      * otherwise empty string.
   665      */
   666     private String profileName(Archive archive, Profile profile) {
   667         if (archive instanceof JDKArchive) {
   668             return Objects.toString(profile, "JDK internal API");
   669         } else {
   670             return "";
   671         }
   672     }
   674     class RawOutputFormatter implements Analyzer.Visitor {
   675         private final PrintWriter writer;
   676         RawOutputFormatter(PrintWriter writer) {
   677             this.writer = writer;
   678         }
   680         private String pkg = "";
   681         @Override
   682         public void visitDependence(String origin, Archive source,
   683                                     String target, Archive archive, Profile profile) {
   684             if (!origin.equals(pkg)) {
   685                 pkg = origin;
   686                 writer.format("   %s (%s)%n", origin, source.getFileName());
   687             }
   688             writer.format("      -> %-50s %s%n", target, getProfileArchiveInfo(archive, profile));
   689         }
   691         @Override
   692         public void visitArchiveDependence(Archive origin, Archive target, Profile profile) {
   693             writer.format("%s -> %s", origin.getPathName(), target.getPathName());
   694             if (options.showProfile && profile != null) {
   695                 writer.format(" (%s)%n", profile);
   696             } else {
   697                 writer.format("%n");
   698             }
   699         }
   700     }
   702     class DotFileFormatter extends DotGraph<String> implements AutoCloseable {
   703         private final PrintWriter writer;
   704         private final String name;
   705         DotFileFormatter(PrintWriter writer, Archive archive) {
   706             this.writer = writer;
   707             this.name = archive.getFileName();
   708             writer.format("digraph \"%s\" {%n", name);
   709             writer.format("    // Path: %s%n", archive.getPathName());
   710         }
   712         @Override
   713         public void close() {
   714             writer.println("}");
   715         }
   717         @Override
   718         public void visitDependence(String origin, Archive source,
   719                                     String target, Archive archive, Profile profile) {
   720             // if -P option is specified, package name -> profile will
   721             // be shown and filter out multiple same edges.
   722             String name = getProfileArchiveInfo(archive, profile);
   723             writeEdge(writer, new Edge(origin, target, getProfileArchiveInfo(archive, profile)));
   724         }
   725         @Override
   726         public void visitArchiveDependence(Archive origin, Archive target, Profile profile) {
   727             throw new UnsupportedOperationException();
   728         }
   729     }
   731     class DotSummaryForArchive extends DotGraph<Archive> {
   732         @Override
   733         public void visitDependence(String origin, Archive source,
   734                                     String target, Archive archive, Profile profile) {
   735             Edge e = findEdge(source, archive);
   736             assert e != null;
   737             // add the dependency to the label if enabled and not compact1
   738             if (profile == Profile.COMPACT1) {
   739                 return;
   740             }
   741             e.addLabel(origin, target, profileName(archive, profile));
   742         }
   743         @Override
   744         public void visitArchiveDependence(Archive origin, Archive target, Profile profile) {
   745             // add an edge with the archive's name with no tag
   746             // so that there is only one node for each JDK archive
   747             // while there may be edges to different profiles
   748             Edge e = addEdge(origin, target, "");
   749             if (target instanceof JDKArchive) {
   750                 // add a label to print the profile
   751                 if (profile == null) {
   752                     e.addLabel("JDK internal API");
   753                 } else if (options.showProfile && !options.showLabel) {
   754                     e.addLabel(profile.toString());
   755                 }
   756             }
   757         }
   758     }
   760     // DotSummaryForPackage generates the summary.dot file for verbose mode
   761     // (-v or -verbose option) that includes all class dependencies.
   762     // The summary.dot file shows package-level dependencies.
   763     class DotSummaryForPackage extends DotGraph<String> {
   764         private String packageOf(String cn) {
   765             int i = cn.lastIndexOf('.');
   766             return i > 0 ? cn.substring(0, i) : "<unnamed>";
   767         }
   768         @Override
   769         public void visitDependence(String origin, Archive source,
   770                                     String target, Archive archive, Profile profile) {
   771             // add a package dependency edge
   772             String from = packageOf(origin);
   773             String to = packageOf(target);
   774             Edge e = addEdge(from, to, getProfileArchiveInfo(archive, profile));
   776             // add the dependency to the label if enabled and not compact1
   777             if (!options.showLabel || profile == Profile.COMPACT1) {
   778                 return;
   779             }
   781             // trim the package name of origin to shorten the label
   782             int i = origin.lastIndexOf('.');
   783             String n1 = i < 0 ? origin : origin.substring(i+1);
   784             e.addLabel(n1, target, profileName(archive, profile));
   785         }
   786         @Override
   787         public void visitArchiveDependence(Archive origin, Archive target, Profile profile) {
   788             // nop
   789         }
   790     }
   791     abstract class DotGraph<T> implements Analyzer.Visitor  {
   792         private final Set<Edge> edges = new LinkedHashSet<>();
   793         private Edge curEdge;
   794         public void writeTo(PrintWriter writer) {
   795             writer.format("digraph \"summary\" {%n");
   796             for (Edge e: edges) {
   797                 writeEdge(writer, e);
   798             }
   799             writer.println("}");
   800         }
   802         void writeEdge(PrintWriter writer, Edge e) {
   803             writer.format("   %-50s -> \"%s\"%s;%n",
   804                           String.format("\"%s\"", e.from.toString()),
   805                           e.tag.isEmpty() ? e.to
   806                                           : String.format("%s (%s)", e.to, e.tag),
   807                           getLabel(e));
   808         }
   810         Edge addEdge(T origin, T target, String tag) {
   811             Edge e = new Edge(origin, target, tag);
   812             if (e.equals(curEdge)) {
   813                 return curEdge;
   814             }
   816             if (edges.contains(e)) {
   817                 for (Edge e1 : edges) {
   818                    if (e.equals(e1)) {
   819                        curEdge = e1;
   820                    }
   821                 }
   822             } else {
   823                 edges.add(e);
   824                 curEdge = e;
   825             }
   826             return curEdge;
   827         }
   829         Edge findEdge(T origin, T target) {
   830             for (Edge e : edges) {
   831                 if (e.from.equals(origin) && e.to.equals(target)) {
   832                     return e;
   833                 }
   834             }
   835             return null;
   836         }
   838         String getLabel(Edge e) {
   839             String label = e.label.toString();
   840             return label.isEmpty() ? "" : String.format("[label=\"%s\",fontsize=9]", label);
   841         }
   843         class Edge {
   844             final T from;
   845             final T to;
   846             final String tag;  // optional tag
   847             final StringBuilder label = new StringBuilder();
   848             Edge(T from, T to, String tag) {
   849                 this.from = from;
   850                 this.to = to;
   851                 this.tag = tag;
   852             }
   853             void addLabel(String s) {
   854                 label.append(s).append("\\n");
   855             }
   856             void addLabel(String origin, String target, String profile) {
   857                 label.append(origin).append(" -> ").append(target);
   858                 if (!profile.isEmpty()) {
   859                     label.append(" (" + profile + ")");
   860                 }
   861                 label.append("\\n");
   862             }
   863             @Override @SuppressWarnings("unchecked")
   864             public boolean equals(Object o) {
   865                 if (o instanceof DotGraph<?>.Edge) {
   866                     DotGraph<?>.Edge e = (DotGraph<?>.Edge)o;
   867                     return this.from.equals(e.from) &&
   868                            this.to.equals(e.to) &&
   869                            this.tag.equals(e.tag);
   870                 }
   871                 return false;
   872             }
   873             @Override
   874             public int hashCode() {
   875                 int hash = 7;
   876                 hash = 67 * hash + Objects.hashCode(this.from) +
   877                        Objects.hashCode(this.to) + Objects.hashCode(this.tag);
   878                 return hash;
   879             }
   880         }
   881     }
   882 }

mercurial