--- /dev/null 2012-11-27 14:58:58.000000000 -0800 +++ new/src/share/classes/com/sun/tools/jdeps/JdepsTask.java 2012-11-27 14:58:58.000000000 -0800 @@ -0,0 +1,675 @@ +/* + * 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.Finder; +import com.sun.tools.classfile.Dependency.Location; +import java.io.*; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +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 = true; + } + }, + new Option(false, "-r", "--reverse") { + void process(JdepsTask task, String opt, String arg) { + task.options.reverse = true; + } + }, + 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(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()) { + 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.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()); + } + }; + } + + Finder finder = Dependencies.getClassDependencyFinder(); + DependencyRecorder recorder = new DependencyRecorder(options.reverse); + findDependencies(finder, filter, recorder); + + if (options.verbose) { + printClassDeps(log, recorder); + } else { + printPackageDeps(log, recorder); + } + return true; + } + + private Map classToReaderMap = new HashMap<>(); + private void findDependencies(Dependency.Finder finder, + Dependency.Filter filter, + DependencyRecorder recorder) + 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<>(); + + // get the immediate dependencies of the input files + for (ClassFileReader r : getClassFileReaders(filenames)) { + for (ClassFile cf : r.getClassFiles()) { + String className; + try { + className = cf.getName().replace('/', '.'); + } catch (ConstantPoolException e) { + throw new ClassFileError(e); + } + if (!classToReaderMap.containsKey(className)) { + classToReaderMap.put(className, r); + } + for (Dependency d : finder.findDependencies(cf)) { + if (filter.accepts(d)) { + String cn = d.getTarget().getClassName(); + if (!classToReaderMap.containsKey(cn) && !deque.contains(cn)) { + deque.add(cn); + } + recorder.addDependency(d); + } + } + } + } + + // add ClassFileReader for looking up classes from the classpath + // for transitive dependency analysis + int depths = options.depth == 0 ? Integer.MAX_VALUE : options.depth; + initJavaHomeReaders(); + List readers = new ArrayList<>(classPathReaders(options.classpath)); + readers.addAll(javaHomeReaders); + while (!deque.isEmpty() && depths-- >= 0) { + Deque unresolved = deque; + deque = new LinkedList<>(); + String className; + while ((className = unresolved.poll()) != null) { + if (classToReaderMap.containsKey(className)) { + continue; + } + + ClassFile cf = null; + for (ClassFileReader r : readers) { + cf = r.getClassFile(className); + if (cf != null) { + classToReaderMap.put(className, r); + 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 (!classToReaderMap.containsKey(cn) && !deque.contains(cn)) { + deque.add(cn); + } + recorder.addDependency(d); + } + } + } + break; + } + } + + if (cf == null) { + warning("warn.class.not.found", className); + classToReaderMap.put(className, null); + } + } + } + } + + private void printPackageDeps(PrintWriter out, DependencyRecorder r) { + if (options.all) { + SortedMap pkgs = new TreeMap<>(); + for (Location loc : r.getAllClasses()) { + String pn = packageOf(loc); + ClassFileReader reader = classToReaderMap.get(loc.getClassName()); + if (!pkgs.containsKey(pn)) { + pkgs.put(pn, reader); + } else if (pkgs.get(pn) != reader) { + warning("warn.split.package", pn, reader, pkgs.get(pn)); + } + } + for (Map.Entry e : pkgs.entrySet()) { + String pn = e.getKey(); + ClassFileReader reader = e.getValue(); + out.format(" %-60s %s%n", pn, getPackageInfo(pn, reader)); + } + } else { + String pkg = ""; + SortedMap> deps = getDependencies(r); + SortedMap targets = new TreeMap<>(); + for (Map.Entry> e : getDependencies(r).entrySet()) { + Location o = e.getKey(); + String p = packageOf(o); + if (!p.equals(pkg)) { + printTargets(out, targets); + pkg = p; + targets.clear(); + out.format("%s (%s)%n", p, classToReaderMap.get(o.getClassName())); + } + + for (Location t : e.getValue()) { + p = packageOf(t); + ClassFileReader reader = classToReaderMap.get(t.getClassName()); + if (!targets.containsKey(p)) { + targets.put(p, reader); + } + } + } + printTargets(out, targets); + } + } + + private void printTargets(PrintWriter out, Map targets) { + String arrow = options.reverse ? "<-" : "->"; + for (Map.Entry t : targets.entrySet()) { + String pn = t.getKey(); + out.format(" %s %-40s %s%n", arrow, pn, getPackageInfo(pn, t.getValue())); + } + } + + private String getPackageInfo(String pn, ClassFileReader reader) { + Profile profile = Profile.getProfile(pn); + if (profile == null && javaHomeReaders.contains(reader)) { + return "JDK internal API"; + } + if (options.showProfile) { + String s = reader != null ? reader.toString() : null; + return profile != null ? profile.toString() : s; + } else { + // default no package information + return ""; + } + } + + 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, DependencyRecorder r) { + if (options.all) { + SortedSet classes = r.getAllClasses(); + for (Location t : classes) { + out.format(" %s%n", t.getClassName()); + } + } else { + String arrow = options.reverse ? "<-" : "->"; + SortedMap> deps = getDependencies(r); + for (Map.Entry> e : deps.entrySet()) { + Location o = e.getKey(); + String cn = o.getClassName(); + ClassFileReader reader = classToReaderMap.get(cn); + out.format("%s (%s)%n", cn, reader); + for (Location t : e.getValue()) { + cn = t.getClassName(); + reader = classToReaderMap.get(cn); + out.format(" %s %-60s %s%n", arrow, cn, getPackageInfo(packageOf(t), reader)); + } + } + } + } + + private SortedMap> getDependencies(DependencyRecorder r) { + return r.getDependencies(new DependencyRecorder.Filter() { + @Override + public boolean accept(Location origin, Location target) { + String o = origin.getClassName(); + String t = target.getClassName(); + return (options.all || classToReaderMap.get(o) != classToReaderMap.get(t)); + }}); + } + + 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 || filenames.isEmpty() || options.help) { + 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); // 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 { + public boolean help; + public boolean verbose; + 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 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"); + } + } + } + + static class DependencyRecorder { + static interface Filter { + boolean accept(Location origin, Location target); + } + public DependencyRecorder(boolean reverse) { + this.reverse = reverse; + } + + public void addDependency(Dependency d) { + Location origin = reverse ? d.getTarget() : d.getOrigin(); + Location target = reverse ? d.getOrigin() : d.getTarget(); + SortedSet odeps = map.get(origin); + if (odeps == null) { + map.put(origin, odeps = new TreeSet<>(locationComparator)); + } + odeps.add(target); + } + + public SortedMap> getClassMap() { + return map; + } + + public SortedSet getAllClasses() { + SortedSet classes = new TreeSet<>(locationComparator); + for (SortedSet set : map.values()) { + classes.addAll(set); + } + return classes; + } + + public SortedMap> getDependencies(Filter filter) { + SortedMap> result = new TreeMap<>(locationComparator); + for (Map.Entry> e : map.entrySet()) { + Location o = e.getKey(); + for (Location t : e.getValue()) { + if (filter.accept(o, t)) { + SortedSet odeps = result.get(o); + if (odeps == null) { + result.put(o, odeps = new TreeSet<>(locationComparator)); + } + odeps.add(t); + } + } + } + return result; + } + + private Comparator locationComparator = + new Comparator() { + public int compare(Location o1, Location o2) { + return o1.toString().compareTo(o2.toString()); + } + }; + private final SortedMap> map = + new TreeMap<>(locationComparator); + private final boolean reverse; + } + + private List getClassFileReaders(List filenames) throws IOException { + List readers = new ArrayList<>(); + for (String s : filenames) { + File f = new File(s); + if (f.exists()) { + readers.add(ClassFileReader.newInstance(f)); + } else { + System.err.println("WARNING: " + s + " not exist"); + } + } + return readers; + } + + private List classPathReaders(String classpath) throws IOException { + List result = new ArrayList<>(); + if (classpath == null || classpath.isEmpty()) { + return result; + } + for (String p : classpath.split(File.pathSeparator)) { + if (p.length() > 0) { + File f = new File(p); + if (f.exists()) { + result.add(ClassFileReader.newInstance(f)); + } + } + } + return result; + } + + private final List javaHomeReaders = new ArrayList<>(); + private void initJavaHomeReaders() throws IOException { + String javaHome = System.getProperty("java.home"); + List files = new ArrayList<>(); + File jre = new File(javaHome, "jre"); + File lib = new File(javaHome, "lib"); + + if (jre.exists() && jre.isDirectory()) { + javaHomeReaders.addAll(addJarFiles(new File(jre, "lib"))); + javaHomeReaders.addAll(addJarFiles(lib)); + } else if (lib.exists() && lib.isDirectory()) { + // either a JRE or a jdk build image + File classes = new File(javaHome, "classes"); + if (classes.exists() && classes.isDirectory()) { + // jdk build outputdir + javaHomeReaders.add(ClassFileReader.newInstance(classes)); + } + // add other JAR files + javaHomeReaders.addAll(addJarFiles(lib)); + } else { + throw new RuntimeException("\"" + javaHome + "\" not a JDK home"); + } + } + + private List addJarFiles(File f) throws IOException { + final List result = new ArrayList<>(); + Files.walkFileTree(f.toPath(), new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + throws IOException { + String fn = file.toFile().getName(); + if (fn.endsWith(".jar") && !fn.equals("alt-rt.jar")) { + result.add(ClassFileReader.newInstance(file.toFile())); + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException e) + throws IOException { + if (e == null) { + return FileVisitResult.CONTINUE; + } else { + // directory iteration failed + throw e; + } + } + }); + return result; + } +}