src/share/classes/com/sun/tools/jdeps/JdepsTask.java

Print this page

        

*** 22,37 **** --- 22,42 ---- * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.tools.jdeps; + import com.sun.tools.classfile.AccessFlags; import com.sun.tools.classfile.ClassFile; import com.sun.tools.classfile.ConstantPoolException; import com.sun.tools.classfile.Dependencies; import com.sun.tools.classfile.Dependencies.ClassFileError; import com.sun.tools.classfile.Dependency; import java.io.*; + import java.nio.file.DirectoryStream; + import java.nio.file.Files; + import java.nio.file.Path; + import java.nio.file.Paths; import java.text.MessageFormat; import java.util.*; import java.util.regex.Pattern; /**
*** 65,80 **** return false; } boolean matches(String opt) { for (String a : aliases) { ! if (a.equals(opt)) { return true; ! } else if (opt.startsWith("--") && hasArg && opt.startsWith(a + "=")) { return true; } - } return false; } boolean ignoreRest() { return false; --- 70,84 ---- return false; } boolean matches(String opt) { for (String a : aliases) { ! if (a.equals(opt)) return true; ! if (hasArg && opt.startsWith(a + "=")) return true; } return false; } boolean ignoreRest() { return false;
*** 94,177 **** return true; } } static Option[] recognizedOptions = { ! new Option(false, "-h", "-?", "--help") { void process(JdepsTask task, String opt, String arg) { task.options.help = true; } }, ! new Option(false, "-s", "--summary") { ! void process(JdepsTask task, String opt, String arg) { ! task.options.showSummary = true; ! task.options.verbose = Analyzer.Type.SUMMARY; } }, ! new Option(false, "-v", "--verbose") { void process(JdepsTask task, String opt, String arg) { ! task.options.verbose = Analyzer.Type.VERBOSE; } }, ! new Option(true, "-V", "--verbose-level") { void process(JdepsTask task, String opt, String arg) throws BadArgs { ! if ("package".equals(arg)) { task.options.verbose = Analyzer.Type.PACKAGE; ! } else if ("class".equals(arg)) { task.options.verbose = Analyzer.Type.CLASS; ! } else { throw new BadArgs("err.invalid.arg.for.option", opt); } } }, ! new Option(true, "-c", "--classpath") { void process(JdepsTask task, String opt, String arg) { task.options.classpath = arg; } }, ! new Option(true, "-p", "--package") { void process(JdepsTask task, String opt, String arg) { task.options.packageNames.add(arg); } }, ! new Option(true, "-e", "--regex") { void process(JdepsTask task, String opt, String arg) { task.options.regex = arg; } }, ! new Option(false, "-P", "--profile") { void process(JdepsTask task, String opt, String arg) throws BadArgs { task.options.showProfile = true; ! if (Profiles.getProfileCount() == 0) { throw new BadArgs("err.option.unsupported", opt, getMessage("err.profiles.msg")); } } }, ! new Option(false, "-R", "--recursive") { void process(JdepsTask task, String opt, String arg) { ! task.options.depth = 0; } }, ! new HiddenOption(true, "-d", "--depth") { ! void process(JdepsTask task, String opt, String arg) throws BadArgs { ! try { ! task.options.depth = Integer.parseInt(arg); ! } catch (NumberFormatException e) { ! throw new BadArgs("err.invalid.arg.for.option", opt); ! } } }, ! new Option(false, "--version") { void process(JdepsTask task, String opt, String arg) { task.options.version = true; } }, ! new HiddenOption(false, "--fullversion") { void process(JdepsTask task, String opt, String arg) { task.options.fullVersion = true; } }, }; private static final String PROGNAME = "jdeps"; private final Options options = new Options(); private final List<String> classes = new ArrayList<String>(); --- 98,205 ---- return true; } } static Option[] recognizedOptions = { ! new Option(false, "-h", "-?", "-help") { void process(JdepsTask task, String opt, String arg) { task.options.help = true; } }, ! new Option(true, "-dotoutput") { ! void process(JdepsTask task, String opt, String arg) throws BadArgs { ! Path p = Paths.get(arg); ! if (Files.exists(p) && (!Files.isDirectory(p) || !Files.isWritable(p))) { ! throw new BadArgs("err.dot.output.path", arg); ! } ! task.options.dotOutputDir = arg; } }, ! new Option(false, "-s", "-summary") { void process(JdepsTask task, String opt, String arg) { ! task.options.showSummary = true; ! task.options.verbose = Analyzer.Type.SUMMARY; } }, ! new Option(false, "-v", "-verbose", ! "-verbose:package", ! "-verbose:class") ! { void process(JdepsTask task, String opt, String arg) throws BadArgs { ! switch (opt) { ! case "-v": ! case "-verbose": ! task.options.verbose = Analyzer.Type.VERBOSE; ! break; ! case "-verbose:package": task.options.verbose = Analyzer.Type.PACKAGE; ! break; ! case "-verbose:class": task.options.verbose = Analyzer.Type.CLASS; ! break; ! default: throw new BadArgs("err.invalid.arg.for.option", opt); } } }, ! new Option(true, "-cp", "-classpath") { void process(JdepsTask task, String opt, String arg) { task.options.classpath = arg; } }, ! new Option(true, "-p", "-package") { void process(JdepsTask task, String opt, String arg) { task.options.packageNames.add(arg); } }, ! new Option(true, "-e", "-regex") { void process(JdepsTask task, String opt, String arg) { task.options.regex = arg; } }, ! new Option(true, "-include") { ! void process(JdepsTask task, String opt, String arg) throws BadArgs { ! task.options.includePattern = Pattern.compile(arg); ! } ! }, ! new Option(false, "-P", "-profile") { void process(JdepsTask task, String opt, String arg) throws BadArgs { task.options.showProfile = true; ! if (Profile.getProfileCount() == 0) { throw new BadArgs("err.option.unsupported", opt, getMessage("err.profiles.msg")); } } }, ! new Option(false, "-apionly") { void process(JdepsTask task, String opt, String arg) { ! task.options.apiOnly = true; } }, ! new Option(false, "-recursive") { ! void process(JdepsTask task, String opt, String arg) { ! task.options.depth = 0; } }, ! new Option(false, "-version") { void process(JdepsTask task, String opt, String arg) { task.options.version = true; } }, ! new HiddenOption(false, "-fullversion") { void process(JdepsTask task, String opt, String arg) { task.options.fullVersion = true; } }, + new HiddenOption(true, "-depth") { + void process(JdepsTask task, String opt, String arg) throws BadArgs { + try { + task.options.depth = Integer.parseInt(arg); + } catch (NumberFormatException e) { + throw new BadArgs("err.invalid.arg.for.option", opt); + } + } + }, }; private static final String PROGNAME = "jdeps"; private final Options options = new Options(); private final List<String> classes = new ArrayList<String>();
*** 200,210 **** showHelp(); } if (options.version || options.fullVersion) { showVersion(options.fullVersion); } ! if (classes.isEmpty() && !options.wildcard) { if (options.help || options.version || options.fullVersion) { return EXIT_OK; } else { showHelp(); return EXIT_CMDERR; --- 228,238 ---- showHelp(); } if (options.version || options.fullVersion) { showVersion(options.fullVersion); } ! if (classes.isEmpty() && options.includePattern == null) { if (options.help || options.version || options.fullVersion) { return EXIT_OK; } else { showHelp(); return EXIT_CMDERR;
*** 231,253 **** } finally { log.flush(); } } ! private final List<Archive> sourceLocations = new ArrayList<Archive>(); private boolean run() throws IOException { findDependencies(); Analyzer analyzer = new Analyzer(options.verbose); analyzer.run(sourceLocations); ! if (options.verbose == Analyzer.Type.SUMMARY) { ! printSummary(log, analyzer); } else { ! printDependencies(log, analyzer); } return true; } private boolean isValidClassName(String name) { if (!Character.isJavaIdentifierStart(name.charAt(0))) { return false; } for (int i=1; i < name.length(); i++) { --- 259,313 ---- } finally { log.flush(); } } ! private final List<Archive> sourceLocations = new ArrayList<>(); private boolean run() throws IOException { findDependencies(); Analyzer analyzer = new Analyzer(options.verbose); analyzer.run(sourceLocations); ! if (options.dotOutputDir != null) { ! Path dir = Paths.get(options.dotOutputDir); ! Files.createDirectories(dir); ! generateDotFiles(dir, analyzer); } else { ! printRawOutput(log, analyzer); } return true; } + private void generateDotFiles(Path dir, Analyzer analyzer) throws IOException { + Path summary = dir.resolve("summary.dot"); + try (PrintWriter sw = new PrintWriter(Files.newOutputStream(summary)); + DotFileFormatter formatter = new DotFileFormatter(sw, "summary")) { + for (Archive archive : sourceLocations) { + analyzer.visitArchiveDependences(archive, formatter); + } + } + if (options.verbose != Analyzer.Type.SUMMARY) { + for (Archive archive : sourceLocations) { + if (analyzer.hasDependences(archive)) { + Path dotfile = dir.resolve(archive.getFileName() + ".dot"); + try (PrintWriter pw = new PrintWriter(Files.newOutputStream(dotfile)); + DotFileFormatter formatter = new DotFileFormatter(pw, archive)) { + analyzer.visitDependences(archive, formatter); + } + } + } + } + } + + private void printRawOutput(PrintWriter writer, Analyzer analyzer) { + for (Archive archive : sourceLocations) { + RawOutputFormatter formatter = new RawOutputFormatter(writer); + analyzer.visitArchiveDependences(archive, formatter); + if (options.verbose != Analyzer.Type.SUMMARY) { + analyzer.visitDependences(archive, formatter); + } + } + } private boolean isValidClassName(String name) { if (!Character.isJavaIdentifierStart(name.charAt(0))) { return false; } for (int i=1; i < name.length(); i++) {
*** 257,299 **** } } return true; } ! private void findDependencies() throws IOException { ! Dependency.Finder finder = Dependencies.getClassDependencyFinder(); ! Dependency.Filter filter; if (options.regex != null) { ! filter = Dependencies.getRegexFilter(Pattern.compile(options.regex)); } else if (options.packageNames.size() > 0) { ! filter = Dependencies.getPackageFilter(options.packageNames, false); } else { ! filter = new Dependency.Filter() { public boolean accepts(Dependency dependency) { return !dependency.getOrigin().equals(dependency.getTarget()); } }; } ! List<Archive> archives = new ArrayList<Archive>(); ! Deque<String> roots = new LinkedList<String>(); for (String s : classes) { ! File f = new File(s); ! if (f.exists()) { ! archives.add(new Archive(f, ClassFileReader.newInstance(f))); } else { if (isValidClassName(s)) { roots.add(s); } else { warning("warn.invalid.arg", s); } } } ! List<Archive> classpaths = new ArrayList<Archive>(); // for class file lookup ! if (options.wildcard) { ! // include all archives from classpath to the initial list archives.addAll(getClassPathArchives(options.classpath)); } else { classpaths.addAll(getClassPathArchives(options.classpath)); } classpaths.addAll(PlatformClassPath.getArchives()); --- 317,374 ---- } } return true; } ! private Dependency.Filter getDependencyFilter() { if (options.regex != null) { ! return Dependencies.getRegexFilter(Pattern.compile(options.regex)); } else if (options.packageNames.size() > 0) { ! return Dependencies.getPackageFilter(options.packageNames, false); } else { ! return new Dependency.Filter() { ! @Override public boolean accepts(Dependency dependency) { return !dependency.getOrigin().equals(dependency.getTarget()); } }; } + } + + private boolean matches(String classname, AccessFlags flags) { + if (options.apiOnly && !flags.is(AccessFlags.ACC_PUBLIC)) { + return false; + } else if (options.includePattern != null) { + return options.includePattern.matcher(classname.replace('/', '.')).matches(); + } else { + return true; + } + } + + private void findDependencies() throws IOException { + Dependency.Finder finder = + options.apiOnly ? Dependencies.getAPIFinder(AccessFlags.ACC_PROTECTED) + : Dependencies.getClassDependencyFinder(); + Dependency.Filter filter = getDependencyFilter(); ! List<Archive> archives = new ArrayList<>(); ! Deque<String> roots = new LinkedList<>(); for (String s : classes) { ! Path p = Paths.get(s); ! if (Files.exists(p)) { ! archives.add(new Archive(p, ClassFileReader.newInstance(p))); } else { if (isValidClassName(s)) { roots.add(s); } else { warning("warn.invalid.arg", s); } } } ! List<Archive> classpaths = new ArrayList<>(); // for class file lookup ! if (options.includePattern != null) { archives.addAll(getClassPathArchives(options.classpath)); } else { classpaths.addAll(getClassPathArchives(options.classpath)); } classpaths.addAll(PlatformClassPath.getArchives());
*** 303,314 **** sourceLocations.addAll(classpaths); // Work queue of names of classfiles to be searched. // Entries will be unique, and for classes that do not yet have // dependencies in the results map. ! Deque<String> deque = new LinkedList<String>(); ! Set<String> doneClasses = new HashSet<String>(); // get the immediate dependencies of the input files for (Archive a : archives) { for (ClassFile cf : a.reader().getClassFiles()) { String classFileName; --- 378,389 ---- sourceLocations.addAll(classpaths); // Work queue of names of classfiles to be searched. // Entries will be unique, and for classes that do not yet have // dependencies in the results map. ! Deque<String> deque = new LinkedList<>(); ! Set<String> doneClasses = new HashSet<>(); // get the immediate dependencies of the input files for (Archive a : archives) { for (ClassFile cf : a.reader().getClassFiles()) { String classFileName;
*** 316,325 **** --- 391,401 ---- classFileName = cf.getName(); } catch (ConstantPoolException e) { throw new ClassFileError(e); } + if (matches(classFileName, cf.access_flags)) { if (!doneClasses.contains(classFileName)) { doneClasses.add(classFileName); } for (Dependency d : finder.findDependencies(cf)) { if (filter.accepts(d)) {
*** 330,339 **** --- 406,416 ---- a.addClass(d.getOrigin(), d.getTarget()); } } } } + } // add Archive for looking up classes from the classpath // for transitive dependency analysis Deque<String> unresolved = roots; int depth = options.depth > 0 ? options.depth : Integer.MAX_VALUE;
*** 377,435 **** if (cf == null) { doneClasses.add(name); } } unresolved = deque; ! deque = new LinkedList<String>(); } while (!unresolved.isEmpty() && depth-- > 0); } - private void printSummary(final PrintWriter out, final Analyzer analyzer) { - Analyzer.Visitor visitor = new Analyzer.Visitor() { - public void visit(String origin, String target, String profile) { - if (options.showProfile) { - out.format("%-30s -> %s%n", origin, target); - } - } - public void visit(Archive origin, Archive target) { - if (!options.showProfile) { - out.format("%-30s -> %s%n", origin, target); - } - } - }; - analyzer.visitSummary(visitor); - } - - private void printDependencies(final PrintWriter out, final Analyzer analyzer) { - Analyzer.Visitor visitor = new Analyzer.Visitor() { - private String pkg = ""; - public void visit(String origin, String target, String profile) { - if (!origin.equals(pkg)) { - pkg = origin; - out.format(" %s (%s)%n", origin, analyzer.getArchive(origin).getFileName()); - } - out.format(" -> %-50s %s%n", target, - (options.showProfile && !profile.isEmpty()) - ? profile - : analyzer.getArchiveName(target, profile)); - } - public void visit(Archive origin, Archive target) { - out.format("%s -> %s%n", origin, target); - } - }; - analyzer.visit(visitor); - } - public void handleOptions(String[] args) throws BadArgs { // process options for (int i=0; i < args.length; i++) { if (args[i].charAt(0) == '-') { String name = args[i]; Option option = getOption(name); String param = null; if (option.hasArg) { ! if (name.startsWith("--") && name.indexOf('=') > 0) { param = name.substring(name.indexOf('=') + 1, name.length()); } else if (i + 1 < args.length) { param = args[++i]; } if (param == null || param.isEmpty() || param.charAt(0) == '-') { --- 454,476 ---- if (cf == null) { doneClasses.add(name); } } unresolved = deque; ! deque = new LinkedList<>(); } while (!unresolved.isEmpty() && depth-- > 0); } public void handleOptions(String[] args) throws BadArgs { // process options for (int i=0; i < args.length; i++) { if (args[i].charAt(0) == '-') { String name = args[i]; Option option = getOption(name); String param = null; if (option.hasArg) { ! if (name.startsWith("-") && name.indexOf('=') > 0) { param = name.substring(name.indexOf('=') + 1, name.length()); } else if (i + 1 < args.length) { param = args[++i]; } if (param == null || param.isEmpty() || param.charAt(0) == '-') {
*** 445,463 **** for (; i < args.length; i++) { String name = args[i]; if (name.charAt(0) == '-') { throw new BadArgs("err.option.after.class", name).showUsage(true); } - if (name.equals("*") || name.equals("\"*\"")) { - options.wildcard = true; - } else { classes.add(name); } } } } - } private Option getOption(String name) throws BadArgs { for (Option o : recognizedOptions) { if (o.matches(name)) { return o; --- 486,500 ----
*** 516,532 **** boolean version; boolean fullVersion; boolean showProfile; boolean showSummary; boolean wildcard; ! String regex; String classpath = ""; int depth = 1; Analyzer.Type verbose = Analyzer.Type.PACKAGE; ! Set<String> packageNames = new HashSet<String>(); } - private static class ResourceBundleHelper { static final ResourceBundle versionRB; static final ResourceBundle bundle; static { --- 553,571 ---- boolean version; boolean fullVersion; boolean showProfile; boolean showSummary; boolean wildcard; ! boolean apiOnly; ! String dotOutputDir; String classpath = ""; int depth = 1; Analyzer.Type verbose = Analyzer.Type.PACKAGE; ! Set<String> packageNames = new HashSet<>(); ! String regex; // apply to the dependences ! Pattern includePattern; // apply to classes } private static class ResourceBundleHelper { static final ResourceBundle versionRB; static final ResourceBundle bundle; static {
*** 545,575 **** } private List<Archive> getArchives(List<String> filenames) throws IOException { List<Archive> result = new ArrayList<Archive>(); for (String s : filenames) { ! File f = new File(s); ! if (f.exists()) { ! result.add(new Archive(f, ClassFileReader.newInstance(f))); } else { warning("warn.file.not.exist", s); } } return result; } private List<Archive> getClassPathArchives(String paths) throws IOException { ! List<Archive> result = new ArrayList<Archive>(); if (paths.isEmpty()) { return result; } for (String p : paths.split(File.pathSeparator)) { if (p.length() > 0) { ! File f = new File(p); ! if (f.exists()) { result.add(new Archive(f, ClassFileReader.newInstance(f))); } } } return result; } } --- 584,727 ---- } private List<Archive> getArchives(List<String> filenames) throws IOException { List<Archive> result = new ArrayList<Archive>(); for (String s : filenames) { ! Path p = Paths.get(s); ! if (Files.exists(p)) { ! result.add(new Archive(p, ClassFileReader.newInstance(p))); } else { warning("warn.file.not.exist", s); } } return result; } private List<Archive> getClassPathArchives(String paths) throws IOException { ! List<Archive> result = new ArrayList<>(); if (paths.isEmpty()) { return result; } for (String p : paths.split(File.pathSeparator)) { if (p.length() > 0) { ! List<Path> files = new ArrayList<>(); ! // wildcard to parse all JAR files e.g. -classpath dir/* ! int i = p.lastIndexOf(".*"); ! if (i > 0) { ! Path dir = Paths.get(p.substring(0, i)); ! try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.jar")) { ! for (Path entry : stream) { ! files.add(entry); ! } ! } ! } else { ! files.add(Paths.get(p)); ! } ! for (Path f : files) { ! if (Files.exists(f)) { result.add(new Archive(f, ClassFileReader.newInstance(f))); } } } + } return result; } + + + /** + * Returns the file name of the archive for non-JRE class or + * internal JRE classes. It returns empty string for SE API. + */ + private static String getArchiveName(Archive source, String profile) { + String name = source.getFileName(); + if (PlatformClassPath.contains(source)) + return profile.isEmpty() ? "JDK internal API (" + name + ")" : ""; + return name; + } + + class RawOutputFormatter implements Analyzer.Visitor { + private final PrintWriter writer; + RawOutputFormatter(PrintWriter writer) { + this.writer = writer; + } + + private String pkg = ""; + @Override + public void visitDependence(String origin, Archive source, + String target, Archive archive, String profile) { + if (!origin.equals(pkg)) { + pkg = origin; + writer.format(" %s (%s)%n", origin, source.getFileName()); + } + String name = (options.showProfile && !profile.isEmpty()) + ? profile + : getArchiveName(archive, profile); + writer.format(" -> %-50s %s%n", target, name); + } + + @Override + public void visitArchiveDependence(Archive origin, Archive target, String profile) { + writer.format("%s -> %s", origin, target); + if (options.showProfile && !profile.isEmpty()) { + writer.format(" (%s)%n", profile); + } else { + writer.format("%n"); + } + } + } + + class DotFileFormatter implements Analyzer.Visitor, AutoCloseable { + private final PrintWriter writer; + private final String name; + DotFileFormatter(PrintWriter writer, String name) { + this.writer = writer; + this.name = name; + writer.format("digraph \"%s\" {%n", name); + } + DotFileFormatter(PrintWriter writer, Archive archive) { + this.writer = writer; + this.name = archive.getFileName(); + writer.format("digraph \"%s\" {%n", name); + writer.format(" // Path: %s%n", archive.toString()); + } + + @Override + public void close() { + writer.println("}"); + } + + private final Set<String> edges = new HashSet<>(); + private String node = ""; + @Override + public void visitDependence(String origin, Archive source, + String target, Archive archive, String profile) { + if (!node.equals(origin)) { + edges.clear(); + node = origin; + } + // if -P option is specified, package name -> profile will + // be shown and filter out multiple same edges. + if (!edges.contains(target)) { + StringBuilder sb = new StringBuilder(); + String name = options.showProfile && !profile.isEmpty() + ? profile + : getArchiveName(archive, profile); + writer.format(" %-50s -> %s;%n", + String.format("\"%s\"", origin), + name.isEmpty() ? String.format("\"%s\"", target) + : String.format("\"%s (%s)\"", target, name)); + edges.add(target); + } + } + + @Override + public void visitArchiveDependence(Archive origin, Archive target, String profile) { + String name = options.showProfile && !profile.isEmpty() + ? profile : ""; + writer.format(" %-30s -> \"%s\";%n", + String.format("\"%s\"", origin.getFileName()), + name.isEmpty() + ? target.getFileName() + : String.format("%s (%s)", target.getFileName(), name)); + } + } }