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@2139: import com.sun.tools.classfile.AccessFlags; 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@2139: import com.sun.tools.jdeps.PlatformClassPath.JDKArchive; mchung@1472: import java.io.*; mchung@2139: import java.nio.file.DirectoryStream; mchung@2139: import java.nio.file.Files; mchung@2139: import java.nio.file.Path; mchung@2139: import java.nio.file.Paths; 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@2139: if (a.equals(opt)) mchung@1472: return true; mchung@2139: if (hasArg && opt.startsWith(a + "=")) mchung@1472: return true; 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@2139: 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@2139: new Option(true, "-dotoutput") { mchung@2139: void process(JdepsTask task, String opt, String arg) throws BadArgs { mchung@2139: Path p = Paths.get(arg); mchung@2139: if (Files.exists(p) && (!Files.isDirectory(p) || !Files.isWritable(p))) { mchung@2139: throw new BadArgs("err.dot.output.path", arg); mchung@2139: } mchung@2139: task.options.dotOutputDir = arg; mchung@2139: } mchung@2139: }, mchung@2139: 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@2139: new Option(false, "-v", "-verbose", mchung@2139: "-verbose:package", mchung@2139: "-verbose:class") mchung@2139: { mchung@1472: void process(JdepsTask task, String opt, String arg) throws BadArgs { mchung@2139: switch (opt) { mchung@2139: case "-v": mchung@2139: case "-verbose": mchung@2139: task.options.verbose = Analyzer.Type.VERBOSE; mchung@2139: break; mchung@2139: case "-verbose:package": mchung@2139: task.options.verbose = Analyzer.Type.PACKAGE; mchung@2139: break; mchung@2139: case "-verbose:class": mchung@2139: task.options.verbose = Analyzer.Type.CLASS; mchung@2139: break; mchung@2139: default: mchung@2139: throw new BadArgs("err.invalid.arg.for.option", opt); mchung@1472: } mchung@1472: } mchung@1472: }, mchung@2139: new Option(true, "-cp", "-classpath") { mchung@1472: void process(JdepsTask task, String opt, String arg) { mchung@1472: task.options.classpath = arg; mchung@1472: } mchung@1472: }, mchung@2139: 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@2139: 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@2139: new Option(true, "-include") { mchung@2139: void process(JdepsTask task, String opt, String arg) throws BadArgs { mchung@2139: task.options.includePattern = Pattern.compile(arg); mchung@2139: } mchung@2139: }, mchung@2139: new Option(false, "-P", "-profile") { mchung@1638: void process(JdepsTask task, String opt, String arg) throws BadArgs { mchung@1472: task.options.showProfile = true; mchung@2139: if (Profile.getProfileCount() == 0) { jjg@1648: throw new BadArgs("err.option.unsupported", opt, getMessage("err.profiles.msg")); mchung@1638: } mchung@1472: } mchung@1472: }, mchung@2139: new Option(false, "-apionly") { mchung@2139: void process(JdepsTask task, String opt, String arg) { mchung@2139: task.options.apiOnly = true; mchung@2139: } mchung@2139: }, mchung@2139: 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@2139: new Option(false, "-version") { mchung@2139: void process(JdepsTask task, String opt, String arg) { mchung@2139: task.options.version = true; mchung@2139: } mchung@2139: }, mchung@2139: new HiddenOption(false, "-fullversion") { mchung@2139: void process(JdepsTask task, String opt, String arg) { mchung@2139: task.options.fullVersion = true; mchung@2139: } mchung@2139: }, mchung@2172: new HiddenOption(false, "-showlabel") { mchung@2172: void process(JdepsTask task, String opt, String arg) { mchung@2172: task.options.showLabel = true; mchung@2172: } mchung@2172: }, mchung@2139: new HiddenOption(true, "-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: }; 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@2139: if (classes.isEmpty() && options.includePattern == null) { 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@2139: 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@2139: if (options.dotOutputDir != null) { mchung@2139: Path dir = Paths.get(options.dotOutputDir); mchung@2139: Files.createDirectories(dir); mchung@2139: generateDotFiles(dir, analyzer); mchung@1577: } else { mchung@2139: printRawOutput(log, analyzer); mchung@1472: } mchung@1472: return true; mchung@1472: } mchung@1472: mchung@2139: private void generateDotFiles(Path dir, Analyzer analyzer) throws IOException { mchung@2139: Path summary = dir.resolve("summary.dot"); mchung@2172: boolean verbose = options.verbose == Analyzer.Type.VERBOSE; mchung@2172: DotGraph graph = verbose ? new DotSummaryForPackage() mchung@2172: : new DotSummaryForArchive(); mchung@2172: for (Archive archive : sourceLocations) { mchung@2172: analyzer.visitArchiveDependences(archive, graph); mchung@2172: if (verbose || options.showLabel) { mchung@2172: // traverse detailed dependences to generate package-level mchung@2172: // summary or build labels for edges mchung@2172: analyzer.visitDependences(archive, graph); mchung@2139: } mchung@2139: } mchung@2172: try (PrintWriter sw = new PrintWriter(Files.newOutputStream(summary))) { mchung@2172: graph.writeTo(sw); mchung@2172: } mchung@2172: // output individual .dot file for each archive mchung@2139: if (options.verbose != Analyzer.Type.SUMMARY) { mchung@2139: for (Archive archive : sourceLocations) { mchung@2139: if (analyzer.hasDependences(archive)) { mchung@2139: Path dotfile = dir.resolve(archive.getFileName() + ".dot"); mchung@2139: try (PrintWriter pw = new PrintWriter(Files.newOutputStream(dotfile)); mchung@2139: DotFileFormatter formatter = new DotFileFormatter(pw, archive)) { mchung@2139: analyzer.visitDependences(archive, formatter); mchung@2139: } mchung@2139: } mchung@2139: } mchung@2139: } mchung@2139: } mchung@2139: mchung@2139: private void printRawOutput(PrintWriter writer, Analyzer analyzer) { mchung@2139: for (Archive archive : sourceLocations) { mchung@2139: RawOutputFormatter formatter = new RawOutputFormatter(writer); mchung@2139: analyzer.visitArchiveDependences(archive, formatter); mchung@2139: if (options.verbose != Analyzer.Type.SUMMARY) { mchung@2139: analyzer.visitDependences(archive, formatter); mchung@2139: } mchung@2139: } mchung@2139: } 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@2139: private Dependency.Filter getDependencyFilter() { mchung@2139: if (options.regex != null) { mchung@2139: return Dependencies.getRegexFilter(Pattern.compile(options.regex)); mchung@1472: } else if (options.packageNames.size() > 0) { mchung@2139: return Dependencies.getPackageFilter(options.packageNames, false); mchung@1472: } else { mchung@2139: return new Dependency.Filter() { mchung@2139: @Override mchung@1472: public boolean accepts(Dependency dependency) { mchung@1472: return !dependency.getOrigin().equals(dependency.getTarget()); mchung@1472: } mchung@1472: }; mchung@1472: } mchung@2139: } mchung@1472: mchung@2139: private boolean matches(String classname, AccessFlags flags) { mchung@2139: if (options.apiOnly && !flags.is(AccessFlags.ACC_PUBLIC)) { mchung@2139: return false; mchung@2139: } else if (options.includePattern != null) { mchung@2139: return options.includePattern.matcher(classname.replace('/', '.')).matches(); mchung@2139: } else { mchung@2139: return true; mchung@2139: } mchung@2139: } mchung@2139: mchung@2139: private void findDependencies() throws IOException { mchung@2139: Dependency.Finder finder = mchung@2139: options.apiOnly ? Dependencies.getAPIFinder(AccessFlags.ACC_PROTECTED) mchung@2139: : Dependencies.getClassDependencyFinder(); mchung@2139: Dependency.Filter filter = getDependencyFilter(); mchung@2139: mchung@2139: List archives = new ArrayList<>(); mchung@2139: Deque roots = new LinkedList<>(); mchung@1472: for (String s : classes) { mchung@2139: Path p = Paths.get(s); mchung@2139: if (Files.exists(p)) { mchung@2139: archives.add(new Archive(p, ClassFileReader.newInstance(p))); 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@2172: sourceLocations.addAll(archives); mchung@1472: mchung@2139: List classpaths = new ArrayList<>(); // for class file lookup mchung@2172: classpaths.addAll(getClassPathArchives(options.classpath)); mchung@2139: if (options.includePattern != null) { mchung@2172: archives.addAll(classpaths); mchung@1472: } mchung@1472: classpaths.addAll(PlatformClassPath.getArchives()); mchung@1472: mchung@2172: // add all classpath archives to the source locations for reporting 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@2139: Deque deque = new LinkedList<>(); mchung@2139: 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@2139: if (matches(classFileName, cf.access_flags)) { mchung@2139: if (!doneClasses.contains(classFileName)) { mchung@2139: doneClasses.add(classFileName); mchung@2139: } mchung@2139: for (Dependency d : finder.findDependencies(cf)) { mchung@2139: if (filter.accepts(d)) { mchung@2139: String cn = d.getTarget().getName(); mchung@2139: if (!doneClasses.contains(cn) && !deque.contains(cn)) { mchung@2139: deque.add(cn); mchung@2139: } mchung@2139: a.addClass(d.getOrigin(), d.getTarget()); mchung@1472: } 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@2139: deque = new LinkedList<>(); mchung@1472: } while (!unresolved.isEmpty() && depth-- > 0); 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@2139: 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@2139: classes.add(name); 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@2139: boolean apiOnly; mchung@2172: boolean showLabel; mchung@2139: String dotOutputDir; mchung@1472: String classpath = ""; mchung@1472: int depth = 1; mchung@1577: Analyzer.Type verbose = Analyzer.Type.PACKAGE; mchung@2139: Set packageNames = new HashSet<>(); mchung@2139: String regex; // apply to the dependences mchung@2139: Pattern includePattern; // apply to classes 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@2139: Path p = Paths.get(s); mchung@2139: if (Files.exists(p)) { mchung@2139: result.add(new Archive(p, ClassFileReader.newInstance(p))); 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@2139: 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@2139: List files = new ArrayList<>(); mchung@2139: // wildcard to parse all JAR files e.g. -classpath dir/* mchung@2139: int i = p.lastIndexOf(".*"); mchung@2139: if (i > 0) { mchung@2139: Path dir = Paths.get(p.substring(0, i)); mchung@2139: try (DirectoryStream stream = Files.newDirectoryStream(dir, "*.jar")) { mchung@2139: for (Path entry : stream) { mchung@2139: files.add(entry); mchung@2139: } mchung@2139: } mchung@2139: } else { mchung@2139: files.add(Paths.get(p)); mchung@2139: } mchung@2139: for (Path f : files) { mchung@2139: if (Files.exists(f)) { mchung@2139: result.add(new Archive(f, ClassFileReader.newInstance(f))); mchung@2139: } mchung@1472: } mchung@1472: } mchung@1472: } mchung@1472: return result; mchung@1472: } mchung@2139: mchung@2172: /** mchung@2172: * If the given archive is JDK archive and non-null Profile, mchung@2172: * this method returns the profile name only if -profile option is specified; mchung@2172: * a null profile indicates it accesses a private JDK API and this method mchung@2172: * will return "JDK internal API". mchung@2172: * mchung@2172: * For non-JDK archives, this method returns the file name of the archive. mchung@2172: */ mchung@2172: private String getProfileArchiveInfo(Archive source, Profile profile) { mchung@2172: if (options.showProfile && profile != null) mchung@2172: return profile.toString(); mchung@2172: mchung@2172: if (source instanceof JDKArchive) { mchung@2172: return profile == null ? "JDK internal API (" + source.getFileName() + ")" : ""; mchung@2172: } mchung@2172: return source.getFileName(); mchung@2172: } mchung@2139: mchung@2139: /** mchung@2172: * Returns the profile name or "JDK internal API" for JDK archive; mchung@2172: * otherwise empty string. mchung@2139: */ mchung@2172: private String profileName(Archive archive, Profile profile) { mchung@2172: if (archive instanceof JDKArchive) { mchung@2172: return Objects.toString(profile, "JDK internal API"); mchung@2172: } else { mchung@2172: return ""; mchung@2172: } mchung@2139: } mchung@2139: mchung@2139: class RawOutputFormatter implements Analyzer.Visitor { mchung@2139: private final PrintWriter writer; mchung@2139: RawOutputFormatter(PrintWriter writer) { mchung@2139: this.writer = writer; mchung@2139: } mchung@2139: mchung@2139: private String pkg = ""; mchung@2139: @Override mchung@2139: public void visitDependence(String origin, Archive source, mchung@2172: String target, Archive archive, Profile profile) { mchung@2139: if (!origin.equals(pkg)) { mchung@2139: pkg = origin; mchung@2139: writer.format(" %s (%s)%n", origin, source.getFileName()); mchung@2139: } mchung@2172: writer.format(" -> %-50s %s%n", target, getProfileArchiveInfo(archive, profile)); mchung@2139: } mchung@2139: mchung@2139: @Override mchung@2172: public void visitArchiveDependence(Archive origin, Archive target, Profile profile) { mchung@2172: writer.format("%s -> %s", origin.getPathName(), target.getPathName()); mchung@2172: if (options.showProfile && profile != null) { mchung@2139: writer.format(" (%s)%n", profile); mchung@2139: } else { mchung@2139: writer.format("%n"); mchung@2139: } mchung@2139: } mchung@2139: } mchung@2139: mchung@2172: class DotFileFormatter extends DotGraph implements AutoCloseable { mchung@2139: private final PrintWriter writer; mchung@2139: private final String name; mchung@2139: DotFileFormatter(PrintWriter writer, Archive archive) { mchung@2139: this.writer = writer; mchung@2139: this.name = archive.getFileName(); mchung@2139: writer.format("digraph \"%s\" {%n", name); mchung@2172: writer.format(" // Path: %s%n", archive.getPathName()); mchung@2139: } mchung@2139: mchung@2139: @Override mchung@2139: public void close() { mchung@2139: writer.println("}"); mchung@2139: } mchung@2139: mchung@2139: @Override mchung@2139: public void visitDependence(String origin, Archive source, mchung@2172: String target, Archive archive, Profile profile) { mchung@2139: // if -P option is specified, package name -> profile will mchung@2139: // be shown and filter out multiple same edges. mchung@2172: String name = getProfileArchiveInfo(archive, profile); mchung@2172: writeEdge(writer, new Edge(origin, target, getProfileArchiveInfo(archive, profile))); mchung@2172: } mchung@2172: @Override mchung@2172: public void visitArchiveDependence(Archive origin, Archive target, Profile profile) { mchung@2172: throw new UnsupportedOperationException(); mchung@2172: } mchung@2172: } mchung@2172: mchung@2172: class DotSummaryForArchive extends DotGraph { mchung@2172: @Override mchung@2172: public void visitDependence(String origin, Archive source, mchung@2172: String target, Archive archive, Profile profile) { mchung@2172: Edge e = findEdge(source, archive); mchung@2172: assert e != null; mchung@2172: // add the dependency to the label if enabled and not compact1 mchung@2172: if (profile == Profile.COMPACT1) { mchung@2172: return; mchung@2172: } mchung@2172: e.addLabel(origin, target, profileName(archive, profile)); mchung@2172: } mchung@2172: @Override mchung@2172: public void visitArchiveDependence(Archive origin, Archive target, Profile profile) { mchung@2172: // add an edge with the archive's name with no tag mchung@2172: // so that there is only one node for each JDK archive mchung@2172: // while there may be edges to different profiles mchung@2172: Edge e = addEdge(origin, target, ""); mchung@2172: if (target instanceof JDKArchive) { mchung@2172: // add a label to print the profile mchung@2172: if (profile == null) { mchung@2172: e.addLabel("JDK internal API"); mchung@2172: } else if (options.showProfile && !options.showLabel) { mchung@2172: e.addLabel(profile.toString()); mchung@2172: } mchung@2139: } mchung@2139: } mchung@2172: } mchung@2139: mchung@2172: // DotSummaryForPackage generates the summary.dot file for verbose mode mchung@2172: // (-v or -verbose option) that includes all class dependencies. mchung@2172: // The summary.dot file shows package-level dependencies. mchung@2172: class DotSummaryForPackage extends DotGraph { mchung@2172: private String packageOf(String cn) { mchung@2172: int i = cn.lastIndexOf('.'); mchung@2172: return i > 0 ? cn.substring(0, i) : ""; mchung@2172: } mchung@2139: @Override mchung@2172: public void visitDependence(String origin, Archive source, mchung@2172: String target, Archive archive, Profile profile) { mchung@2172: // add a package dependency edge mchung@2172: String from = packageOf(origin); mchung@2172: String to = packageOf(target); mchung@2172: Edge e = addEdge(from, to, getProfileArchiveInfo(archive, profile)); mchung@2172: mchung@2172: // add the dependency to the label if enabled and not compact1 mchung@2172: if (!options.showLabel || profile == Profile.COMPACT1) { mchung@2172: return; mchung@2172: } mchung@2172: mchung@2172: // trim the package name of origin to shorten the label mchung@2172: int i = origin.lastIndexOf('.'); mchung@2172: String n1 = i < 0 ? origin : origin.substring(i+1); mchung@2172: e.addLabel(n1, target, profileName(archive, profile)); mchung@2172: } mchung@2172: @Override mchung@2172: public void visitArchiveDependence(Archive origin, Archive target, Profile profile) { mchung@2172: // nop mchung@2172: } mchung@2172: } mchung@2172: abstract class DotGraph implements Analyzer.Visitor { mchung@2172: private final Set edges = new LinkedHashSet<>(); mchung@2172: private Edge curEdge; mchung@2172: public void writeTo(PrintWriter writer) { mchung@2172: writer.format("digraph \"summary\" {%n"); mchung@2172: for (Edge e: edges) { mchung@2172: writeEdge(writer, e); mchung@2172: } mchung@2172: writer.println("}"); mchung@2172: } mchung@2172: mchung@2172: void writeEdge(PrintWriter writer, Edge e) { mchung@2172: writer.format(" %-50s -> \"%s\"%s;%n", mchung@2172: String.format("\"%s\"", e.from.toString()), mchung@2172: e.tag.isEmpty() ? e.to mchung@2172: : String.format("%s (%s)", e.to, e.tag), mchung@2172: getLabel(e)); mchung@2172: } mchung@2172: mchung@2172: Edge addEdge(T origin, T target, String tag) { mchung@2172: Edge e = new Edge(origin, target, tag); mchung@2172: if (e.equals(curEdge)) { mchung@2172: return curEdge; mchung@2172: } mchung@2172: mchung@2172: if (edges.contains(e)) { mchung@2172: for (Edge e1 : edges) { mchung@2172: if (e.equals(e1)) { mchung@2172: curEdge = e1; mchung@2172: } mchung@2172: } mchung@2172: } else { mchung@2172: edges.add(e); mchung@2172: curEdge = e; mchung@2172: } mchung@2172: return curEdge; mchung@2172: } mchung@2172: mchung@2172: Edge findEdge(T origin, T target) { mchung@2172: for (Edge e : edges) { mchung@2172: if (e.from.equals(origin) && e.to.equals(target)) { mchung@2172: return e; mchung@2172: } mchung@2172: } mchung@2172: return null; mchung@2172: } mchung@2172: mchung@2172: String getLabel(Edge e) { mchung@2172: String label = e.label.toString(); mchung@2172: return label.isEmpty() ? "" : String.format("[label=\"%s\",fontsize=9]", label); mchung@2172: } mchung@2172: mchung@2172: class Edge { mchung@2172: final T from; mchung@2172: final T to; mchung@2172: final String tag; // optional tag mchung@2172: final StringBuilder label = new StringBuilder(); mchung@2172: Edge(T from, T to, String tag) { mchung@2172: this.from = from; mchung@2172: this.to = to; mchung@2172: this.tag = tag; mchung@2172: } mchung@2172: void addLabel(String s) { mchung@2172: label.append(s).append("\\n"); mchung@2172: } mchung@2172: void addLabel(String origin, String target, String profile) { mchung@2172: label.append(origin).append(" -> ").append(target); mchung@2172: if (!profile.isEmpty()) { mchung@2172: label.append(" (" + profile + ")"); mchung@2172: } mchung@2172: label.append("\\n"); mchung@2172: } mchung@2172: @Override @SuppressWarnings("unchecked") mchung@2172: public boolean equals(Object o) { mchung@2172: if (o instanceof DotGraph.Edge) { mchung@2172: DotGraph.Edge e = (DotGraph.Edge)o; mchung@2172: return this.from.equals(e.from) && mchung@2172: this.to.equals(e.to) && mchung@2172: this.tag.equals(e.tag); mchung@2172: } mchung@2172: return false; mchung@2172: } mchung@2172: @Override mchung@2172: public int hashCode() { mchung@2172: int hash = 7; mchung@2172: hash = 67 * hash + Objects.hashCode(this.from) + mchung@2172: Objects.hashCode(this.to) + Objects.hashCode(this.tag); mchung@2172: return hash; mchung@2172: } mchung@2139: } mchung@2139: } mchung@1472: }