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

Wed, 23 Jan 2013 13:27:24 -0800

author
jjg
date
Wed, 23 Jan 2013 13:27:24 -0800
changeset 1521
71f35e4b93a5
parent 1472
0c244701188e
child 1577
88286a36bb34
permissions
-rw-r--r--

8006775: JSR 308: Compiler changes in JDK8
Reviewed-by: jjg
Contributed-by: mernst@cs.washington.edu, wmdietl@cs.washington.edu, mpapi@csail.mit.edu, mahmood@notnoop.com

     1 /*
     2  * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     4  *
     5  * This code is free software; you can redistribute it and/or modify it
     6  * under the terms of the GNU General Public License version 2 only, as
     7  * published by the Free Software Foundation.  Oracle designates this
     8  * particular file as subject to the "Classpath" exception as provided
     9  * by Oracle in the LICENSE file that accompanied this code.
    10  *
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
    14  * version 2 for more details (a copy is included in the LICENSE file that
    15  * accompanied this code).
    16  *
    17  * You should have received a copy of the GNU General Public License version
    18  * 2 along with this work; if not, write to the Free Software Foundation,
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
    20  *
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
    22  * or visit www.oracle.com if you need additional information or have any
    23  * questions.
    24  */
    25 package com.sun.tools.jdeps;
    27 import com.sun.tools.classfile.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 com.sun.tools.classfile.Dependency.Location;
    33 import java.io.*;
    34 import java.text.MessageFormat;
    35 import java.util.*;
    36 import java.util.regex.Pattern;
    38 /**
    39  * Implementation for the jdeps tool for static class dependency analysis.
    40  */
    41 class JdepsTask {
    42     class BadArgs extends Exception {
    43         static final long serialVersionUID = 8765093759964640721L;
    44         BadArgs(String key, Object... args) {
    45             super(JdepsTask.this.getMessage(key, args));
    46             this.key = key;
    47             this.args = args;
    48         }
    50         BadArgs showUsage(boolean b) {
    51             showUsage = b;
    52             return this;
    53         }
    54         final String key;
    55         final Object[] args;
    56         boolean showUsage;
    57     }
    59     static abstract class Option {
    60         Option(boolean hasArg, String... aliases) {
    61             this.hasArg = hasArg;
    62             this.aliases = aliases;
    63         }
    65         boolean isHidden() {
    66             return false;
    67         }
    69         boolean matches(String opt) {
    70             for (String a : aliases) {
    71                 if (a.equals(opt)) {
    72                     return true;
    73                 } else if (opt.startsWith("--") && hasArg && opt.startsWith(a + "=")) {
    74                     return true;
    75                 }
    76             }
    77             return false;
    78         }
    80         boolean ignoreRest() {
    81             return false;
    82         }
    84         abstract void process(JdepsTask task, String opt, String arg) throws BadArgs;
    85         final boolean hasArg;
    86         final String[] aliases;
    87     }
    89     static abstract class HiddenOption extends Option {
    90         HiddenOption(boolean hasArg, String... aliases) {
    91             super(hasArg, aliases);
    92         }
    94         boolean isHidden() {
    95             return true;
    96         }
    97     }
    99     static Option[] recognizedOptions = {
   100         new Option(false, "-h", "-?", "--help") {
   101             void process(JdepsTask task, String opt, String arg) {
   102                 task.options.help = true;
   103             }
   104         },
   105         new Option(false, "-s", "--summary") {
   106             void process(JdepsTask task, String opt, String arg) {
   107                 task.options.showSummary = true;
   108                 task.options.verbose = Options.Verbose.SUMMARY;
   109             }
   110         },
   111         new Option(false, "-v", "--verbose") {
   112             void process(JdepsTask task, String opt, String arg) {
   113                 task.options.verbose = Options.Verbose.VERBOSE;
   114             }
   115         },
   116         new Option(true, "-V", "--verbose-level") {
   117             void process(JdepsTask task, String opt, String arg) throws BadArgs {
   118                 switch (arg) {
   119                     case "package":
   120                         task.options.verbose = Options.Verbose.PACKAGE;
   121                         break;
   122                     case "class":
   123                         task.options.verbose = Options.Verbose.CLASS;
   124                         break;
   125                     default:
   126                         throw task.new BadArgs("err.invalid.arg.for.option", opt);
   127                 }
   128             }
   129         },
   130         new Option(true, "-c", "--classpath") {
   131             void process(JdepsTask task, String opt, String arg) {
   132                 task.options.classpath = arg;
   133             }
   134         },
   135         new Option(true, "-p", "--package") {
   136             void process(JdepsTask task, String opt, String arg) {
   137                 task.options.packageNames.add(arg);
   138             }
   139         },
   140         new Option(true, "-e", "--regex") {
   141             void process(JdepsTask task, String opt, String arg) {
   142                 task.options.regex = arg;
   143             }
   144         },
   145         new Option(false, "-P", "--profile") {
   146             void process(JdepsTask task, String opt, String arg) {
   147                 task.options.showProfile = true;
   148             }
   149         },
   150         new Option(false, "-R", "--recursive") {
   151             void process(JdepsTask task, String opt, String arg) {
   152                 task.options.depth = 0;
   153             }
   154         },
   155         new HiddenOption(true, "-d", "--depth") {
   156             void process(JdepsTask task, String opt, String arg) throws BadArgs {
   157                 try {
   158                     task.options.depth = Integer.parseInt(arg);
   159                 } catch (NumberFormatException e) {
   160                     throw task.new BadArgs("err.invalid.arg.for.option", opt);
   161                 }
   162             }
   163         },
   164         new Option(false, "--version") {
   165             void process(JdepsTask task, String opt, String arg) {
   166                 task.options.version = true;
   167             }
   168         },
   169         new HiddenOption(false, "--fullversion") {
   170             void process(JdepsTask task, String opt, String arg) {
   171                 task.options.fullVersion = true;
   172             }
   173         },
   175     };
   177     private static final String PROGNAME = "jdeps";
   178     private final Options options = new Options();
   179     private final List<String> classes = new ArrayList<String>();
   181     private PrintWriter log;
   182     void setLog(PrintWriter out) {
   183         log = out;
   184     }
   186     /**
   187      * Result codes.
   188      */
   189     static final int EXIT_OK = 0, // Completed with no errors.
   190                      EXIT_ERROR = 1, // Completed but reported errors.
   191                      EXIT_CMDERR = 2, // Bad command-line arguments
   192                      EXIT_SYSERR = 3, // System error or resource exhaustion.
   193                      EXIT_ABNORMAL = 4;// terminated abnormally
   195     int run(String[] args) {
   196         if (log == null) {
   197             log = new PrintWriter(System.out);
   198         }
   199         try {
   200             handleOptions(args);
   201             if (options.help) {
   202                 showHelp();
   203             }
   204             if (options.version || options.fullVersion) {
   205                 showVersion(options.fullVersion);
   206             }
   207             if (classes.isEmpty() && !options.wildcard) {
   208                 if (options.help || options.version || options.fullVersion) {
   209                     return EXIT_OK;
   210                 } else {
   211                     showHelp();
   212                     return EXIT_CMDERR;
   213                 }
   214             }
   215             if (options.regex != null && options.packageNames.size() > 0) {
   216                 showHelp();
   217                 return EXIT_CMDERR;
   218             }
   219             if (options.showSummary && options.verbose != Options.Verbose.SUMMARY) {
   220                 showHelp();
   221                 return EXIT_CMDERR;
   222             }
   223             boolean ok = run();
   224             return ok ? EXIT_OK : EXIT_ERROR;
   225         } catch (BadArgs e) {
   226             reportError(e.key, e.args);
   227             if (e.showUsage) {
   228                 log.println(getMessage("main.usage.summary", PROGNAME));
   229             }
   230             return EXIT_CMDERR;
   231         } catch (IOException e) {
   232             return EXIT_ABNORMAL;
   233         } finally {
   234             log.flush();
   235         }
   236     }
   238     private final List<Archive> sourceLocations = new ArrayList<Archive>();
   239     private final Archive NOT_FOUND = new Archive(getMessage("artifact.not.found"));
   240     private boolean run() throws IOException {
   241         findDependencies();
   242         switch (options.verbose) {
   243             case VERBOSE:
   244             case CLASS:
   245                 printClassDeps(log);
   246                 break;
   247             case PACKAGE:
   248                 printPackageDeps(log);
   249                 break;
   250             case SUMMARY:
   251                 for (Archive origin : sourceLocations) {
   252                     for (Archive target : origin.getRequiredArchives()) {
   253                         log.format("%-30s -> %s%n", origin, target);
   254                     }
   255                 }
   256                 break;
   257             default:
   258                 throw new InternalError("Should not reach here");
   259         }
   260         return true;
   261     }
   263     private boolean isValidClassName(String name) {
   264         if (!Character.isJavaIdentifierStart(name.charAt(0))) {
   265             return false;
   266         }
   267         for (int i=1; i < name.length(); i++) {
   268             char c = name.charAt(i);
   269             if (c != '.'  && !Character.isJavaIdentifierPart(c)) {
   270                 return false;
   271             }
   272         }
   273         return true;
   274     }
   276     private void findDependencies() throws IOException {
   277         Dependency.Finder finder = Dependencies.getClassDependencyFinder();
   278         Dependency.Filter filter;
   279         if (options.regex != null) {
   280             filter = Dependencies.getRegexFilter(Pattern.compile(options.regex));
   281         } else if (options.packageNames.size() > 0) {
   282             filter = Dependencies.getPackageFilter(options.packageNames, false);
   283         } else {
   284             filter = new Dependency.Filter() {
   285                 public boolean accepts(Dependency dependency) {
   286                     return !dependency.getOrigin().equals(dependency.getTarget());
   287                 }
   288             };
   289         }
   291         List<Archive> archives = new ArrayList<Archive>();
   292         Deque<String> roots = new LinkedList<String>();
   293         for (String s : classes) {
   294             File f = new File(s);
   295             if (f.exists()) {
   296                 archives.add(new Archive(f, ClassFileReader.newInstance(f)));
   297             } else {
   298                 if (isValidClassName(s)) {
   299                     roots.add(s);
   300                 } else {
   301                     warning("warn.invalid.arg", s);
   302                 }
   303             }
   304         }
   306         List<Archive> classpaths = new ArrayList<Archive>(); // for class file lookup
   307         if (options.wildcard) {
   308             // include all archives from classpath to the initial list
   309             archives.addAll(getClassPathArchives(options.classpath));
   310         } else {
   311             classpaths.addAll(getClassPathArchives(options.classpath));
   312         }
   313         classpaths.addAll(PlatformClassPath.getArchives());
   315         // add all archives to the source locations for reporting
   316         sourceLocations.addAll(archives);
   317         sourceLocations.addAll(classpaths);
   319         // Work queue of names of classfiles to be searched.
   320         // Entries will be unique, and for classes that do not yet have
   321         // dependencies in the results map.
   322         Deque<String> deque = new LinkedList<String>();
   323         Set<String> doneClasses = new HashSet<String>();
   325         // get the immediate dependencies of the input files
   326         for (Archive a : archives) {
   327             for (ClassFile cf : a.reader().getClassFiles()) {
   328                 String classFileName;
   329                 try {
   330                     classFileName = cf.getName();
   331                 } catch (ConstantPoolException e) {
   332                     throw new ClassFileError(e);
   333                 }
   334                 a.addClass(classFileName);
   335                 if (!doneClasses.contains(classFileName)) {
   336                     doneClasses.add(classFileName);
   337                 }
   338                 for (Dependency d : finder.findDependencies(cf)) {
   339                     if (filter.accepts(d)) {
   340                         String cn = d.getTarget().getName();
   341                         if (!doneClasses.contains(cn) && !deque.contains(cn)) {
   342                             deque.add(cn);
   343                         }
   344                         a.addDependency(d);
   345                     }
   346                 }
   347             }
   348         }
   350         // add Archive for looking up classes from the classpath
   351         // for transitive dependency analysis
   352         Deque<String> unresolved = roots;
   353         int depth = options.depth > 0 ? options.depth : Integer.MAX_VALUE;
   354         do {
   355             String name;
   356             while ((name = unresolved.poll()) != null) {
   357                 if (doneClasses.contains(name)) {
   358                     continue;
   359                 }
   360                 ClassFile cf = null;
   361                 for (Archive a : classpaths) {
   362                     cf = a.reader().getClassFile(name);
   363                     if (cf != null) {
   364                         String classFileName;
   365                         try {
   366                             classFileName = cf.getName();
   367                         } catch (ConstantPoolException e) {
   368                             throw new ClassFileError(e);
   369                         }
   370                         a.addClass(classFileName);
   371                         if (!doneClasses.contains(classFileName)) {
   372                             // if name is a fully-qualified class name specified
   373                             // from command-line, this class might already be parsed
   374                             doneClasses.add(classFileName);
   375                             if (depth > 0) {
   376                                 for (Dependency d : finder.findDependencies(cf)) {
   377                                     if (filter.accepts(d)) {
   378                                         String cn = d.getTarget().getName();
   379                                         if (!doneClasses.contains(cn) && !deque.contains(cn)) {
   380                                             deque.add(cn);
   381                                         }
   382                                         a.addDependency(d);
   383                                     }
   384                                 }
   385                             }
   386                         }
   387                         break;
   388                     }
   389                 }
   390                 if (cf == null) {
   391                     NOT_FOUND.addClass(name);
   392                 }
   393             }
   394             unresolved = deque;
   395             deque = new LinkedList<String>();
   396         } while (!unresolved.isEmpty() && depth-- > 0);
   397     }
   399     private void printPackageDeps(PrintWriter out) {
   400         for (Archive source : sourceLocations) {
   401             SortedMap<Location, SortedSet<Location>> deps = source.getDependencies();
   402             if (deps.isEmpty())
   403                 continue;
   405             for (Archive target : source.getRequiredArchives()) {
   406                 out.format("%s -> %s%n", source, target);
   407             }
   409             Map<String, Archive> pkgs = new TreeMap<String, Archive>();
   410             SortedMap<String, Archive> targets = new TreeMap<String, Archive>();
   411             String pkg = "";
   412             for (Map.Entry<Location, SortedSet<Location>> e : deps.entrySet()) {
   413                 String cn = e.getKey().getClassName();
   414                 String p = packageOf(e.getKey());
   415                 Archive origin = Archive.find(e.getKey());
   416                 assert origin != null;
   417                 if (!pkgs.containsKey(p)) {
   418                     pkgs.put(p, origin);
   419                 } else if (pkgs.get(p) != origin) {
   420                     warning("warn.split.package", p, origin, pkgs.get(p));
   421                 }
   423                 if (!p.equals(pkg)) {
   424                     printTargets(out, targets);
   425                     pkg = p;
   426                     targets.clear();
   427                     out.format("   %s (%s)%n", p, origin.getFileName());
   428                 }
   430                 for (Location t : e.getValue()) {
   431                     p = packageOf(t);
   432                     Archive target = Archive.find(t);
   433                     if (!targets.containsKey(p)) {
   434                         targets.put(p, target);
   435                     }
   436                 }
   437             }
   438             printTargets(out, targets);
   439             out.println();
   440         }
   441     }
   443     private void printTargets(PrintWriter out, Map<String, Archive> targets) {
   444         for (Map.Entry<String, Archive> t : targets.entrySet()) {
   445             String pn = t.getKey();
   446             out.format("      -> %-40s %s%n", pn, getPackageInfo(pn, t.getValue()));
   447         }
   448     }
   450     private String getPackageInfo(String pn, Archive source) {
   451         if (PlatformClassPath.contains(source)) {
   452             String name = PlatformClassPath.getProfileName(pn);
   453             if (name.isEmpty()) {
   454                 return "JDK internal API (" + source.getFileName() + ")";
   455             }
   456             return options.showProfile ? name : "";
   457         }
   458         return source.getFileName();
   459     }
   461     private static String packageOf(Location loc) {
   462         String pkg = loc.getPackageName();
   463         return pkg.isEmpty() ? "<unnamed>" : pkg;
   464     }
   466     private void printClassDeps(PrintWriter out) {
   467         for (Archive source : sourceLocations) {
   468             SortedMap<Location, SortedSet<Location>> deps = source.getDependencies();
   469             if (deps.isEmpty())
   470                 continue;
   472             for (Archive target : source.getRequiredArchives()) {
   473                 out.format("%s -> %s%n", source, target);
   474             }
   475             out.format("%s%n", source);
   476             for (Map.Entry<Location, SortedSet<Location>> e : deps.entrySet()) {
   477                 String cn = e.getKey().getClassName();
   478                 Archive origin = Archive.find(e.getKey());
   479                 out.format("   %s (%s)%n", cn, origin.getFileName());
   480                 for (Location t : e.getValue()) {
   481                     cn = t.getClassName();
   482                     Archive target = Archive.find(t);
   483                     out.format("      -> %-60s %s%n", cn, getPackageInfo(t.getPackageName(), target));
   484                 }
   485             }
   486             out.println();
   487         }
   488     }
   489     public void handleOptions(String[] args) throws BadArgs {
   490         // process options
   491         for (int i=0; i < args.length; i++) {
   492             if (args[i].charAt(0) == '-') {
   493                 String name = args[i];
   494                 Option option = getOption(name);
   495                 String param = null;
   496                 if (option.hasArg) {
   497                     if (name.startsWith("--") && name.indexOf('=') > 0) {
   498                         param = name.substring(name.indexOf('=') + 1, name.length());
   499                     } else if (i + 1 < args.length) {
   500                         param = args[++i];
   501                     }
   502                     if (param == null || param.isEmpty() || param.charAt(0) == '-') {
   503                         throw new BadArgs("err.missing.arg", name).showUsage(true);
   504                     }
   505                 }
   506                 option.process(this, name, param);
   507                 if (option.ignoreRest()) {
   508                     i = args.length;
   509                 }
   510             } else {
   511                 // process rest of the input arguments
   512                 for (; i < args.length; i++) {
   513                     String name = args[i];
   514                     if (name.charAt(0) == '-') {
   515                         throw new BadArgs("err.option.after.class", name).showUsage(true);
   516                     }
   517                     if (name.equals("*") || name.equals("\"*\"")) {
   518                         options.wildcard = true;
   519                     } else {
   520                         classes.add(name);
   521                     }
   522                 }
   523             }
   524         }
   525     }
   527     private Option getOption(String name) throws BadArgs {
   528         for (Option o : recognizedOptions) {
   529             if (o.matches(name)) {
   530                 return o;
   531             }
   532         }
   533         throw new BadArgs("err.unknown.option", name).showUsage(true);
   534     }
   536     private void reportError(String key, Object... args) {
   537         log.println(getMessage("error.prefix") + " " + getMessage(key, args));
   538     }
   540     private void warning(String key, Object... args) {
   541         log.println(getMessage("warn.prefix") + " " + getMessage(key, args));
   542     }
   544     private void showHelp() {
   545         log.println(getMessage("main.usage", PROGNAME));
   546         for (Option o : recognizedOptions) {
   547             String name = o.aliases[0].substring(1); // there must always be at least one name
   548             name = name.charAt(0) == '-' ? name.substring(1) : name;
   549             if (o.isHidden() || name.equals("h")) {
   550                 continue;
   551             }
   552             log.println(getMessage("main.opt." + name));
   553         }
   554     }
   556     private void showVersion(boolean full) {
   557         log.println(version(full ? "full" : "release"));
   558     }
   560     private String version(String key) {
   561         // key=version:  mm.nn.oo[-milestone]
   562         // key=full:     mm.mm.oo[-milestone]-build
   563         if (ResourceBundleHelper.versionRB == null) {
   564             return System.getProperty("java.version");
   565         }
   566         try {
   567             return ResourceBundleHelper.versionRB.getString(key);
   568         } catch (MissingResourceException e) {
   569             return getMessage("version.unknown", System.getProperty("java.version"));
   570         }
   571     }
   573     public String getMessage(String key, Object... args) {
   574         try {
   575             return MessageFormat.format(ResourceBundleHelper.bundle.getString(key), args);
   576         } catch (MissingResourceException e) {
   577             throw new InternalError("Missing message: " + key);
   578         }
   579     }
   581     private static class Options {
   582         enum Verbose {
   583             CLASS,
   584             PACKAGE,
   585             SUMMARY,
   586             VERBOSE
   587         };
   589         boolean help;
   590         boolean version;
   591         boolean fullVersion;
   592         boolean showFlags;
   593         boolean showProfile;
   594         boolean showSummary;
   595         boolean wildcard;
   596         String regex;
   597         String classpath = "";
   598         int depth = 1;
   599         Verbose verbose = Verbose.PACKAGE;
   600         Set<String> packageNames = new HashSet<String>();
   601     }
   603     private static class ResourceBundleHelper {
   604         static final ResourceBundle versionRB;
   605         static final ResourceBundle bundle;
   607         static {
   608             Locale locale = Locale.getDefault();
   609             try {
   610                 bundle = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.jdeps", locale);
   611             } catch (MissingResourceException e) {
   612                 throw new InternalError("Cannot find jdeps resource bundle for locale " + locale);
   613             }
   614             try {
   615                 versionRB = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.version");
   616             } catch (MissingResourceException e) {
   617                 throw new InternalError("version.resource.missing");
   618             }
   619         }
   620     }
   622     private List<Archive> getArchives(List<String> filenames) throws IOException {
   623         List<Archive> result = new ArrayList<Archive>();
   624         for (String s : filenames) {
   625             File f = new File(s);
   626             if (f.exists()) {
   627                 result.add(new Archive(f, ClassFileReader.newInstance(f)));
   628             } else {
   629                 warning("warn.file.not.exist", s);
   630             }
   631         }
   632         return result;
   633     }
   635     private List<Archive> getClassPathArchives(String paths) throws IOException {
   636         List<Archive> result = new ArrayList<Archive>();
   637         if (paths.isEmpty()) {
   638             return result;
   639         }
   640         for (String p : paths.split(File.pathSeparator)) {
   641             if (p.length() > 0) {
   642                 File f = new File(p);
   643                 if (f.exists()) {
   644                     result.add(new Archive(f, ClassFileReader.newInstance(f)));
   645                 }
   646             }
   647         }
   648         return result;
   649     }
   650 }

mercurial