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