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

Thu, 17 Oct 2013 13:19:48 -0700

author
mchung
date
Thu, 17 Oct 2013 13:19:48 -0700
changeset 2139
defadd528513
parent 1648
a03c4a86ea2b
child 2172
aa91bc6e8480
permissions
-rw-r--r--

8015912: jdeps support to output in dot file format
8026255: Switch jdeps to follow traditional Java option style
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, "-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(true, "-depth") {
   194             void process(JdepsTask task, String opt, String arg) throws BadArgs {
   195                 try {
   196                     task.options.depth = Integer.parseInt(arg);
   197                 } catch (NumberFormatException e) {
   198                     throw new BadArgs("err.invalid.arg.for.option", opt);
   199                 }
   200             }
   201         },
   202     };
   204     private static final String PROGNAME = "jdeps";
   205     private final Options options = new Options();
   206     private final List<String> classes = new ArrayList<String>();
   208     private PrintWriter log;
   209     void setLog(PrintWriter out) {
   210         log = out;
   211     }
   213     /**
   214      * Result codes.
   215      */
   216     static final int EXIT_OK = 0, // Completed with no errors.
   217                      EXIT_ERROR = 1, // Completed but reported errors.
   218                      EXIT_CMDERR = 2, // Bad command-line arguments
   219                      EXIT_SYSERR = 3, // System error or resource exhaustion.
   220                      EXIT_ABNORMAL = 4;// terminated abnormally
   222     int run(String[] args) {
   223         if (log == null) {
   224             log = new PrintWriter(System.out);
   225         }
   226         try {
   227             handleOptions(args);
   228             if (options.help) {
   229                 showHelp();
   230             }
   231             if (options.version || options.fullVersion) {
   232                 showVersion(options.fullVersion);
   233             }
   234             if (classes.isEmpty() && options.includePattern == null) {
   235                 if (options.help || options.version || options.fullVersion) {
   236                     return EXIT_OK;
   237                 } else {
   238                     showHelp();
   239                     return EXIT_CMDERR;
   240                 }
   241             }
   242             if (options.regex != null && options.packageNames.size() > 0) {
   243                 showHelp();
   244                 return EXIT_CMDERR;
   245             }
   246             if (options.showSummary && options.verbose != Analyzer.Type.SUMMARY) {
   247                 showHelp();
   248                 return EXIT_CMDERR;
   249             }
   250             boolean ok = run();
   251             return ok ? EXIT_OK : EXIT_ERROR;
   252         } catch (BadArgs e) {
   253             reportError(e.key, e.args);
   254             if (e.showUsage) {
   255                 log.println(getMessage("main.usage.summary", PROGNAME));
   256             }
   257             return EXIT_CMDERR;
   258         } catch (IOException e) {
   259             return EXIT_ABNORMAL;
   260         } finally {
   261             log.flush();
   262         }
   263     }
   265     private final List<Archive> sourceLocations = new ArrayList<>();
   266     private boolean run() throws IOException {
   267         findDependencies();
   268         Analyzer analyzer = new Analyzer(options.verbose);
   269         analyzer.run(sourceLocations);
   270         if (options.dotOutputDir != null) {
   271             Path dir = Paths.get(options.dotOutputDir);
   272             Files.createDirectories(dir);
   273             generateDotFiles(dir, analyzer);
   274         } else {
   275             printRawOutput(log, analyzer);
   276         }
   277         return true;
   278     }
   280     private void generateDotFiles(Path dir, Analyzer analyzer) throws IOException {
   281         Path summary = dir.resolve("summary.dot");
   282         try (PrintWriter sw = new PrintWriter(Files.newOutputStream(summary));
   283              DotFileFormatter formatter = new DotFileFormatter(sw, "summary")) {
   284             for (Archive archive : sourceLocations) {
   285                  analyzer.visitArchiveDependences(archive, formatter);
   286             }
   287         }
   288         if (options.verbose != Analyzer.Type.SUMMARY) {
   289             for (Archive archive : sourceLocations) {
   290                 if (analyzer.hasDependences(archive)) {
   291                     Path dotfile = dir.resolve(archive.getFileName() + ".dot");
   292                     try (PrintWriter pw = new PrintWriter(Files.newOutputStream(dotfile));
   293                          DotFileFormatter formatter = new DotFileFormatter(pw, archive)) {
   294                         analyzer.visitDependences(archive, formatter);
   295                     }
   296                 }
   297             }
   298         }
   299     }
   301     private void printRawOutput(PrintWriter writer, Analyzer analyzer) {
   302         for (Archive archive : sourceLocations) {
   303             RawOutputFormatter formatter = new RawOutputFormatter(writer);
   304             analyzer.visitArchiveDependences(archive, formatter);
   305             if (options.verbose != Analyzer.Type.SUMMARY) {
   306                 analyzer.visitDependences(archive, formatter);
   307             }
   308         }
   309     }
   310     private boolean isValidClassName(String name) {
   311         if (!Character.isJavaIdentifierStart(name.charAt(0))) {
   312             return false;
   313         }
   314         for (int i=1; i < name.length(); i++) {
   315             char c = name.charAt(i);
   316             if (c != '.'  && !Character.isJavaIdentifierPart(c)) {
   317                 return false;
   318             }
   319         }
   320         return true;
   321     }
   323     private Dependency.Filter getDependencyFilter() {
   324          if (options.regex != null) {
   325             return Dependencies.getRegexFilter(Pattern.compile(options.regex));
   326         } else if (options.packageNames.size() > 0) {
   327             return Dependencies.getPackageFilter(options.packageNames, false);
   328         } else {
   329             return new Dependency.Filter() {
   330                 @Override
   331                 public boolean accepts(Dependency dependency) {
   332                     return !dependency.getOrigin().equals(dependency.getTarget());
   333                 }
   334             };
   335         }
   336     }
   338     private boolean matches(String classname, AccessFlags flags) {
   339         if (options.apiOnly && !flags.is(AccessFlags.ACC_PUBLIC)) {
   340             return false;
   341         } else if (options.includePattern != null) {
   342             return options.includePattern.matcher(classname.replace('/', '.')).matches();
   343         } else {
   344             return true;
   345         }
   346     }
   348     private void findDependencies() throws IOException {
   349         Dependency.Finder finder =
   350             options.apiOnly ? Dependencies.getAPIFinder(AccessFlags.ACC_PROTECTED)
   351                             : Dependencies.getClassDependencyFinder();
   352         Dependency.Filter filter = getDependencyFilter();
   354         List<Archive> archives = new ArrayList<>();
   355         Deque<String> roots = new LinkedList<>();
   356         for (String s : classes) {
   357             Path p = Paths.get(s);
   358             if (Files.exists(p)) {
   359                 archives.add(new Archive(p, ClassFileReader.newInstance(p)));
   360             } else {
   361                 if (isValidClassName(s)) {
   362                     roots.add(s);
   363                 } else {
   364                     warning("warn.invalid.arg", s);
   365                 }
   366             }
   367         }
   369         List<Archive> classpaths = new ArrayList<>(); // for class file lookup
   370         if (options.includePattern != null) {
   371             archives.addAll(getClassPathArchives(options.classpath));
   372         } else {
   373             classpaths.addAll(getClassPathArchives(options.classpath));
   374         }
   375         classpaths.addAll(PlatformClassPath.getArchives());
   377         // add all archives to the source locations for reporting
   378         sourceLocations.addAll(archives);
   379         sourceLocations.addAll(classpaths);
   381         // Work queue of names of classfiles to be searched.
   382         // Entries will be unique, and for classes that do not yet have
   383         // dependencies in the results map.
   384         Deque<String> deque = new LinkedList<>();
   385         Set<String> doneClasses = new HashSet<>();
   387         // get the immediate dependencies of the input files
   388         for (Archive a : archives) {
   389             for (ClassFile cf : a.reader().getClassFiles()) {
   390                 String classFileName;
   391                 try {
   392                     classFileName = cf.getName();
   393                 } catch (ConstantPoolException e) {
   394                     throw new ClassFileError(e);
   395                 }
   397                 if (matches(classFileName, cf.access_flags)) {
   398                     if (!doneClasses.contains(classFileName)) {
   399                         doneClasses.add(classFileName);
   400                     }
   401                     for (Dependency d : finder.findDependencies(cf)) {
   402                         if (filter.accepts(d)) {
   403                             String cn = d.getTarget().getName();
   404                             if (!doneClasses.contains(cn) && !deque.contains(cn)) {
   405                                 deque.add(cn);
   406                             }
   407                             a.addClass(d.getOrigin(), d.getTarget());
   408                         }
   409                     }
   410                 }
   411             }
   412         }
   414         // add Archive for looking up classes from the classpath
   415         // for transitive dependency analysis
   416         Deque<String> unresolved = roots;
   417         int depth = options.depth > 0 ? options.depth : Integer.MAX_VALUE;
   418         do {
   419             String name;
   420             while ((name = unresolved.poll()) != null) {
   421                 if (doneClasses.contains(name)) {
   422                     continue;
   423                 }
   424                 ClassFile cf = null;
   425                 for (Archive a : classpaths) {
   426                     cf = a.reader().getClassFile(name);
   427                     if (cf != null) {
   428                         String classFileName;
   429                         try {
   430                             classFileName = cf.getName();
   431                         } catch (ConstantPoolException e) {
   432                             throw new ClassFileError(e);
   433                         }
   434                         if (!doneClasses.contains(classFileName)) {
   435                             // if name is a fully-qualified class name specified
   436                             // from command-line, this class might already be parsed
   437                             doneClasses.add(classFileName);
   438                             for (Dependency d : finder.findDependencies(cf)) {
   439                                 if (depth == 0) {
   440                                     // ignore the dependency
   441                                     a.addClass(d.getOrigin());
   442                                     break;
   443                                 } else if (filter.accepts(d)) {
   444                                     a.addClass(d.getOrigin(), d.getTarget());
   445                                     String cn = d.getTarget().getName();
   446                                     if (!doneClasses.contains(cn) && !deque.contains(cn)) {
   447                                         deque.add(cn);
   448                                     }
   449                                 }
   450                             }
   451                         }
   452                         break;
   453                     }
   454                 }
   455                 if (cf == null) {
   456                     doneClasses.add(name);
   457                 }
   458             }
   459             unresolved = deque;
   460             deque = new LinkedList<>();
   461         } while (!unresolved.isEmpty() && depth-- > 0);
   462     }
   464     public void handleOptions(String[] args) throws BadArgs {
   465         // process options
   466         for (int i=0; i < args.length; i++) {
   467             if (args[i].charAt(0) == '-') {
   468                 String name = args[i];
   469                 Option option = getOption(name);
   470                 String param = null;
   471                 if (option.hasArg) {
   472                     if (name.startsWith("-") && name.indexOf('=') > 0) {
   473                         param = name.substring(name.indexOf('=') + 1, name.length());
   474                     } else if (i + 1 < args.length) {
   475                         param = args[++i];
   476                     }
   477                     if (param == null || param.isEmpty() || param.charAt(0) == '-') {
   478                         throw new BadArgs("err.missing.arg", name).showUsage(true);
   479                     }
   480                 }
   481                 option.process(this, name, param);
   482                 if (option.ignoreRest()) {
   483                     i = args.length;
   484                 }
   485             } else {
   486                 // process rest of the input arguments
   487                 for (; i < args.length; i++) {
   488                     String name = args[i];
   489                     if (name.charAt(0) == '-') {
   490                         throw new BadArgs("err.option.after.class", name).showUsage(true);
   491                     }
   492                     classes.add(name);
   493                 }
   494             }
   495         }
   496     }
   498     private Option getOption(String name) throws BadArgs {
   499         for (Option o : recognizedOptions) {
   500             if (o.matches(name)) {
   501                 return o;
   502             }
   503         }
   504         throw new BadArgs("err.unknown.option", name).showUsage(true);
   505     }
   507     private void reportError(String key, Object... args) {
   508         log.println(getMessage("error.prefix") + " " + getMessage(key, args));
   509     }
   511     private void warning(String key, Object... args) {
   512         log.println(getMessage("warn.prefix") + " " + getMessage(key, args));
   513     }
   515     private void showHelp() {
   516         log.println(getMessage("main.usage", PROGNAME));
   517         for (Option o : recognizedOptions) {
   518             String name = o.aliases[0].substring(1); // there must always be at least one name
   519             name = name.charAt(0) == '-' ? name.substring(1) : name;
   520             if (o.isHidden() || name.equals("h")) {
   521                 continue;
   522             }
   523             log.println(getMessage("main.opt." + name));
   524         }
   525     }
   527     private void showVersion(boolean full) {
   528         log.println(version(full ? "full" : "release"));
   529     }
   531     private String version(String key) {
   532         // key=version:  mm.nn.oo[-milestone]
   533         // key=full:     mm.mm.oo[-milestone]-build
   534         if (ResourceBundleHelper.versionRB == null) {
   535             return System.getProperty("java.version");
   536         }
   537         try {
   538             return ResourceBundleHelper.versionRB.getString(key);
   539         } catch (MissingResourceException e) {
   540             return getMessage("version.unknown", System.getProperty("java.version"));
   541         }
   542     }
   544     static String getMessage(String key, Object... args) {
   545         try {
   546             return MessageFormat.format(ResourceBundleHelper.bundle.getString(key), args);
   547         } catch (MissingResourceException e) {
   548             throw new InternalError("Missing message: " + key);
   549         }
   550     }
   552     private static class Options {
   553         boolean help;
   554         boolean version;
   555         boolean fullVersion;
   556         boolean showProfile;
   557         boolean showSummary;
   558         boolean wildcard;
   559         boolean apiOnly;
   560         String dotOutputDir;
   561         String classpath = "";
   562         int depth = 1;
   563         Analyzer.Type verbose = Analyzer.Type.PACKAGE;
   564         Set<String> packageNames = new HashSet<>();
   565         String regex;             // apply to the dependences
   566         Pattern includePattern;   // apply to classes
   567     }
   568     private static class ResourceBundleHelper {
   569         static final ResourceBundle versionRB;
   570         static final ResourceBundle bundle;
   572         static {
   573             Locale locale = Locale.getDefault();
   574             try {
   575                 bundle = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.jdeps", locale);
   576             } catch (MissingResourceException e) {
   577                 throw new InternalError("Cannot find jdeps resource bundle for locale " + locale);
   578             }
   579             try {
   580                 versionRB = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.version");
   581             } catch (MissingResourceException e) {
   582                 throw new InternalError("version.resource.missing");
   583             }
   584         }
   585     }
   587     private List<Archive> getArchives(List<String> filenames) throws IOException {
   588         List<Archive> result = new ArrayList<Archive>();
   589         for (String s : filenames) {
   590             Path p = Paths.get(s);
   591             if (Files.exists(p)) {
   592                 result.add(new Archive(p, ClassFileReader.newInstance(p)));
   593             } else {
   594                 warning("warn.file.not.exist", s);
   595             }
   596         }
   597         return result;
   598     }
   600     private List<Archive> getClassPathArchives(String paths) throws IOException {
   601         List<Archive> result = new ArrayList<>();
   602         if (paths.isEmpty()) {
   603             return result;
   604         }
   605         for (String p : paths.split(File.pathSeparator)) {
   606             if (p.length() > 0) {
   607                 List<Path> files = new ArrayList<>();
   608                 // wildcard to parse all JAR files e.g. -classpath dir/*
   609                 int i = p.lastIndexOf(".*");
   610                 if (i > 0) {
   611                     Path dir = Paths.get(p.substring(0, i));
   612                     try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.jar")) {
   613                         for (Path entry : stream) {
   614                             files.add(entry);
   615                         }
   616                     }
   617                 } else {
   618                     files.add(Paths.get(p));
   619                 }
   620                 for (Path f : files) {
   621                     if (Files.exists(f)) {
   622                         result.add(new Archive(f, ClassFileReader.newInstance(f)));
   623                     }
   624                 }
   625             }
   626         }
   627         return result;
   628     }
   631     /**
   632      * Returns the file name of the archive for non-JRE class or
   633      * internal JRE classes.  It returns empty string for SE API.
   634      */
   635     private static String getArchiveName(Archive source, String profile) {
   636         String name = source.getFileName();
   637         if (source instanceof JDKArchive)
   638             return profile.isEmpty() ? "JDK internal API (" + name + ")" : "";
   639         return name;
   640     }
   642     class RawOutputFormatter implements Analyzer.Visitor {
   643         private final PrintWriter writer;
   644         RawOutputFormatter(PrintWriter writer) {
   645             this.writer = writer;
   646         }
   648         private String pkg = "";
   649         @Override
   650         public void visitDependence(String origin, Archive source,
   651                                     String target, Archive archive, String profile) {
   652             if (!origin.equals(pkg)) {
   653                 pkg = origin;
   654                 writer.format("   %s (%s)%n", origin, source.getFileName());
   655             }
   656             String name = (options.showProfile && !profile.isEmpty())
   657                                 ? profile
   658                                 : getArchiveName(archive, profile);
   659             writer.format("      -> %-50s %s%n", target, name);
   660         }
   662         @Override
   663         public void visitArchiveDependence(Archive origin, Archive target, String profile) {
   664             writer.format("%s -> %s", origin, target);
   665             if (options.showProfile && !profile.isEmpty()) {
   666                 writer.format(" (%s)%n", profile);
   667             } else {
   668                 writer.format("%n");
   669             }
   670         }
   671     }
   673     class DotFileFormatter implements Analyzer.Visitor, AutoCloseable {
   674         private final PrintWriter writer;
   675         private final String name;
   676         DotFileFormatter(PrintWriter writer, String name) {
   677             this.writer = writer;
   678             this.name = name;
   679             writer.format("digraph \"%s\" {%n", name);
   680         }
   681         DotFileFormatter(PrintWriter writer, Archive archive) {
   682             this.writer = writer;
   683             this.name = archive.getFileName();
   684             writer.format("digraph \"%s\" {%n", name);
   685             writer.format("    // Path: %s%n", archive.toString());
   686         }
   688         @Override
   689         public void close() {
   690             writer.println("}");
   691         }
   693         private final Set<String> edges = new HashSet<>();
   694         private String node = "";
   695         @Override
   696         public void visitDependence(String origin, Archive source,
   697                                     String target, Archive archive, String profile) {
   698             if (!node.equals(origin)) {
   699                 edges.clear();
   700                 node = origin;
   701             }
   702             // if -P option is specified, package name -> profile will
   703             // be shown and filter out multiple same edges.
   704             if (!edges.contains(target)) {
   705                 StringBuilder sb = new StringBuilder();
   706                 String name = options.showProfile && !profile.isEmpty()
   707                                   ? profile
   708                                   : getArchiveName(archive, profile);
   709                 writer.format("   %-50s -> %s;%n",
   710                                  String.format("\"%s\"", origin),
   711                                  name.isEmpty() ? String.format("\"%s\"", target)
   712                                                 :  String.format("\"%s (%s)\"", target, name));
   713                 edges.add(target);
   714             }
   715         }
   717         @Override
   718         public void visitArchiveDependence(Archive origin, Archive target, String profile) {
   719              String name = options.showProfile && !profile.isEmpty()
   720                                 ? profile : "";
   721              writer.format("   %-30s -> \"%s\";%n",
   722                            String.format("\"%s\"", origin.getFileName()),
   723                            name.isEmpty()
   724                                ? target.getFileName()
   725                                : String.format("%s (%s)", target.getFileName(), name));
   726         }
   727     }
   728 }

mercurial