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@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@2139: try (PrintWriter sw = new PrintWriter(Files.newOutputStream(summary)); mchung@2139: DotFileFormatter formatter = new DotFileFormatter(sw, "summary")) { mchung@2139: for (Archive archive : sourceLocations) { mchung@2139: analyzer.visitArchiveDependences(archive, formatter); mchung@2139: } mchung@2139: } 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@1472: mchung@2139: List classpaths = new ArrayList<>(); // for class file lookup mchung@2139: if (options.includePattern != null) { 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@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@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@2139: mchung@2139: /** mchung@2139: * Returns the file name of the archive for non-JRE class or mchung@2139: * internal JRE classes. It returns empty string for SE API. mchung@2139: */ mchung@2139: private static String getArchiveName(Archive source, String profile) { mchung@2139: String name = source.getFileName(); mchung@2139: if (source instanceof JDKArchive) mchung@2139: return profile.isEmpty() ? "JDK internal API (" + name + ")" : ""; mchung@2139: return name; 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@2139: String target, Archive archive, String profile) { mchung@2139: if (!origin.equals(pkg)) { mchung@2139: pkg = origin; mchung@2139: writer.format(" %s (%s)%n", origin, source.getFileName()); mchung@2139: } mchung@2139: String name = (options.showProfile && !profile.isEmpty()) mchung@2139: ? profile mchung@2139: : getArchiveName(archive, profile); mchung@2139: writer.format(" -> %-50s %s%n", target, name); mchung@2139: } mchung@2139: mchung@2139: @Override mchung@2139: public void visitArchiveDependence(Archive origin, Archive target, String profile) { mchung@2139: writer.format("%s -> %s", origin, target); mchung@2139: if (options.showProfile && !profile.isEmpty()) { mchung@2139: writer.format(" (%s)%n", profile); mchung@2139: } else { mchung@2139: writer.format("%n"); mchung@2139: } mchung@2139: } mchung@2139: } mchung@2139: mchung@2139: class DotFileFormatter implements Analyzer.Visitor, AutoCloseable { mchung@2139: private final PrintWriter writer; mchung@2139: private final String name; mchung@2139: DotFileFormatter(PrintWriter writer, String name) { mchung@2139: this.writer = writer; mchung@2139: this.name = name; mchung@2139: writer.format("digraph \"%s\" {%n", name); mchung@2139: } 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@2139: writer.format(" // Path: %s%n", archive.toString()); mchung@2139: } mchung@2139: mchung@2139: @Override mchung@2139: public void close() { mchung@2139: writer.println("}"); mchung@2139: } mchung@2139: mchung@2139: private final Set edges = new HashSet<>(); mchung@2139: private String node = ""; mchung@2139: @Override mchung@2139: public void visitDependence(String origin, Archive source, mchung@2139: String target, Archive archive, String profile) { mchung@2139: if (!node.equals(origin)) { mchung@2139: edges.clear(); mchung@2139: node = origin; mchung@2139: } mchung@2139: // if -P option is specified, package name -> profile will mchung@2139: // be shown and filter out multiple same edges. mchung@2139: if (!edges.contains(target)) { mchung@2139: StringBuilder sb = new StringBuilder(); mchung@2139: String name = options.showProfile && !profile.isEmpty() mchung@2139: ? profile mchung@2139: : getArchiveName(archive, profile); mchung@2139: writer.format(" %-50s -> %s;%n", mchung@2139: String.format("\"%s\"", origin), mchung@2139: name.isEmpty() ? String.format("\"%s\"", target) mchung@2139: : String.format("\"%s (%s)\"", target, name)); mchung@2139: edges.add(target); mchung@2139: } mchung@2139: } mchung@2139: mchung@2139: @Override mchung@2139: public void visitArchiveDependence(Archive origin, Archive target, String profile) { mchung@2139: String name = options.showProfile && !profile.isEmpty() mchung@2139: ? profile : ""; mchung@2139: writer.format(" %-30s -> \"%s\";%n", mchung@2139: String.format("\"%s\"", origin.getFileName()), mchung@2139: name.isEmpty() mchung@2139: ? target.getFileName() mchung@2139: : String.format("%s (%s)", target.getFileName(), name)); mchung@2139: } mchung@2139: } mchung@1472: }