langtools/src/share/classes/com/sun/tools/jdeps/JdepsTask.java
changeset 21046 ebf16a1a6328
parent 16560 c6c7f0c26568
child 21503 45fc62482cae
equal deleted inserted replaced
21045:a7a1562c97be 21046:ebf16a1a6328
    22  * or visit www.oracle.com if you need additional information or have any
    22  * or visit www.oracle.com if you need additional information or have any
    23  * questions.
    23  * questions.
    24  */
    24  */
    25 package com.sun.tools.jdeps;
    25 package com.sun.tools.jdeps;
    26 
    26 
       
    27 import com.sun.tools.classfile.AccessFlags;
    27 import com.sun.tools.classfile.ClassFile;
    28 import com.sun.tools.classfile.ClassFile;
    28 import com.sun.tools.classfile.ConstantPoolException;
    29 import com.sun.tools.classfile.ConstantPoolException;
    29 import com.sun.tools.classfile.Dependencies;
    30 import com.sun.tools.classfile.Dependencies;
    30 import com.sun.tools.classfile.Dependencies.ClassFileError;
    31 import com.sun.tools.classfile.Dependencies.ClassFileError;
    31 import com.sun.tools.classfile.Dependency;
    32 import com.sun.tools.classfile.Dependency;
       
    33 import com.sun.tools.jdeps.PlatformClassPath.JDKArchive;
    32 import java.io.*;
    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;
    33 import java.text.MessageFormat;
    39 import java.text.MessageFormat;
    34 import java.util.*;
    40 import java.util.*;
    35 import java.util.regex.Pattern;
    41 import java.util.regex.Pattern;
    36 
    42 
    37 /**
    43 /**
    65             return false;
    71             return false;
    66         }
    72         }
    67 
    73 
    68         boolean matches(String opt) {
    74         boolean matches(String opt) {
    69             for (String a : aliases) {
    75             for (String a : aliases) {
    70                 if (a.equals(opt)) {
    76                 if (a.equals(opt))
    71                     return true;
    77                     return true;
    72                 } else if (opt.startsWith("--") && hasArg && opt.startsWith(a + "=")) {
    78                 if (hasArg && opt.startsWith(a + "="))
    73                     return true;
    79                     return true;
    74                 }
       
    75             }
    80             }
    76             return false;
    81             return false;
    77         }
    82         }
    78 
    83 
    79         boolean ignoreRest() {
    84         boolean ignoreRest() {
    94             return true;
    99             return true;
    95         }
   100         }
    96     }
   101     }
    97 
   102 
    98     static Option[] recognizedOptions = {
   103     static Option[] recognizedOptions = {
    99         new Option(false, "-h", "-?", "--help") {
   104         new Option(false, "-h", "-?", "-help") {
   100             void process(JdepsTask task, String opt, String arg) {
   105             void process(JdepsTask task, String opt, String arg) {
   101                 task.options.help = true;
   106                 task.options.help = true;
   102             }
   107             }
   103         },
   108         },
   104         new Option(false, "-s", "--summary") {
   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") {
   105             void process(JdepsTask task, String opt, String arg) {
   119             void process(JdepsTask task, String opt, String arg) {
   106                 task.options.showSummary = true;
   120                 task.options.showSummary = true;
   107                 task.options.verbose = Analyzer.Type.SUMMARY;
   121                 task.options.verbose = Analyzer.Type.SUMMARY;
   108             }
   122             }
   109         },
   123         },
   110         new Option(false, "-v", "--verbose") {
   124         new Option(false, "-v", "-verbose",
   111             void process(JdepsTask task, String opt, String arg) {
   125                           "-verbose:package",
   112                 task.options.verbose = Analyzer.Type.VERBOSE;
   126                           "-verbose:class")
   113             }
   127         {
   114         },
       
   115         new Option(true, "-V", "--verbose-level") {
       
   116             void process(JdepsTask task, String opt, String arg) throws BadArgs {
   128             void process(JdepsTask task, String opt, String arg) throws BadArgs {
   117                 if ("package".equals(arg)) {
   129                 switch (opt) {
   118                     task.options.verbose = Analyzer.Type.PACKAGE;
   130                     case "-v":
   119                 } else if ("class".equals(arg)) {
   131                     case "-verbose":
   120                     task.options.verbose = Analyzer.Type.CLASS;
   132                         task.options.verbose = Analyzer.Type.VERBOSE;
   121                 } else {
   133                         break;
   122                     throw new BadArgs("err.invalid.arg.for.option", opt);
   134                     case "-verbose:package":
   123                 }
   135                             task.options.verbose = Analyzer.Type.PACKAGE;
   124             }
   136                             break;
   125         },
   137                     case "-verbose:class":
   126         new Option(true, "-c", "--classpath") {
   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") {
   127             void process(JdepsTask task, String opt, String arg) {
   146             void process(JdepsTask task, String opt, String arg) {
   128                 task.options.classpath = arg;
   147                 task.options.classpath = arg;
   129             }
   148             }
   130         },
   149         },
   131         new Option(true, "-p", "--package") {
   150         new Option(true, "-p", "-package") {
   132             void process(JdepsTask task, String opt, String arg) {
   151             void process(JdepsTask task, String opt, String arg) {
   133                 task.options.packageNames.add(arg);
   152                 task.options.packageNames.add(arg);
   134             }
   153             }
   135         },
   154         },
   136         new Option(true, "-e", "--regex") {
   155         new Option(true, "-e", "-regex") {
   137             void process(JdepsTask task, String opt, String arg) {
   156             void process(JdepsTask task, String opt, String arg) {
   138                 task.options.regex = arg;
   157                 task.options.regex = arg;
   139             }
   158             }
   140         },
   159         },
   141         new Option(false, "-P", "--profile") {
   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") {
   142             void process(JdepsTask task, String opt, String arg) throws BadArgs {
   166             void process(JdepsTask task, String opt, String arg) throws BadArgs {
   143                 task.options.showProfile = true;
   167                 task.options.showProfile = true;
   144                 if (Profiles.getProfileCount() == 0) {
   168                 if (Profile.getProfileCount() == 0) {
   145                     throw new BadArgs("err.option.unsupported", opt, getMessage("err.profiles.msg"));
   169                     throw new BadArgs("err.option.unsupported", opt, getMessage("err.profiles.msg"));
   146                 }
   170                 }
   147             }
   171             }
   148         },
   172         },
   149         new Option(false, "-R", "--recursive") {
   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") {
   150             void process(JdepsTask task, String opt, String arg) {
   179             void process(JdepsTask task, String opt, String arg) {
   151                 task.options.depth = 0;
   180                 task.options.depth = 0;
   152             }
   181             }
   153         },
   182         },
   154         new HiddenOption(true, "-d", "--depth") {
   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") {
   155             void process(JdepsTask task, String opt, String arg) throws BadArgs {
   194             void process(JdepsTask task, String opt, String arg) throws BadArgs {
   156                 try {
   195                 try {
   157                     task.options.depth = Integer.parseInt(arg);
   196                     task.options.depth = Integer.parseInt(arg);
   158                 } catch (NumberFormatException e) {
   197                 } catch (NumberFormatException e) {
   159                     throw new BadArgs("err.invalid.arg.for.option", opt);
   198                     throw new BadArgs("err.invalid.arg.for.option", opt);
   160                 }
   199                 }
   161             }
       
   162         },
       
   163         new Option(false, "--version") {
       
   164             void process(JdepsTask task, String opt, String arg) {
       
   165                 task.options.version = true;
       
   166             }
       
   167         },
       
   168         new HiddenOption(false, "--fullversion") {
       
   169             void process(JdepsTask task, String opt, String arg) {
       
   170                 task.options.fullVersion = true;
       
   171             }
   200             }
   172         },
   201         },
   173     };
   202     };
   174 
   203 
   175     private static final String PROGNAME = "jdeps";
   204     private static final String PROGNAME = "jdeps";
   200                 showHelp();
   229                 showHelp();
   201             }
   230             }
   202             if (options.version || options.fullVersion) {
   231             if (options.version || options.fullVersion) {
   203                 showVersion(options.fullVersion);
   232                 showVersion(options.fullVersion);
   204             }
   233             }
   205             if (classes.isEmpty() && !options.wildcard) {
   234             if (classes.isEmpty() && options.includePattern == null) {
   206                 if (options.help || options.version || options.fullVersion) {
   235                 if (options.help || options.version || options.fullVersion) {
   207                     return EXIT_OK;
   236                     return EXIT_OK;
   208                 } else {
   237                 } else {
   209                     showHelp();
   238                     showHelp();
   210                     return EXIT_CMDERR;
   239                     return EXIT_CMDERR;
   231         } finally {
   260         } finally {
   232             log.flush();
   261             log.flush();
   233         }
   262         }
   234     }
   263     }
   235 
   264 
   236     private final List<Archive> sourceLocations = new ArrayList<Archive>();
   265     private final List<Archive> sourceLocations = new ArrayList<>();
   237     private boolean run() throws IOException {
   266     private boolean run() throws IOException {
   238         findDependencies();
   267         findDependencies();
   239         Analyzer analyzer = new Analyzer(options.verbose);
   268         Analyzer analyzer = new Analyzer(options.verbose);
   240         analyzer.run(sourceLocations);
   269         analyzer.run(sourceLocations);
   241         if (options.verbose == Analyzer.Type.SUMMARY) {
   270         if (options.dotOutputDir != null) {
   242             printSummary(log, analyzer);
   271             Path dir = Paths.get(options.dotOutputDir);
       
   272             Files.createDirectories(dir);
       
   273             generateDotFiles(dir, analyzer);
   243         } else {
   274         } else {
   244             printDependencies(log, analyzer);
   275             printRawOutput(log, analyzer);
   245         }
   276         }
   246         return true;
   277         return true;
   247     }
   278     }
   248 
   279 
       
   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     }
       
   300 
       
   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     }
   249     private boolean isValidClassName(String name) {
   310     private boolean isValidClassName(String name) {
   250         if (!Character.isJavaIdentifierStart(name.charAt(0))) {
   311         if (!Character.isJavaIdentifierStart(name.charAt(0))) {
   251             return false;
   312             return false;
   252         }
   313         }
   253         for (int i=1; i < name.length(); i++) {
   314         for (int i=1; i < name.length(); i++) {
   257             }
   318             }
   258         }
   319         }
   259         return true;
   320         return true;
   260     }
   321     }
   261 
   322 
   262     private void findDependencies() throws IOException {
   323     private Dependency.Filter getDependencyFilter() {
   263         Dependency.Finder finder = Dependencies.getClassDependencyFinder();
   324          if (options.regex != null) {
   264         Dependency.Filter filter;
   325             return Dependencies.getRegexFilter(Pattern.compile(options.regex));
   265         if (options.regex != null) {
       
   266             filter = Dependencies.getRegexFilter(Pattern.compile(options.regex));
       
   267         } else if (options.packageNames.size() > 0) {
   326         } else if (options.packageNames.size() > 0) {
   268             filter = Dependencies.getPackageFilter(options.packageNames, false);
   327             return Dependencies.getPackageFilter(options.packageNames, false);
   269         } else {
   328         } else {
   270             filter = new Dependency.Filter() {
   329             return new Dependency.Filter() {
       
   330                 @Override
   271                 public boolean accepts(Dependency dependency) {
   331                 public boolean accepts(Dependency dependency) {
   272                     return !dependency.getOrigin().equals(dependency.getTarget());
   332                     return !dependency.getOrigin().equals(dependency.getTarget());
   273                 }
   333                 }
   274             };
   334             };
   275         }
   335         }
   276 
   336     }
   277         List<Archive> archives = new ArrayList<Archive>();
   337 
   278         Deque<String> roots = new LinkedList<String>();
   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     }
       
   347 
       
   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();
       
   353 
       
   354         List<Archive> archives = new ArrayList<>();
       
   355         Deque<String> roots = new LinkedList<>();
   279         for (String s : classes) {
   356         for (String s : classes) {
   280             File f = new File(s);
   357             Path p = Paths.get(s);
   281             if (f.exists()) {
   358             if (Files.exists(p)) {
   282                 archives.add(new Archive(f, ClassFileReader.newInstance(f)));
   359                 archives.add(new Archive(p, ClassFileReader.newInstance(p)));
   283             } else {
   360             } else {
   284                 if (isValidClassName(s)) {
   361                 if (isValidClassName(s)) {
   285                     roots.add(s);
   362                     roots.add(s);
   286                 } else {
   363                 } else {
   287                     warning("warn.invalid.arg", s);
   364                     warning("warn.invalid.arg", s);
   288                 }
   365                 }
   289             }
   366             }
   290         }
   367         }
   291 
   368 
   292         List<Archive> classpaths = new ArrayList<Archive>(); // for class file lookup
   369         List<Archive> classpaths = new ArrayList<>(); // for class file lookup
   293         if (options.wildcard) {
   370         if (options.includePattern != null) {
   294             // include all archives from classpath to the initial list
       
   295             archives.addAll(getClassPathArchives(options.classpath));
   371             archives.addAll(getClassPathArchives(options.classpath));
   296         } else {
   372         } else {
   297             classpaths.addAll(getClassPathArchives(options.classpath));
   373             classpaths.addAll(getClassPathArchives(options.classpath));
   298         }
   374         }
   299         classpaths.addAll(PlatformClassPath.getArchives());
   375         classpaths.addAll(PlatformClassPath.getArchives());
   303         sourceLocations.addAll(classpaths);
   379         sourceLocations.addAll(classpaths);
   304 
   380 
   305         // Work queue of names of classfiles to be searched.
   381         // Work queue of names of classfiles to be searched.
   306         // Entries will be unique, and for classes that do not yet have
   382         // Entries will be unique, and for classes that do not yet have
   307         // dependencies in the results map.
   383         // dependencies in the results map.
   308         Deque<String> deque = new LinkedList<String>();
   384         Deque<String> deque = new LinkedList<>();
   309         Set<String> doneClasses = new HashSet<String>();
   385         Set<String> doneClasses = new HashSet<>();
   310 
   386 
   311         // get the immediate dependencies of the input files
   387         // get the immediate dependencies of the input files
   312         for (Archive a : archives) {
   388         for (Archive a : archives) {
   313             for (ClassFile cf : a.reader().getClassFiles()) {
   389             for (ClassFile cf : a.reader().getClassFiles()) {
   314                 String classFileName;
   390                 String classFileName;
   316                     classFileName = cf.getName();
   392                     classFileName = cf.getName();
   317                 } catch (ConstantPoolException e) {
   393                 } catch (ConstantPoolException e) {
   318                     throw new ClassFileError(e);
   394                     throw new ClassFileError(e);
   319                 }
   395                 }
   320 
   396 
   321                 if (!doneClasses.contains(classFileName)) {
   397                 if (matches(classFileName, cf.access_flags)) {
   322                     doneClasses.add(classFileName);
   398                     if (!doneClasses.contains(classFileName)) {
   323                 }
   399                         doneClasses.add(classFileName);
   324                 for (Dependency d : finder.findDependencies(cf)) {
   400                     }
   325                     if (filter.accepts(d)) {
   401                     for (Dependency d : finder.findDependencies(cf)) {
   326                         String cn = d.getTarget().getName();
   402                         if (filter.accepts(d)) {
   327                         if (!doneClasses.contains(cn) && !deque.contains(cn)) {
   403                             String cn = d.getTarget().getName();
   328                             deque.add(cn);
   404                             if (!doneClasses.contains(cn) && !deque.contains(cn)) {
       
   405                                 deque.add(cn);
       
   406                             }
       
   407                             a.addClass(d.getOrigin(), d.getTarget());
   329                         }
   408                         }
   330                         a.addClass(d.getOrigin(), d.getTarget());
       
   331                     }
   409                     }
   332                 }
   410                 }
   333             }
   411             }
   334         }
   412         }
   335 
   413 
   377                 if (cf == null) {
   455                 if (cf == null) {
   378                     doneClasses.add(name);
   456                     doneClasses.add(name);
   379                 }
   457                 }
   380             }
   458             }
   381             unresolved = deque;
   459             unresolved = deque;
   382             deque = new LinkedList<String>();
   460             deque = new LinkedList<>();
   383         } while (!unresolved.isEmpty() && depth-- > 0);
   461         } while (!unresolved.isEmpty() && depth-- > 0);
   384     }
       
   385 
       
   386     private void printSummary(final PrintWriter out, final Analyzer analyzer) {
       
   387         Analyzer.Visitor visitor = new Analyzer.Visitor() {
       
   388             public void visit(String origin, String target, String profile) {
       
   389                 if (options.showProfile) {
       
   390                     out.format("%-30s -> %s%n", origin, target);
       
   391                 }
       
   392             }
       
   393             public void visit(Archive origin, Archive target) {
       
   394                 if (!options.showProfile) {
       
   395                     out.format("%-30s -> %s%n", origin, target);
       
   396                 }
       
   397             }
       
   398         };
       
   399         analyzer.visitSummary(visitor);
       
   400     }
       
   401 
       
   402     private void printDependencies(final PrintWriter out, final Analyzer analyzer) {
       
   403         Analyzer.Visitor visitor = new Analyzer.Visitor() {
       
   404             private String pkg = "";
       
   405             public void visit(String origin, String target, String profile) {
       
   406                 if (!origin.equals(pkg)) {
       
   407                     pkg = origin;
       
   408                     out.format("   %s (%s)%n", origin, analyzer.getArchive(origin).getFileName());
       
   409                 }
       
   410                 out.format("      -> %-50s %s%n", target,
       
   411                            (options.showProfile && !profile.isEmpty())
       
   412                                ? profile
       
   413                                : analyzer.getArchiveName(target, profile));
       
   414             }
       
   415             public void visit(Archive origin, Archive target) {
       
   416                 out.format("%s -> %s%n", origin, target);
       
   417             }
       
   418         };
       
   419         analyzer.visit(visitor);
       
   420     }
   462     }
   421 
   463 
   422     public void handleOptions(String[] args) throws BadArgs {
   464     public void handleOptions(String[] args) throws BadArgs {
   423         // process options
   465         // process options
   424         for (int i=0; i < args.length; i++) {
   466         for (int i=0; i < args.length; i++) {
   425             if (args[i].charAt(0) == '-') {
   467             if (args[i].charAt(0) == '-') {
   426                 String name = args[i];
   468                 String name = args[i];
   427                 Option option = getOption(name);
   469                 Option option = getOption(name);
   428                 String param = null;
   470                 String param = null;
   429                 if (option.hasArg) {
   471                 if (option.hasArg) {
   430                     if (name.startsWith("--") && name.indexOf('=') > 0) {
   472                     if (name.startsWith("-") && name.indexOf('=') > 0) {
   431                         param = name.substring(name.indexOf('=') + 1, name.length());
   473                         param = name.substring(name.indexOf('=') + 1, name.length());
   432                     } else if (i + 1 < args.length) {
   474                     } else if (i + 1 < args.length) {
   433                         param = args[++i];
   475                         param = args[++i];
   434                     }
   476                     }
   435                     if (param == null || param.isEmpty() || param.charAt(0) == '-') {
   477                     if (param == null || param.isEmpty() || param.charAt(0) == '-') {
   445                 for (; i < args.length; i++) {
   487                 for (; i < args.length; i++) {
   446                     String name = args[i];
   488                     String name = args[i];
   447                     if (name.charAt(0) == '-') {
   489                     if (name.charAt(0) == '-') {
   448                         throw new BadArgs("err.option.after.class", name).showUsage(true);
   490                         throw new BadArgs("err.option.after.class", name).showUsage(true);
   449                     }
   491                     }
   450                     if (name.equals("*") || name.equals("\"*\"")) {
   492                     classes.add(name);
   451                         options.wildcard = true;
       
   452                     } else {
       
   453                         classes.add(name);
       
   454                     }
       
   455                 }
   493                 }
   456             }
   494             }
   457         }
   495         }
   458     }
   496     }
   459 
   497 
   516         boolean version;
   554         boolean version;
   517         boolean fullVersion;
   555         boolean fullVersion;
   518         boolean showProfile;
   556         boolean showProfile;
   519         boolean showSummary;
   557         boolean showSummary;
   520         boolean wildcard;
   558         boolean wildcard;
   521         String regex;
   559         boolean apiOnly;
       
   560         String dotOutputDir;
   522         String classpath = "";
   561         String classpath = "";
   523         int depth = 1;
   562         int depth = 1;
   524         Analyzer.Type verbose = Analyzer.Type.PACKAGE;
   563         Analyzer.Type verbose = Analyzer.Type.PACKAGE;
   525         Set<String> packageNames = new HashSet<String>();
   564         Set<String> packageNames = new HashSet<>();
   526     }
   565         String regex;             // apply to the dependences
   527 
   566         Pattern includePattern;   // apply to classes
       
   567     }
   528     private static class ResourceBundleHelper {
   568     private static class ResourceBundleHelper {
   529         static final ResourceBundle versionRB;
   569         static final ResourceBundle versionRB;
   530         static final ResourceBundle bundle;
   570         static final ResourceBundle bundle;
   531 
   571 
   532         static {
   572         static {
   545     }
   585     }
   546 
   586 
   547     private List<Archive> getArchives(List<String> filenames) throws IOException {
   587     private List<Archive> getArchives(List<String> filenames) throws IOException {
   548         List<Archive> result = new ArrayList<Archive>();
   588         List<Archive> result = new ArrayList<Archive>();
   549         for (String s : filenames) {
   589         for (String s : filenames) {
   550             File f = new File(s);
   590             Path p = Paths.get(s);
   551             if (f.exists()) {
   591             if (Files.exists(p)) {
   552                 result.add(new Archive(f, ClassFileReader.newInstance(f)));
   592                 result.add(new Archive(p, ClassFileReader.newInstance(p)));
   553             } else {
   593             } else {
   554                 warning("warn.file.not.exist", s);
   594                 warning("warn.file.not.exist", s);
   555             }
   595             }
   556         }
   596         }
   557         return result;
   597         return result;
   558     }
   598     }
   559 
   599 
   560     private List<Archive> getClassPathArchives(String paths) throws IOException {
   600     private List<Archive> getClassPathArchives(String paths) throws IOException {
   561         List<Archive> result = new ArrayList<Archive>();
   601         List<Archive> result = new ArrayList<>();
   562         if (paths.isEmpty()) {
   602         if (paths.isEmpty()) {
   563             return result;
   603             return result;
   564         }
   604         }
   565         for (String p : paths.split(File.pathSeparator)) {
   605         for (String p : paths.split(File.pathSeparator)) {
   566             if (p.length() > 0) {
   606             if (p.length() > 0) {
   567                 File f = new File(p);
   607                 List<Path> files = new ArrayList<>();
   568                 if (f.exists()) {
   608                 // wildcard to parse all JAR files e.g. -classpath dir/*
   569                     result.add(new Archive(f, ClassFileReader.newInstance(f)));
   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                     }
   570                 }
   624                 }
   571             }
   625             }
   572         }
   626         }
   573         return result;
   627         return result;
   574     }
   628     }
       
   629 
       
   630 
       
   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     }
       
   641 
       
   642     class RawOutputFormatter implements Analyzer.Visitor {
       
   643         private final PrintWriter writer;
       
   644         RawOutputFormatter(PrintWriter writer) {
       
   645             this.writer = writer;
       
   646         }
       
   647 
       
   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         }
       
   661 
       
   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     }
       
   672 
       
   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         }
       
   687 
       
   688         @Override
       
   689         public void close() {
       
   690             writer.println("}");
       
   691         }
       
   692 
       
   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         }
       
   716 
       
   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     }
   575 }
   728 }