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

Wed, 04 Dec 2013 15:39:36 -0800

author
mchung
date
Wed, 04 Dec 2013 15:39:36 -0800
changeset 2214
4a2ed1900428
parent 2172
aa91bc6e8480
child 2525
2eb010b6cb22
child 2538
1e39ae45d8ac
permissions
-rw-r--r--

8029216: (jdeps) Provide a specific option to report JDK internal APIs
Reviewed-by: alanb

     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, "-jdkinternals") {
   184             void process(JdepsTask task, String opt, String arg) {
   185                 task.options.findJDKInternals = true;
   186                 task.options.verbose = Analyzer.Type.CLASS;
   187                 if (task.options.includePattern == null) {
   188                     task.options.includePattern = Pattern.compile(".*");
   189                 }
   190             }
   191         },
   192         new Option(false, "-version") {
   193             void process(JdepsTask task, String opt, String arg) {
   194                 task.options.version = true;
   195             }
   196         },
   197         new HiddenOption(false, "-fullversion") {
   198             void process(JdepsTask task, String opt, String arg) {
   199                 task.options.fullVersion = true;
   200             }
   201         },
   202         new HiddenOption(false, "-showlabel") {
   203             void process(JdepsTask task, String opt, String arg) {
   204                 task.options.showLabel = true;
   205             }
   206         },
   207         new HiddenOption(true, "-depth") {
   208             void process(JdepsTask task, String opt, String arg) throws BadArgs {
   209                 try {
   210                     task.options.depth = Integer.parseInt(arg);
   211                 } catch (NumberFormatException e) {
   212                     throw new BadArgs("err.invalid.arg.for.option", opt);
   213                 }
   214             }
   215         },
   216     };
   218     private static final String PROGNAME = "jdeps";
   219     private final Options options = new Options();
   220     private final List<String> classes = new ArrayList<String>();
   222     private PrintWriter log;
   223     void setLog(PrintWriter out) {
   224         log = out;
   225     }
   227     /**
   228      * Result codes.
   229      */
   230     static final int EXIT_OK = 0, // Completed with no errors.
   231                      EXIT_ERROR = 1, // Completed but reported errors.
   232                      EXIT_CMDERR = 2, // Bad command-line arguments
   233                      EXIT_SYSERR = 3, // System error or resource exhaustion.
   234                      EXIT_ABNORMAL = 4;// terminated abnormally
   236     int run(String[] args) {
   237         if (log == null) {
   238             log = new PrintWriter(System.out);
   239         }
   240         try {
   241             handleOptions(args);
   242             if (options.help) {
   243                 showHelp();
   244             }
   245             if (options.version || options.fullVersion) {
   246                 showVersion(options.fullVersion);
   247             }
   248             if (classes.isEmpty() && options.includePattern == null) {
   249                 if (options.help || options.version || options.fullVersion) {
   250                     return EXIT_OK;
   251                 } else {
   252                     showHelp();
   253                     return EXIT_CMDERR;
   254                 }
   255             }
   256             if (options.regex != null && options.packageNames.size() > 0) {
   257                 showHelp();
   258                 return EXIT_CMDERR;
   259             }
   260             if (options.findJDKInternals &&
   261                    (options.regex != null || options.packageNames.size() > 0 || options.showSummary)) {
   262                 showHelp();
   263                 return EXIT_CMDERR;
   264             }
   265             if (options.showSummary && options.verbose != Analyzer.Type.SUMMARY) {
   266                 showHelp();
   267                 return EXIT_CMDERR;
   268             }
   269             boolean ok = run();
   270             return ok ? EXIT_OK : EXIT_ERROR;
   271         } catch (BadArgs e) {
   272             reportError(e.key, e.args);
   273             if (e.showUsage) {
   274                 log.println(getMessage("main.usage.summary", PROGNAME));
   275             }
   276             return EXIT_CMDERR;
   277         } catch (IOException e) {
   278             return EXIT_ABNORMAL;
   279         } finally {
   280             log.flush();
   281         }
   282     }
   284     private final List<Archive> sourceLocations = new ArrayList<>();
   285     private boolean run() throws IOException {
   286         findDependencies();
   287         Analyzer analyzer = new Analyzer(options.verbose);
   288         analyzer.run(sourceLocations);
   289         if (options.dotOutputDir != null) {
   290             Path dir = Paths.get(options.dotOutputDir);
   291             Files.createDirectories(dir);
   292             generateDotFiles(dir, analyzer);
   293         } else {
   294             printRawOutput(log, analyzer);
   295         }
   296         return true;
   297     }
   299     private void generateDotFiles(Path dir, Analyzer analyzer) throws IOException {
   300         Path summary = dir.resolve("summary.dot");
   301         boolean verbose = options.verbose == Analyzer.Type.VERBOSE;
   302         DotGraph<?> graph = verbose ? new DotSummaryForPackage()
   303                                     : new DotSummaryForArchive();
   304         for (Archive archive : sourceLocations) {
   305             analyzer.visitArchiveDependences(archive, graph);
   306             if (verbose || options.showLabel) {
   307                 // traverse detailed dependences to generate package-level
   308                 // summary or build labels for edges
   309                 analyzer.visitDependences(archive, graph);
   310             }
   311         }
   312         try (PrintWriter sw = new PrintWriter(Files.newOutputStream(summary))) {
   313             graph.writeTo(sw);
   314         }
   315         // output individual .dot file for each archive
   316         if (options.verbose != Analyzer.Type.SUMMARY) {
   317             for (Archive archive : sourceLocations) {
   318                 if (analyzer.hasDependences(archive)) {
   319                     Path dotfile = dir.resolve(archive.getFileName() + ".dot");
   320                     try (PrintWriter pw = new PrintWriter(Files.newOutputStream(dotfile));
   321                          DotFileFormatter formatter = new DotFileFormatter(pw, archive)) {
   322                         analyzer.visitDependences(archive, formatter);
   323                     }
   324                 }
   325             }
   326         }
   327     }
   329     private void printRawOutput(PrintWriter writer, Analyzer analyzer) {
   330         for (Archive archive : sourceLocations) {
   331             RawOutputFormatter formatter = new RawOutputFormatter(writer);
   332             analyzer.visitArchiveDependences(archive, formatter);
   333             if (options.verbose != Analyzer.Type.SUMMARY) {
   334                 analyzer.visitDependences(archive, formatter);
   335             }
   336         }
   337     }
   338     private boolean isValidClassName(String name) {
   339         if (!Character.isJavaIdentifierStart(name.charAt(0))) {
   340             return false;
   341         }
   342         for (int i=1; i < name.length(); i++) {
   343             char c = name.charAt(i);
   344             if (c != '.'  && !Character.isJavaIdentifierPart(c)) {
   345                 return false;
   346             }
   347         }
   348         return true;
   349     }
   351     private Dependency.Filter getDependencyFilter() {
   352          if (options.regex != null) {
   353             return Dependencies.getRegexFilter(Pattern.compile(options.regex));
   354         } else if (options.packageNames.size() > 0) {
   355             return Dependencies.getPackageFilter(options.packageNames, false);
   356         } else {
   357             return new Dependency.Filter() {
   358                 @Override
   359                 public boolean accepts(Dependency dependency) {
   360                     return !dependency.getOrigin().equals(dependency.getTarget());
   361                 }
   362             };
   363         }
   364     }
   366     private boolean matches(String classname, AccessFlags flags) {
   367         if (options.apiOnly && !flags.is(AccessFlags.ACC_PUBLIC)) {
   368             return false;
   369         } else if (options.includePattern != null) {
   370             return options.includePattern.matcher(classname.replace('/', '.')).matches();
   371         } else {
   372             return true;
   373         }
   374     }
   376     private void findDependencies() throws IOException {
   377         Dependency.Finder finder =
   378             options.apiOnly ? Dependencies.getAPIFinder(AccessFlags.ACC_PROTECTED)
   379                             : Dependencies.getClassDependencyFinder();
   380         Dependency.Filter filter = getDependencyFilter();
   382         List<Archive> archives = new ArrayList<>();
   383         Deque<String> roots = new LinkedList<>();
   384         for (String s : classes) {
   385             Path p = Paths.get(s);
   386             if (Files.exists(p)) {
   387                 archives.add(new Archive(p, ClassFileReader.newInstance(p)));
   388             } else {
   389                 if (isValidClassName(s)) {
   390                     roots.add(s);
   391                 } else {
   392                     warning("warn.invalid.arg", s);
   393                 }
   394             }
   395         }
   396         sourceLocations.addAll(archives);
   398         List<Archive> classpaths = new ArrayList<>(); // for class file lookup
   399         classpaths.addAll(getClassPathArchives(options.classpath));
   400         if (options.includePattern != null) {
   401             archives.addAll(classpaths);
   402         }
   403         classpaths.addAll(PlatformClassPath.getArchives());
   405         // add all classpath archives to the source locations for reporting
   406         sourceLocations.addAll(classpaths);
   408         // Work queue of names of classfiles to be searched.
   409         // Entries will be unique, and for classes that do not yet have
   410         // dependencies in the results map.
   411         Deque<String> deque = new LinkedList<>();
   412         Set<String> doneClasses = new HashSet<>();
   414         // get the immediate dependencies of the input files
   415         for (Archive a : archives) {
   416             for (ClassFile cf : a.reader().getClassFiles()) {
   417                 String classFileName;
   418                 try {
   419                     classFileName = cf.getName();
   420                 } catch (ConstantPoolException e) {
   421                     throw new ClassFileError(e);
   422                 }
   424                 if (matches(classFileName, cf.access_flags)) {
   425                     if (!doneClasses.contains(classFileName)) {
   426                         doneClasses.add(classFileName);
   427                     }
   428                     for (Dependency d : finder.findDependencies(cf)) {
   429                         if (filter.accepts(d)) {
   430                             String cn = d.getTarget().getName();
   431                             if (!doneClasses.contains(cn) && !deque.contains(cn)) {
   432                                 deque.add(cn);
   433                             }
   434                             a.addClass(d.getOrigin(), d.getTarget());
   435                         }
   436                     }
   437                 }
   438             }
   439         }
   441         // add Archive for looking up classes from the classpath
   442         // for transitive dependency analysis
   443         Deque<String> unresolved = roots;
   444         int depth = options.depth > 0 ? options.depth : Integer.MAX_VALUE;
   445         do {
   446             String name;
   447             while ((name = unresolved.poll()) != null) {
   448                 if (doneClasses.contains(name)) {
   449                     continue;
   450                 }
   451                 ClassFile cf = null;
   452                 for (Archive a : classpaths) {
   453                     cf = a.reader().getClassFile(name);
   454                     if (cf != null) {
   455                         String classFileName;
   456                         try {
   457                             classFileName = cf.getName();
   458                         } catch (ConstantPoolException e) {
   459                             throw new ClassFileError(e);
   460                         }
   461                         if (!doneClasses.contains(classFileName)) {
   462                             // if name is a fully-qualified class name specified
   463                             // from command-line, this class might already be parsed
   464                             doneClasses.add(classFileName);
   465                             for (Dependency d : finder.findDependencies(cf)) {
   466                                 if (depth == 0) {
   467                                     // ignore the dependency
   468                                     a.addClass(d.getOrigin());
   469                                     break;
   470                                 } else if (filter.accepts(d)) {
   471                                     a.addClass(d.getOrigin(), d.getTarget());
   472                                     String cn = d.getTarget().getName();
   473                                     if (!doneClasses.contains(cn) && !deque.contains(cn)) {
   474                                         deque.add(cn);
   475                                     }
   476                                 }
   477                             }
   478                         }
   479                         break;
   480                     }
   481                 }
   482                 if (cf == null) {
   483                     doneClasses.add(name);
   484                 }
   485             }
   486             unresolved = deque;
   487             deque = new LinkedList<>();
   488         } while (!unresolved.isEmpty() && depth-- > 0);
   489     }
   491     public void handleOptions(String[] args) throws BadArgs {
   492         // process options
   493         for (int i=0; i < args.length; i++) {
   494             if (args[i].charAt(0) == '-') {
   495                 String name = args[i];
   496                 Option option = getOption(name);
   497                 String param = null;
   498                 if (option.hasArg) {
   499                     if (name.startsWith("-") && name.indexOf('=') > 0) {
   500                         param = name.substring(name.indexOf('=') + 1, name.length());
   501                     } else if (i + 1 < args.length) {
   502                         param = args[++i];
   503                     }
   504                     if (param == null || param.isEmpty() || param.charAt(0) == '-') {
   505                         throw new BadArgs("err.missing.arg", name).showUsage(true);
   506                     }
   507                 }
   508                 option.process(this, name, param);
   509                 if (option.ignoreRest()) {
   510                     i = args.length;
   511                 }
   512             } else {
   513                 // process rest of the input arguments
   514                 for (; i < args.length; i++) {
   515                     String name = args[i];
   516                     if (name.charAt(0) == '-') {
   517                         throw new BadArgs("err.option.after.class", name).showUsage(true);
   518                     }
   519                     classes.add(name);
   520                 }
   521             }
   522         }
   523     }
   525     private Option getOption(String name) throws BadArgs {
   526         for (Option o : recognizedOptions) {
   527             if (o.matches(name)) {
   528                 return o;
   529             }
   530         }
   531         throw new BadArgs("err.unknown.option", name).showUsage(true);
   532     }
   534     private void reportError(String key, Object... args) {
   535         log.println(getMessage("error.prefix") + " " + getMessage(key, args));
   536     }
   538     private void warning(String key, Object... args) {
   539         log.println(getMessage("warn.prefix") + " " + getMessage(key, args));
   540     }
   542     private void showHelp() {
   543         log.println(getMessage("main.usage", PROGNAME));
   544         for (Option o : recognizedOptions) {
   545             String name = o.aliases[0].substring(1); // there must always be at least one name
   546             name = name.charAt(0) == '-' ? name.substring(1) : name;
   547             if (o.isHidden() || name.equals("h")) {
   548                 continue;
   549             }
   550             log.println(getMessage("main.opt." + name));
   551         }
   552     }
   554     private void showVersion(boolean full) {
   555         log.println(version(full ? "full" : "release"));
   556     }
   558     private String version(String key) {
   559         // key=version:  mm.nn.oo[-milestone]
   560         // key=full:     mm.mm.oo[-milestone]-build
   561         if (ResourceBundleHelper.versionRB == null) {
   562             return System.getProperty("java.version");
   563         }
   564         try {
   565             return ResourceBundleHelper.versionRB.getString(key);
   566         } catch (MissingResourceException e) {
   567             return getMessage("version.unknown", System.getProperty("java.version"));
   568         }
   569     }
   571     static String getMessage(String key, Object... args) {
   572         try {
   573             return MessageFormat.format(ResourceBundleHelper.bundle.getString(key), args);
   574         } catch (MissingResourceException e) {
   575             throw new InternalError("Missing message: " + key);
   576         }
   577     }
   579     private static class Options {
   580         boolean help;
   581         boolean version;
   582         boolean fullVersion;
   583         boolean showProfile;
   584         boolean showSummary;
   585         boolean wildcard;
   586         boolean apiOnly;
   587         boolean showLabel;
   588         boolean findJDKInternals;
   589         String dotOutputDir;
   590         String classpath = "";
   591         int depth = 1;
   592         Analyzer.Type verbose = Analyzer.Type.PACKAGE;
   593         Set<String> packageNames = new HashSet<>();
   594         String regex;             // apply to the dependences
   595         Pattern includePattern;   // apply to classes
   596     }
   597     private static class ResourceBundleHelper {
   598         static final ResourceBundle versionRB;
   599         static final ResourceBundle bundle;
   601         static {
   602             Locale locale = Locale.getDefault();
   603             try {
   604                 bundle = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.jdeps", locale);
   605             } catch (MissingResourceException e) {
   606                 throw new InternalError("Cannot find jdeps resource bundle for locale " + locale);
   607             }
   608             try {
   609                 versionRB = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.version");
   610             } catch (MissingResourceException e) {
   611                 throw new InternalError("version.resource.missing");
   612             }
   613         }
   614     }
   616     private List<Archive> getArchives(List<String> filenames) throws IOException {
   617         List<Archive> result = new ArrayList<Archive>();
   618         for (String s : filenames) {
   619             Path p = Paths.get(s);
   620             if (Files.exists(p)) {
   621                 result.add(new Archive(p, ClassFileReader.newInstance(p)));
   622             } else {
   623                 warning("warn.file.not.exist", s);
   624             }
   625         }
   626         return result;
   627     }
   629     private List<Archive> getClassPathArchives(String paths) throws IOException {
   630         List<Archive> result = new ArrayList<>();
   631         if (paths.isEmpty()) {
   632             return result;
   633         }
   634         for (String p : paths.split(File.pathSeparator)) {
   635             if (p.length() > 0) {
   636                 List<Path> files = new ArrayList<>();
   637                 // wildcard to parse all JAR files e.g. -classpath dir/*
   638                 int i = p.lastIndexOf(".*");
   639                 if (i > 0) {
   640                     Path dir = Paths.get(p.substring(0, i));
   641                     try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.jar")) {
   642                         for (Path entry : stream) {
   643                             files.add(entry);
   644                         }
   645                     }
   646                 } else {
   647                     files.add(Paths.get(p));
   648                 }
   649                 for (Path f : files) {
   650                     if (Files.exists(f)) {
   651                         result.add(new Archive(f, ClassFileReader.newInstance(f)));
   652                     }
   653                 }
   654             }
   655         }
   656         return result;
   657     }
   659     /**
   660      * If the given archive is JDK archive and non-null Profile,
   661      * this method returns the profile name only if -profile option is specified;
   662      * a null profile indicates it accesses a private JDK API and this method
   663      * will return "JDK internal API".
   664      *
   665      * For non-JDK archives, this method returns the file name of the archive.
   666      */
   667     private String getProfileArchiveInfo(Archive source, Profile profile) {
   668         if (options.showProfile && profile != null)
   669             return profile.toString();
   671         if (source instanceof JDKArchive) {
   672             return profile == null ? "JDK internal API (" + source.getFileName() + ")" : "";
   673         }
   674         return source.getFileName();
   675     }
   677     /**
   678      * Returns the profile name or "JDK internal API" for JDK archive;
   679      * otherwise empty string.
   680      */
   681     private String profileName(Archive archive, Profile profile) {
   682         if (archive instanceof JDKArchive) {
   683             return Objects.toString(profile, "JDK internal API");
   684         } else {
   685             return "";
   686         }
   687     }
   689     class RawOutputFormatter implements Analyzer.Visitor {
   690         private final PrintWriter writer;
   691         RawOutputFormatter(PrintWriter writer) {
   692             this.writer = writer;
   693         }
   695         private String pkg = "";
   696         @Override
   697         public void visitDependence(String origin, Archive source,
   698                                     String target, Archive archive, Profile profile) {
   699             if (options.findJDKInternals &&
   700                     !(archive instanceof JDKArchive && profile == null)) {
   701                 // filter dependences other than JDK internal APIs
   702                 return;
   703             }
   704             if (options.verbose == Analyzer.Type.VERBOSE) {
   705                 writer.format("   %-50s -> %-50s %s%n",
   706                               origin, target, getProfileArchiveInfo(archive, profile));
   707             } else {
   708                 if (!origin.equals(pkg)) {
   709                     pkg = origin;
   710                     writer.format("   %s (%s)%n", origin, source.getFileName());
   711                 }
   712                 writer.format("      -> %-50s %s%n",
   713                               target, getProfileArchiveInfo(archive, profile));
   714             }
   715         }
   717         @Override
   718         public void visitArchiveDependence(Archive origin, Archive target, Profile profile) {
   719             writer.format("%s -> %s", origin.getPathName(), target.getPathName());
   720             if (options.showProfile && profile != null) {
   721                 writer.format(" (%s)%n", profile);
   722             } else {
   723                 writer.format("%n");
   724             }
   725         }
   726     }
   728     class DotFileFormatter extends DotGraph<String> implements AutoCloseable {
   729         private final PrintWriter writer;
   730         private final String name;
   731         DotFileFormatter(PrintWriter writer, Archive archive) {
   732             this.writer = writer;
   733             this.name = archive.getFileName();
   734             writer.format("digraph \"%s\" {%n", name);
   735             writer.format("    // Path: %s%n", archive.getPathName());
   736         }
   738         @Override
   739         public void close() {
   740             writer.println("}");
   741         }
   743         @Override
   744         public void visitDependence(String origin, Archive source,
   745                                     String target, Archive archive, Profile profile) {
   746             if (options.findJDKInternals &&
   747                     !(archive instanceof JDKArchive && profile == null)) {
   748                 // filter dependences other than JDK internal APIs
   749                 return;
   750             }
   751             // if -P option is specified, package name -> profile will
   752             // be shown and filter out multiple same edges.
   753             String name = getProfileArchiveInfo(archive, profile);
   754             writeEdge(writer, new Edge(origin, target, getProfileArchiveInfo(archive, profile)));
   755         }
   756         @Override
   757         public void visitArchiveDependence(Archive origin, Archive target, Profile profile) {
   758             throw new UnsupportedOperationException();
   759         }
   760     }
   762     class DotSummaryForArchive extends DotGraph<Archive> {
   763         @Override
   764         public void visitDependence(String origin, Archive source,
   765                                     String target, Archive archive, Profile profile) {
   766             Edge e = findEdge(source, archive);
   767             assert e != null;
   768             // add the dependency to the label if enabled and not compact1
   769             if (profile == Profile.COMPACT1) {
   770                 return;
   771             }
   772             e.addLabel(origin, target, profileName(archive, profile));
   773         }
   774         @Override
   775         public void visitArchiveDependence(Archive origin, Archive target, Profile profile) {
   776             // add an edge with the archive's name with no tag
   777             // so that there is only one node for each JDK archive
   778             // while there may be edges to different profiles
   779             Edge e = addEdge(origin, target, "");
   780             if (target instanceof JDKArchive) {
   781                 // add a label to print the profile
   782                 if (profile == null) {
   783                     e.addLabel("JDK internal API");
   784                 } else if (options.showProfile && !options.showLabel) {
   785                     e.addLabel(profile.toString());
   786                 }
   787             }
   788         }
   789     }
   791     // DotSummaryForPackage generates the summary.dot file for verbose mode
   792     // (-v or -verbose option) that includes all class dependencies.
   793     // The summary.dot file shows package-level dependencies.
   794     class DotSummaryForPackage extends DotGraph<String> {
   795         private String packageOf(String cn) {
   796             int i = cn.lastIndexOf('.');
   797             return i > 0 ? cn.substring(0, i) : "<unnamed>";
   798         }
   799         @Override
   800         public void visitDependence(String origin, Archive source,
   801                                     String target, Archive archive, Profile profile) {
   802             // add a package dependency edge
   803             String from = packageOf(origin);
   804             String to = packageOf(target);
   805             Edge e = addEdge(from, to, getProfileArchiveInfo(archive, profile));
   807             // add the dependency to the label if enabled and not compact1
   808             if (!options.showLabel || profile == Profile.COMPACT1) {
   809                 return;
   810             }
   812             // trim the package name of origin to shorten the label
   813             int i = origin.lastIndexOf('.');
   814             String n1 = i < 0 ? origin : origin.substring(i+1);
   815             e.addLabel(n1, target, profileName(archive, profile));
   816         }
   817         @Override
   818         public void visitArchiveDependence(Archive origin, Archive target, Profile profile) {
   819             // nop
   820         }
   821     }
   822     abstract class DotGraph<T> implements Analyzer.Visitor  {
   823         private final Set<Edge> edges = new LinkedHashSet<>();
   824         private Edge curEdge;
   825         public void writeTo(PrintWriter writer) {
   826             writer.format("digraph \"summary\" {%n");
   827             for (Edge e: edges) {
   828                 writeEdge(writer, e);
   829             }
   830             writer.println("}");
   831         }
   833         void writeEdge(PrintWriter writer, Edge e) {
   834             writer.format("   %-50s -> \"%s\"%s;%n",
   835                           String.format("\"%s\"", e.from.toString()),
   836                           e.tag.isEmpty() ? e.to
   837                                           : String.format("%s (%s)", e.to, e.tag),
   838                           getLabel(e));
   839         }
   841         Edge addEdge(T origin, T target, String tag) {
   842             Edge e = new Edge(origin, target, tag);
   843             if (e.equals(curEdge)) {
   844                 return curEdge;
   845             }
   847             if (edges.contains(e)) {
   848                 for (Edge e1 : edges) {
   849                    if (e.equals(e1)) {
   850                        curEdge = e1;
   851                    }
   852                 }
   853             } else {
   854                 edges.add(e);
   855                 curEdge = e;
   856             }
   857             return curEdge;
   858         }
   860         Edge findEdge(T origin, T target) {
   861             for (Edge e : edges) {
   862                 if (e.from.equals(origin) && e.to.equals(target)) {
   863                     return e;
   864                 }
   865             }
   866             return null;
   867         }
   869         String getLabel(Edge e) {
   870             String label = e.label.toString();
   871             return label.isEmpty() ? "" : String.format("[label=\"%s\",fontsize=9]", label);
   872         }
   874         class Edge {
   875             final T from;
   876             final T to;
   877             final String tag;  // optional tag
   878             final StringBuilder label = new StringBuilder();
   879             Edge(T from, T to, String tag) {
   880                 this.from = from;
   881                 this.to = to;
   882                 this.tag = tag;
   883             }
   884             void addLabel(String s) {
   885                 label.append(s).append("\\n");
   886             }
   887             void addLabel(String origin, String target, String profile) {
   888                 label.append(origin).append(" -> ").append(target);
   889                 if (!profile.isEmpty()) {
   890                     label.append(" (" + profile + ")");
   891                 }
   892                 label.append("\\n");
   893             }
   894             @Override @SuppressWarnings("unchecked")
   895             public boolean equals(Object o) {
   896                 if (o instanceof DotGraph<?>.Edge) {
   897                     DotGraph<?>.Edge e = (DotGraph<?>.Edge)o;
   898                     return this.from.equals(e.from) &&
   899                            this.to.equals(e.to) &&
   900                            this.tag.equals(e.tag);
   901                 }
   902                 return false;
   903             }
   904             @Override
   905             public int hashCode() {
   906                 int hash = 7;
   907                 hash = 67 * hash + Objects.hashCode(this.from) +
   908                        Objects.hashCode(this.to) + Objects.hashCode(this.tag);
   909                 return hash;
   910             }
   911         }
   912     }
   913 }

mercurial