diff -r bb1eb01b8c41 -r 2d8dec41f029 langtools/src/share/classes/com/sun/tools/jdeps/JdepsTask.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/langtools/src/share/classes/com/sun/tools/jdeps/JdepsTask.java Fri Dec 28 22:25:21 2012 -0800 @@ -0,0 +1,650 @@ +/* + * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.sun.tools.jdeps; + +import com.sun.tools.classfile.ClassFile; +import com.sun.tools.classfile.ConstantPoolException; +import com.sun.tools.classfile.Dependencies; +import com.sun.tools.classfile.Dependencies.ClassFileError; +import com.sun.tools.classfile.Dependency; +import com.sun.tools.classfile.Dependency.Location; +import java.io.*; +import java.text.MessageFormat; +import java.util.*; +import java.util.regex.Pattern; + +/** + * Implementation for the jdeps tool for static class dependency analysis. + */ +class JdepsTask { + class BadArgs extends Exception { + static final long serialVersionUID = 8765093759964640721L; + BadArgs(String key, Object... args) { + super(JdepsTask.this.getMessage(key, args)); + this.key = key; + this.args = args; + } + + BadArgs showUsage(boolean b) { + showUsage = b; + return this; + } + final String key; + final Object[] args; + boolean showUsage; + } + + static abstract class Option { + Option(boolean hasArg, String... aliases) { + this.hasArg = hasArg; + this.aliases = aliases; + } + + boolean isHidden() { + return false; + } + + boolean matches(String opt) { + for (String a : aliases) { + if (a.equals(opt)) { + return true; + } else if (opt.startsWith("--") && hasArg && opt.startsWith(a + "=")) { + return true; + } + } + return false; + } + + boolean ignoreRest() { + return false; + } + + abstract void process(JdepsTask task, String opt, String arg) throws BadArgs; + final boolean hasArg; + final String[] aliases; + } + + static abstract class HiddenOption extends Option { + HiddenOption(boolean hasArg, String... aliases) { + super(hasArg, aliases); + } + + boolean isHidden() { + return true; + } + } + + static Option[] recognizedOptions = { + new Option(false, "-h", "-?", "--help") { + void process(JdepsTask task, String opt, String arg) { + task.options.help = true; + } + }, + new Option(false, "-s", "--summary") { + void process(JdepsTask task, String opt, String arg) { + task.options.showSummary = true; + task.options.verbose = Options.Verbose.SUMMARY; + } + }, + new Option(false, "-v", "--verbose") { + void process(JdepsTask task, String opt, String arg) { + task.options.verbose = Options.Verbose.VERBOSE; + } + }, + new Option(true, "-V", "--verbose-level") { + void process(JdepsTask task, String opt, String arg) throws BadArgs { + switch (arg) { + case "package": + task.options.verbose = Options.Verbose.PACKAGE; + break; + case "class": + task.options.verbose = Options.Verbose.CLASS; + break; + default: + throw task.new BadArgs("err.invalid.arg.for.option", opt); + } + } + }, + new Option(true, "-c", "--classpath") { + void process(JdepsTask task, String opt, String arg) { + task.options.classpath = arg; + } + }, + new Option(true, "-p", "--package") { + void process(JdepsTask task, String opt, String arg) { + task.options.packageNames.add(arg); + } + }, + new Option(true, "-e", "--regex") { + void process(JdepsTask task, String opt, String arg) { + task.options.regex = arg; + } + }, + new Option(false, "-P", "--profile") { + void process(JdepsTask task, String opt, String arg) { + task.options.showProfile = true; + } + }, + new Option(false, "-R", "--recursive") { + void process(JdepsTask task, String opt, String arg) { + task.options.depth = 0; + } + }, + new HiddenOption(true, "-d", "--depth") { + void process(JdepsTask task, String opt, String arg) throws BadArgs { + try { + task.options.depth = Integer.parseInt(arg); + } catch (NumberFormatException e) { + throw task.new BadArgs("err.invalid.arg.for.option", opt); + } + } + }, + new Option(false, "--version") { + void process(JdepsTask task, String opt, String arg) { + task.options.version = true; + } + }, + new HiddenOption(false, "--fullversion") { + void process(JdepsTask task, String opt, String arg) { + task.options.fullVersion = true; + } + }, + + }; + + private static final String PROGNAME = "jdeps"; + private final Options options = new Options(); + private final List classes = new ArrayList(); + + private PrintWriter log; + void setLog(PrintWriter out) { + log = out; + } + + /** + * Result codes. + */ + static final int EXIT_OK = 0, // Completed with no errors. + EXIT_ERROR = 1, // Completed but reported errors. + EXIT_CMDERR = 2, // Bad command-line arguments + EXIT_SYSERR = 3, // System error or resource exhaustion. + EXIT_ABNORMAL = 4;// terminated abnormally + + int run(String[] args) { + if (log == null) { + log = new PrintWriter(System.out); + } + try { + handleOptions(args); + if (options.help) { + showHelp(); + } + if (options.version || options.fullVersion) { + showVersion(options.fullVersion); + } + if (classes.isEmpty() && !options.wildcard) { + if (options.help || options.version || options.fullVersion) { + return EXIT_OK; + } else { + showHelp(); + return EXIT_CMDERR; + } + } + if (options.regex != null && options.packageNames.size() > 0) { + showHelp(); + return EXIT_CMDERR; + } + if (options.showSummary && options.verbose != Options.Verbose.SUMMARY) { + showHelp(); + return EXIT_CMDERR; + } + boolean ok = run(); + return ok ? EXIT_OK : EXIT_ERROR; + } catch (BadArgs e) { + reportError(e.key, e.args); + if (e.showUsage) { + log.println(getMessage("main.usage.summary", PROGNAME)); + } + return EXIT_CMDERR; + } catch (IOException e) { + return EXIT_ABNORMAL; + } finally { + log.flush(); + } + } + + private final List sourceLocations = new ArrayList(); + private final Archive NOT_FOUND = new Archive(getMessage("artifact.not.found")); + private boolean run() throws IOException { + findDependencies(); + switch (options.verbose) { + case VERBOSE: + case CLASS: + printClassDeps(log); + break; + case PACKAGE: + printPackageDeps(log); + break; + case SUMMARY: + for (Archive origin : sourceLocations) { + for (Archive target : origin.getRequiredArchives()) { + log.format("%-30s -> %s%n", origin, target); + } + } + break; + default: + throw new InternalError("Should not reach here"); + } + return true; + } + + private boolean isValidClassName(String name) { + if (!Character.isJavaIdentifierStart(name.charAt(0))) { + return false; + } + for (int i=1; i < name.length(); i++) { + char c = name.charAt(i); + if (c != '.' && !Character.isJavaIdentifierPart(c)) { + return false; + } + } + return true; + } + + private void findDependencies() throws IOException { + Dependency.Finder finder = Dependencies.getClassDependencyFinder(); + Dependency.Filter filter; + if (options.regex != null) { + filter = Dependencies.getRegexFilter(Pattern.compile(options.regex)); + } else if (options.packageNames.size() > 0) { + filter = Dependencies.getPackageFilter(options.packageNames, false); + } else { + filter = new Dependency.Filter() { + public boolean accepts(Dependency dependency) { + return !dependency.getOrigin().equals(dependency.getTarget()); + } + }; + } + + List archives = new ArrayList(); + Deque roots = new LinkedList(); + for (String s : classes) { + File f = new File(s); + if (f.exists()) { + archives.add(new Archive(f, ClassFileReader.newInstance(f))); + } else { + if (isValidClassName(s)) { + roots.add(s); + } else { + warning("warn.invalid.arg", s); + } + } + } + + List classpaths = new ArrayList(); // for class file lookup + if (options.wildcard) { + // include all archives from classpath to the initial list + archives.addAll(getClassPathArchives(options.classpath)); + } else { + classpaths.addAll(getClassPathArchives(options.classpath)); + } + classpaths.addAll(PlatformClassPath.getArchives()); + + // add all archives to the source locations for reporting + sourceLocations.addAll(archives); + sourceLocations.addAll(classpaths); + + // Work queue of names of classfiles to be searched. + // Entries will be unique, and for classes that do not yet have + // dependencies in the results map. + Deque deque = new LinkedList(); + Set doneClasses = new HashSet(); + + // get the immediate dependencies of the input files + for (Archive a : archives) { + for (ClassFile cf : a.reader().getClassFiles()) { + String classFileName; + try { + classFileName = cf.getName(); + } catch (ConstantPoolException e) { + throw new ClassFileError(e); + } + a.addClass(classFileName); + if (!doneClasses.contains(classFileName)) { + doneClasses.add(classFileName); + } + for (Dependency d : finder.findDependencies(cf)) { + if (filter.accepts(d)) { + String cn = d.getTarget().getName(); + if (!doneClasses.contains(cn) && !deque.contains(cn)) { + deque.add(cn); + } + a.addDependency(d); + } + } + } + } + + // add Archive for looking up classes from the classpath + // for transitive dependency analysis + Deque unresolved = roots; + int depth = options.depth > 0 ? options.depth : Integer.MAX_VALUE; + do { + String name; + while ((name = unresolved.poll()) != null) { + if (doneClasses.contains(name)) { + continue; + } + ClassFile cf = null; + for (Archive a : classpaths) { + cf = a.reader().getClassFile(name); + if (cf != null) { + String classFileName; + try { + classFileName = cf.getName(); + } catch (ConstantPoolException e) { + throw new ClassFileError(e); + } + a.addClass(classFileName); + if (!doneClasses.contains(classFileName)) { + // if name is a fully-qualified class name specified + // from command-line, this class might already be parsed + doneClasses.add(classFileName); + if (depth > 0) { + for (Dependency d : finder.findDependencies(cf)) { + if (filter.accepts(d)) { + String cn = d.getTarget().getName(); + if (!doneClasses.contains(cn) && !deque.contains(cn)) { + deque.add(cn); + } + a.addDependency(d); + } + } + } + } + break; + } + } + if (cf == null) { + NOT_FOUND.addClass(name); + } + } + unresolved = deque; + deque = new LinkedList(); + } while (!unresolved.isEmpty() && depth-- > 0); + } + + private void printPackageDeps(PrintWriter out) { + for (Archive source : sourceLocations) { + SortedMap> deps = source.getDependencies(); + if (deps.isEmpty()) + continue; + + for (Archive target : source.getRequiredArchives()) { + out.format("%s -> %s%n", source, target); + } + + Map pkgs = new TreeMap(); + SortedMap targets = new TreeMap(); + String pkg = ""; + for (Map.Entry> e : deps.entrySet()) { + String cn = e.getKey().getClassName(); + String p = packageOf(e.getKey()); + Archive origin = Archive.find(e.getKey()); + assert origin != null; + if (!pkgs.containsKey(p)) { + pkgs.put(p, origin); + } else if (pkgs.get(p) != origin) { + warning("warn.split.package", p, origin, pkgs.get(p)); + } + + if (!p.equals(pkg)) { + printTargets(out, targets); + pkg = p; + targets.clear(); + out.format(" %s (%s)%n", p, origin.getFileName()); + } + + for (Location t : e.getValue()) { + p = packageOf(t); + Archive target = Archive.find(t); + if (!targets.containsKey(p)) { + targets.put(p, target); + } + } + } + printTargets(out, targets); + out.println(); + } + } + + private void printTargets(PrintWriter out, Map targets) { + for (Map.Entry t : targets.entrySet()) { + String pn = t.getKey(); + out.format(" -> %-40s %s%n", pn, getPackageInfo(pn, t.getValue())); + } + } + + private String getPackageInfo(String pn, Archive source) { + if (PlatformClassPath.contains(source)) { + String name = PlatformClassPath.getProfileName(pn); + if (name.isEmpty()) { + return "JDK internal API (" + source.getFileName() + ")"; + } + return options.showProfile ? name : ""; + } + return source.getFileName(); + } + + private static String packageOf(Location loc) { + String pkg = loc.getPackageName(); + return pkg.isEmpty() ? "" : pkg; + } + + private void printClassDeps(PrintWriter out) { + for (Archive source : sourceLocations) { + SortedMap> deps = source.getDependencies(); + if (deps.isEmpty()) + continue; + + for (Archive target : source.getRequiredArchives()) { + out.format("%s -> %s%n", source, target); + } + out.format("%s%n", source); + for (Map.Entry> e : deps.entrySet()) { + String cn = e.getKey().getClassName(); + Archive origin = Archive.find(e.getKey()); + out.format(" %s (%s)%n", cn, origin.getFileName()); + for (Location t : e.getValue()) { + cn = t.getClassName(); + Archive target = Archive.find(t); + out.format(" -> %-60s %s%n", cn, getPackageInfo(t.getPackageName(), target)); + } + } + out.println(); + } + } + public void handleOptions(String[] args) throws BadArgs { + // process options + for (int i=0; i < args.length; i++) { + if (args[i].charAt(0) == '-') { + String name = args[i]; + Option option = getOption(name); + String param = null; + if (option.hasArg) { + if (name.startsWith("--") && name.indexOf('=') > 0) { + param = name.substring(name.indexOf('=') + 1, name.length()); + } else if (i + 1 < args.length) { + param = args[++i]; + } + if (param == null || param.isEmpty() || param.charAt(0) == '-') { + throw new BadArgs("err.missing.arg", name).showUsage(true); + } + } + option.process(this, name, param); + if (option.ignoreRest()) { + i = args.length; + } + } else { + // process rest of the input arguments + for (; i < args.length; i++) { + String name = args[i]; + if (name.charAt(0) == '-') { + throw new BadArgs("err.option.after.class", name).showUsage(true); + } + if (name.equals("*") || name.equals("\"*\"")) { + options.wildcard = true; + } else { + classes.add(name); + } + } + } + } + } + + private Option getOption(String name) throws BadArgs { + for (Option o : recognizedOptions) { + if (o.matches(name)) { + return o; + } + } + throw new BadArgs("err.unknown.option", name).showUsage(true); + } + + private void reportError(String key, Object... args) { + log.println(getMessage("error.prefix") + " " + getMessage(key, args)); + } + + private void warning(String key, Object... args) { + log.println(getMessage("warn.prefix") + " " + getMessage(key, args)); + } + + private void showHelp() { + log.println(getMessage("main.usage", PROGNAME)); + for (Option o : recognizedOptions) { + String name = o.aliases[0].substring(1); // there must always be at least one name + name = name.charAt(0) == '-' ? name.substring(1) : name; + if (o.isHidden() || name.equals("h")) { + continue; + } + log.println(getMessage("main.opt." + name)); + } + } + + private void showVersion(boolean full) { + log.println(version(full ? "full" : "release")); + } + + private String version(String key) { + // key=version: mm.nn.oo[-milestone] + // key=full: mm.mm.oo[-milestone]-build + if (ResourceBundleHelper.versionRB == null) { + return System.getProperty("java.version"); + } + try { + return ResourceBundleHelper.versionRB.getString(key); + } catch (MissingResourceException e) { + return getMessage("version.unknown", System.getProperty("java.version")); + } + } + + public String getMessage(String key, Object... args) { + try { + return MessageFormat.format(ResourceBundleHelper.bundle.getString(key), args); + } catch (MissingResourceException e) { + throw new InternalError("Missing message: " + key); + } + } + + private static class Options { + enum Verbose { + CLASS, + PACKAGE, + SUMMARY, + VERBOSE + }; + + boolean help; + boolean version; + boolean fullVersion; + boolean showFlags; + boolean showProfile; + boolean showSummary; + boolean wildcard; + String regex; + String classpath = ""; + int depth = 1; + Verbose verbose = Verbose.PACKAGE; + Set packageNames = new HashSet(); + } + + private static class ResourceBundleHelper { + static final ResourceBundle versionRB; + static final ResourceBundle bundle; + + static { + Locale locale = Locale.getDefault(); + try { + bundle = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.jdeps", locale); + } catch (MissingResourceException e) { + throw new InternalError("Cannot find jdeps resource bundle for locale " + locale); + } + try { + versionRB = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.version"); + } catch (MissingResourceException e) { + throw new InternalError("version.resource.missing"); + } + } + } + + private List getArchives(List filenames) throws IOException { + List result = new ArrayList(); + for (String s : filenames) { + File f = new File(s); + if (f.exists()) { + result.add(new Archive(f, ClassFileReader.newInstance(f))); + } else { + warning("warn.file.not.exist", s); + } + } + return result; + } + + private List getClassPathArchives(String paths) throws IOException { + List result = new ArrayList(); + if (paths.isEmpty()) { + return result; + } + for (String p : paths.split(File.pathSeparator)) { + if (p.length() > 0) { + File f = new File(p); + if (f.exists()) { + result.add(new Archive(f, ClassFileReader.newInstance(f))); + } + } + } + return result; + } +}