langtools/src/share/classes/com/sun/tools/jdeps/JdepsTask.java
changeset 25442 755ff386d1ac
parent 22163 3651128c74eb
child 25692 39537fdca12c
equal deleted inserted replaced
25441:92dcee5a4741 25442:755ff386d1ac
     1 /*
     1 /*
     2  * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
     2  * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved.
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     4  *
     4  *
     5  * This code is free software; you can redistribute it and/or modify it
     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
     6  * under the terms of the GNU General Public License version 2 only, as
     7  * published by the Free Software Foundation.  Oracle designates this
     7  * published by the Free Software Foundation.  Oracle designates this
    28 import com.sun.tools.classfile.ClassFile;
    28 import com.sun.tools.classfile.ClassFile;
    29 import com.sun.tools.classfile.ConstantPoolException;
    29 import com.sun.tools.classfile.ConstantPoolException;
    30 import com.sun.tools.classfile.Dependencies;
    30 import com.sun.tools.classfile.Dependencies;
    31 import com.sun.tools.classfile.Dependencies.ClassFileError;
    31 import com.sun.tools.classfile.Dependencies.ClassFileError;
    32 import com.sun.tools.classfile.Dependency;
    32 import com.sun.tools.classfile.Dependency;
       
    33 import com.sun.tools.classfile.Dependency.Location;
    33 import com.sun.tools.jdeps.PlatformClassPath.JDKArchive;
    34 import com.sun.tools.jdeps.PlatformClassPath.JDKArchive;
       
    35 import static com.sun.tools.jdeps.Analyzer.Type.*;
    34 import java.io.*;
    36 import java.io.*;
    35 import java.nio.file.DirectoryStream;
    37 import java.nio.file.DirectoryStream;
    36 import java.nio.file.Files;
    38 import java.nio.file.Files;
    37 import java.nio.file.Path;
    39 import java.nio.file.Path;
    38 import java.nio.file.Paths;
    40 import java.nio.file.Paths;
   108         },
   110         },
   109         new Option(true, "-dotoutput") {
   111         new Option(true, "-dotoutput") {
   110             void process(JdepsTask task, String opt, String arg) throws BadArgs {
   112             void process(JdepsTask task, String opt, String arg) throws BadArgs {
   111                 Path p = Paths.get(arg);
   113                 Path p = Paths.get(arg);
   112                 if (Files.exists(p) && (!Files.isDirectory(p) || !Files.isWritable(p))) {
   114                 if (Files.exists(p) && (!Files.isDirectory(p) || !Files.isWritable(p))) {
   113                     throw new BadArgs("err.dot.output.path", arg);
   115                     throw new BadArgs("err.invalid.path", arg);
   114                 }
   116                 }
   115                 task.options.dotOutputDir = arg;
   117                 task.options.dotOutputDir = arg;
   116             }
   118             }
   117         },
   119         },
   118         new Option(false, "-s", "-summary") {
   120         new Option(false, "-s", "-summary") {
   119             void process(JdepsTask task, String opt, String arg) {
   121             void process(JdepsTask task, String opt, String arg) {
   120                 task.options.showSummary = true;
   122                 task.options.showSummary = true;
   121                 task.options.verbose = Analyzer.Type.SUMMARY;
   123                 task.options.verbose = SUMMARY;
   122             }
   124             }
   123         },
   125         },
   124         new Option(false, "-v", "-verbose",
   126         new Option(false, "-v", "-verbose",
   125                           "-verbose:package",
   127                           "-verbose:package",
   126                           "-verbose:class")
   128                           "-verbose:class") {
   127         {
       
   128             void process(JdepsTask task, String opt, String arg) throws BadArgs {
   129             void process(JdepsTask task, String opt, String arg) throws BadArgs {
   129                 switch (opt) {
   130                 switch (opt) {
   130                     case "-v":
   131                     case "-v":
   131                     case "-verbose":
   132                     case "-verbose":
   132                         task.options.verbose = Analyzer.Type.VERBOSE;
   133                         task.options.verbose = VERBOSE;
       
   134                         task.options.filterSameArchive = false;
       
   135                         task.options.filterSamePackage = false;
   133                         break;
   136                         break;
   134                     case "-verbose:package":
   137                     case "-verbose:package":
   135                             task.options.verbose = Analyzer.Type.PACKAGE;
   138                         task.options.verbose = PACKAGE;
   136                             break;
   139                         break;
   137                     case "-verbose:class":
   140                     case "-verbose:class":
   138                             task.options.verbose = Analyzer.Type.CLASS;
   141                         task.options.verbose = CLASS;
   139                             break;
   142                         break;
   140                     default:
   143                     default:
   141                         throw new BadArgs("err.invalid.arg.for.option", opt);
   144                         throw new BadArgs("err.invalid.arg.for.option", opt);
   142                 }
   145                 }
   143             }
   146             }
   144         },
   147         },
   153             }
   156             }
   154         },
   157         },
   155         new Option(true, "-e", "-regex") {
   158         new Option(true, "-e", "-regex") {
   156             void process(JdepsTask task, String opt, String arg) {
   159             void process(JdepsTask task, String opt, String arg) {
   157                 task.options.regex = arg;
   160                 task.options.regex = arg;
       
   161             }
       
   162         },
       
   163 
       
   164         new Option(true, "-f", "-filter") {
       
   165             void process(JdepsTask task, String opt, String arg) {
       
   166                 task.options.filterRegex = arg;
       
   167             }
       
   168         },
       
   169         new Option(false, "-filter:package",
       
   170                           "-filter:archive",
       
   171                           "-filter:none") {
       
   172             void process(JdepsTask task, String opt, String arg) {
       
   173                 switch (opt) {
       
   174                     case "-filter:package":
       
   175                         task.options.filterSamePackage = true;
       
   176                         task.options.filterSameArchive = false;
       
   177                         break;
       
   178                     case "-filter:archive":
       
   179                         task.options.filterSameArchive = true;
       
   180                         task.options.filterSamePackage = false;
       
   181                         break;
       
   182                     case "-filter:none":
       
   183                         task.options.filterSameArchive = false;
       
   184                         task.options.filterSamePackage = false;
       
   185                         break;
       
   186                 }
   158             }
   187             }
   159         },
   188         },
   160         new Option(true, "-include") {
   189         new Option(true, "-include") {
   161             void process(JdepsTask task, String opt, String arg) throws BadArgs {
   190             void process(JdepsTask task, String opt, String arg) throws BadArgs {
   162                 task.options.includePattern = Pattern.compile(arg);
   191                 task.options.includePattern = Pattern.compile(arg);
   176             }
   205             }
   177         },
   206         },
   178         new Option(false, "-R", "-recursive") {
   207         new Option(false, "-R", "-recursive") {
   179             void process(JdepsTask task, String opt, String arg) {
   208             void process(JdepsTask task, String opt, String arg) {
   180                 task.options.depth = 0;
   209                 task.options.depth = 0;
       
   210                 // turn off filtering
       
   211                 task.options.filterSameArchive = false;
       
   212                 task.options.filterSamePackage = false;
   181             }
   213             }
   182         },
   214         },
   183         new Option(false, "-jdkinternals") {
   215         new Option(false, "-jdkinternals") {
   184             void process(JdepsTask task, String opt, String arg) {
   216             void process(JdepsTask task, String opt, String arg) {
   185                 task.options.findJDKInternals = true;
   217                 task.options.findJDKInternals = true;
   186                 task.options.verbose = Analyzer.Type.CLASS;
   218                 task.options.verbose = CLASS;
   187                 if (task.options.includePattern == null) {
   219                 if (task.options.includePattern == null) {
   188                     task.options.includePattern = Pattern.compile(".*");
   220                     task.options.includePattern = Pattern.compile(".*");
   189                 }
   221                 }
   190             }
   222             }
   191         },
   223         },
   260             if (options.findJDKInternals &&
   292             if (options.findJDKInternals &&
   261                    (options.regex != null || options.packageNames.size() > 0 || options.showSummary)) {
   293                    (options.regex != null || options.packageNames.size() > 0 || options.showSummary)) {
   262                 showHelp();
   294                 showHelp();
   263                 return EXIT_CMDERR;
   295                 return EXIT_CMDERR;
   264             }
   296             }
   265             if (options.showSummary && options.verbose != Analyzer.Type.SUMMARY) {
   297             if (options.showSummary && options.verbose != SUMMARY) {
   266                 showHelp();
   298                 showHelp();
   267                 return EXIT_CMDERR;
   299                 return EXIT_CMDERR;
   268             }
   300             }
   269             boolean ok = run();
   301             boolean ok = run();
   270             return ok ? EXIT_OK : EXIT_ERROR;
   302             return ok ? EXIT_OK : EXIT_ERROR;
   281         }
   313         }
   282     }
   314     }
   283 
   315 
   284     private final List<Archive> sourceLocations = new ArrayList<>();
   316     private final List<Archive> sourceLocations = new ArrayList<>();
   285     private boolean run() throws IOException {
   317     private boolean run() throws IOException {
       
   318         // parse classfiles and find all dependencies
   286         findDependencies();
   319         findDependencies();
   287         Analyzer analyzer = new Analyzer(options.verbose);
   320 
       
   321         Analyzer analyzer = new Analyzer(options.verbose, new Analyzer.Filter() {
       
   322             @Override
       
   323             public boolean accepts(Location origin, Archive originArchive, Location target, Archive targetArchive) {
       
   324                 if (options.findJDKInternals) {
       
   325                     // accepts target that is JDK class but not exported
       
   326                     return isJDKArchive(targetArchive) &&
       
   327                               !((JDKArchive) targetArchive).isExported(target.getClassName());
       
   328                 } else if (options.filterSameArchive) {
       
   329                     // accepts origin and target that from different archive
       
   330                     return originArchive != targetArchive;
       
   331                 }
       
   332                 return true;
       
   333             }
       
   334         });
       
   335 
       
   336         // analyze the dependencies
   288         analyzer.run(sourceLocations);
   337         analyzer.run(sourceLocations);
       
   338 
       
   339         // output result
   289         if (options.dotOutputDir != null) {
   340         if (options.dotOutputDir != null) {
   290             Path dir = Paths.get(options.dotOutputDir);
   341             Path dir = Paths.get(options.dotOutputDir);
   291             Files.createDirectories(dir);
   342             Files.createDirectories(dir);
   292             generateDotFiles(dir, analyzer);
   343             generateDotFiles(dir, analyzer);
   293         } else {
   344         } else {
   294             printRawOutput(log, analyzer);
   345             printRawOutput(log, analyzer);
   295         }
   346         }
   296         return true;
   347         return true;
   297     }
   348     }
   298 
   349 
       
   350     private void generateSummaryDotFile(Path dir, Analyzer analyzer) throws IOException {
       
   351         // If verbose mode (-v or -verbose option),
       
   352         // the summary.dot file shows package-level dependencies.
       
   353         Analyzer.Type summaryType =
       
   354             (options.verbose == PACKAGE || options.verbose == SUMMARY) ? SUMMARY : PACKAGE;
       
   355         Path summary = dir.resolve("summary.dot");
       
   356         try (PrintWriter sw = new PrintWriter(Files.newOutputStream(summary));
       
   357              SummaryDotFile dotfile = new SummaryDotFile(sw, summaryType)) {
       
   358             for (Archive archive : sourceLocations) {
       
   359                 if (!archive.isEmpty()) {
       
   360                     if (options.verbose == PACKAGE || options.verbose == SUMMARY) {
       
   361                         if (options.showLabel) {
       
   362                             // build labels listing package-level dependencies
       
   363                             analyzer.visitDependences(archive, dotfile.labelBuilder(), PACKAGE);
       
   364                         }
       
   365                     }
       
   366                     analyzer.visitDependences(archive, dotfile, summaryType);
       
   367                 }
       
   368             }
       
   369         }
       
   370     }
       
   371 
   299     private void generateDotFiles(Path dir, Analyzer analyzer) throws IOException {
   372     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
   373         // output individual .dot file for each archive
   316         if (options.verbose != Analyzer.Type.SUMMARY) {
   374         if (options.verbose != SUMMARY) {
   317             for (Archive archive : sourceLocations) {
   375             for (Archive archive : sourceLocations) {
   318                 if (analyzer.hasDependences(archive)) {
   376                 if (analyzer.hasDependences(archive)) {
   319                     Path dotfile = dir.resolve(archive.getFileName() + ".dot");
   377                     Path dotfile = dir.resolve(archive.getName() + ".dot");
   320                     try (PrintWriter pw = new PrintWriter(Files.newOutputStream(dotfile));
   378                     try (PrintWriter pw = new PrintWriter(Files.newOutputStream(dotfile));
   321                          DotFileFormatter formatter = new DotFileFormatter(pw, archive)) {
   379                          DotFileFormatter formatter = new DotFileFormatter(pw, archive)) {
   322                         analyzer.visitDependences(archive, formatter);
   380                         analyzer.visitDependences(archive, formatter);
   323                     }
   381                     }
   324                 }
   382                 }
   325             }
   383             }
   326         }
   384         }
       
   385         // generate summary dot file
       
   386         generateSummaryDotFile(dir, analyzer);
   327     }
   387     }
   328 
   388 
   329     private void printRawOutput(PrintWriter writer, Analyzer analyzer) {
   389     private void printRawOutput(PrintWriter writer, Analyzer analyzer) {
       
   390         RawOutputFormatter depFormatter = new RawOutputFormatter(writer);
       
   391         RawSummaryFormatter summaryFormatter = new RawSummaryFormatter(writer);
   330         for (Archive archive : sourceLocations) {
   392         for (Archive archive : sourceLocations) {
   331             RawOutputFormatter formatter = new RawOutputFormatter(writer);
   393             if (!archive.isEmpty()) {
   332             analyzer.visitArchiveDependences(archive, formatter);
   394                 analyzer.visitDependences(archive, summaryFormatter, SUMMARY);
   333             if (options.verbose != Analyzer.Type.SUMMARY) {
   395                 if (analyzer.hasDependences(archive) && options.verbose != SUMMARY) {
   334                 analyzer.visitDependences(archive, formatter);
   396                     analyzer.visitDependences(archive, depFormatter);
   335             }
   397                 }
   336         }
   398             }
   337     }
   399         }
       
   400     }
       
   401 
   338     private boolean isValidClassName(String name) {
   402     private boolean isValidClassName(String name) {
   339         if (!Character.isJavaIdentifierStart(name.charAt(0))) {
   403         if (!Character.isJavaIdentifierStart(name.charAt(0))) {
   340             return false;
   404             return false;
   341         }
   405         }
   342         for (int i=1; i < name.length(); i++) {
   406         for (int i=1; i < name.length(); i++) {
   346             }
   410             }
   347         }
   411         }
   348         return true;
   412         return true;
   349     }
   413     }
   350 
   414 
   351     private Dependency.Filter getDependencyFilter() {
   415     /*
   352          if (options.regex != null) {
   416      * Dep Filter configured based on the input jdeps option
   353             return Dependencies.getRegexFilter(Pattern.compile(options.regex));
   417      * 1. -p and -regex to match target dependencies
   354         } else if (options.packageNames.size() > 0) {
   418      * 2. -filter:package to filter out same-package dependencies
   355             return Dependencies.getPackageFilter(options.packageNames, false);
   419      *
   356         } else {
   420      * This filter is applied when jdeps parses the class files
   357             return new Dependency.Filter() {
   421      * and filtered dependencies are not stored in the Analyzer.
   358                 @Override
   422      *
   359                 public boolean accepts(Dependency dependency) {
   423      * -filter:archive is applied later in the Analyzer as the
   360                     return !dependency.getOrigin().equals(dependency.getTarget());
   424      * containing archive of a target class may not be known until
   361                 }
   425      * the entire archive
   362             };
   426      */
   363         }
   427     class DependencyFilter implements Dependency.Filter {
   364     }
   428         final Dependency.Filter filter;
   365 
   429         final Pattern filterPattern;
       
   430         DependencyFilter() {
       
   431             if (options.regex != null) {
       
   432                 this.filter = Dependencies.getRegexFilter(Pattern.compile(options.regex));
       
   433             } else if (options.packageNames.size() > 0) {
       
   434                 this.filter = Dependencies.getPackageFilter(options.packageNames, false);
       
   435             } else {
       
   436                 this.filter = null;
       
   437             }
       
   438 
       
   439             this.filterPattern =
       
   440                 options.filterRegex != null ? Pattern.compile(options.filterRegex) : null;
       
   441         }
       
   442         @Override
       
   443         public boolean accepts(Dependency d) {
       
   444             if (d.getOrigin().equals(d.getTarget())) {
       
   445                 return false;
       
   446             }
       
   447             String pn = d.getTarget().getPackageName();
       
   448             if (options.filterSamePackage && d.getOrigin().getPackageName().equals(pn)) {
       
   449                 return false;
       
   450             }
       
   451 
       
   452             if (filterPattern != null && filterPattern.matcher(pn).matches()) {
       
   453                 return false;
       
   454             }
       
   455             return filter != null ? filter.accepts(d) : true;
       
   456         }
       
   457     }
       
   458 
       
   459     /**
       
   460      * Tests if the given class matches the pattern given in the -include option
       
   461      * or if it's a public class if -apionly option is specified
       
   462      */
   366     private boolean matches(String classname, AccessFlags flags) {
   463     private boolean matches(String classname, AccessFlags flags) {
   367         if (options.apiOnly && !flags.is(AccessFlags.ACC_PUBLIC)) {
   464         if (options.apiOnly && !flags.is(AccessFlags.ACC_PUBLIC)) {
   368             return false;
   465             return false;
   369         } else if (options.includePattern != null) {
   466         } else if (options.includePattern != null) {
   370             return options.includePattern.matcher(classname.replace('/', '.')).matches();
   467             return options.includePattern.matcher(classname.replace('/', '.')).matches();
   375 
   472 
   376     private void findDependencies() throws IOException {
   473     private void findDependencies() throws IOException {
   377         Dependency.Finder finder =
   474         Dependency.Finder finder =
   378             options.apiOnly ? Dependencies.getAPIFinder(AccessFlags.ACC_PROTECTED)
   475             options.apiOnly ? Dependencies.getAPIFinder(AccessFlags.ACC_PROTECTED)
   379                             : Dependencies.getClassDependencyFinder();
   476                             : Dependencies.getClassDependencyFinder();
   380         Dependency.Filter filter = getDependencyFilter();
   477         Dependency.Filter filter = new DependencyFilter();
   381 
   478 
   382         List<Archive> archives = new ArrayList<>();
   479         List<Archive> archives = new ArrayList<>();
   383         Deque<String> roots = new LinkedList<>();
   480         Deque<String> roots = new LinkedList<>();
   384         for (String s : classes) {
   481         for (String s : classes) {
   385             Path p = Paths.get(s);
   482             Path p = Paths.get(s);
   386             if (Files.exists(p)) {
   483             if (Files.exists(p)) {
   387                 archives.add(new Archive(p, ClassFileReader.newInstance(p)));
   484                 archives.add(Archive.getInstance(p));
   388             } else {
   485             } else {
   389                 if (isValidClassName(s)) {
   486                 if (isValidClassName(s)) {
   390                     roots.add(s);
   487                     roots.add(s);
   391                 } else {
   488                 } else {
   392                     warning("warn.invalid.arg", s);
   489                     warning("warn.invalid.arg", s);
   419                     classFileName = cf.getName();
   516                     classFileName = cf.getName();
   420                 } catch (ConstantPoolException e) {
   517                 } catch (ConstantPoolException e) {
   421                     throw new ClassFileError(e);
   518                     throw new ClassFileError(e);
   422                 }
   519                 }
   423 
   520 
   424                 if (matches(classFileName, cf.access_flags)) {
   521                 // tests if this class matches the -include or -apiOnly option if specified
   425                     if (!doneClasses.contains(classFileName)) {
   522                 if (!matches(classFileName, cf.access_flags)) {
   426                         doneClasses.add(classFileName);
   523                     continue;
       
   524                 }
       
   525 
       
   526                 if (!doneClasses.contains(classFileName)) {
       
   527                     doneClasses.add(classFileName);
       
   528                 }
       
   529 
       
   530                 for (Dependency d : finder.findDependencies(cf)) {
       
   531                     if (filter.accepts(d)) {
       
   532                         String cn = d.getTarget().getName();
       
   533                         if (!doneClasses.contains(cn) && !deque.contains(cn)) {
       
   534                             deque.add(cn);
       
   535                         }
       
   536                         a.addClass(d.getOrigin(), d.getTarget());
   427                     }
   537                     }
   428                     for (Dependency d : finder.findDependencies(cf)) {
   538                 }
   429                         if (filter.accepts(d)) {
   539                 for (String name : a.reader().skippedEntries()) {
   430                             String cn = d.getTarget().getName();
   540                     warning("warn.skipped.entry", name, a.getPathName());
   431                             if (!doneClasses.contains(cn) && !deque.contains(cn)) {
       
   432                                 deque.add(cn);
       
   433                             }
       
   434                             a.addClass(d.getOrigin(), d.getTarget());
       
   435                         }
       
   436                     }
       
   437                 }
   541                 }
   438             }
   542             }
   439         }
   543         }
   440 
   544 
   441         // add Archive for looking up classes from the classpath
   545         // add Archive for looking up classes from the classpath
   460                         }
   564                         }
   461                         if (!doneClasses.contains(classFileName)) {
   565                         if (!doneClasses.contains(classFileName)) {
   462                             // if name is a fully-qualified class name specified
   566                             // if name is a fully-qualified class name specified
   463                             // from command-line, this class might already be parsed
   567                             // from command-line, this class might already be parsed
   464                             doneClasses.add(classFileName);
   568                             doneClasses.add(classFileName);
       
   569                             // process @jdk.Exported for JDK classes
       
   570                             if (isJDKArchive(a)) {
       
   571                                 ((JDKArchive)a).processJdkExported(cf);
       
   572                             }
   465                             for (Dependency d : finder.findDependencies(cf)) {
   573                             for (Dependency d : finder.findDependencies(cf)) {
   466                                 if (depth == 0) {
   574                                 if (depth == 0) {
   467                                     // ignore the dependency
   575                                     // ignore the dependency
   468                                     a.addClass(d.getOrigin());
   576                                     a.addClass(d.getOrigin());
   469                                     break;
   577                                     break;
   542     private void showHelp() {
   650     private void showHelp() {
   543         log.println(getMessage("main.usage", PROGNAME));
   651         log.println(getMessage("main.usage", PROGNAME));
   544         for (Option o : recognizedOptions) {
   652         for (Option o : recognizedOptions) {
   545             String name = o.aliases[0].substring(1); // there must always be at least one name
   653             String name = o.aliases[0].substring(1); // there must always be at least one name
   546             name = name.charAt(0) == '-' ? name.substring(1) : name;
   654             name = name.charAt(0) == '-' ? name.substring(1) : name;
   547             if (o.isHidden() || name.equals("h")) {
   655             if (o.isHidden() || name.equals("h") || name.startsWith("filter:")) {
   548                 continue;
   656                 continue;
   549             }
   657             }
   550             log.println(getMessage("main.opt." + name));
   658             log.println(getMessage("main.opt." + name));
   551         }
   659         }
   552     }
   660     }
   580         boolean help;
   688         boolean help;
   581         boolean version;
   689         boolean version;
   582         boolean fullVersion;
   690         boolean fullVersion;
   583         boolean showProfile;
   691         boolean showProfile;
   584         boolean showSummary;
   692         boolean showSummary;
   585         boolean wildcard;
       
   586         boolean apiOnly;
   693         boolean apiOnly;
   587         boolean showLabel;
   694         boolean showLabel;
   588         boolean findJDKInternals;
   695         boolean findJDKInternals;
       
   696         // default is to show package-level dependencies
       
   697         // and filter references from same package
       
   698         Analyzer.Type verbose = PACKAGE;
       
   699         boolean filterSamePackage = true;
       
   700         boolean filterSameArchive = false;
       
   701         String filterRegex;
   589         String dotOutputDir;
   702         String dotOutputDir;
   590         String classpath = "";
   703         String classpath = "";
   591         int depth = 1;
   704         int depth = 1;
   592         Analyzer.Type verbose = Analyzer.Type.PACKAGE;
       
   593         Set<String> packageNames = new HashSet<>();
   705         Set<String> packageNames = new HashSet<>();
   594         String regex;             // apply to the dependences
   706         String regex;             // apply to the dependences
   595         Pattern includePattern;   // apply to classes
   707         Pattern includePattern;   // apply to classes
   596     }
   708     }
   597     private static class ResourceBundleHelper {
   709     private static class ResourceBundleHelper {
   609                 versionRB = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.version");
   721                 versionRB = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.version");
   610             } catch (MissingResourceException e) {
   722             } catch (MissingResourceException e) {
   611                 throw new InternalError("version.resource.missing");
   723                 throw new InternalError("version.resource.missing");
   612             }
   724             }
   613         }
   725         }
   614     }
       
   615 
       
   616     private List<Archive> getArchives(List<String> filenames) throws IOException {
       
   617         List<Archive> result = new ArrayList<>();
       
   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     }
   726     }
   628 
   727 
   629     private List<Archive> getClassPathArchives(String paths) throws IOException {
   728     private List<Archive> getClassPathArchives(String paths) throws IOException {
   630         List<Archive> result = new ArrayList<>();
   729         List<Archive> result = new ArrayList<>();
   631         if (paths.isEmpty()) {
   730         if (paths.isEmpty()) {
   646                 } else {
   745                 } else {
   647                     files.add(Paths.get(p));
   746                     files.add(Paths.get(p));
   648                 }
   747                 }
   649                 for (Path f : files) {
   748                 for (Path f : files) {
   650                     if (Files.exists(f)) {
   749                     if (Files.exists(f)) {
   651                         result.add(new Archive(f, ClassFileReader.newInstance(f)));
   750                         result.add(Archive.getInstance(f));
   652                     }
   751                     }
   653                 }
   752                 }
   654             }
   753             }
   655         }
   754         }
   656         return result;
   755         return result;
   657     }
       
   658 
       
   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();
       
   670 
       
   671         if (source instanceof JDKArchive) {
       
   672             return profile == null ? "JDK internal API (" + source.getFileName() + ")" : "";
       
   673         }
       
   674         return source.getFileName();
       
   675     }
       
   676 
       
   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     }
   756     }
   688 
   757 
   689     class RawOutputFormatter implements Analyzer.Visitor {
   758     class RawOutputFormatter implements Analyzer.Visitor {
   690         private final PrintWriter writer;
   759         private final PrintWriter writer;
       
   760         private String pkg = "";
   691         RawOutputFormatter(PrintWriter writer) {
   761         RawOutputFormatter(PrintWriter writer) {
   692             this.writer = writer;
   762             this.writer = writer;
   693         }
   763         }
   694 
       
   695         private String pkg = "";
       
   696         @Override
   764         @Override
   697         public void visitDependence(String origin, Archive source,
   765         public void visitDependence(String origin, Archive originArchive,
   698                                     String target, Archive archive, Profile profile) {
   766                                     String target, Archive targetArchive) {
   699             if (options.findJDKInternals &&
   767             String tag = toTag(target, targetArchive);
   700                     !(archive instanceof JDKArchive && profile == null)) {
   768             if (options.verbose == VERBOSE) {
   701                 // filter dependences other than JDK internal APIs
   769                 writer.format("   %-50s -> %-50s %s%n", origin, target, tag);
   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 {
   770             } else {
   708                 if (!origin.equals(pkg)) {
   771                 if (!origin.equals(pkg)) {
   709                     pkg = origin;
   772                     pkg = origin;
   710                     writer.format("   %s (%s)%n", origin, source.getFileName());
   773                     writer.format("   %s (%s)%n", origin, originArchive.getName());
   711                 }
   774                 }
   712                 writer.format("      -> %-50s %s%n",
   775                 writer.format("      -> %-50s %s%n", target, tag);
   713                               target, getProfileArchiveInfo(archive, profile));
   776             }
   714             }
   777         }
   715         }
   778     }
   716 
   779 
       
   780     class RawSummaryFormatter implements Analyzer.Visitor {
       
   781         private final PrintWriter writer;
       
   782         RawSummaryFormatter(PrintWriter writer) {
       
   783             this.writer = writer;
       
   784         }
   717         @Override
   785         @Override
   718         public void visitArchiveDependence(Archive origin, Archive target, Profile profile) {
   786         public void visitDependence(String origin, Archive originArchive,
   719             writer.format("%s -> %s", origin.getPathName(), target.getPathName());
   787                                     String target, Archive targetArchive) {
   720             if (options.showProfile && profile != null) {
   788             writer.format("%s -> %s", originArchive.getName(), targetArchive.getPathName());
   721                 writer.format(" (%s)%n", profile);
   789             if (options.showProfile && JDKArchive.isProfileArchive(targetArchive)) {
   722             } else {
   790                 writer.format(" (%s)", target);
   723                 writer.format("%n");
   791             }
   724             }
   792             writer.format("%n");
   725         }
   793         }
   726     }
   794     }
   727 
   795 
   728     class DotFileFormatter extends DotGraph<String> implements AutoCloseable {
   796     class DotFileFormatter implements Analyzer.Visitor, AutoCloseable {
   729         private final PrintWriter writer;
   797         private final PrintWriter writer;
   730         private final String name;
   798         private final String name;
   731         DotFileFormatter(PrintWriter writer, Archive archive) {
   799         DotFileFormatter(PrintWriter writer, Archive archive) {
   732             this.writer = writer;
   800             this.writer = writer;
   733             this.name = archive.getFileName();
   801             this.name = archive.getName();
   734             writer.format("digraph \"%s\" {%n", name);
   802             writer.format("digraph \"%s\" {%n", name);
   735             writer.format("    // Path: %s%n", archive.getPathName());
   803             writer.format("    // Path: %s%n", archive.getPathName());
   736         }
   804         }
   737 
   805 
   738         @Override
   806         @Override
   739         public void close() {
   807         public void close() {
   740             writer.println("}");
   808             writer.println("}");
   741         }
   809         }
   742 
   810 
   743         @Override
   811         @Override
   744         public void visitDependence(String origin, Archive source,
   812         public void visitDependence(String origin, Archive originArchive,
   745                                     String target, Archive archive, Profile profile) {
   813                                     String target, Archive targetArchive) {
   746             if (options.findJDKInternals &&
   814             String tag = toTag(target, targetArchive);
   747                     !(archive instanceof JDKArchive && profile == null)) {
   815             writer.format("   %-50s -> \"%s\";%n",
   748                 // filter dependences other than JDK internal APIs
   816                           String.format("\"%s\"", origin),
   749                 return;
   817                           tag.isEmpty() ? target
   750             }
   818                                         : String.format("%s (%s)", target, tag));
   751             // if -P option is specified, package name -> profile will
   819         }
   752             // be shown and filter out multiple same edges.
   820     }
   753             String name = getProfileArchiveInfo(archive, profile);
   821 
   754             writeEdge(writer, new Edge(origin, target, getProfileArchiveInfo(archive, profile)));
   822     class SummaryDotFile implements Analyzer.Visitor, AutoCloseable {
   755         }
   823         private final PrintWriter writer;
       
   824         private final Analyzer.Type type;
       
   825         private final Map<Archive, Map<Archive,StringBuilder>> edges = new HashMap<>();
       
   826         SummaryDotFile(PrintWriter writer, Analyzer.Type type) {
       
   827             this.writer = writer;
       
   828             this.type = type;
       
   829             writer.format("digraph \"summary\" {%n");
       
   830         }
       
   831 
   756         @Override
   832         @Override
   757         public void visitArchiveDependence(Archive origin, Archive target, Profile profile) {
   833         public void close() {
   758             throw new UnsupportedOperationException();
   834             writer.println("}");
   759         }
   835         }
   760     }
   836 
   761 
       
   762     class DotSummaryForArchive extends DotGraph<Archive> {
       
   763         @Override
   837         @Override
   764         public void visitDependence(String origin, Archive source,
   838         public void visitDependence(String origin, Archive originArchive,
   765                                     String target, Archive archive, Profile profile) {
   839                                     String target, Archive targetArchive) {
   766             Edge e = findEdge(source, archive);
   840             String targetName = type == PACKAGE ? target : targetArchive.getName();
   767             assert e != null;
   841             if (type == PACKAGE) {
   768             // add the dependency to the label if enabled and not compact1
   842                 String tag = toTag(target, targetArchive, type);
   769             if (profile == Profile.COMPACT1) {
   843                 if (!tag.isEmpty())
   770                 return;
   844                     targetName += " (" + tag + ")";
   771             }
   845             } else if (options.showProfile && JDKArchive.isProfileArchive(targetArchive)) {
   772             e.addLabel(origin, target, profileName(archive, profile));
   846                 targetName += " (" + target + ")";
   773         }
   847             }
   774         @Override
   848             String label = getLabel(originArchive, targetArchive);
   775         public void visitArchiveDependence(Archive origin, Archive target, Profile profile) {
   849             writer.format("  %-50s -> \"%s\"%s;%n",
   776             // add an edge with the archive's name with no tag
   850                           String.format("\"%s\"", origin), targetName, label);
   777             // so that there is only one node for each JDK archive
   851         }
   778             // while there may be edges to different profiles
   852 
   779             Edge e = addEdge(origin, target, "");
   853         String getLabel(Archive origin, Archive target) {
   780             if (target instanceof JDKArchive) {
   854             if (edges.isEmpty())
   781                 // add a label to print the profile
   855                 return "";
   782                 if (profile == null) {
   856 
   783                     e.addLabel("JDK internal API");
   857             StringBuilder label = edges.get(origin).get(target);
   784                 } else if (options.showProfile && !options.showLabel) {
   858             return label == null ? "" : String.format(" [label=\"%s\",fontsize=9]", label.toString());
   785                     e.addLabel(profile.toString());
   859         }
   786                 }
   860 
   787             }
   861         Analyzer.Visitor labelBuilder() {
   788         }
   862             // show the package-level dependencies as labels in the dot graph
   789     }
   863             return new Analyzer.Visitor() {
   790 
   864                 @Override
   791     // DotSummaryForPackage generates the summary.dot file for verbose mode
   865                 public void visitDependence(String origin, Archive originArchive, String target, Archive targetArchive) {
   792     // (-v or -verbose option) that includes all class dependencies.
   866                     edges.putIfAbsent(originArchive, new HashMap<>());
   793     // The summary.dot file shows package-level dependencies.
   867                     edges.get(originArchive).putIfAbsent(targetArchive, new StringBuilder());
   794     class DotSummaryForPackage extends DotGraph<String> {
   868                     StringBuilder sb = edges.get(originArchive).get(targetArchive);
   795         private String packageOf(String cn) {
   869                     String tag = toTag(target, targetArchive, PACKAGE);
   796             int i = cn.lastIndexOf('.');
   870                     addLabel(sb, origin, target, tag);
   797             return i > 0 ? cn.substring(0, i) : "<unnamed>";
   871                 }
   798         }
   872 
   799         @Override
   873                 void addLabel(StringBuilder label, String origin, String target, String tag) {
   800         public void visitDependence(String origin, Archive source,
   874                     label.append(origin).append(" -> ").append(target);
   801                                     String target, Archive archive, Profile profile) {
   875                     if (!tag.isEmpty()) {
   802             // add a package dependency edge
   876                         label.append(" (" + tag + ")");
   803             String from = packageOf(origin);
   877                     }
   804             String to = packageOf(target);
   878                     label.append("\\n");
   805             Edge e = addEdge(from, to, getProfileArchiveInfo(archive, profile));
   879                 }
   806 
   880             };
   807             // add the dependency to the label if enabled and not compact1
   881         }
   808             if (!options.showLabel || profile == Profile.COMPACT1) {
   882     }
   809                 return;
   883 
   810             }
   884     /**
   811 
   885      * Test if the given archive is part of the JDK
   812             // trim the package name of origin to shorten the label
   886      */
   813             int i = origin.lastIndexOf('.');
   887     private boolean isJDKArchive(Archive archive) {
   814             String n1 = i < 0 ? origin : origin.substring(i+1);
   888         return JDKArchive.class.isInstance(archive);
   815             e.addLabel(n1, target, profileName(archive, profile));
   889     }
   816         }
   890 
   817         @Override
   891     /**
   818         public void visitArchiveDependence(Archive origin, Archive target, Profile profile) {
   892      * If the given archive is JDK archive, this method returns the profile name
   819             // nop
   893      * only if -profile option is specified; it accesses a private JDK API and
   820         }
   894      * the returned value will have "JDK internal API" prefix
   821     }
   895      *
   822     abstract class DotGraph<T> implements Analyzer.Visitor  {
   896      * For non-JDK archives, this method returns the file name of the archive.
   823         private final Set<Edge> edges = new LinkedHashSet<>();
   897      */
   824         private Edge curEdge;
   898     private String toTag(String name, Archive source, Analyzer.Type type) {
   825         public void writeTo(PrintWriter writer) {
   899         if (!isJDKArchive(source)) {
   826             writer.format("digraph \"summary\" {%n");
   900             return source.getName();
   827             for (Edge e: edges) {
   901         }
   828                 writeEdge(writer, e);
   902 
   829             }
   903         JDKArchive jdk = (JDKArchive)source;
   830             writer.println("}");
   904         boolean isExported = false;
   831         }
   905         if (type == CLASS || type == VERBOSE) {
   832 
   906             isExported = jdk.isExported(name);
   833         void writeEdge(PrintWriter writer, Edge e) {
   907         } else {
   834             writer.format("   %-50s -> \"%s\"%s;%n",
   908             isExported = jdk.isExportedPackage(name);
   835                           String.format("\"%s\"", e.from.toString()),
   909         }
   836                           e.tag.isEmpty() ? e.to
   910         Profile p = getProfile(name, type);
   837                                           : String.format("%s (%s)", e.to, e.tag),
   911         if (isExported) {
   838                           getLabel(e));
   912             // exported API
   839         }
   913             return options.showProfile && p != null ? p.profileName() : "";
   840 
   914         } else {
   841         Edge addEdge(T origin, T target, String tag) {
   915             return "JDK internal API (" + source.getName() + ")";
   842             Edge e = new Edge(origin, target, tag);
   916         }
   843             if (e.equals(curEdge)) {
   917     }
   844                 return curEdge;
   918 
   845             }
   919     private String toTag(String name, Archive source) {
   846 
   920         return toTag(name, source, options.verbose);
   847             if (edges.contains(e)) {
   921     }
   848                 for (Edge e1 : edges) {
   922 
   849                    if (e.equals(e1)) {
   923     private Profile getProfile(String name, Analyzer.Type type) {
   850                        curEdge = e1;
   924         String pn = name;
   851                    }
   925         if (type == CLASS || type == VERBOSE) {
   852                 }
   926             int i = name.lastIndexOf('.');
   853             } else {
   927             pn = i > 0 ? name.substring(0, i) : "";
   854                 edges.add(e);
   928         }
   855                 curEdge = e;
   929         return Profile.getProfile(pn);
   856             }
       
   857             return curEdge;
       
   858         }
       
   859 
       
   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         }
       
   868 
       
   869         String getLabel(Edge e) {
       
   870             String label = e.label.toString();
       
   871             return label.isEmpty() ? "" : String.format("[label=\"%s\",fontsize=9]", label);
       
   872         }
       
   873 
       
   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     }
   930     }
   913 }
   931 }