--- old/src/share/classes/com/sun/tools/jdeps/Analyzer.java 2013-10-10 23:35:36.000000000 -0700 +++ new/src/share/classes/com/sun/tools/jdeps/Analyzer.java 2013-10-10 23:35:36.000000000 -0700 @@ -25,9 +25,9 @@ package com.sun.tools.jdeps; import com.sun.tools.classfile.Dependency.Location; -import java.util.ArrayList; +import com.sun.tools.jdeps.Profile; import java.util.HashMap; -import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -52,8 +52,8 @@ }; private final Type type; - private final List results = new ArrayList(); - private final Map map = new HashMap(); + private final Map results = new LinkedHashMap<>(); + private final Map map = new HashMap<>(); private final Archive NOT_FOUND = new Archive(JdepsTask.getMessage("artifact.not.found")); @@ -78,27 +78,27 @@ deps = new PackageVisitor(archive); } archive.visit(deps); - results.add(deps); + results.put(archive, deps); } // set the required dependencies - for (ArchiveDeps result: results) { + for (ArchiveDeps result: results.values()) { for (Set set : result.deps.values()) { for (String target : set) { Archive source = getArchive(target); if (result.archive != source) { - if (!result.requiredArchives.contains(source)) { - result.requiredArchives.add(source); + String profile = ""; + if (PlatformClassPath.contains(source)) { + profile = result.profile != null ? result.profile.toString() : ""; + if (result.getTargetProfile(target) == null) { + profile += ", JDK internal API"; + // override the value if it accesses any JDK internal + result.requireArchives.put(source, profile); + continue; + } } - // either a profile name or the archive name - String tname = result.getTargetProfile(target); - if (tname.isEmpty()) { - tname = PlatformClassPath.contains(source) - ? "JDK internal API (" + source.getFileName() + ")" - : source.toString(); - } - if (!result.targetNames.contains(tname)) { - result.targetNames.add(tname); + if (!result.requireArchives.containsKey(source)) { + result.requireArchives.put(source, profile); } } } @@ -106,42 +106,46 @@ } } + public boolean hasDependences(Archive archive) { + if (results.containsKey(archive)) { + return results.get(archive).deps.size() > 0; + } + return false; + } + public interface Visitor { /** + * Visits the source archive to its destination archive of + * a recorded dependency. + */ + void visitArchiveDependence(Archive origin, Archive target, String profile); + /** * Visits a recorded dependency from origin to target which can be * a fully-qualified classname, a package name, a profile or * archive name depending on the Analyzer's type. */ - void visit(String origin, String target, String profile); - /** - * Visits the source archive to its destination archive of - * a recorded dependency. - */ - void visit(Archive source, Archive dest); + void visitDependence(String origin, Archive source, String target, Archive archive, String profile); } - public void visitSummary(Visitor v) { - for (ArchiveDeps r : results) { - for (Archive a : r.requiredArchives) { - v.visit(r.archive, a); - } - for (String name : r.targetNames) { - v.visit(r.archive.getFileName(), name, name); - } + public void visitArchiveDependences(Archive source, Visitor v) { + ArchiveDeps r = results.get(source); + for (Map.Entry e : r.requireArchives.entrySet()) { + v.visitArchiveDependence(r.archive, e.getKey(), e.getValue()); } } - public void visit(Visitor v) { - for (ArchiveDeps r: results) { - for (Archive a : r.requiredArchives) { - v.visit(r.archive, a); - } - for (String origin : r.deps.keySet()) { - for (String target : r.deps.get(origin)) { - // filter intra-dependency unless in verbose mode - if (type == Type.VERBOSE || getArchive(origin) != getArchive(target)) { - v.visit(origin, target, r.getTargetProfile(target)); - } + public void visitDependences(Archive source, Visitor v) { + ArchiveDeps r = results.get(source); + for (String origin : r.deps.keySet()) { + for (String target : r.deps.get(origin)) { + Archive archive = getArchive(target); + assert source == getArchive(origin); + Profile profile = r.getTargetProfile(target); + + // filter intra-dependency unless in verbose mode + if (type == Type.VERBOSE || archive != source) { + v.visitDependence(origin, source, target, archive, + profile != null ? profile.toString() : ""); } } } @@ -151,29 +155,15 @@ return map.containsKey(name) ? map.get(name) : NOT_FOUND; } - /** - * Returns the file name of the archive for non-JRE class or - * internal JRE classes. It returns empty string for SE API. - */ - public String getArchiveName(String target, String profile) { - Archive source = getArchive(target); - String name = source.getFileName(); - if (PlatformClassPath.contains(source)) - return profile.isEmpty() ? "JDK internal API (" + name + ")" : ""; - return name; - } - private abstract class ArchiveDeps implements Archive.Visitor { final Archive archive; - final Set requiredArchives; - final SortedSet targetNames; + final Map requireArchives; final SortedMap> deps; - + Profile profile = null; ArchiveDeps(Archive archive) { this.archive = archive; - this.requiredArchives = new HashSet(); - this.targetNames = new TreeSet(); - this.deps = new TreeMap>(); + this.requireArchives = new HashMap<>(); + this.deps = new TreeMap<>(); } void add(String loc) { @@ -188,17 +178,19 @@ void add(String origin, String target) { SortedSet set = deps.get(origin); if (set == null) { - set = new TreeSet(); - deps.put(origin, set); + deps.put(origin, set = new TreeSet<>()); } if (!set.contains(target)) { set.add(target); + // find the corresponding profile + Profile p = getTargetProfile(target); + if (profile == null || (p != null && profile.profile < p.profile)) { + profile = p; + } } } - public abstract void visit(Location o, Location t); - public abstract String getTargetProfile(String target); - + public abstract Profile getTargetProfile(String target); } private class ClassVisitor extends ArchiveDeps { @@ -211,9 +203,9 @@ public void visit(Location o, Location t) { add(o.getClassName(), t.getClassName()); } - public String getTargetProfile(String target) { + public Profile getTargetProfile(String target) { int i = target.lastIndexOf('.'); - return (i > 0) ? Profiles.getProfileName(target.substring(0, i)) : ""; + return (i > 0) ? Profile.getProfile(target.substring(0, i)) : null; } } @@ -231,8 +223,8 @@ String pkg = loc.getPackageName(); return pkg.isEmpty() ? "" : pkg; } - public String getTargetProfile(String target) { - return Profiles.getProfileName(target); + public Profile getTargetProfile(String target) { + return Profile.getProfile(target); } } } --- old/src/share/classes/com/sun/tools/jdeps/Archive.java 2013-10-10 23:35:37.000000000 -0700 +++ new/src/share/classes/com/sun/tools/jdeps/Archive.java 2013-10-10 23:35:36.000000000 -0700 @@ -25,7 +25,7 @@ package com.sun.tools.jdeps; import com.sun.tools.classfile.Dependency.Location; -import java.io.File; +import java.nio.file.Path; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -35,21 +35,20 @@ * Represents the source of the class files. */ public class Archive { - private final File file; + private final Path path; private final String filename; private final ClassFileReader reader; - private final Map> deps - = new HashMap>(); + private final Map> deps = new HashMap<>(); public Archive(String name) { - this.file = null; + this.path = null; this.filename = name; this.reader = null; } - public Archive(File f, ClassFileReader reader) { - this.file = f; - this.filename = f.getName(); + public Archive(Path p, ClassFileReader reader) { + this.path = p; + this.filename = path.getFileName().toString(); this.reader = reader; } @@ -64,14 +63,14 @@ public void addClass(Location origin) { Set set = deps.get(origin); if (set == null) { - set = new HashSet(); + set = new HashSet<>(); deps.put(origin, set); } } public void addClass(Location origin, Location target) { Set set = deps.get(origin); if (set == null) { - set = new HashSet(); + set = new HashSet<>(); deps.put(origin, set); } set.add(target); @@ -87,7 +86,7 @@ } public String toString() { - return file != null ? file.getPath() : filename; + return path != null ? path.toString() : filename; } interface Visitor { --- old/src/share/classes/com/sun/tools/jdeps/ClassFileReader.java 2013-10-10 23:35:37.000000000 -0700 +++ new/src/share/classes/com/sun/tools/jdeps/ClassFileReader.java 2013-10-10 23:35:37.000000000 -0700 @@ -45,17 +45,17 @@ /** * Returns a ClassFileReader instance of a given path. */ - public static ClassFileReader newInstance(File path) throws IOException { - if (!path.exists()) { - throw new FileNotFoundException(path.getAbsolutePath()); + public static ClassFileReader newInstance(Path path) throws IOException { + if (!Files.exists(path)) { + throw new FileNotFoundException(path.toString()); } - if (path.isDirectory()) { - return new DirectoryReader(path.toPath()); - } else if (path.getName().endsWith(".jar")) { - return new JarFileReader(path.toPath()); + if (Files.isDirectory(path)) { + return new DirectoryReader(path); + } else if (path.getFileName().toString().endsWith(".jar")) { + return new JarFileReader(path); } else { - return new ClassFileReader(path.toPath()); + return new ClassFileReader(path); } } @@ -163,16 +163,16 @@ int i = name.lastIndexOf('.'); String pathname = name.replace('.', File.separatorChar) + ".class"; Path p = path.resolve(pathname); - if (!p.toFile().exists()) { + if (!Files.exists(p)) { p = path.resolve(pathname.substring(0, i) + "$" + pathname.substring(i+1, pathname.length())); } - if (p.toFile().exists()) { + if (Files.exists(p)) { return readClassFile(p); } } else { Path p = path.resolve(name + ".class"); - if (p.toFile().exists()) { + if (Files.exists(p)) { return readClassFile(p); } } @@ -193,7 +193,7 @@ Files.walkFileTree(dir, new SimpleFileVisitor() { public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - if (file.toFile().getName().endsWith(".class")) { + if (file.getFileName().toString().endsWith(".class")) { files.add(file); } return FileVisitResult.CONTINUE; --- old/src/share/classes/com/sun/tools/jdeps/JdepsTask.java 2013-10-10 23:35:38.000000000 -0700 +++ new/src/share/classes/com/sun/tools/jdeps/JdepsTask.java 2013-10-10 23:35:38.000000000 -0700 @@ -24,12 +24,17 @@ */ 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; @@ -67,11 +72,10 @@ boolean matches(String opt) { for (String a : aliases) { - if (a.equals(opt)) { + if (a.equals(opt)) return true; - } else if (opt.startsWith("--") && hasArg && opt.startsWith(a + "=")) { + if (hasArg && opt.startsWith(a + "=")) return true; - } } return false; } @@ -96,80 +100,104 @@ } static Option[] recognizedOptions = { - new Option(false, "-h", "-?", "--help") { + 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(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, "-v", "--verbose") { + new Option(false, "-s", "-summary") { void process(JdepsTask task, String opt, String arg) { - task.options.verbose = Analyzer.Type.VERBOSE; + task.options.showSummary = true; + task.options.verbose = Analyzer.Type.SUMMARY; } }, - new Option(true, "-V", "--verbose-level") { + new Option(false, "-v", "-verbose", + "-verbose:package", + "-verbose:class") + { 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); + 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, "-c", "--classpath") { + new Option(true, "-cp", "-classpath") { void process(JdepsTask task, String opt, String arg) { task.options.classpath = arg; } }, - new Option(true, "-p", "--package") { + new Option(true, "-p", "-package") { void process(JdepsTask task, String opt, String arg) { task.options.packageNames.add(arg); } }, - new Option(true, "-e", "--regex") { + new Option(true, "-e", "-regex") { void process(JdepsTask task, String opt, String arg) { task.options.regex = arg; } }, - new Option(false, "-P", "--profile") { + 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 (Profiles.getProfileCount() == 0) { + if (Profile.getProfileCount() == 0) { throw new BadArgs("err.option.unsupported", opt, getMessage("err.profiles.msg")); } } }, - new Option(false, "-R", "--recursive") { + new Option(false, "-apionly") { void process(JdepsTask task, String opt, String arg) { - task.options.depth = 0; + task.options.apiOnly = true; } }, - 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, "-recursive") { + void process(JdepsTask task, String opt, String arg) { + task.options.depth = 0; } }, - new Option(false, "--version") { + new Option(false, "-version") { void process(JdepsTask task, String opt, String arg) { task.options.version = true; } }, - new HiddenOption(false, "--fullversion") { + 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"; @@ -202,7 +230,7 @@ if (options.version || options.fullVersion) { showVersion(options.fullVersion); } - if (classes.isEmpty() && !options.wildcard) { + if (classes.isEmpty() && options.includePattern == null) { if (options.help || options.version || options.fullVersion) { return EXIT_OK; } else { @@ -233,19 +261,51 @@ } } - private final List sourceLocations = new ArrayList(); + private final List sourceLocations = new ArrayList<>(); private boolean run() throws IOException { findDependencies(); Analyzer analyzer = new Analyzer(options.verbose); analyzer.run(sourceLocations); - if (options.verbose == Analyzer.Type.SUMMARY) { - printSummary(log, analyzer); + if (options.dotOutputDir != null) { + Path dir = Paths.get(options.dotOutputDir); + Files.createDirectories(dir); + generateDotFiles(dir, analyzer); } else { - printDependencies(log, analyzer); + 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; @@ -259,27 +319,43 @@ 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)); + private Dependency.Filter getDependencyFilter() { + if (options.regex != null) { + return Dependencies.getRegexFilter(Pattern.compile(options.regex)); } else if (options.packageNames.size() > 0) { - filter = Dependencies.getPackageFilter(options.packageNames, false); + return Dependencies.getPackageFilter(options.packageNames, false); } else { - filter = new Dependency.Filter() { + 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; + } + } - List archives = new ArrayList(); - Deque roots = new LinkedList(); + private void findDependencies() throws IOException { + Dependency.Finder finder = + options.apiOnly ? Dependencies.getAPIFinder(AccessFlags.ACC_PROTECTED) + : Dependencies.getClassDependencyFinder(); + Dependency.Filter filter = getDependencyFilter(); + + List archives = new ArrayList<>(); + Deque roots = new LinkedList<>(); for (String s : classes) { - File f = new File(s); - if (f.exists()) { - archives.add(new Archive(f, ClassFileReader.newInstance(f))); + Path p = Paths.get(s); + if (Files.exists(p)) { + archives.add(new Archive(p, ClassFileReader.newInstance(p))); } else { if (isValidClassName(s)) { roots.add(s); @@ -289,9 +365,8 @@ } } - List classpaths = new ArrayList(); // for class file lookup - if (options.wildcard) { - // include all archives from classpath to the initial list + List classpaths = new ArrayList<>(); // for class file lookup + if (options.includePattern != null) { archives.addAll(getClassPathArchives(options.classpath)); } else { classpaths.addAll(getClassPathArchives(options.classpath)); @@ -305,8 +380,8 @@ // 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 deque = new LinkedList(); - Set doneClasses = new HashSet(); + Deque deque = new LinkedList<>(); + Set doneClasses = new HashSet<>(); // get the immediate dependencies of the input files for (Archive a : archives) { @@ -318,16 +393,18 @@ throw new ClassFileError(e); } - if (!doneClasses.contains(classFileName)) { - doneClasses.add(classFileName); - } - for (Dependency d : finder.findDependencies(cf)) { - if (filter.accepts(d)) { - String cn = d.getTarget().getName(); - if (!doneClasses.contains(cn) && !deque.contains(cn)) { - deque.add(cn); + if (matches(classFileName, cf.access_flags)) { + if (!doneClasses.contains(classFileName)) { + doneClasses.add(classFileName); + } + for (Dependency d : finder.findDependencies(cf)) { + if (filter.accepts(d)) { + String cn = d.getTarget().getName(); + if (!doneClasses.contains(cn) && !deque.contains(cn)) { + deque.add(cn); + } + a.addClass(d.getOrigin(), d.getTarget()); } - a.addClass(d.getOrigin(), d.getTarget()); } } } @@ -379,46 +456,10 @@ } } unresolved = deque; - deque = new LinkedList(); + deque = new LinkedList<>(); } 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++) { @@ -427,7 +468,7 @@ Option option = getOption(name); String param = null; if (option.hasArg) { - if (name.startsWith("--") && name.indexOf('=') > 0) { + if (name.startsWith("-") && name.indexOf('=') > 0) { param = name.substring(name.indexOf('=') + 1, name.length()); } else if (i + 1 < args.length) { param = args[++i]; @@ -447,11 +488,7 @@ 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); - } + classes.add(name); } } } @@ -518,13 +555,15 @@ boolean showProfile; boolean showSummary; boolean wildcard; - String regex; + boolean apiOnly; + String dotOutputDir; String classpath = ""; int depth = 1; Analyzer.Type verbose = Analyzer.Type.PACKAGE; - Set packageNames = new HashSet(); + Set 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; @@ -547,9 +586,9 @@ private List getArchives(List filenames) throws IOException { List result = new ArrayList(); for (String s : filenames) { - File f = new File(s); - if (f.exists()) { - result.add(new Archive(f, ClassFileReader.newInstance(f))); + Path p = Paths.get(s); + if (Files.exists(p)) { + result.add(new Archive(p, ClassFileReader.newInstance(p))); } else { warning("warn.file.not.exist", s); } @@ -558,18 +597,131 @@ } private List getClassPathArchives(String paths) throws IOException { - List result = new ArrayList(); + List result = new ArrayList<>(); 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))); + List 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 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 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)); + } + } } --- old/src/share/classes/com/sun/tools/jdeps/PlatformClassPath.java 2013-10-10 23:35:38.000000000 -0700 +++ new/src/share/classes/com/sun/tools/jdeps/PlatformClassPath.java 2013-10-10 23:35:38.000000000 -0700 @@ -24,11 +24,11 @@ */ package com.sun.tools.jdeps; -import java.io.File; import java.io.IOException; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.*; @@ -38,45 +38,44 @@ */ class PlatformClassPath { private final static List javaHomeArchives = init(); + static List getArchives() { return javaHomeArchives; } static boolean contains(Archive archive) { - return javaHomeArchives.contains(archive); + // jfxrt.jar is in jre/lib/ext directory but it does not belong + // to any profile + return javaHomeArchives.contains(archive) && !archive.getFileName().equals("jfxrt.jar"); } private static List init() { - List result = new ArrayList(); - String javaHome = System.getProperty("java.home"); - File jre = new File(javaHome, "jre"); - File lib = new File(javaHome, "lib"); - + List result = new ArrayList<>(); + Path home = Paths.get(System.getProperty("java.home")); try { - if (jre.exists() && jre.isDirectory()) { - result.addAll(addJarFiles(new File(jre, "lib"))); - result.addAll(addJarFiles(lib)); - } else if (lib.exists() && lib.isDirectory()) { + if (home.endsWith("jre")) { + // jar files in /jre/lib + result.addAll(addJarFiles(home.resolve("lib"))); + } else if (Files.exists(home.resolve("lib"))) { // either a JRE or a jdk build image - File classes = new File(javaHome, "classes"); - if (classes.exists() && classes.isDirectory()) { + Path classes = home.resolve("classes"); + if (Files.isDirectory(classes)) { // jdk build outputdir result.add(new Archive(classes, ClassFileReader.newInstance(classes))); } // add other JAR files - result.addAll(addJarFiles(lib)); + result.addAll(addJarFiles(home.resolve("lib"))); } else { - throw new RuntimeException("\"" + javaHome + "\" not a JDK home"); + throw new RuntimeException("\"" + home + "\" not a JDK home"); } + return result; } catch (IOException e) { - throw new RuntimeException(e); + throw new Error(e); } - return result; } - private static List addJarFiles(File f) throws IOException { - final List result = new ArrayList(); - final Path root = f.toPath(); + private static List addJarFiles(final Path root) throws IOException { + final List result = new ArrayList<>(); final Path ext = root.resolve("ext"); Files.walkFileTree(root, new SimpleFileVisitor() { @Override @@ -91,13 +90,12 @@ } } @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + public FileVisitResult visitFile(Path p, BasicFileAttributes attrs) throws IOException { - File f = file.toFile(); - String fn = f.getName(); + String fn = p.getFileName().toString(); if (fn.endsWith(".jar") && !fn.equals("alt-rt.jar")) { - result.add(new Archive(f, ClassFileReader.newInstance(f))); + result.add(new Archive(p, ClassFileReader.newInstance(p))); } return FileVisitResult.CONTINUE; } --- old/src/share/classes/com/sun/tools/jdeps/resources/jdeps.properties 2013-10-10 23:35:39.000000000 -0700 +++ new/src/share/classes/com/sun/tools/jdeps/resources/jdeps.properties 2013-10-10 23:35:39.000000000 -0700 @@ -5,46 +5,63 @@ main.usage=\ Usage: {0} \n\ where can be a pathname to a .class file, a directory, a JAR file,\n\ -or a fully-qualified classname or wildcard "*". Possible options include: +or a fully-qualified class name. Possible options include: error.prefix=Error: warn.prefix=Warning: main.opt.h=\ -\ -h -? --help Print this usage message +\ -h -? -help Print this usage message main.opt.version=\ -\ --version Version information - -main.opt.V=\ -\ -V --verbose-level= Print package-level or class-level dependencies\n\ -\ Valid levels are: "package" and "class" +\ -version Version information main.opt.v=\ -\ -v --verbose Print additional information +\ -v -verbose Print all class level dependencies\n\ +\ -verbose:package Print package-level dependencies excluding\n\ +\ dependencies within the same archive\n\ +\ -verbose:class Print class-level dependencies excluding\n\ +\ dependencies within the same archive main.opt.s=\ -\ -s --summary Print dependency summary only +\ -s -summary Print dependency summary only main.opt.p=\ -\ -p --package= Restrict analysis to classes in this package\n\ -\ (may be given multiple times) +\ -p -package Finds dependences in the given package\n\ +\ (may be given multiple times) main.opt.e=\ -\ -e --regex= Restrict analysis to packages matching pattern\n\ -\ (-p and -e are exclusive) +\ -e -regex Finds dependences in packages matching pattern\n\ +\ (-p and -e are exclusive) + +main.opt.include=\ +\ -include Restrict analysis to classes matching pattern\n\ +\ This option filters the list of classes to\n\ +\ be analyzed. It can be used together with\n\ +\ -p and -e which apply pattern to the dependences main.opt.P=\ -\ -P --profile Show profile or the file containing a package +\ -P -profile Show profile or the file containing a package -main.opt.c=\ -\ -c --classpath= Specify where to find class files +main.opt.cp=\ +\ -cp -classpath Specify where to find class files -main.opt.R=\ -\ -R --recursive Recursively traverse all dependencies +main.opt.recursive=\ +\ -recursive Recursively traverse all dependencies -main.opt.d=\ -\ -d --depth= Specify the depth of the transitive dependency analysis +main.opt.apionly=\ +\ -apionly Restrict analysis to APIs i.e. dependences\n\ +\ from the signature of public and protected\n\ +\ members of public classes including field\n\ +\ type, method parameter types, returned type,\n\ +\ checked exception types etc + +main.opt.dotoutput=\ +\ -dotoutput Destination directory for DOT file output + +main.opt.depth=\ +\ -depth= Specify the depth of the transitive\n\ +\ dependency analysis err.unknown.option=unknown option: {0} err.missing.arg=no value given for {0} @@ -53,6 +70,7 @@ err.option.after.class=option must be specified before classes: {0} err.option.unsupported={0} not supported: {1} err.profiles.msg=No profile information +err.dot.output.path=invalid path: {0} warn.invalid.arg=Invalid classname or pathname not exist: {0} warn.split.package=package {0} defined in {1} {2} --- old/test/tools/jdeps/Basic.java 2013-10-10 23:35:40.000000000 -0700 +++ new/test/tools/jdeps/Basic.java 2013-10-10 23:35:40.000000000 -0700 @@ -23,7 +23,7 @@ /* * @test - * @bug 8003562 8005428 + * @bug 8003562 8005428 8015912 * @summary Basic tests for jdeps tool * @build Test p.Foo * @run main Basic @@ -79,40 +79,33 @@ new String[] {"compact1", "compact1", "compact3"}); // test class-level dependency output test(new File(testDir, "Test.class"), - new String[] {"java.lang.Object", "p.Foo"}, - new String[] {"compact1", "not found"}, - new String[] {"-V", "class"}); + new String[] {"java.lang.Object", "java.lang.String", "p.Foo"}, + new String[] {"compact1", "compact1", "not found"}, + new String[] {"-verbose:class"}); // test -p option test(new File(testDir, "Test.class"), new String[] {"p.Foo"}, new String[] {"not found"}, - new String[] {"--verbose-level=class", "-p", "p"}); + new String[] {"-verbose:class", "-p", "p"}); // test -e option test(new File(testDir, "Test.class"), new String[] {"p.Foo"}, new String[] {"not found"}, - new String[] {"-V", "class", "-e", "p\\..*"}); + new String[] {"-verbose:class", "-e", "p\\..*"}); test(new File(testDir, "Test.class"), new String[] {"java.lang"}, new String[] {"compact1"}, - new String[] {"-V", "package", "-e", "java\\.lang\\..*"}); - // test -classpath and wildcard options + new String[] {"-verbose:package", "-e", "java\\.lang\\..*"}); + // test -classpath and -include options test(null, - new String[] {"com.sun.tools.jdeps", "java.lang", "java.util", - "java.util.regex", "java.io", "java.nio.file", + new String[] {"java.lang", "java.util", "java.lang.management"}, - new String[] {(symbolFileExist? "not found" : "JDK internal API (classes)"), - "compact1", "compact1", "compact1", - "compact1", "compact1", "compact3"}, - new String[] {"--classpath", testDir.getPath(), "*"}); - /* Temporary disable this test case. Test.class has a dependency - * on java.lang.String on certain windows machine (8008479). - // -v shows intra-dependency - test(new File(testDir, "Test.class"), - new String[] {"java.lang.Object", "p.Foo"}, - new String[] {"compact1", testDir.getName()}, - new String[] {"-v", "--classpath", testDir.getPath(), "Test.class"}); - */ + new String[] {"compact1", "compact1", "compact3"}, + new String[] {"-classpath", testDir.getPath(), "-include", "p.+|Test.class"}); + test(new File(testDir, "Test.class"), + new String[] {"java.lang.Object", "java.lang.String", "p.Foo"}, + new String[] {"compact1", "compact1", testDir.getName()}, + new String[] {"-v", "-classpath", testDir.getPath(), "Test.class"}); return errors; } --- old/test/tools/jdeps/Test.java 2013-10-10 23:35:40.000000000 -0700 +++ new/test/tools/jdeps/Test.java 2013-10-10 23:35:40.000000000 -0700 @@ -25,4 +25,7 @@ public void test() { p.Foo f = new p.Foo(); } + private String name() { + return "this test"; + } } --- old/src/share/classes/com/sun/tools/jdeps/Profiles.java 2013-10-10 23:35:41.000000000 -0700 +++ /dev/null 2013-10-10 23:35:41.000000000 -0700 @@ -1,241 +0,0 @@ -/* - * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package com.sun.tools.jdeps; - -import com.sun.tools.classfile.Annotation; -import com.sun.tools.classfile.Annotation.*; -import com.sun.tools.classfile.Attribute; -import com.sun.tools.classfile.ClassFile; -import com.sun.tools.classfile.ConstantPool; -import com.sun.tools.classfile.ConstantPool.*; -import com.sun.tools.classfile.ConstantPoolException; -import com.sun.tools.classfile.RuntimeAnnotations_attribute; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.*; -import java.util.jar.JarFile; - -/** - * Build the profile information from ct.sym if exists. - */ -class Profiles { - private static final Map map = initProfiles(); - /** - * Returns the name of the profile for the given package name. - * It returns an empty string if the given package is not in any profile. - */ - public static String getProfileName(String pn) { - Profile profile = map.get(pn); - return (profile != null && profile.packages.contains(pn)) - ? profile.name : ""; - } - - public static int getProfileCount() { - return new HashSet(map.values()).size(); - } - - private static Map initProfiles() { - List profiles = new ArrayList(); - try { - String profilesProps = System.getProperty("jdeps.profiles"); - if (profilesProps != null) { - // for testing for JDK development build where ct.sym doesn't exist - initProfilesFromProperties(profiles, profilesProps); - } else { - Path home = Paths.get(System.getProperty("java.home")); - if (home.endsWith("jre")) { - home = home.getParent(); - } - Path ctsym = home.resolve("lib").resolve("ct.sym"); - if (ctsym.toFile().exists()) { - // add a default Full JRE - profiles.add(0, new Profile("Full JRE", 0)); - // parse ct.sym and load information about profiles - try (JarFile jf = new JarFile(ctsym.toFile())) { - ClassFileReader reader = ClassFileReader.newInstance(ctsym, jf); - for (ClassFile cf : reader.getClassFiles()) { - findProfile(profiles, cf); - } - } - - // merge the last Profile with the "Full JRE" - if (profiles.size() > 1) { - Profile fullJRE = profiles.get(0); - Profile p = profiles.remove(profiles.size() - 1); - for (String pn : fullJRE.packages) { - // The last profile contains the packages determined from ct.sym. - // Move classes annotated profile==0 or no attribute that are - // added in the fullJRE profile to either supported or proprietary - // packages appropriately - if (p.proprietaryPkgs.contains(pn)) { - p.proprietaryPkgs.add(pn); - } else { - p.packages.add(pn); - } - } - fullJRE.packages.clear(); - fullJRE.proprietaryPkgs.clear(); - fullJRE.packages.addAll(p.packages); - fullJRE.proprietaryPkgs.addAll(p.proprietaryPkgs); - } - } - } - } catch (IOException | ConstantPoolException e) { - throw new Error(e); - } - HashMap map = new HashMap(); - for (Profile profile : profiles) { - // Inner classes are not annotated with the profile annotation - // packages may be in one profile but also appear in the Full JRE - // Full JRE is always the first element in profiles list and - // so the map will contain the appropriate Profile - for (String pn : profile.packages) { - map.put(pn, profile); - } - for (String pn : profile.proprietaryPkgs) { - map.put(pn, profile); - } - } - return map; - } - - private static final String PROFILE_ANNOTATION = "Ljdk/Profile+Annotation;"; - private static final String PROPRIETARY_ANNOTATION = "Lsun/Proprietary+Annotation;"; - private static Profile findProfile(List profiles, ClassFile cf) - throws ConstantPoolException - { - RuntimeAnnotations_attribute attr = (RuntimeAnnotations_attribute) - cf.attributes.get(Attribute.RuntimeInvisibleAnnotations); - int index = 0; - boolean proprietary = false; - if (attr != null) { - for (int i = 0; i < attr.annotations.length; i++) { - Annotation ann = attr.annotations[i]; - String annType = cf.constant_pool.getUTF8Value(ann.type_index); - if (PROFILE_ANNOTATION.equals(annType)) { - for (int j = 0; j < ann.num_element_value_pairs; j++) { - Annotation.element_value_pair pair = ann.element_value_pairs[j]; - Primitive_element_value ev = (Primitive_element_value)pair.value; - CONSTANT_Integer_info info = (CONSTANT_Integer_info) - cf.constant_pool.get(ev.const_value_index); - index = info.value; - break; - } - } else if (PROPRIETARY_ANNOTATION.equals(annType)) { - proprietary = true; - } - } - if (index >= profiles.size()) { - Profile p = null; - for (int i = profiles.size(); i <= index; i++) { - p = new Profile(i); - profiles.add(p); - } - } - } - - Profile p = profiles.get(index); - String name = cf.getName(); - int i = name.lastIndexOf('/'); - name = (i > 0) ? name.substring(0, i).replace('/','.') : ""; - if (proprietary) { - p.proprietaryPkgs.add(name); - } else { - p.packages.add(name); - } - return p; - } - - private static void initProfilesFromProperties(List profiles, String path) - throws IOException - { - Properties props = new Properties(); - try (FileReader reader = new FileReader(path)) { - props.load(reader); - } - int i=1; - String key; - while (props.containsKey((key = "profile." + i + ".name"))) { - Profile profile = new Profile(props.getProperty(key), i); - profiles.add(profile); - String n = props.getProperty("profile." + i + ".packages"); - String[] pkgs = n.split("\\s+"); - for (String p : pkgs) { - if (p.isEmpty()) continue; - profile.packages.add(p); - } - i++; - } - } - - private static class Profile { - final String name; - final int profile; - final Set packages; - final Set proprietaryPkgs; - Profile(int profile) { - this("compact" + profile, profile); - } - Profile(String name, int profile) { - this.name = name; - this.profile = profile; - this.packages = new HashSet(); - this.proprietaryPkgs = new HashSet(); - } - public String toString() { - return name; - } - } - - // for debugging - public static void main(String[] args) { - if (args.length == 0) { - Profile[] profiles = new Profile[getProfileCount()]; - for (Profile p : map.values()) { - // move the zeroth profile to the last - int index = p.profile == 0 ? profiles.length-1 : p.profile-1; - profiles[index] = p; - } - for (Profile p : profiles) { - String profileName = p.name; - SortedSet set = new TreeSet(p.packages); - for (String s : set) { - // filter out the inner classes that are not annotated with - // the profile annotation - if (map.get(s) == p) { - System.out.format("%-10s %s%n", profileName, s); - profileName = ""; - } - } - } - } - for (String pn : args) { - System.out.format("%s in %s%n", pn, getProfileName(pn)); - } - } -} --- /dev/null 2013-10-10 23:35:41.000000000 -0700 +++ new/src/share/classes/com/sun/tools/jdeps/Profile.java 2013-10-10 23:35:41.000000000 -0700 @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.sun.tools.jdeps; + +import com.sun.tools.classfile.Annotation; +import com.sun.tools.classfile.Annotation.*; +import com.sun.tools.classfile.Attribute; +import com.sun.tools.classfile.ClassFile; +import com.sun.tools.classfile.ConstantPool.*; +import com.sun.tools.classfile.ConstantPoolException; +import com.sun.tools.classfile.RuntimeAnnotations_attribute; +import java.io.FileReader; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; +import java.util.jar.JarFile; + +/** + * Build the profile information from ct.sym if exists. + */ +enum Profile { + + COMPACT1("compact1", 1), + COMPACT2("compact2", 2), + COMPACT3("compact3", 3), + FULL_JRE("Full JRE", 4); + + final String name; + final int profile; + final Set packages; + final Set proprietaryPkgs; + + Profile(String name, int profile) { + this.name = name; + this.profile = profile; + this.packages = new HashSet<>(); + this.proprietaryPkgs = new HashSet<>(); + } + + @Override + public String toString() { + return name; + } + + public static int getProfileCount() { + return PackageToProfile.map.values().size(); + } + + /** + * Returns the Profile for the given package name. It returns an empty + * string if the given package is not in any profile. + */ + public static Profile getProfile(String pn) { + Profile profile = PackageToProfile.map.get(pn); + return (profile != null && profile.packages.contains(pn)) + ? profile : null; + } + + static class PackageToProfile { + static Map map = initProfiles(); + + private static Map initProfiles() { + try { + String profilesProps = System.getProperty("jdeps.profiles"); + if (profilesProps != null) { + // for testing for JDK development build where ct.sym doesn't exist + initProfilesFromProperties(profilesProps); + } else { + Path home = Paths.get(System.getProperty("java.home")); + if (home.endsWith("jre")) { + home = home.getParent(); + } + Path ctsym = home.resolve("lib").resolve("ct.sym"); + if (Files.exists(ctsym)) { + // parse ct.sym and load information about profiles + try (JarFile jf = new JarFile(ctsym.toFile())) { + ClassFileReader reader = ClassFileReader.newInstance(ctsym, jf); + for (ClassFile cf : reader.getClassFiles()) { + findProfile(cf); + } + } + } + } + } catch (IOException | ConstantPoolException e) { + throw new Error(e); + } + HashMap map = new HashMap<>(); + for (Profile profile : Profile.values()) { + for (String pn : profile.packages) { + if (!map.containsKey(pn)) { + // split packages in the JRE: use the smaller compact + map.put(pn, profile); + } + } + for (String pn : profile.proprietaryPkgs) { + if (!map.containsKey(pn)) { + map.put(pn, profile); + } + } + } + return map; + } + private static final String PROFILE_ANNOTATION = "Ljdk/Profile+Annotation;"; + private static final String PROPRIETARY_ANNOTATION = "Lsun/Proprietary+Annotation;"; + private static Profile findProfile(ClassFile cf) throws ConstantPoolException { + RuntimeAnnotations_attribute attr = (RuntimeAnnotations_attribute) + cf.attributes.get(Attribute.RuntimeInvisibleAnnotations); + int index = 0; + boolean proprietary = false; + if (attr != null) { + for (int i = 0; i < attr.annotations.length; i++) { + Annotation ann = attr.annotations[i]; + String annType = cf.constant_pool.getUTF8Value(ann.type_index); + if (PROFILE_ANNOTATION.equals(annType)) { + for (int j = 0; j < ann.num_element_value_pairs; j++) { + Annotation.element_value_pair pair = ann.element_value_pairs[j]; + Primitive_element_value ev = (Primitive_element_value) pair.value; + CONSTANT_Integer_info info = (CONSTANT_Integer_info) + cf.constant_pool.get(ev.const_value_index); + index = info.value; + break; + } + } else if (PROPRIETARY_ANNOTATION.equals(annType)) { + proprietary = true; + } + } + } + + Profile p = null; // default + switch (index) { + case 1: + p = Profile.COMPACT1; break; + case 2: + p = Profile.COMPACT2; break; + case 3: + p = Profile.COMPACT3; break; + case 4: + p = Profile.FULL_JRE; break; + default: + // skip classes with profile=0 + // Inner classes are not annotated with the profile annotation + return null; + } + + String name = cf.getName(); + int i = name.lastIndexOf('/'); + name = (i > 0) ? name.substring(0, i).replace('/', '.') : ""; + if (proprietary) { + p.proprietaryPkgs.add(name); + } else { + p.packages.add(name); + } + return p; + } + + private static void initProfilesFromProperties(String path) throws IOException { + Properties props = new Properties(); + try (FileReader reader = new FileReader(path)) { + props.load(reader); + } + for (Profile prof : Profile.values()) { + int i = prof.profile; + String key = props.getProperty("profile." + i + ".name"); + if (key == null) { + throw new RuntimeException(key + " missing in " + path); + } + String n = props.getProperty("profile." + i + ".packages"); + String[] pkgs = n.split("\\s+"); + for (String p : pkgs) { + if (p.isEmpty()) continue; + prof.packages.add(p); + } + } + } + } + + // for debugging + public static void main(String[] args) { + if (args.length == 0) { + if (Profile.getProfileCount() == 0) { + System.err.println("No profile is present in this JDK"); + } + for (Profile p : Profile.values()) { + String profileName = p.name; + SortedSet set = new TreeSet<>(p.packages); + for (String s : set) { + // filter out the inner classes that are not annotated with + // the profile annotation + if (PackageToProfile.map.get(s) == p) { + System.out.format("%2d: %-10s %s%n", p.profile, profileName, s); + profileName = ""; + } else { + System.err.format("Split package: %s in %s and %s %n", + s, PackageToProfile.map.get(s).name, p.name); + } + } + } + } + for (String pn : args) { + System.out.format("%s in %s%n", pn, getProfile(pn)); + } + } +} --- /dev/null 2013-10-10 23:35:41.000000000 -0700 +++ new/test/tools/jdeps/APIDeps.java 2013-10-10 23:35:41.000000000 -0700 @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8015912 + * @summary find API dependencies + * @build m.Bar m.Foo m.Gee b.B c.C c.I d.D e.E f.F g.G + * @run main APIDeps + */ + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; +import java.util.regex.*; + +public class APIDeps { + private static boolean symbolFileExist = initProfiles(); + private static boolean initProfiles() { + // check if ct.sym exists; if not use the profiles.properties file + Path home = Paths.get(System.getProperty("java.home")); + if (home.endsWith("jre")) { + home = home.getParent(); + } + Path ctsym = home.resolve("lib").resolve("ct.sym"); + boolean symbolExists = ctsym.toFile().exists(); + if (!symbolExists) { + Path testSrcProfiles = + Paths.get(System.getProperty("test.src", "."), "profiles.properties"); + if (!testSrcProfiles.toFile().exists()) + throw new Error(testSrcProfiles + " does not exist"); + System.out.format("%s doesn't exist.%nUse %s to initialize profiles info%n", + ctsym, testSrcProfiles); + System.setProperty("jdeps.profiles", testSrcProfiles.toString()); + } + return symbolExists; + } + + public static void main(String... args) throws Exception { + int errors = 0; + errors += new APIDeps().run(); + if (errors > 0) + throw new Exception(errors + " errors found"); + } + + int run() throws IOException { + File testDir = new File(System.getProperty("test.classes", ".")); + String testDirBasename = testDir.toPath().getFileName().toString(); + File mDir = new File(testDir, "m"); + // all dependencies + test(new File(mDir, "Bar.class"), + new String[] {"java.lang.Object", "java.lang.String", + "java.util.Set", "java.util.HashSet", + "java.lang.management.ManagementFactory", + "java.lang.management.RuntimeMXBean", + "b.B", "c.C", "d.D", "f.F", "g.G"}, + new String[] {"compact1", "compact3", testDirBasename}, + new String[] {"-classpath", testDir.getPath(), "-verbose", "-P"}); + test(new File(mDir, "Foo.class"), + new String[] {"c.I", "e.E", "f.F", "m.Bar"}, + new String[] {testDirBasename}, + new String[] {"-classpath", testDir.getPath(), "-verbose", "-P"}); + test(new File(mDir, "Gee.class"), + new String[] {"g.G", "sun.misc.Lock"}, + new String[] {testDirBasename, "JDK internal API"}, + new String[] {"-classpath", testDir.getPath(), "-verbose"}); + // parse only APIs + test(mDir, + new String[] {"java.lang.Object", "java.lang.String", + "java.util.Set", + "c.C", "d.D", "c.I", "e.E", "m.Bar"}, + new String[] {"compact1", testDirBasename, mDir.getName()}, + new String[] {"-classpath", testDir.getPath(), "-verbose", "-P", "-apionly"}); + return errors; + } + + void test(File file, String[] expect, String[] profiles) { + test(file, expect, profiles, new String[0]); + } + + void test(File file, String[] expect, String[] profiles, String[] options) { + List args = new ArrayList<>(Arrays.asList(options)); + if (file != null) { + args.add(file.getPath()); + } + checkResult("api-dependencies", expect, profiles, + jdeps(args.toArray(new String[0]))); + } + + Map jdeps(String... args) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + System.err.println("jdeps " + Arrays.toString(args)); + int rc = com.sun.tools.jdeps.Main.run(args, pw); + pw.close(); + String out = sw.toString(); + if (!out.isEmpty()) + System.err.println(out); + if (rc != 0) + throw new Error("jdeps failed: rc=" + rc); + return findDeps(out); + } + + // Pattern used to parse lines + private static Pattern linePattern = Pattern.compile(".*\r?\n"); + private static Pattern pattern = Pattern.compile("\\s+ -> (\\S+) +(.*)"); + + // Use the linePattern to break the given String into lines, applying + // the pattern to each line to see if we have a match + private static Map findDeps(String out) { + Map result = new HashMap<>(); + Matcher lm = linePattern.matcher(out); // Line matcher + Matcher pm = null; // Pattern matcher + int lines = 0; + while (lm.find()) { + lines++; + CharSequence cs = lm.group(); // The current line + if (pm == null) + pm = pattern.matcher(cs); + else + pm.reset(cs); + if (pm.find()) + result.put(pm.group(1), pm.group(2).trim()); + if (lm.end() == out.length()) + break; + } + return result; + } + + void checkResult(String label, String[] expect, Collection found) { + List list = Arrays.asList(expect); + if (!isEqual(list, found)) + error("Unexpected " + label + " found: '" + found + "', expected: '" + list + "'"); + } + + void checkResult(String label, String[] expect, String[] profiles, Map result) { + // check the dependencies + checkResult(label, expect, result.keySet()); + // check profile information + Set values = new TreeSet<>(); + String internal = "JDK internal API"; + for (String s: result.values()) { + if (s.startsWith(internal)){ + values.add(internal); + } else { + values.add(s); + } + } + checkResult(label, profiles, values); + } + + boolean isEqual(List expected, Collection found) { + if (expected.size() != found.size()) + return false; + + List list = new ArrayList<>(found); + list.removeAll(expected); + return list.isEmpty(); + } + + void error(String msg) { + System.err.println("Error: " + msg); + errors++; + } + + int errors; +} --- /dev/null 2013-10-10 23:35:42.000000000 -0700 +++ new/test/tools/jdeps/b/B.java 2013-10-10 23:35:42.000000000 -0700 @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package b; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.*; + +@Retention(RetentionPolicy.RUNTIME) +@Target({TYPE, METHOD, FIELD}) +public @interface B { +} --- /dev/null 2013-10-10 23:35:42.000000000 -0700 +++ new/test/tools/jdeps/c/C.java 2013-10-10 23:35:42.000000000 -0700 @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package c; + +public class C { +} --- /dev/null 2013-10-10 23:35:43.000000000 -0700 +++ new/test/tools/jdeps/c/I.java 2013-10-10 23:35:43.000000000 -0700 @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package c; + +public interface I { + void run(); +} --- /dev/null 2013-10-10 23:35:43.000000000 -0700 +++ new/test/tools/jdeps/d/D.java 2013-10-10 23:35:43.000000000 -0700 @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package d; + +public class D { +} --- /dev/null 2013-10-10 23:35:44.000000000 -0700 +++ new/test/tools/jdeps/e/E.java 2013-10-10 23:35:44.000000000 -0700 @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package e; + +// use compact2 +public class E extends java.rmi.RemoteException { +} --- /dev/null 2013-10-10 23:35:44.000000000 -0700 +++ new/test/tools/jdeps/f/F.java 2013-10-10 23:35:44.000000000 -0700 @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package f; + +public class F { + public F() { + // jdk internal API + sun.misc.Unsafe.getUnsafe(); + } +} --- /dev/null 2013-10-10 23:35:45.000000000 -0700 +++ new/test/tools/jdeps/g/G.java 2013-10-10 23:35:45.000000000 -0700 @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package g; + +public class G { + // Full JRE + private static final boolean gui = java.beans.Beans.isGuiAvailable(); +} --- /dev/null 2013-10-10 23:35:46.000000000 -0700 +++ new/test/tools/jdeps/m/Bar.java 2013-10-10 23:35:45.000000000 -0700 @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package m; + +import java.util.*; + +@b.B +public class Bar { + public final Set set = new HashSet<>(); + protected d.D d; + private f.F f; + + public Bar() { + // compact3 + java.lang.management.ManagementFactory.getRuntimeMXBean(); + } + + protected c.C c() { + return new c.C(); + } + + /* package private */ void setF(f.F o) { + f = o; + } + + private g.G g() { + return new g.G(); + } +} --- /dev/null 2013-10-10 23:35:46.000000000 -0700 +++ new/test/tools/jdeps/m/Foo.java 2013-10-10 23:35:46.000000000 -0700 @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package m; + +public class Foo extends Bar implements c.I { + public void foo() throws e.E { + } + public void run() { + setF(new f.F()); + } +} + --- /dev/null 2013-10-10 23:35:47.000000000 -0700 +++ new/test/tools/jdeps/m/Gee.java 2013-10-10 23:35:46.000000000 -0700 @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package m; + + +class Gee extends g.G { + public sun.misc.Lock lock; +} +