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

Tue, 17 Sep 2013 14:17:13 -0700

author
jjg
date
Tue, 17 Sep 2013 14:17:13 -0700
changeset 2033
fdfbc5f0c4ed
parent 1648
a03c4a86ea2b
child 2139
defadd528513
permissions
-rw-r--r--

8024538: -Xdoclint + -Xprefer:source + incremental compilation == FAIL
Reviewed-by: darcy

     1 /*
     2  * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     4  *
     5  * This code is free software; you can redistribute it and/or modify it
     6  * under the terms of the GNU General Public License version 2 only, as
     7  * published by the Free Software Foundation.  Oracle designates this
     8  * particular file as subject to the "Classpath" exception as provided
     9  * by Oracle in the LICENSE file that accompanied this code.
    10  *
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
    14  * version 2 for more details (a copy is included in the LICENSE file that
    15  * accompanied this code).
    16  *
    17  * You should have received a copy of the GNU General Public License version
    18  * 2 along with this work; if not, write to the Free Software Foundation,
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
    20  *
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
    22  * or visit www.oracle.com if you need additional information or have any
    23  * questions.
    24  */
    25 package com.sun.tools.jdeps;
    27 import com.sun.tools.classfile.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;
    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         }
    49         BadArgs showUsage(boolean b) {
    50             showUsage = b;
    51             return this;
    52         }
    53         final String key;
    54         final Object[] args;
    55         boolean showUsage;
    56     }
    58     static abstract class Option {
    59         Option(boolean hasArg, String... aliases) {
    60             this.hasArg = hasArg;
    61             this.aliases = aliases;
    62         }
    64         boolean isHidden() {
    65             return false;
    66         }
    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         }
    79         boolean ignoreRest() {
    80             return false;
    81         }
    83         abstract void process(JdepsTask task, String opt, String arg) throws BadArgs;
    84         final boolean hasArg;
    85         final String[] aliases;
    86     }
    88     static abstract class HiddenOption extends Option {
    89         HiddenOption(boolean hasArg, String... aliases) {
    90             super(hasArg, aliases);
    91         }
    93         boolean isHidden() {
    94             return true;
    95         }
    96     }
    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     };
   175     private static final String PROGNAME = "jdeps";
   176     private final Options options = new Options();
   177     private final List<String> classes = new ArrayList<String>();
   179     private PrintWriter log;
   180     void setLog(PrintWriter out) {
   181         log = out;
   182     }
   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
   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     }
   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     }
   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     }
   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         }
   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         }
   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());
   301         // add all archives to the source locations for reporting
   302         sourceLocations.addAll(archives);
   303         sourceLocations.addAll(classpaths);
   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>();
   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                 }
   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         }
   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);
   355                         }
   356                         if (!doneClasses.contains(classFileName)) {
   357                             // if name is a fully-qualified class name specified
   358                             // from command-line, this class might already be parsed
   359                             doneClasses.add(classFileName);
   360                             for (Dependency d : finder.findDependencies(cf)) {
   361                                 if (depth == 0) {
   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     }
   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     }
   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     }
   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     }
   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     }
   469     private void reportError(String key, Object... args) {
   470         log.println(getMessage("error.prefix") + " " + getMessage(key, args));
   471     }
   473     private void warning(String key, Object... args) {
   474         log.println(getMessage("warn.prefix") + " " + getMessage(key, args));
   475     }
   477     private void showHelp() {
   478         log.println(getMessage("main.usage", PROGNAME));
   479         for (Option o : recognizedOptions) {
   480             String name = o.aliases[0].substring(1); // there must always be at least one name
   481             name = name.charAt(0) == '-' ? name.substring(1) : name;
   482             if (o.isHidden() || name.equals("h")) {
   483                 continue;
   484             }
   485             log.println(getMessage("main.opt." + name));
   486         }
   487     }
   489     private void showVersion(boolean full) {
   490         log.println(version(full ? "full" : "release"));
   491     }
   493     private String version(String key) {
   494         // key=version:  mm.nn.oo[-milestone]
   495         // key=full:     mm.mm.oo[-milestone]-build
   496         if (ResourceBundleHelper.versionRB == null) {
   497             return System.getProperty("java.version");
   498         }
   499         try {
   500             return ResourceBundleHelper.versionRB.getString(key);
   501         } catch (MissingResourceException e) {
   502             return getMessage("version.unknown", System.getProperty("java.version"));
   503         }
   504     }
   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     }
   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     }
   528     private static class ResourceBundleHelper {
   529         static final ResourceBundle versionRB;
   530         static final ResourceBundle bundle;
   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     }
   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     }
   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 }

mercurial