--- /dev/null 2012-12-04 11:52:37.000000000 -0800 +++ new/src/share/classes/com/sun/tools/jdeps/JdepsTask.java 2012-12-04 11:52:37.000000000 -0800 @@ -0,0 +1,600 @@ +/* + * 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; + } + } + return false; + } + + boolean ignoreRest() { + return false; + } + + abstract void process(JdepsTask task, String opt, String arg) throws BadArgs; + final boolean hasArg; + final String[] aliases; + } + + static Option[] recognizedOptions = { + new Option(false, "-h", "--help") { + void process(JdepsTask task, String opt, String arg) { + task.options.help = true; + } + }, + new Option(false, "-version") { + void process(JdepsTask task, String opt, String arg) { + task.options.version = true; + } + }, + new Option(false, "-fullversion") { + void process(JdepsTask task, String opt, String arg) { + task.options.fullVersion = true; + } + }, + new Option(true, "-classpath") { + void process(JdepsTask task, String opt, String arg) { + task.options.classpath = arg; + } + }, + new Option(false, "-v", "--verbose") { + void process(JdepsTask task, String opt, String arg) { + task.options.verbose = Options.Verbose.CLASS; + } + }, + new Option(false, "-v:class", "--verbose:class") { + void process(JdepsTask task, String opt, String arg) { + task.options.verbose = Options.Verbose.CLASS; + } + }, + new Option(false, "-v:package", "--verbose:package") { + void process(JdepsTask task, String opt, String arg) { + task.options.verbose = Options.Verbose.PACKAGE; + } + }, + new Option(false, "-v:summary", "--verbose:summary") { + void process(JdepsTask task, String opt, String arg) { + task.options.verbose = Options.Verbose.SUMMARY; + } + }, + new Option(true, "-p") { + 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 Option(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, "-all") { + void process(JdepsTask task, String opt, String arg) { + task.options.all = true; + } + }}; + + private static final String PROGNAME = "jdeps"; + private final Options options = new Options(); + private final List filenames = 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(Arrays.asList(args)); + if (filenames.isEmpty() && (!options.all || options.classpath.isEmpty())) { + if (options.help || options.version || options.fullVersion) { + return EXIT_OK; + } else { + return EXIT_CMDERR; + } + } + if (options.regex != null && options.packageNames.size() > 0) { + 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 boolean run() 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()); + } + }; + } + + + findDependencies(finder, filter); + + switch (options.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 final List sourceLocations = new ArrayList<>(); + private final Archive NOT_FOUND = new Archive(null, null); + private void findDependencies(Dependency.Finder finder, + Dependency.Filter filter) + throws IOException + { + // 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<>(); + + List archives = new ArrayList<>(getArchives(filenames)); + List classpaths = classPathArchives(options.classpath); + List platformClassPath = PlatformClassPath.getArchives(); + sourceLocations.addAll(archives); + sourceLocations.addAll(classpaths); + sourceLocations.addAll(platformClassPath); + if (options.all) { + archives.addAll(classpaths); + } + + // get the immediate dependencies of the input files + for (Archive a : archives) { + for (ClassFile cf : a.reader().getClassFiles()) { + String className; + try { + className = cf.getName().replace('/', '.'); + } catch (ConstantPoolException e) { + throw new ClassFileError(e); + } + a.addClass(className); + if (!doneClasses.contains(className)) { + doneClasses.add(className); + } + for (Dependency d : finder.findDependencies(cf)) { + if (filter.accepts(d)) { + String cn = d.getTarget().getClassName(); + 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 + int depths = options.depth == 0 ? Integer.MAX_VALUE : options.depth; + List cpathReaders = new ArrayList<>(); + if (!options.all) { + cpathReaders.addAll(classpaths); + } + cpathReaders.addAll(platformClassPath); + while (!deque.isEmpty() && depths-- >= 0) { + Deque unresolved = deque; + deque = new LinkedList<>(); + String className; + while ((className = unresolved.poll()) != null) { + if (doneClasses.contains(className)) { + continue; + } + + ClassFile cf = null; + for (Archive a : cpathReaders) { + cf = a.reader().getClassFile(className); + if (cf != null) { + a.addClass(className); + doneClasses.add(className); + if (depths > 0) { + // do dependency analysis if depths > 0 + for (Dependency d : finder.findDependencies(cf)) { + if (filter.accepts(d)) { + String cn = d.getTarget().getClassName(); + if (!doneClasses.contains(cn) && !deque.contains(cn)) { + deque.add(cn); + } + a.addDependency(d); + } + } + } + break; + } + } + + if (cf == null) { + NOT_FOUND.addClass(className); + } + } + } + } + + 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()) { + Location o = e.getKey(); + String p = packageOf(o); + Archive origin = Archive.find(o.getClassName()); + 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, Archive.find(o.getClassName())); + } + + for (Location t : e.getValue()) { + p = packageOf(t); + Archive target = Archive.find(t.getClassName()); + if (!targets.containsKey(p)) { + targets.put(p, target); + } + } + } + printTargets(out, targets); + } + } + + 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 cn = loc.getClassName(); + int i = cn.lastIndexOf('.'); + return i > 0 ? cn.substring(0, i) : ""; + } + + 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()) { + Location o = e.getKey(); + String cn = o.getClassName(); + Archive origin = Archive.find(cn); + out.format(" %s (%s)%n", cn, origin); + for (Location t : e.getValue()) { + cn = t.getClassName(); + Archive target = Archive.find(cn); + out.format(" -> %-60s %s%n", cn, getPackageInfo(packageOf(t), target)); + } + } + } + } + + public void handleOptions(Iterable args) throws BadArgs { + Iterator iter = args.iterator(); + boolean noArgs = !iter.hasNext(); + while (iter.hasNext()) { + String arg = iter.next(); + if (arg.startsWith("-")) { + handleOption(arg, iter); + } else { + filenames.add(arg); + while (iter.hasNext()) { + filenames.add(iter.next()); + } + } + } + + if (noArgs || options.help || + (filenames.isEmpty() && (!options.all || options.classpath.isEmpty()))) { + showHelp(); + } + + if (options.version || options.fullVersion) { + showVersion(options.fullVersion); + } + } + + private void handleOption(String name, Iterator rest) throws BadArgs { + for (Option o : recognizedOptions) { + if (o.matches(name)) { + if (o.hasArg) { + String arg = rest.hasNext() ? rest.next() : "-"; + if (arg.startsWith("-")) { + throw new BadArgs("err.missing.arg", name).showUsage(true); + } else { + o.process(this, name, arg); + } + } else { + o.process(this, name, null); + } + + if (o.ignoreRest()) { + while (rest.hasNext()) { + rest.next(); + } + } + return; + } + } + 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).replace(':', '.'); // there must always be at least one name + if (name.equals("fullversion") || 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 + }; + + public boolean help; + public boolean version; + public boolean fullVersion; + public boolean showFlags; + public boolean reverse; + public boolean all; + public boolean showProfile; + public String regex; + public String classpath = ""; + public int depth = 1; + public Verbose verbose = Verbose.PACKAGE; + public 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()) { + ClassFileReader reader = ClassFileReader.newInstance(f); + Archive archive = new Archive(f, reader); + result.add(archive); + } else { + warning("warn.file.not.exist", s); + } + } + return result; + } + + private List classPathArchives(String classpath) throws IOException { + List result = new ArrayList<>(); + if (classpath.isEmpty()) { + return result; + } + for (String p : classpath.split(File.pathSeparator)) { + if (p.length() > 0) { + File f = new File(p); + if (f.exists()) { + ClassFileReader reader = ClassFileReader.newInstance(f); + Archive archive = new Archive(f, reader); + result.add(archive); + } + } + } + return result; + } +}