mchung@1472: /* mchung@2538: * Copyright (c) 2012, 2014, 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@2538: import com.sun.tools.classfile.Dependency.Location; mchung@2139: import com.sun.tools.jdeps.PlatformClassPath.JDKArchive; mchung@2538: import static com.sun.tools.jdeps.Analyzer.Type.*; 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@2538: throw new BadArgs("err.invalid.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@2538: task.options.verbose = SUMMARY; mchung@1472: } mchung@1472: }, mchung@2139: new Option(false, "-v", "-verbose", mchung@2139: "-verbose:package", mchung@2538: "-verbose:class") { 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@2538: task.options.verbose = VERBOSE; mchung@2538: task.options.filterSameArchive = false; mchung@2538: task.options.filterSamePackage = false; mchung@2139: break; mchung@2139: case "-verbose:package": mchung@2538: task.options.verbose = PACKAGE; mchung@2538: break; mchung@2139: case "-verbose:class": mchung@2538: task.options.verbose = CLASS; mchung@2538: 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@2538: mchung@2538: new Option(true, "-f", "-filter") { mchung@2538: void process(JdepsTask task, String opt, String arg) { mchung@2538: task.options.filterRegex = arg; mchung@2538: } mchung@2538: }, mchung@2538: new Option(false, "-filter:package", mchung@2538: "-filter:archive", mchung@2538: "-filter:none") { mchung@2538: void process(JdepsTask task, String opt, String arg) { mchung@2538: switch (opt) { mchung@2538: case "-filter:package": mchung@2538: task.options.filterSamePackage = true; mchung@2538: task.options.filterSameArchive = false; mchung@2538: break; mchung@2538: case "-filter:archive": mchung@2538: task.options.filterSameArchive = true; mchung@2538: task.options.filterSamePackage = false; mchung@2538: break; mchung@2538: case "-filter:none": mchung@2538: task.options.filterSameArchive = false; mchung@2538: task.options.filterSamePackage = false; mchung@2538: break; mchung@2538: } mchung@2538: } mchung@2538: }, 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@2538: // turn off filtering mchung@2538: task.options.filterSameArchive = false; mchung@2538: task.options.filterSamePackage = false; mchung@1472: } mchung@1472: }, mchung@2214: new Option(false, "-jdkinternals") { mchung@2214: void process(JdepsTask task, String opt, String arg) { mchung@2214: task.options.findJDKInternals = true; mchung@2538: task.options.verbose = CLASS; mchung@2214: if (task.options.includePattern == null) { mchung@2214: task.options.includePattern = Pattern.compile(".*"); mchung@2214: } mchung@2214: } mchung@2214: }, 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@2539: new HiddenOption(false, "-q", "-quiet") { mchung@2539: void process(JdepsTask task, String opt, String arg) { mchung@2539: task.options.nowarning = true; mchung@2539: } mchung@2539: }, 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@2539: 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@2214: if (options.findJDKInternals && mchung@2214: (options.regex != null || options.packageNames.size() > 0 || options.showSummary)) { mchung@2214: showHelp(); mchung@2214: return EXIT_CMDERR; mchung@2214: } mchung@2538: if (options.showSummary && options.verbose != 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@2538: // parse classfiles and find all dependencies mchung@1472: findDependencies(); mchung@2538: mchung@2538: Analyzer analyzer = new Analyzer(options.verbose, new Analyzer.Filter() { mchung@2538: @Override mchung@2539: public boolean accepts(Location origin, Archive originArchive, mchung@2539: Location target, Archive targetArchive) mchung@2539: { mchung@2538: if (options.findJDKInternals) { mchung@2538: // accepts target that is JDK class but not exported mchung@2538: return isJDKArchive(targetArchive) && mchung@2538: !((JDKArchive) targetArchive).isExported(target.getClassName()); mchung@2538: } else if (options.filterSameArchive) { mchung@2538: // accepts origin and target that from different archive mchung@2538: return originArchive != targetArchive; mchung@2538: } mchung@2538: return true; mchung@2538: } mchung@2538: }); mchung@2538: mchung@2538: // analyze the dependencies mchung@1577: analyzer.run(sourceLocations); mchung@2538: mchung@2538: // output result 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@2539: mchung@2539: if (options.findJDKInternals && !options.nowarning) { mchung@2539: showReplacements(analyzer); mchung@2539: } mchung@1472: return true; mchung@1472: } mchung@1472: mchung@2538: private void generateSummaryDotFile(Path dir, Analyzer analyzer) throws IOException { mchung@2538: // If verbose mode (-v or -verbose option), mchung@2538: // the summary.dot file shows package-level dependencies. mchung@2538: Analyzer.Type summaryType = mchung@2538: (options.verbose == PACKAGE || options.verbose == SUMMARY) ? SUMMARY : PACKAGE; mchung@2139: Path summary = dir.resolve("summary.dot"); mchung@2538: try (PrintWriter sw = new PrintWriter(Files.newOutputStream(summary)); mchung@2538: SummaryDotFile dotfile = new SummaryDotFile(sw, summaryType)) { mchung@2538: for (Archive archive : sourceLocations) { mchung@2538: if (!archive.isEmpty()) { mchung@2538: if (options.verbose == PACKAGE || options.verbose == SUMMARY) { mchung@2538: if (options.showLabel) { mchung@2538: // build labels listing package-level dependencies mchung@2538: analyzer.visitDependences(archive, dotfile.labelBuilder(), PACKAGE); mchung@2538: } mchung@2538: } mchung@2538: analyzer.visitDependences(archive, dotfile, summaryType); mchung@2538: } mchung@2139: } mchung@2139: } mchung@2538: } mchung@2538: mchung@2538: private void generateDotFiles(Path dir, Analyzer analyzer) throws IOException { mchung@2172: // output individual .dot file for each archive mchung@2538: if (options.verbose != SUMMARY) { mchung@2139: for (Archive archive : sourceLocations) { mchung@2139: if (analyzer.hasDependences(archive)) { mchung@2538: Path dotfile = dir.resolve(archive.getName() + ".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@2538: // generate summary dot file mchung@2538: generateSummaryDotFile(dir, analyzer); mchung@2139: } mchung@2139: mchung@2139: private void printRawOutput(PrintWriter writer, Analyzer analyzer) { mchung@2538: RawOutputFormatter depFormatter = new RawOutputFormatter(writer); mchung@2538: RawSummaryFormatter summaryFormatter = new RawSummaryFormatter(writer); mchung@2139: for (Archive archive : sourceLocations) { mchung@2538: if (!archive.isEmpty()) { mchung@2538: analyzer.visitDependences(archive, summaryFormatter, SUMMARY); mchung@2538: if (analyzer.hasDependences(archive) && options.verbose != SUMMARY) { mchung@2538: analyzer.visitDependences(archive, depFormatter); mchung@2538: } mchung@2139: } mchung@2139: } mchung@2139: } mchung@2538: 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@2538: /* mchung@2538: * Dep Filter configured based on the input jdeps option mchung@2538: * 1. -p and -regex to match target dependencies mchung@2538: * 2. -filter:package to filter out same-package dependencies mchung@2538: * mchung@2538: * This filter is applied when jdeps parses the class files mchung@2538: * and filtered dependencies are not stored in the Analyzer. mchung@2538: * mchung@2538: * -filter:archive is applied later in the Analyzer as the mchung@2538: * containing archive of a target class may not be known until mchung@2538: * the entire archive mchung@2538: */ mchung@2538: class DependencyFilter implements Dependency.Filter { mchung@2538: final Dependency.Filter filter; mchung@2538: final Pattern filterPattern; mchung@2538: DependencyFilter() { mchung@2538: if (options.regex != null) { mchung@2538: this.filter = Dependencies.getRegexFilter(Pattern.compile(options.regex)); mchung@2538: } else if (options.packageNames.size() > 0) { mchung@2538: this.filter = Dependencies.getPackageFilter(options.packageNames, false); mchung@2538: } else { mchung@2538: this.filter = null; mchung@2538: } mchung@2538: mchung@2538: this.filterPattern = mchung@2538: options.filterRegex != null ? Pattern.compile(options.filterRegex) : null; mchung@2538: } mchung@2538: @Override mchung@2538: public boolean accepts(Dependency d) { mchung@2538: if (d.getOrigin().equals(d.getTarget())) { mchung@2538: return false; mchung@2538: } mchung@2538: String pn = d.getTarget().getPackageName(); mchung@2538: if (options.filterSamePackage && d.getOrigin().getPackageName().equals(pn)) { mchung@2538: return false; mchung@2538: } mchung@2538: mchung@2538: if (filterPattern != null && filterPattern.matcher(pn).matches()) { mchung@2538: return false; mchung@2538: } mchung@2538: return filter != null ? filter.accepts(d) : true; mchung@1472: } mchung@2139: } mchung@1472: mchung@2538: /** mchung@2538: * Tests if the given class matches the pattern given in the -include option mchung@2538: * or if it's a public class if -apionly option is specified mchung@2538: */ 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@2538: Dependency.Filter filter = new DependencyFilter(); mchung@2139: mchung@2139: List archives = new ArrayList<>(); mchung@2139: Deque roots = new LinkedList<>(); mchung@2802: List paths = new ArrayList<>(); mchung@1472: for (String s : classes) { mchung@2139: Path p = Paths.get(s); mchung@2139: if (Files.exists(p)) { mchung@2802: paths.add(p); mchung@2538: archives.add(Archive.getInstance(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@2802: classpaths.addAll(getClassPathArchives(options.classpath, paths)); 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@2538: // tests if this class matches the -include or -apiOnly option if specified mchung@2538: if (!matches(classFileName, cf.access_flags)) { mchung@2538: continue; mchung@2538: } mchung@2538: mchung@2538: if (!doneClasses.contains(classFileName)) { mchung@2538: doneClasses.add(classFileName); mchung@2538: } mchung@2538: mchung@2538: for (Dependency d : finder.findDependencies(cf)) { mchung@2538: if (filter.accepts(d)) { mchung@2538: String cn = d.getTarget().getName(); mchung@2538: if (!doneClasses.contains(cn) && !deque.contains(cn)) { mchung@2538: deque.add(cn); mchung@2538: } mchung@2538: a.addClass(d.getOrigin(), d.getTarget()); mchung@2802: } else { mchung@2802: // ensure that the parsed class is added the archive mchung@2802: a.addClass(d.getOrigin()); mchung@2139: } mchung@2538: } mchung@2538: for (String name : a.reader().skippedEntries()) { mchung@2538: warning("warn.skipped.entry", name, a.getPathName()); 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@2538: // process @jdk.Exported for JDK classes mchung@2538: if (isJDKArchive(a)) { mchung@2538: ((JDKArchive)a).processJdkExported(cf); mchung@2538: } 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@2802: } else { mchung@2802: // ensure that the parsed class is added the archive mchung@2802: a.addClass(d.getOrigin()); 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@2538: if (o.isHidden() || name.equals("h") || name.startsWith("filter:")) { 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@2139: boolean apiOnly; mchung@2172: boolean showLabel; mchung@2214: boolean findJDKInternals; mchung@2539: boolean nowarning; mchung@2538: // default is to show package-level dependencies mchung@2538: // and filter references from same package mchung@2538: Analyzer.Type verbose = PACKAGE; mchung@2538: boolean filterSamePackage = true; mchung@2538: boolean filterSameArchive = false; mchung@2538: String filterRegex; mchung@2139: String dotOutputDir; mchung@1472: String classpath = ""; mchung@1472: int depth = 1; 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@2539: static final ResourceBundle jdkinternals; 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@2539: try { mchung@2539: jdkinternals = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.jdkinternals"); mchung@2539: } catch (MissingResourceException e) { mchung@2539: throw new InternalError("Cannot find jdkinternals resource bundle"); mchung@2539: } mchung@1472: } mchung@1472: } mchung@1472: mchung@2802: /* mchung@2802: * Returns the list of Archive specified in cpaths and not included mchung@2802: * initialArchives mchung@2802: */ mchung@2802: private List getClassPathArchives(String cpaths, List initialArchives) mchung@2802: throws IOException mchung@2802: { mchung@2139: List result = new ArrayList<>(); mchung@2802: if (cpaths.isEmpty()) { mchung@1472: return result; mchung@1472: } mchung@2802: mchung@2802: List paths = new ArrayList<>(); mchung@2802: for (String p : cpaths.split(File.pathSeparator)) { mchung@1472: if (p.length() > 0) { 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@2802: paths.add(entry); mchung@2139: } mchung@2139: } mchung@2139: } else { mchung@2802: paths.add(Paths.get(p)); mchung@1472: } mchung@1472: } mchung@1472: } mchung@2802: for (Path p : paths) { mchung@2802: if (Files.exists(p) && !hasSameFile(initialArchives, p)) { mchung@2802: result.add(Archive.getInstance(p)); mchung@2802: } mchung@2802: } mchung@1472: return result; mchung@1472: } mchung@2139: mchung@2802: private boolean hasSameFile(List paths, Path p2) throws IOException { mchung@2802: for (Path p1 : paths) { mchung@2802: if (Files.isSameFile(p1, p2)) { mchung@2802: return true; mchung@2802: } mchung@2802: } mchung@2802: return false; mchung@2802: } mchung@2802: mchung@2139: class RawOutputFormatter implements Analyzer.Visitor { mchung@2139: private final PrintWriter writer; mchung@2538: private String pkg = ""; mchung@2139: RawOutputFormatter(PrintWriter writer) { mchung@2139: this.writer = writer; mchung@2139: } mchung@2139: @Override mchung@2538: public void visitDependence(String origin, Archive originArchive, mchung@2538: String target, Archive targetArchive) { mchung@2538: String tag = toTag(target, targetArchive); mchung@2538: if (options.verbose == VERBOSE) { mchung@2538: writer.format(" %-50s -> %-50s %s%n", origin, target, tag); mchung@2214: } else { mchung@2214: if (!origin.equals(pkg)) { mchung@2214: pkg = origin; mchung@2538: writer.format(" %s (%s)%n", origin, originArchive.getName()); mchung@2214: } mchung@2538: writer.format(" -> %-50s %s%n", target, tag); mchung@2139: } mchung@2139: } mchung@2139: } mchung@2139: mchung@2538: class RawSummaryFormatter implements Analyzer.Visitor { mchung@2538: private final PrintWriter writer; mchung@2538: RawSummaryFormatter(PrintWriter writer) { mchung@2538: this.writer = writer; mchung@2538: } mchung@2538: @Override mchung@2538: public void visitDependence(String origin, Archive originArchive, mchung@2538: String target, Archive targetArchive) { mchung@2538: writer.format("%s -> %s", originArchive.getName(), targetArchive.getPathName()); mchung@2538: if (options.showProfile && JDKArchive.isProfileArchive(targetArchive)) { mchung@2538: writer.format(" (%s)", target); mchung@2538: } mchung@2538: writer.format("%n"); mchung@2538: } mchung@2538: } mchung@2538: mchung@2538: class DotFileFormatter implements Analyzer.Visitor, 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@2538: this.name = archive.getName(); 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@2538: public void visitDependence(String origin, Archive originArchive, mchung@2538: String target, Archive targetArchive) { mchung@2538: String tag = toTag(target, targetArchive); mchung@2538: writer.format(" %-50s -> \"%s\";%n", mchung@2538: String.format("\"%s\"", origin), mchung@2538: tag.isEmpty() ? target mchung@2538: : String.format("%s (%s)", target, tag)); mchung@2172: } mchung@2172: } mchung@2172: mchung@2538: class SummaryDotFile implements Analyzer.Visitor, AutoCloseable { mchung@2538: private final PrintWriter writer; mchung@2538: private final Analyzer.Type type; mchung@2538: private final Map> edges = new HashMap<>(); mchung@2538: SummaryDotFile(PrintWriter writer, Analyzer.Type type) { mchung@2538: this.writer = writer; mchung@2538: this.type = type; mchung@2538: writer.format("digraph \"summary\" {%n"); mchung@2538: } mchung@2538: mchung@2172: @Override mchung@2538: public void close() { mchung@2538: writer.println("}"); mchung@2538: } mchung@2538: mchung@2538: @Override mchung@2538: public void visitDependence(String origin, Archive originArchive, mchung@2538: String target, Archive targetArchive) { mchung@2538: String targetName = type == PACKAGE ? target : targetArchive.getName(); mchung@2538: if (type == PACKAGE) { mchung@2538: String tag = toTag(target, targetArchive, type); mchung@2538: if (!tag.isEmpty()) mchung@2538: targetName += " (" + tag + ")"; mchung@2538: } else if (options.showProfile && JDKArchive.isProfileArchive(targetArchive)) { mchung@2538: targetName += " (" + target + ")"; mchung@2172: } mchung@2538: String label = getLabel(originArchive, targetArchive); mchung@2538: writer.format(" %-50s -> \"%s\"%s;%n", mchung@2538: String.format("\"%s\"", origin), targetName, label); mchung@2172: } mchung@2538: mchung@2538: String getLabel(Archive origin, Archive target) { mchung@2538: if (edges.isEmpty()) mchung@2538: return ""; mchung@2538: mchung@2538: StringBuilder label = edges.get(origin).get(target); mchung@2538: return label == null ? "" : String.format(" [label=\"%s\",fontsize=9]", label.toString()); mchung@2538: } mchung@2538: mchung@2538: Analyzer.Visitor labelBuilder() { mchung@2538: // show the package-level dependencies as labels in the dot graph mchung@2538: return new Analyzer.Visitor() { mchung@2538: @Override mchung@2538: public void visitDependence(String origin, Archive originArchive, mchung@2538: String target, Archive targetArchive) mchung@2538: { mchung@2538: Map labels = edges.get(originArchive); mchung@2538: if (!edges.containsKey(originArchive)) { mchung@2538: edges.put(originArchive, labels = new HashMap<>()); mchung@2538: } mchung@2538: StringBuilder sb = labels.get(targetArchive); mchung@2538: if (sb == null) { mchung@2538: labels.put(targetArchive, sb = new StringBuilder()); mchung@2538: } mchung@2538: String tag = toTag(target, targetArchive, PACKAGE); mchung@2538: addLabel(sb, origin, target, tag); mchung@2172: } mchung@2538: mchung@2538: void addLabel(StringBuilder label, String origin, String target, String tag) { mchung@2538: label.append(origin).append(" -> ").append(target); mchung@2538: if (!tag.isEmpty()) { mchung@2538: label.append(" (" + tag + ")"); mchung@2538: } mchung@2538: label.append("\\n"); mchung@2538: } mchung@2538: }; mchung@2139: } mchung@2172: } mchung@2139: mchung@2538: /** mchung@2538: * Test if the given archive is part of the JDK mchung@2538: */ mchung@2538: private boolean isJDKArchive(Archive archive) { mchung@2538: return JDKArchive.class.isInstance(archive); mchung@2538: } mchung@2538: mchung@2538: /** mchung@2538: * If the given archive is JDK archive, this method returns the profile name mchung@2538: * only if -profile option is specified; it accesses a private JDK API and mchung@2538: * the returned value will have "JDK internal API" prefix mchung@2538: * mchung@2538: * For non-JDK archives, this method returns the file name of the archive. mchung@2538: */ mchung@2538: private String toTag(String name, Archive source, Analyzer.Type type) { mchung@2538: if (!isJDKArchive(source)) { mchung@2538: return source.getName(); mchung@2172: } mchung@2172: mchung@2538: JDKArchive jdk = (JDKArchive)source; mchung@2538: boolean isExported = false; mchung@2538: if (type == CLASS || type == VERBOSE) { mchung@2538: isExported = jdk.isExported(name); mchung@2538: } else { mchung@2538: isExported = jdk.isExportedPackage(name); mchung@2172: } mchung@2538: Profile p = getProfile(name, type); mchung@2538: if (isExported) { mchung@2538: // exported API mchung@2538: return options.showProfile && p != null ? p.profileName() : ""; mchung@2538: } else { mchung@2538: return "JDK internal API (" + source.getName() + ")"; mchung@2172: } mchung@2172: } mchung@2538: mchung@2538: private String toTag(String name, Archive source) { mchung@2538: return toTag(name, source, options.verbose); mchung@2538: } mchung@2538: mchung@2538: private Profile getProfile(String name, Analyzer.Type type) { mchung@2538: String pn = name; mchung@2538: if (type == CLASS || type == VERBOSE) { mchung@2538: int i = name.lastIndexOf('.'); mchung@2538: pn = i > 0 ? name.substring(0, i) : ""; mchung@2172: } mchung@2538: return Profile.getProfile(pn); mchung@2139: } mchung@2539: mchung@2539: /** mchung@2539: * Returns the recommended replacement API for the given classname; mchung@2539: * or return null if replacement API is not known. mchung@2539: */ mchung@2539: private String replacementFor(String cn) { mchung@2539: String name = cn; mchung@2539: String value = null; mchung@2539: while (value == null && name != null) { mchung@2539: try { mchung@2539: value = ResourceBundleHelper.jdkinternals.getString(name); mchung@2539: } catch (MissingResourceException e) { mchung@2539: // go up one subpackage level mchung@2539: int i = name.lastIndexOf('.'); mchung@2539: name = i > 0 ? name.substring(0, i) : null; mchung@2539: } mchung@2539: } mchung@2539: return value; mchung@2539: }; mchung@2539: mchung@2539: private void showReplacements(Analyzer analyzer) { mchung@2539: Map jdkinternals = new TreeMap<>(); mchung@2539: boolean useInternals = false; mchung@2539: for (Archive source : sourceLocations) { mchung@2539: useInternals = useInternals || analyzer.hasDependences(source); mchung@2539: for (String cn : analyzer.dependences(source)) { mchung@2539: String repl = replacementFor(cn); mchung@2539: if (repl != null && !jdkinternals.containsKey(cn)) { mchung@2539: jdkinternals.put(cn, repl); mchung@2539: } mchung@2539: } mchung@2539: } mchung@2539: if (useInternals) { mchung@2539: log.println(); mchung@2539: warning("warn.replace.useJDKInternals", getMessage("jdeps.wiki.url")); mchung@2539: } mchung@2539: if (!jdkinternals.isEmpty()) { mchung@2539: log.println(); mchung@2539: log.format("%-40s %s%n", "JDK Internal API", "Suggested Replacement"); mchung@2539: log.format("%-40s %s%n", "----------------", "---------------------"); mchung@2539: for (Map.Entry e : jdkinternals.entrySet()) { mchung@2539: log.format("%-40s %s%n", e.getKey(), e.getValue()); mchung@2539: } mchung@2539: } mchung@2539: mchung@2539: } mchung@1472: }