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

Print this page




   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package com.sun.tools.jdeps;
  26 

  27 import com.sun.tools.classfile.ClassFile;
  28 import com.sun.tools.classfile.ConstantPoolException;
  29 import com.sun.tools.classfile.Dependencies;
  30 import com.sun.tools.classfile.Dependencies.ClassFileError;
  31 import com.sun.tools.classfile.Dependency;
  32 import java.io.*;




  33 import java.text.MessageFormat;
  34 import java.util.*;
  35 import java.util.regex.Pattern;
  36 
  37 /**
  38  * Implementation for the jdeps tool for static class dependency analysis.
  39  */
  40 class JdepsTask {
  41     static class BadArgs extends Exception {
  42         static final long serialVersionUID = 8765093759964640721L;
  43         BadArgs(String key, Object... args) {
  44             super(JdepsTask.getMessage(key, args));
  45             this.key = key;
  46             this.args = args;
  47         }
  48 
  49         BadArgs showUsage(boolean b) {
  50             showUsage = b;
  51             return this;
  52         }
  53         final String key;
  54         final Object[] args;
  55         boolean showUsage;
  56     }
  57 
  58     static abstract class Option {
  59         Option(boolean hasArg, String... aliases) {
  60             this.hasArg = hasArg;
  61             this.aliases = aliases;
  62         }
  63 
  64         boolean isHidden() {
  65             return false;
  66         }
  67 
  68         boolean matches(String opt) {
  69             for (String a : aliases) {
  70                 if (a.equals(opt)) {
  71                     return true;
  72                 } else if (opt.startsWith("--") && hasArg && opt.startsWith(a + "=")) {
  73                     return true;
  74                 }
  75             }
  76             return false;
  77         }
  78 
  79         boolean ignoreRest() {
  80             return false;
  81         }
  82 
  83         abstract void process(JdepsTask task, String opt, String arg) throws BadArgs;
  84         final boolean hasArg;
  85         final String[] aliases;
  86     }
  87 
  88     static abstract class HiddenOption extends Option {
  89         HiddenOption(boolean hasArg, String... aliases) {
  90             super(hasArg, aliases);
  91         }
  92 
  93         boolean isHidden() {
  94             return true;
  95         }
  96     }
  97 
  98     static Option[] recognizedOptions = {
  99         new Option(false, "-h", "-?", "--help") {
 100             void process(JdepsTask task, String opt, String arg) {
 101                 task.options.help = true;
 102             }
 103         },
 104         new Option(false, "-s", "--summary") {
 105             void process(JdepsTask task, String opt, String arg) {
 106                 task.options.showSummary = true;
 107                 task.options.verbose = Analyzer.Type.SUMMARY;



 108             }
 109         },
 110         new Option(false, "-v", "--verbose") {
 111             void process(JdepsTask task, String opt, String arg) {
 112                 task.options.verbose = Analyzer.Type.VERBOSE;

 113             }
 114         },
 115         new Option(true, "-V", "--verbose-level") {



 116             void process(JdepsTask task, String opt, String arg) throws BadArgs {
 117                 if ("package".equals(arg)) {





 118                     task.options.verbose = Analyzer.Type.PACKAGE;
 119                 } else if ("class".equals(arg)) {

 120                     task.options.verbose = Analyzer.Type.CLASS;
 121                 } else {

 122                     throw new BadArgs("err.invalid.arg.for.option", opt);
 123                 }
 124             }
 125         },
 126         new Option(true, "-c", "--classpath") {
 127             void process(JdepsTask task, String opt, String arg) {
 128                 task.options.classpath = arg;
 129             }
 130         },
 131         new Option(true, "-p", "--package") {
 132             void process(JdepsTask task, String opt, String arg) {
 133                 task.options.packageNames.add(arg);
 134             }
 135         },
 136         new Option(true, "-e", "--regex") {
 137             void process(JdepsTask task, String opt, String arg) {
 138                 task.options.regex = arg;
 139             }
 140         },
 141         new Option(false, "-P", "--profile") {





 142             void process(JdepsTask task, String opt, String arg) throws BadArgs {
 143                 task.options.showProfile = true;
 144                 if (Profiles.getProfileCount() == 0) {
 145                     throw new BadArgs("err.option.unsupported", opt, getMessage("err.profiles.msg"));
 146                 }
 147             }
 148         },
 149         new Option(false, "-R", "--recursive") {
 150             void process(JdepsTask task, String opt, String arg) {
 151                 task.options.depth = 0;
 152             }
 153         },
 154         new HiddenOption(true, "-d", "--depth") {
 155             void process(JdepsTask task, String opt, String arg) throws BadArgs {
 156                 try {
 157                     task.options.depth = Integer.parseInt(arg);
 158                 } catch (NumberFormatException e) {
 159                     throw new BadArgs("err.invalid.arg.for.option", opt);
 160                 }
 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             }
 172         },









 173     };
 174 
 175     private static final String PROGNAME = "jdeps";
 176     private final Options options = new Options();
 177     private final List<String> classes = new ArrayList<String>();
 178 
 179     private PrintWriter log;
 180     void setLog(PrintWriter out) {
 181         log = out;
 182     }
 183 
 184     /**
 185      * Result codes.
 186      */
 187     static final int EXIT_OK = 0, // Completed with no errors.
 188                      EXIT_ERROR = 1, // Completed but reported errors.
 189                      EXIT_CMDERR = 2, // Bad command-line arguments
 190                      EXIT_SYSERR = 3, // System error or resource exhaustion.
 191                      EXIT_ABNORMAL = 4;// terminated abnormally
 192 
 193     int run(String[] args) {
 194         if (log == null) {
 195             log = new PrintWriter(System.out);
 196         }
 197         try {
 198             handleOptions(args);
 199             if (options.help) {
 200                 showHelp();
 201             }
 202             if (options.version || options.fullVersion) {
 203                 showVersion(options.fullVersion);
 204             }
 205             if (classes.isEmpty() && !options.wildcard) {
 206                 if (options.help || options.version || options.fullVersion) {
 207                     return EXIT_OK;
 208                 } else {
 209                     showHelp();
 210                     return EXIT_CMDERR;
 211                 }
 212             }
 213             if (options.regex != null && options.packageNames.size() > 0) {
 214                 showHelp();
 215                 return EXIT_CMDERR;
 216             }
 217             if (options.showSummary && options.verbose != Analyzer.Type.SUMMARY) {
 218                 showHelp();
 219                 return EXIT_CMDERR;
 220             }
 221             boolean ok = run();
 222             return ok ? EXIT_OK : EXIT_ERROR;
 223         } catch (BadArgs e) {
 224             reportError(e.key, e.args);
 225             if (e.showUsage) {
 226                 log.println(getMessage("main.usage.summary", PROGNAME));
 227             }
 228             return EXIT_CMDERR;
 229         } catch (IOException e) {
 230             return EXIT_ABNORMAL;
 231         } finally {
 232             log.flush();
 233         }
 234     }
 235 
 236     private final List<Archive> sourceLocations = new ArrayList<Archive>();
 237     private boolean run() throws IOException {
 238         findDependencies();
 239         Analyzer analyzer = new Analyzer(options.verbose);
 240         analyzer.run(sourceLocations);
 241         if (options.verbose == Analyzer.Type.SUMMARY) {
 242             printSummary(log, analyzer);


 243         } else {
 244             printDependencies(log, analyzer);
 245         }
 246         return true;
 247     }
 248 






























 249     private boolean isValidClassName(String name) {
 250         if (!Character.isJavaIdentifierStart(name.charAt(0))) {
 251             return false;
 252         }
 253         for (int i=1; i < name.length(); i++) {
 254             char c = name.charAt(i);
 255             if (c != '.'  && !Character.isJavaIdentifierPart(c)) {
 256                 return false;
 257             }
 258         }
 259         return true;
 260     }
 261 
 262     private void findDependencies() throws IOException {
 263         Dependency.Finder finder = Dependencies.getClassDependencyFinder();
 264         Dependency.Filter filter;
 265         if (options.regex != null) {
 266             filter = Dependencies.getRegexFilter(Pattern.compile(options.regex));
 267         } else if (options.packageNames.size() > 0) {
 268             filter = Dependencies.getPackageFilter(options.packageNames, false);
 269         } else {
 270             filter = new Dependency.Filter() {

 271                 public boolean accepts(Dependency dependency) {
 272                     return !dependency.getOrigin().equals(dependency.getTarget());
 273                 }
 274             };
 275         }

















 276 
 277         List<Archive> archives = new ArrayList<Archive>();
 278         Deque<String> roots = new LinkedList<String>();
 279         for (String s : classes) {
 280             File f = new File(s);
 281             if (f.exists()) {
 282                 archives.add(new Archive(f, ClassFileReader.newInstance(f)));
 283             } else {
 284                 if (isValidClassName(s)) {
 285                     roots.add(s);
 286                 } else {
 287                     warning("warn.invalid.arg", s);
 288                 }
 289             }
 290         }
 291 
 292         List<Archive> classpaths = new ArrayList<Archive>(); // for class file lookup
 293         if (options.wildcard) {
 294             // include all archives from classpath to the initial list
 295             archives.addAll(getClassPathArchives(options.classpath));
 296         } else {
 297             classpaths.addAll(getClassPathArchives(options.classpath));
 298         }
 299         classpaths.addAll(PlatformClassPath.getArchives());
 300 
 301         // add all archives to the source locations for reporting
 302         sourceLocations.addAll(archives);
 303         sourceLocations.addAll(classpaths);
 304 
 305         // Work queue of names of classfiles to be searched.
 306         // Entries will be unique, and for classes that do not yet have
 307         // dependencies in the results map.
 308         Deque<String> deque = new LinkedList<String>();
 309         Set<String> doneClasses = new HashSet<String>();
 310 
 311         // get the immediate dependencies of the input files
 312         for (Archive a : archives) {
 313             for (ClassFile cf : a.reader().getClassFiles()) {
 314                 String classFileName;
 315                 try {
 316                     classFileName = cf.getName();
 317                 } catch (ConstantPoolException e) {
 318                     throw new ClassFileError(e);
 319                 }
 320 

 321                 if (!doneClasses.contains(classFileName)) {
 322                     doneClasses.add(classFileName);
 323                 }
 324                 for (Dependency d : finder.findDependencies(cf)) {
 325                     if (filter.accepts(d)) {
 326                         String cn = d.getTarget().getName();
 327                         if (!doneClasses.contains(cn) && !deque.contains(cn)) {
 328                             deque.add(cn);
 329                         }
 330                         a.addClass(d.getOrigin(), d.getTarget());
 331                     }
 332                 }
 333             }
 334         }

 335 
 336         // add Archive for looking up classes from the classpath
 337         // for transitive dependency analysis
 338         Deque<String> unresolved = roots;
 339         int depth = options.depth > 0 ? options.depth : Integer.MAX_VALUE;
 340         do {
 341             String name;
 342             while ((name = unresolved.poll()) != null) {
 343                 if (doneClasses.contains(name)) {
 344                     continue;
 345                 }
 346                 ClassFile cf = null;
 347                 for (Archive a : classpaths) {
 348                     cf = a.reader().getClassFile(name);
 349                     if (cf != null) {
 350                         String classFileName;
 351                         try {
 352                             classFileName = cf.getName();
 353                         } catch (ConstantPoolException e) {
 354                             throw new ClassFileError(e);


 362                                     // ignore the dependency
 363                                     a.addClass(d.getOrigin());
 364                                     break;
 365                                 } else if (filter.accepts(d)) {
 366                                     a.addClass(d.getOrigin(), d.getTarget());
 367                                     String cn = d.getTarget().getName();
 368                                     if (!doneClasses.contains(cn) && !deque.contains(cn)) {
 369                                         deque.add(cn);
 370                                     }
 371                                 }
 372                             }
 373                         }
 374                         break;
 375                     }
 376                 }
 377                 if (cf == null) {
 378                     doneClasses.add(name);
 379                 }
 380             }
 381             unresolved = deque;
 382             deque = new LinkedList<String>();
 383         } 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     }
 421 
 422     public void handleOptions(String[] args) throws BadArgs {
 423         // process options
 424         for (int i=0; i < args.length; i++) {
 425             if (args[i].charAt(0) == '-') {
 426                 String name = args[i];
 427                 Option option = getOption(name);
 428                 String param = null;
 429                 if (option.hasArg) {
 430                     if (name.startsWith("--") && name.indexOf('=') > 0) {
 431                         param = name.substring(name.indexOf('=') + 1, name.length());
 432                     } else if (i + 1 < args.length) {
 433                         param = args[++i];
 434                     }
 435                     if (param == null || param.isEmpty() || param.charAt(0) == '-') {
 436                         throw new BadArgs("err.missing.arg", name).showUsage(true);
 437                     }
 438                 }
 439                 option.process(this, name, param);
 440                 if (option.ignoreRest()) {
 441                     i = args.length;
 442                 }
 443             } else {
 444                 // process rest of the input arguments
 445                 for (; i < args.length; i++) {
 446                     String name = args[i];
 447                     if (name.charAt(0) == '-') {
 448                         throw new BadArgs("err.option.after.class", name).showUsage(true);
 449                     }
 450                     if (name.equals("*") || name.equals("\"*\"")) {
 451                         options.wildcard = true;
 452                     } else {
 453                         classes.add(name);
 454                     }
 455                 }
 456             }
 457         }
 458     }
 459 
 460     private Option getOption(String name) throws BadArgs {
 461         for (Option o : recognizedOptions) {
 462             if (o.matches(name)) {
 463                 return o;
 464             }
 465         }
 466         throw new BadArgs("err.unknown.option", name).showUsage(true);
 467     }
 468 
 469     private void reportError(String key, Object... args) {
 470         log.println(getMessage("error.prefix") + " " + getMessage(key, args));
 471     }
 472 
 473     private void warning(String key, Object... args) {
 474         log.println(getMessage("warn.prefix") + " " + getMessage(key, args));
 475     }
 476 
 477     private void showHelp() {
 478         log.println(getMessage("main.usage", PROGNAME));


 501         } catch (MissingResourceException e) {
 502             return getMessage("version.unknown", System.getProperty("java.version"));
 503         }
 504     }
 505 
 506     static String getMessage(String key, Object... args) {
 507         try {
 508             return MessageFormat.format(ResourceBundleHelper.bundle.getString(key), args);
 509         } catch (MissingResourceException e) {
 510             throw new InternalError("Missing message: " + key);
 511         }
 512     }
 513 
 514     private static class Options {
 515         boolean help;
 516         boolean version;
 517         boolean fullVersion;
 518         boolean showProfile;
 519         boolean showSummary;
 520         boolean wildcard;
 521         String regex;

 522         String classpath = "";
 523         int depth = 1;
 524         Analyzer.Type verbose = Analyzer.Type.PACKAGE;
 525         Set<String> packageNames = new HashSet<String>();


 526     }
 527 
 528     private static class ResourceBundleHelper {
 529         static final ResourceBundle versionRB;
 530         static final ResourceBundle bundle;
 531 
 532         static {
 533             Locale locale = Locale.getDefault();
 534             try {
 535                 bundle = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.jdeps", locale);
 536             } catch (MissingResourceException e) {
 537                 throw new InternalError("Cannot find jdeps resource bundle for locale " + locale);
 538             }
 539             try {
 540                 versionRB = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.version");
 541             } catch (MissingResourceException e) {
 542                 throw new InternalError("version.resource.missing");
 543             }
 544         }
 545     }
 546 
 547     private List<Archive> getArchives(List<String> filenames) throws IOException {
 548         List<Archive> result = new ArrayList<Archive>();
 549         for (String s : filenames) {
 550             File f = new File(s);
 551             if (f.exists()) {
 552                 result.add(new Archive(f, ClassFileReader.newInstance(f)));
 553             } else {
 554                 warning("warn.file.not.exist", s);
 555             }
 556         }
 557         return result;
 558     }
 559 
 560     private List<Archive> getClassPathArchives(String paths) throws IOException {
 561         List<Archive> result = new ArrayList<Archive>();
 562         if (paths.isEmpty()) {
 563             return result;
 564         }
 565         for (String p : paths.split(File.pathSeparator)) {
 566             if (p.length() > 0) {
 567                 File f = new File(p);
 568                 if (f.exists()) {













 569                     result.add(new Archive(f, ClassFileReader.newInstance(f)));
 570                 }
 571             }
 572         }

 573         return result;
 574     }



































































































 575 }


   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package com.sun.tools.jdeps;
  26 
  27 import com.sun.tools.classfile.AccessFlags;
  28 import com.sun.tools.classfile.ClassFile;
  29 import com.sun.tools.classfile.ConstantPoolException;
  30 import com.sun.tools.classfile.Dependencies;
  31 import com.sun.tools.classfile.Dependencies.ClassFileError;
  32 import com.sun.tools.classfile.Dependency;
  33 import java.io.*;
  34 import java.nio.file.DirectoryStream;
  35 import java.nio.file.Files;
  36 import java.nio.file.Path;
  37 import java.nio.file.Paths;
  38 import java.text.MessageFormat;
  39 import java.util.*;
  40 import java.util.regex.Pattern;
  41 
  42 /**
  43  * Implementation for the jdeps tool for static class dependency analysis.
  44  */
  45 class JdepsTask {
  46     static class BadArgs extends Exception {
  47         static final long serialVersionUID = 8765093759964640721L;
  48         BadArgs(String key, Object... args) {
  49             super(JdepsTask.getMessage(key, args));
  50             this.key = key;
  51             this.args = args;
  52         }
  53 
  54         BadArgs showUsage(boolean b) {
  55             showUsage = b;
  56             return this;
  57         }
  58         final String key;
  59         final Object[] args;
  60         boolean showUsage;
  61     }
  62 
  63     static abstract class Option {
  64         Option(boolean hasArg, String... aliases) {
  65             this.hasArg = hasArg;
  66             this.aliases = aliases;
  67         }
  68 
  69         boolean isHidden() {
  70             return false;
  71         }
  72 
  73         boolean matches(String opt) {
  74             for (String a : aliases) {
  75                 if (a.equals(opt))
  76                     return true;
  77                 if (hasArg && opt.startsWith(a + "="))
  78                     return true;
  79             }

  80             return false;
  81         }
  82 
  83         boolean ignoreRest() {
  84             return false;
  85         }
  86 
  87         abstract void process(JdepsTask task, String opt, String arg) throws BadArgs;
  88         final boolean hasArg;
  89         final String[] aliases;
  90     }
  91 
  92     static abstract class HiddenOption extends Option {
  93         HiddenOption(boolean hasArg, String... aliases) {
  94             super(hasArg, aliases);
  95         }
  96 
  97         boolean isHidden() {
  98             return true;
  99         }
 100     }
 101 
 102     static Option[] recognizedOptions = {
 103         new Option(false, "-h", "-?", "-help") {
 104             void process(JdepsTask task, String opt, String arg) {
 105                 task.options.help = true;
 106             }
 107         },
 108         new Option(true, "-dotoutput") {
 109             void process(JdepsTask task, String opt, String arg) throws BadArgs {
 110                 Path p = Paths.get(arg);
 111                 if (Files.exists(p) && (!Files.isDirectory(p) || !Files.isWritable(p))) {
 112                     throw new BadArgs("err.dot.output.path", arg);
 113                 }
 114                 task.options.dotOutputDir = arg;
 115             }
 116         },
 117         new Option(false, "-s", "-summary") {
 118             void process(JdepsTask task, String opt, String arg) {
 119                 task.options.showSummary = true;
 120                 task.options.verbose = Analyzer.Type.SUMMARY;
 121             }
 122         },
 123         new Option(false, "-v", "-verbose",
 124                           "-verbose:package",
 125                           "-verbose:class")
 126         {
 127             void process(JdepsTask task, String opt, String arg) throws BadArgs {
 128                 switch (opt) {
 129                     case "-v":
 130                     case "-verbose":
 131                         task.options.verbose = Analyzer.Type.VERBOSE;
 132                         break;
 133                     case "-verbose:package":
 134                             task.options.verbose = Analyzer.Type.PACKAGE;
 135                             break;
 136                     case "-verbose:class":
 137                             task.options.verbose = Analyzer.Type.CLASS;
 138                             break;
 139                     default:
 140                         throw new BadArgs("err.invalid.arg.for.option", opt);
 141                 }
 142             }
 143         },
 144         new Option(true, "-cp", "-classpath") {
 145             void process(JdepsTask task, String opt, String arg) {
 146                 task.options.classpath = arg;
 147             }
 148         },
 149         new Option(true, "-p", "-package") {
 150             void process(JdepsTask task, String opt, String arg) {
 151                 task.options.packageNames.add(arg);
 152             }
 153         },
 154         new Option(true, "-e", "-regex") {
 155             void process(JdepsTask task, String opt, String arg) {
 156                 task.options.regex = arg;
 157             }
 158         },
 159         new Option(true, "-include") {
 160             void process(JdepsTask task, String opt, String arg) throws BadArgs {
 161                 task.options.includePattern = Pattern.compile(arg);
 162             }
 163         },
 164         new Option(false, "-P", "-profile") {
 165             void process(JdepsTask task, String opt, String arg) throws BadArgs {
 166                 task.options.showProfile = true;
 167                 if (Profile.getProfileCount() == 0) {
 168                     throw new BadArgs("err.option.unsupported", opt, getMessage("err.profiles.msg"));
 169                 }
 170             }
 171         },
 172         new Option(false, "-apionly") {
 173             void process(JdepsTask task, String opt, String arg) {
 174                 task.options.apiOnly = true;
 175             }
 176         },
 177         new Option(false, "-recursive") {
 178             void process(JdepsTask task, String opt, String arg) {
 179                 task.options.depth = 0;




 180             }
 181         },
 182         new Option(false, "-version") {
 183             void process(JdepsTask task, String opt, String arg) {
 184                 task.options.version = true;
 185             }
 186         },
 187         new HiddenOption(false, "-fullversion") {
 188             void process(JdepsTask task, String opt, String arg) {
 189                 task.options.fullVersion = true;
 190             }
 191         },
 192         new HiddenOption(true, "-depth") {
 193             void process(JdepsTask task, String opt, String arg) throws BadArgs {
 194                 try {
 195                     task.options.depth = Integer.parseInt(arg);
 196                 } catch (NumberFormatException e) {
 197                     throw new BadArgs("err.invalid.arg.for.option", opt);
 198                 }
 199             }
 200         },
 201     };
 202 
 203     private static final String PROGNAME = "jdeps";
 204     private final Options options = new Options();
 205     private final List<String> classes = new ArrayList<String>();
 206 
 207     private PrintWriter log;
 208     void setLog(PrintWriter out) {
 209         log = out;
 210     }
 211 
 212     /**
 213      * Result codes.
 214      */
 215     static final int EXIT_OK = 0, // Completed with no errors.
 216                      EXIT_ERROR = 1, // Completed but reported errors.
 217                      EXIT_CMDERR = 2, // Bad command-line arguments
 218                      EXIT_SYSERR = 3, // System error or resource exhaustion.
 219                      EXIT_ABNORMAL = 4;// terminated abnormally
 220 
 221     int run(String[] args) {
 222         if (log == null) {
 223             log = new PrintWriter(System.out);
 224         }
 225         try {
 226             handleOptions(args);
 227             if (options.help) {
 228                 showHelp();
 229             }
 230             if (options.version || options.fullVersion) {
 231                 showVersion(options.fullVersion);
 232             }
 233             if (classes.isEmpty() && options.includePattern == null) {
 234                 if (options.help || options.version || options.fullVersion) {
 235                     return EXIT_OK;
 236                 } else {
 237                     showHelp();
 238                     return EXIT_CMDERR;
 239                 }
 240             }
 241             if (options.regex != null && options.packageNames.size() > 0) {
 242                 showHelp();
 243                 return EXIT_CMDERR;
 244             }
 245             if (options.showSummary && options.verbose != Analyzer.Type.SUMMARY) {
 246                 showHelp();
 247                 return EXIT_CMDERR;
 248             }
 249             boolean ok = run();
 250             return ok ? EXIT_OK : EXIT_ERROR;
 251         } catch (BadArgs e) {
 252             reportError(e.key, e.args);
 253             if (e.showUsage) {
 254                 log.println(getMessage("main.usage.summary", PROGNAME));
 255             }
 256             return EXIT_CMDERR;
 257         } catch (IOException e) {
 258             return EXIT_ABNORMAL;
 259         } finally {
 260             log.flush();
 261         }
 262     }
 263 
 264     private final List<Archive> sourceLocations = new ArrayList<>();
 265     private boolean run() throws IOException {
 266         findDependencies();
 267         Analyzer analyzer = new Analyzer(options.verbose);
 268         analyzer.run(sourceLocations);
 269         if (options.dotOutputDir != null) {
 270             Path dir = Paths.get(options.dotOutputDir);
 271             Files.createDirectories(dir);
 272             generateDotFiles(dir, analyzer);
 273         } else {
 274             printRawOutput(log, analyzer);
 275         }
 276         return true;
 277     }
 278 
 279     private void generateDotFiles(Path dir, Analyzer analyzer) throws IOException {
 280         Path summary = dir.resolve("summary.dot");
 281         try (PrintWriter sw = new PrintWriter(Files.newOutputStream(summary));
 282              DotFileFormatter formatter = new DotFileFormatter(sw, "summary")) {
 283             for (Archive archive : sourceLocations) {
 284                  analyzer.visitArchiveDependences(archive, formatter);
 285             }
 286         }
 287         if (options.verbose != Analyzer.Type.SUMMARY) {
 288             for (Archive archive : sourceLocations) {
 289                 if (analyzer.hasDependences(archive)) {
 290                     Path dotfile = dir.resolve(archive.getFileName() + ".dot");
 291                     try (PrintWriter pw = new PrintWriter(Files.newOutputStream(dotfile));
 292                          DotFileFormatter formatter = new DotFileFormatter(pw, archive)) {
 293                         analyzer.visitDependences(archive, formatter);
 294                     }
 295                 }
 296             }
 297         }
 298     }
 299 
 300     private void printRawOutput(PrintWriter writer, Analyzer analyzer) {
 301         for (Archive archive : sourceLocations) {
 302             RawOutputFormatter formatter = new RawOutputFormatter(writer);
 303             analyzer.visitArchiveDependences(archive, formatter);
 304             if (options.verbose != Analyzer.Type.SUMMARY) {
 305                 analyzer.visitDependences(archive, formatter);
 306             }
 307         }
 308     }
 309     private boolean isValidClassName(String name) {
 310         if (!Character.isJavaIdentifierStart(name.charAt(0))) {
 311             return false;
 312         }
 313         for (int i=1; i < name.length(); i++) {
 314             char c = name.charAt(i);
 315             if (c != '.'  && !Character.isJavaIdentifierPart(c)) {
 316                 return false;
 317             }
 318         }
 319         return true;
 320     }
 321 
 322     private Dependency.Filter getDependencyFilter() {


 323          if (options.regex != null) {
 324             return Dependencies.getRegexFilter(Pattern.compile(options.regex));
 325         } else if (options.packageNames.size() > 0) {
 326             return Dependencies.getPackageFilter(options.packageNames, false);
 327         } else {
 328             return new Dependency.Filter() {
 329                 @Override
 330                 public boolean accepts(Dependency dependency) {
 331                     return !dependency.getOrigin().equals(dependency.getTarget());
 332                 }
 333             };
 334         }
 335     }
 336 
 337     private boolean matches(String classname, AccessFlags flags) {
 338         if (options.apiOnly && !flags.is(AccessFlags.ACC_PUBLIC)) {
 339             return false;
 340         } else if (options.includePattern != null) {
 341             return options.includePattern.matcher(classname.replace('/', '.')).matches();
 342         } else {
 343             return true;
 344         }
 345     }
 346 
 347     private void findDependencies() throws IOException {
 348         Dependency.Finder finder =
 349             options.apiOnly ? Dependencies.getAPIFinder(AccessFlags.ACC_PROTECTED)
 350                             : Dependencies.getClassDependencyFinder();
 351         Dependency.Filter filter = getDependencyFilter();
 352 
 353         List<Archive> archives = new ArrayList<>();
 354         Deque<String> roots = new LinkedList<>();
 355         for (String s : classes) {
 356             Path p = Paths.get(s);
 357             if (Files.exists(p)) {
 358                 archives.add(new Archive(p, ClassFileReader.newInstance(p)));
 359             } else {
 360                 if (isValidClassName(s)) {
 361                     roots.add(s);
 362                 } else {
 363                     warning("warn.invalid.arg", s);
 364                 }
 365             }
 366         }
 367 
 368         List<Archive> classpaths = new ArrayList<>(); // for class file lookup
 369         if (options.includePattern != null) {

 370             archives.addAll(getClassPathArchives(options.classpath));
 371         } else {
 372             classpaths.addAll(getClassPathArchives(options.classpath));
 373         }
 374         classpaths.addAll(PlatformClassPath.getArchives());
 375 
 376         // add all archives to the source locations for reporting
 377         sourceLocations.addAll(archives);
 378         sourceLocations.addAll(classpaths);
 379 
 380         // Work queue of names of classfiles to be searched.
 381         // Entries will be unique, and for classes that do not yet have
 382         // dependencies in the results map.
 383         Deque<String> deque = new LinkedList<>();
 384         Set<String> doneClasses = new HashSet<>();
 385 
 386         // get the immediate dependencies of the input files
 387         for (Archive a : archives) {
 388             for (ClassFile cf : a.reader().getClassFiles()) {
 389                 String classFileName;
 390                 try {
 391                     classFileName = cf.getName();
 392                 } catch (ConstantPoolException e) {
 393                     throw new ClassFileError(e);
 394                 }
 395 
 396                 if (matches(classFileName, cf.access_flags)) {
 397                     if (!doneClasses.contains(classFileName)) {
 398                         doneClasses.add(classFileName);
 399                     }
 400                     for (Dependency d : finder.findDependencies(cf)) {
 401                         if (filter.accepts(d)) {
 402                             String cn = d.getTarget().getName();
 403                             if (!doneClasses.contains(cn) && !deque.contains(cn)) {
 404                                 deque.add(cn);
 405                             }
 406                             a.addClass(d.getOrigin(), d.getTarget());
 407                         }
 408                     }
 409                 }
 410             }
 411         }
 412 
 413         // add Archive for looking up classes from the classpath
 414         // for transitive dependency analysis
 415         Deque<String> unresolved = roots;
 416         int depth = options.depth > 0 ? options.depth : Integer.MAX_VALUE;
 417         do {
 418             String name;
 419             while ((name = unresolved.poll()) != null) {
 420                 if (doneClasses.contains(name)) {
 421                     continue;
 422                 }
 423                 ClassFile cf = null;
 424                 for (Archive a : classpaths) {
 425                     cf = a.reader().getClassFile(name);
 426                     if (cf != null) {
 427                         String classFileName;
 428                         try {
 429                             classFileName = cf.getName();
 430                         } catch (ConstantPoolException e) {
 431                             throw new ClassFileError(e);


 439                                     // ignore the dependency
 440                                     a.addClass(d.getOrigin());
 441                                     break;
 442                                 } else if (filter.accepts(d)) {
 443                                     a.addClass(d.getOrigin(), d.getTarget());
 444                                     String cn = d.getTarget().getName();
 445                                     if (!doneClasses.contains(cn) && !deque.contains(cn)) {
 446                                         deque.add(cn);
 447                                     }
 448                                 }
 449                             }
 450                         }
 451                         break;
 452                     }
 453                 }
 454                 if (cf == null) {
 455                     doneClasses.add(name);
 456                 }
 457             }
 458             unresolved = deque;
 459             deque = new LinkedList<>();
 460         } while (!unresolved.isEmpty() && depth-- > 0);
 461     }
 462 




































 463     public void handleOptions(String[] args) throws BadArgs {
 464         // process options
 465         for (int i=0; i < args.length; i++) {
 466             if (args[i].charAt(0) == '-') {
 467                 String name = args[i];
 468                 Option option = getOption(name);
 469                 String param = null;
 470                 if (option.hasArg) {
 471                     if (name.startsWith("-") && name.indexOf('=') > 0) {
 472                         param = name.substring(name.indexOf('=') + 1, name.length());
 473                     } else if (i + 1 < args.length) {
 474                         param = args[++i];
 475                     }
 476                     if (param == null || param.isEmpty() || param.charAt(0) == '-') {
 477                         throw new BadArgs("err.missing.arg", name).showUsage(true);
 478                     }
 479                 }
 480                 option.process(this, name, param);
 481                 if (option.ignoreRest()) {
 482                     i = args.length;
 483                 }
 484             } else {
 485                 // process rest of the input arguments
 486                 for (; i < args.length; i++) {
 487                     String name = args[i];
 488                     if (name.charAt(0) == '-') {
 489                         throw new BadArgs("err.option.after.class", name).showUsage(true);
 490                     }



 491                     classes.add(name);
 492                 }
 493             }
 494         }
 495     }

 496 
 497     private Option getOption(String name) throws BadArgs {
 498         for (Option o : recognizedOptions) {
 499             if (o.matches(name)) {
 500                 return o;
 501             }
 502         }
 503         throw new BadArgs("err.unknown.option", name).showUsage(true);
 504     }
 505 
 506     private void reportError(String key, Object... args) {
 507         log.println(getMessage("error.prefix") + " " + getMessage(key, args));
 508     }
 509 
 510     private void warning(String key, Object... args) {
 511         log.println(getMessage("warn.prefix") + " " + getMessage(key, args));
 512     }
 513 
 514     private void showHelp() {
 515         log.println(getMessage("main.usage", PROGNAME));


 538         } catch (MissingResourceException e) {
 539             return getMessage("version.unknown", System.getProperty("java.version"));
 540         }
 541     }
 542 
 543     static String getMessage(String key, Object... args) {
 544         try {
 545             return MessageFormat.format(ResourceBundleHelper.bundle.getString(key), args);
 546         } catch (MissingResourceException e) {
 547             throw new InternalError("Missing message: " + key);
 548         }
 549     }
 550 
 551     private static class Options {
 552         boolean help;
 553         boolean version;
 554         boolean fullVersion;
 555         boolean showProfile;
 556         boolean showSummary;
 557         boolean wildcard;
 558         boolean apiOnly;
 559         String dotOutputDir;
 560         String classpath = "";
 561         int depth = 1;
 562         Analyzer.Type verbose = Analyzer.Type.PACKAGE;
 563         Set<String> packageNames = new HashSet<>();
 564         String regex;             // apply to the dependences
 565         Pattern includePattern;   // apply to classes
 566     }

 567     private static class ResourceBundleHelper {
 568         static final ResourceBundle versionRB;
 569         static final ResourceBundle bundle;
 570 
 571         static {
 572             Locale locale = Locale.getDefault();
 573             try {
 574                 bundle = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.jdeps", locale);
 575             } catch (MissingResourceException e) {
 576                 throw new InternalError("Cannot find jdeps resource bundle for locale " + locale);
 577             }
 578             try {
 579                 versionRB = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.version");
 580             } catch (MissingResourceException e) {
 581                 throw new InternalError("version.resource.missing");
 582             }
 583         }
 584     }
 585 
 586     private List<Archive> getArchives(List<String> filenames) throws IOException {
 587         List<Archive> result = new ArrayList<Archive>();
 588         for (String s : filenames) {
 589             Path p = Paths.get(s);
 590             if (Files.exists(p)) {
 591                 result.add(new Archive(p, ClassFileReader.newInstance(p)));
 592             } else {
 593                 warning("warn.file.not.exist", s);
 594             }
 595         }
 596         return result;
 597     }
 598 
 599     private List<Archive> getClassPathArchives(String paths) throws IOException {
 600         List<Archive> result = new ArrayList<>();
 601         if (paths.isEmpty()) {
 602             return result;
 603         }
 604         for (String p : paths.split(File.pathSeparator)) {
 605             if (p.length() > 0) {
 606                 List<Path> files = new ArrayList<>();
 607                 // wildcard to parse all JAR files e.g. -classpath dir/*
 608                 int i = p.lastIndexOf(".*");
 609                 if (i > 0) {
 610                     Path dir = Paths.get(p.substring(0, i));
 611                     try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.jar")) {
 612                         for (Path entry : stream) {
 613                             files.add(entry);
 614                         }
 615                     }
 616                 } else {
 617                     files.add(Paths.get(p));
 618                 }
 619                 for (Path f : files) {
 620                     if (Files.exists(f)) {
 621                         result.add(new Archive(f, ClassFileReader.newInstance(f)));
 622                     }
 623                 }
 624             }
 625         }
 626         return result;
 627     }
 628 
 629 
 630     /**
 631      * Returns the file name of the archive for non-JRE class or
 632      * internal JRE classes.  It returns empty string for SE API.
 633      */
 634     private static String getArchiveName(Archive source, String profile) {
 635         String name = source.getFileName();
 636         if (PlatformClassPath.contains(source))
 637             return profile.isEmpty() ? "JDK internal API (" + name + ")" : "";
 638         return name;
 639     }
 640 
 641     class RawOutputFormatter implements Analyzer.Visitor {
 642         private final PrintWriter writer;
 643         RawOutputFormatter(PrintWriter writer) {
 644             this.writer = writer;
 645         }
 646 
 647         private String pkg = "";
 648         @Override
 649         public void visitDependence(String origin, Archive source,
 650                                     String target, Archive archive, String profile) {
 651             if (!origin.equals(pkg)) {
 652                 pkg = origin;
 653                 writer.format("   %s (%s)%n", origin, source.getFileName());
 654             }
 655             String name = (options.showProfile && !profile.isEmpty())
 656                                 ? profile
 657                                 : getArchiveName(archive, profile);
 658             writer.format("      -> %-50s %s%n", target, name);
 659         }
 660 
 661         @Override
 662         public void visitArchiveDependence(Archive origin, Archive target, String profile) {
 663             writer.format("%s -> %s", origin, target);
 664             if (options.showProfile && !profile.isEmpty()) {
 665                 writer.format(" (%s)%n", profile);
 666             } else {
 667                 writer.format("%n");
 668             }
 669         }
 670     }
 671 
 672     class DotFileFormatter implements Analyzer.Visitor, AutoCloseable {
 673         private final PrintWriter writer;
 674         private final String name;
 675         DotFileFormatter(PrintWriter writer, String name) {
 676             this.writer = writer;
 677             this.name = name;
 678             writer.format("digraph \"%s\" {%n", name);
 679         }
 680         DotFileFormatter(PrintWriter writer, Archive archive) {
 681             this.writer = writer;
 682             this.name = archive.getFileName();
 683             writer.format("digraph \"%s\" {%n", name);
 684             writer.format("    // Path: %s%n", archive.toString());
 685         }
 686 
 687         @Override
 688         public void close() {
 689             writer.println("}");
 690         }
 691 
 692         private final Set<String> edges = new HashSet<>();
 693         private String node = "";
 694         @Override
 695         public void visitDependence(String origin, Archive source,
 696                                     String target, Archive archive, String profile) {
 697             if (!node.equals(origin)) {
 698                 edges.clear();
 699                 node = origin;
 700             }
 701             // if -P option is specified, package name -> profile will
 702             // be shown and filter out multiple same edges.
 703             if (!edges.contains(target)) {
 704                 StringBuilder sb = new StringBuilder();
 705                 String name = options.showProfile && !profile.isEmpty()
 706                                   ? profile
 707                                   : getArchiveName(archive, profile);
 708                 writer.format("   %-50s -> %s;%n",
 709                                  String.format("\"%s\"", origin),
 710                                  name.isEmpty() ? String.format("\"%s\"", target)
 711                                                 :  String.format("\"%s (%s)\"", target, name));
 712                 edges.add(target);
 713             }
 714         }
 715 
 716         @Override
 717         public void visitArchiveDependence(Archive origin, Archive target, String profile) {
 718              String name = options.showProfile && !profile.isEmpty()
 719                                 ? profile : "";
 720              writer.format("   %-30s -> \"%s\";%n",
 721                            String.format("\"%s\"", origin.getFileName()),
 722                            name.isEmpty()
 723                                ? target.getFileName()
 724                                : String.format("%s (%s)", target.getFileName(), name));
 725         }
 726     }
 727 }