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

Thu, 14 Feb 2013 09:43:00 -0800

author
mchung
date
Thu, 14 Feb 2013 09:43:00 -0800
changeset 1577
88286a36bb34
parent 1472
0c244701188e
child 1638
fd3fdaff0257
permissions
-rw-r--r--

8006225: tools/jdeps/Basic.java failes with AssertionError
Reviewed-by: alanb

     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 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     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 task.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) {
   143                 task.options.showProfile = true;
   144             }
   145         },
   146         new Option(false, "-R", "--recursive") {
   147             void process(JdepsTask task, String opt, String arg) {
   148                 task.options.depth = 0;
   149             }
   150         },
   151         new HiddenOption(true, "-d", "--depth") {
   152             void process(JdepsTask task, String opt, String arg) throws BadArgs {
   153                 try {
   154                     task.options.depth = Integer.parseInt(arg);
   155                 } catch (NumberFormatException e) {
   156                     throw task.new BadArgs("err.invalid.arg.for.option", opt);
   157                 }
   158             }
   159         },
   160         new Option(false, "--version") {
   161             void process(JdepsTask task, String opt, String arg) {
   162                 task.options.version = true;
   163             }
   164         },
   165         new HiddenOption(false, "--fullversion") {
   166             void process(JdepsTask task, String opt, String arg) {
   167                 task.options.fullVersion = true;
   168             }
   169         },
   170     };
   172     private static final String PROGNAME = "jdeps";
   173     private final Options options = new Options();
   174     private final List<String> classes = new ArrayList<String>();
   176     private PrintWriter log;
   177     void setLog(PrintWriter out) {
   178         log = out;
   179     }
   181     /**
   182      * Result codes.
   183      */
   184     static final int EXIT_OK = 0, // Completed with no errors.
   185                      EXIT_ERROR = 1, // Completed but reported errors.
   186                      EXIT_CMDERR = 2, // Bad command-line arguments
   187                      EXIT_SYSERR = 3, // System error or resource exhaustion.
   188                      EXIT_ABNORMAL = 4;// terminated abnormally
   190     int run(String[] args) {
   191         if (log == null) {
   192             log = new PrintWriter(System.out);
   193         }
   194         try {
   195             handleOptions(args);
   196             if (options.help) {
   197                 showHelp();
   198             }
   199             if (options.version || options.fullVersion) {
   200                 showVersion(options.fullVersion);
   201             }
   202             if (classes.isEmpty() && !options.wildcard) {
   203                 if (options.help || options.version || options.fullVersion) {
   204                     return EXIT_OK;
   205                 } else {
   206                     showHelp();
   207                     return EXIT_CMDERR;
   208                 }
   209             }
   210             if (options.regex != null && options.packageNames.size() > 0) {
   211                 showHelp();
   212                 return EXIT_CMDERR;
   213             }
   214             if (options.showSummary && options.verbose != Analyzer.Type.SUMMARY) {
   215                 showHelp();
   216                 return EXIT_CMDERR;
   217             }
   218             boolean ok = run();
   219             return ok ? EXIT_OK : EXIT_ERROR;
   220         } catch (BadArgs e) {
   221             reportError(e.key, e.args);
   222             if (e.showUsage) {
   223                 log.println(getMessage("main.usage.summary", PROGNAME));
   224             }
   225             return EXIT_CMDERR;
   226         } catch (IOException e) {
   227             return EXIT_ABNORMAL;
   228         } finally {
   229             log.flush();
   230         }
   231     }
   233     private final List<Archive> sourceLocations = new ArrayList<Archive>();
   234     private boolean run() throws IOException {
   235         findDependencies();
   236         Analyzer analyzer = new Analyzer(options.verbose);
   237         analyzer.run(sourceLocations);
   238         if (options.verbose == Analyzer.Type.SUMMARY) {
   239             printSummary(log, analyzer);
   240         } else {
   241             printDependencies(log, analyzer);
   242         }
   243         return true;
   244     }
   246     private boolean isValidClassName(String name) {
   247         if (!Character.isJavaIdentifierStart(name.charAt(0))) {
   248             return false;
   249         }
   250         for (int i=1; i < name.length(); i++) {
   251             char c = name.charAt(i);
   252             if (c != '.'  && !Character.isJavaIdentifierPart(c)) {
   253                 return false;
   254             }
   255         }
   256         return true;
   257     }
   259     private void findDependencies() throws IOException {
   260         Dependency.Finder finder = Dependencies.getClassDependencyFinder();
   261         Dependency.Filter filter;
   262         if (options.regex != null) {
   263             filter = Dependencies.getRegexFilter(Pattern.compile(options.regex));
   264         } else if (options.packageNames.size() > 0) {
   265             filter = Dependencies.getPackageFilter(options.packageNames, false);
   266         } else {
   267             filter = new Dependency.Filter() {
   268                 public boolean accepts(Dependency dependency) {
   269                     return !dependency.getOrigin().equals(dependency.getTarget());
   270                 }
   271             };
   272         }
   274         List<Archive> archives = new ArrayList<Archive>();
   275         Deque<String> roots = new LinkedList<String>();
   276         for (String s : classes) {
   277             File f = new File(s);
   278             if (f.exists()) {
   279                 archives.add(new Archive(f, ClassFileReader.newInstance(f)));
   280             } else {
   281                 if (isValidClassName(s)) {
   282                     roots.add(s);
   283                 } else {
   284                     warning("warn.invalid.arg", s);
   285                 }
   286             }
   287         }
   289         List<Archive> classpaths = new ArrayList<Archive>(); // for class file lookup
   290         if (options.wildcard) {
   291             // include all archives from classpath to the initial list
   292             archives.addAll(getClassPathArchives(options.classpath));
   293         } else {
   294             classpaths.addAll(getClassPathArchives(options.classpath));
   295         }
   296         classpaths.addAll(PlatformClassPath.getArchives());
   298         // add all archives to the source locations for reporting
   299         sourceLocations.addAll(archives);
   300         sourceLocations.addAll(classpaths);
   302         // Work queue of names of classfiles to be searched.
   303         // Entries will be unique, and for classes that do not yet have
   304         // dependencies in the results map.
   305         Deque<String> deque = new LinkedList<String>();
   306         Set<String> doneClasses = new HashSet<String>();
   308         // get the immediate dependencies of the input files
   309         for (Archive a : archives) {
   310             for (ClassFile cf : a.reader().getClassFiles()) {
   311                 String classFileName;
   312                 try {
   313                     classFileName = cf.getName();
   314                 } catch (ConstantPoolException e) {
   315                     throw new ClassFileError(e);
   316                 }
   318                 if (!doneClasses.contains(classFileName)) {
   319                     doneClasses.add(classFileName);
   320                 }
   321                 for (Dependency d : finder.findDependencies(cf)) {
   322                     if (filter.accepts(d)) {
   323                         String cn = d.getTarget().getName();
   324                         if (!doneClasses.contains(cn) && !deque.contains(cn)) {
   325                             deque.add(cn);
   326                         }
   327                         a.addClass(d.getOrigin(), d.getTarget());
   328                     }
   329                 }
   330             }
   331         }
   333         // add Archive for looking up classes from the classpath
   334         // for transitive dependency analysis
   335         Deque<String> unresolved = roots;
   336         int depth = options.depth > 0 ? options.depth : Integer.MAX_VALUE;
   337         do {
   338             String name;
   339             while ((name = unresolved.poll()) != null) {
   340                 if (doneClasses.contains(name)) {
   341                     continue;
   342                 }
   343                 ClassFile cf = null;
   344                 for (Archive a : classpaths) {
   345                     cf = a.reader().getClassFile(name);
   346                     if (cf != null) {
   347                         String classFileName;
   348                         try {
   349                             classFileName = cf.getName();
   350                         } catch (ConstantPoolException e) {
   351                             throw new ClassFileError(e);
   352                         }
   353                         if (!doneClasses.contains(classFileName)) {
   354                             // if name is a fully-qualified class name specified
   355                             // from command-line, this class might already be parsed
   356                             doneClasses.add(classFileName);
   357                             for (Dependency d : finder.findDependencies(cf)) {
   358                                 if (depth == 0) {
   359                                     // ignore the dependency
   360                                     a.addClass(d.getOrigin());
   361                                     break;
   362                                 } else if (filter.accepts(d)) {
   363                                     a.addClass(d.getOrigin(), d.getTarget());
   364                                     String cn = d.getTarget().getName();
   365                                     if (!doneClasses.contains(cn) && !deque.contains(cn)) {
   366                                         deque.add(cn);
   367                                     }
   368                                 }
   369                             }
   370                         }
   371                         break;
   372                     }
   373                 }
   374                 if (cf == null) {
   375                     doneClasses.add(name);
   376                 }
   377             }
   378             unresolved = deque;
   379             deque = new LinkedList<String>();
   380         } while (!unresolved.isEmpty() && depth-- > 0);
   381     }
   383     private void printSummary(final PrintWriter out, final Analyzer analyzer) {
   384         Analyzer.Visitor visitor = new Analyzer.Visitor() {
   385             public void visit(String origin, String profile) {
   386                 if (options.showProfile) {
   387                     out.format("%-30s -> %s%n", origin, profile);
   388                 }
   389             }
   390             public void visit(Archive origin, Archive target) {
   391                 if (!options.showProfile) {
   392                     out.format("%-30s -> %s%n", origin, target);
   393                 }
   394             }
   395         };
   396         analyzer.visitSummary(visitor);
   397     }
   399     private void printDependencies(final PrintWriter out, final Analyzer analyzer) {
   400         Analyzer.Visitor visitor = new Analyzer.Visitor() {
   401             private String pkg = "";
   402             public void visit(String origin, String target) {
   403                 if (!origin.equals(pkg)) {
   404                     pkg = origin;
   405                     out.format("   %s (%s)%n", origin, analyzer.getArchiveName(origin));
   406                 }
   407                 Archive source = analyzer.getArchive(target);
   408                 String profile = options.showProfile ? analyzer.getProfile(target) : "";
   409                 out.format("      -> %-50s %s%n", target,
   410                            PlatformClassPath.contains(source)
   411                                ? profile
   412                                : analyzer.getArchiveName(target));
   413             }
   414             public void visit(Archive origin, Archive target) {
   415                 out.format("%s -> %s%n", origin, target);
   416             }
   417         };
   418         analyzer.visit(visitor);
   419     }
   421     public void handleOptions(String[] args) throws BadArgs {
   422         // process options
   423         for (int i=0; i < args.length; i++) {
   424             if (args[i].charAt(0) == '-') {
   425                 String name = args[i];
   426                 Option option = getOption(name);
   427                 String param = null;
   428                 if (option.hasArg) {
   429                     if (name.startsWith("--") && name.indexOf('=') > 0) {
   430                         param = name.substring(name.indexOf('=') + 1, name.length());
   431                     } else if (i + 1 < args.length) {
   432                         param = args[++i];
   433                     }
   434                     if (param == null || param.isEmpty() || param.charAt(0) == '-') {
   435                         throw new BadArgs("err.missing.arg", name).showUsage(true);
   436                     }
   437                 }
   438                 option.process(this, name, param);
   439                 if (option.ignoreRest()) {
   440                     i = args.length;
   441                 }
   442             } else {
   443                 // process rest of the input arguments
   444                 for (; i < args.length; i++) {
   445                     String name = args[i];
   446                     if (name.charAt(0) == '-') {
   447                         throw new BadArgs("err.option.after.class", name).showUsage(true);
   448                     }
   449                     if (name.equals("*") || name.equals("\"*\"")) {
   450                         options.wildcard = true;
   451                     } else {
   452                         classes.add(name);
   453                     }
   454                 }
   455             }
   456         }
   457     }
   459     private Option getOption(String name) throws BadArgs {
   460         for (Option o : recognizedOptions) {
   461             if (o.matches(name)) {
   462                 return o;
   463             }
   464         }
   465         throw new BadArgs("err.unknown.option", name).showUsage(true);
   466     }
   468     private void reportError(String key, Object... args) {
   469         log.println(getMessage("error.prefix") + " " + getMessage(key, args));
   470     }
   472     private void warning(String key, Object... args) {
   473         log.println(getMessage("warn.prefix") + " " + getMessage(key, args));
   474     }
   476     private void showHelp() {
   477         log.println(getMessage("main.usage", PROGNAME));
   478         for (Option o : recognizedOptions) {
   479             String name = o.aliases[0].substring(1); // there must always be at least one name
   480             name = name.charAt(0) == '-' ? name.substring(1) : name;
   481             if (o.isHidden() || name.equals("h")) {
   482                 continue;
   483             }
   484             log.println(getMessage("main.opt." + name));
   485         }
   486     }
   488     private void showVersion(boolean full) {
   489         log.println(version(full ? "full" : "release"));
   490     }
   492     private String version(String key) {
   493         // key=version:  mm.nn.oo[-milestone]
   494         // key=full:     mm.mm.oo[-milestone]-build
   495         if (ResourceBundleHelper.versionRB == null) {
   496             return System.getProperty("java.version");
   497         }
   498         try {
   499             return ResourceBundleHelper.versionRB.getString(key);
   500         } catch (MissingResourceException e) {
   501             return getMessage("version.unknown", System.getProperty("java.version"));
   502         }
   503     }
   505     static String getMessage(String key, Object... args) {
   506         try {
   507             return MessageFormat.format(ResourceBundleHelper.bundle.getString(key), args);
   508         } catch (MissingResourceException e) {
   509             throw new InternalError("Missing message: " + key);
   510         }
   511     }
   513     private static class Options {
   514         boolean help;
   515         boolean version;
   516         boolean fullVersion;
   517         boolean showFlags;
   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