8029548: (jdeps) use @jdk.Exported to determine supported vs JDK internal API
8031092: jdeps does not recognize --help option.
8048063: (jdeps) Add filtering capability
Reviewed-by: alanb, dfuchs
--- a/langtools/src/share/classes/com/sun/tools/jdeps/Analyzer.java Tue Jul 08 15:42:04 2014 +0100
+++ b/langtools/src/share/classes/com/sun/tools/jdeps/Analyzer.java Tue Jul 08 18:26:34 2014 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2014, 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
@@ -26,16 +26,13 @@
import com.sun.tools.classfile.Dependency.Location;
import com.sun.tools.jdeps.PlatformClassPath.JDKArchive;
-import java.util.ArrayList;
+import java.util.Comparator;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
-import java.util.SortedMap;
-import java.util.SortedSet;
-import java.util.TreeMap;
-import java.util.TreeSet;
/**
* Dependency Analyzer.
@@ -52,7 +49,16 @@
VERBOSE
}
+ /**
+ * Filter to be applied when analyzing the dependencies from the given archives.
+ * Only the accepted dependencies are recorded.
+ */
+ interface Filter {
+ boolean accepts(Location origin, Archive originArchive, Location target, Archive targetArchive);
+ }
+
private final Type type;
+ private final Filter filter;
private final Map<Archive, ArchiveDeps> results = new HashMap<>();
private final Map<Location, Archive> map = new HashMap<>();
private final Archive NOT_FOUND
@@ -62,9 +68,11 @@
* Constructs an Analyzer instance.
*
* @param type Type of the dependency analysis
+ * @param filter
*/
- public Analyzer(Type type) {
+ public Analyzer(Type type, Filter filter) {
this.type = type;
+ this.filter = filter;
}
/**
@@ -72,6 +80,18 @@
*/
public void run(List<Archive> archives) {
// build a map from Location to Archive
+ buildLocationArchiveMap(archives);
+
+ // traverse and analyze all dependencies
+ for (Archive archive : archives) {
+ ArchiveDeps deps = new ArchiveDeps(archive, type);
+ archive.visitDependences(deps);
+ results.put(archive, deps);
+ }
+ }
+
+ private void buildLocationArchiveMap(List<Archive> archives) {
+ // build a map from Location to Archive
for (Archive archive: archives) {
for (Location l: archive.getClasses()) {
if (!map.containsKey(l)) {
@@ -81,190 +101,202 @@
}
}
}
- // traverse and analyze all dependencies
- for (Archive archive : archives) {
- ArchiveDeps deps;
- if (type == Type.CLASS || type == Type.VERBOSE) {
- deps = new ClassVisitor(archive);
- } else {
- deps = new PackageVisitor(archive);
- }
- archive.visitDependences(deps);
- results.put(archive, deps);
- }
}
public boolean hasDependences(Archive archive) {
if (results.containsKey(archive)) {
- return results.get(archive).deps.size() > 0;
+ return results.get(archive).dependencies().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, Profile profile);
- /**
* Visits a recorded dependency from origin to target which can be
- * a fully-qualified classname, a package name, a profile or
+ * a fully-qualified classname, a package name, a module or
* archive name depending on the Analyzer's type.
*/
- void visitDependence(String origin, Archive source, String target, Archive archive, Profile profile);
+ public void visitDependence(String origin, Archive originArchive,
+ String target, Archive targetArchive);
}
- public void visitArchiveDependences(Archive source, Visitor v) {
- ArchiveDeps r = results.get(source);
- for (ArchiveDeps.Dep d: r.requireArchives()) {
- v.visitArchiveDependence(r.archive, d.archive, d.profile);
+ /**
+ * Visit the dependencies of the given source.
+ * If the requested level is SUMMARY, it will visit the required archives list.
+ */
+ public void visitDependences(Archive source, Visitor v, Type level) {
+ if (level == Type.SUMMARY) {
+ final ArchiveDeps result = results.get(source);
+ result.requires().stream()
+ .sorted(Comparator.comparing(Archive::getName))
+ .forEach(archive -> {
+ Profile profile = result.getTargetProfile(archive);
+ v.visitDependence(source.getName(), source,
+ profile != null ? profile.profileName() : archive.getName(), archive);
+ });
+ } else {
+ ArchiveDeps result = results.get(source);
+ if (level != type) {
+ // requesting different level of analysis
+ result = new ArchiveDeps(source, level);
+ source.visitDependences(result);
+ }
+ result.dependencies().stream()
+ .sorted(Comparator.comparing(Dep::origin)
+ .thenComparing(Dep::target))
+ .forEach(d -> v.visitDependence(d.origin(), d.originArchive(), d.target(), d.targetArchive()));
}
}
public void visitDependences(Archive source, Visitor v) {
- ArchiveDeps r = results.get(source);
- for (Map.Entry<String, SortedSet<ArchiveDeps.Dep>> e: r.deps.entrySet()) {
- String origin = e.getKey();
- for (ArchiveDeps.Dep d: e.getValue()) {
- // filter intra-dependency unless in verbose mode
- if (type == Type.VERBOSE || d.archive != source) {
- v.visitDependence(origin, source, d.target, d.archive, d.profile);
- }
- }
- }
+ visitDependences(source, v, type);
}
/**
- * ArchiveDeps contains the dependencies for an Archive that
- * can have one or more classes.
+ * ArchiveDeps contains the dependencies for an Archive that can have one or
+ * more classes.
*/
- private abstract class ArchiveDeps implements Archive.Visitor {
- final Archive archive;
- final SortedMap<String, SortedSet<Dep>> deps;
- ArchiveDeps(Archive archive) {
+ class ArchiveDeps implements Archive.Visitor {
+ protected final Archive archive;
+ protected final Set<Archive> requires;
+ protected final Set<Dep> deps;
+ protected final Type level;
+ private Profile profile;
+ ArchiveDeps(Archive archive, Type level) {
this.archive = archive;
- this.deps = new TreeMap<>();
- }
-
- void add(String origin, String target, Archive targetArchive, String pkgName) {
- SortedSet<Dep> set = deps.get(origin);
- if (set == null) {
- deps.put(origin, set = new TreeSet<>());
- }
- Profile p = targetArchive instanceof JDKArchive
- ? Profile.getProfile(pkgName) : null;
- set.add(new Dep(target, targetArchive, p));
+ this.deps = new HashSet<>();
+ this.requires = new HashSet<>();
+ this.level = level;
}
- /**
- * Returns the list of Archive dependences. The returned
- * list contains one {@code Dep} instance per one archive
- * and with the minimum profile this archive depends on.
- */
- List<Dep> requireArchives() {
- Map<Archive,Profile> map = new HashMap<>();
- for (Set<Dep> set: deps.values()) {
- for (Dep d: set) {
- if (this.archive != d.archive) {
- Profile p = map.get(d.archive);
- if (p == null || (d.profile != null && p.profile < d.profile.profile)) {
- map.put(d.archive, d.profile);
- }
- }
- }
+ Set<Dep> dependencies() {
+ return deps;
+ }
+
+ Set<Archive> requires() {
+ return requires;
+ }
+
+ Profile getTargetProfile(Archive target) {
+ return JDKArchive.isProfileArchive(target) ? profile : null;
+ }
+
+ Archive findArchive(Location t) {
+ Archive target = archive.getClasses().contains(t) ? archive : map.get(t);
+ if (target == null) {
+ map.put(t, target = NOT_FOUND);
}
- List<Dep> list = new ArrayList<>();
- for (Map.Entry<Archive,Profile> e: map.entrySet()) {
- list.add(new Dep("", e.getKey(), e.getValue()));
- }
- return list;
+ return target;
}
- /**
- * Dep represents a dependence where the target can be
- * a classname or packagename and the archive and profile
- * the target belongs to.
- */
- class Dep implements Comparable<Dep> {
- final String target;
- final Archive archive;
- final Profile profile;
- Dep(String target, Archive archive, Profile p) {
- this.target = target;
- this.archive = archive;
- this.profile = p;
+ // return classname or package name depedning on the level
+ private String getLocationName(Location o) {
+ if (level == Type.CLASS || level == Type.VERBOSE) {
+ return o.getClassName();
+ } else {
+ String pkg = o.getPackageName();
+ return pkg.isEmpty() ? "<unnamed>" : pkg;
+ }
+ }
+
+ @Override
+ public void visit(Location o, Location t) {
+ Archive targetArchive = findArchive(t);
+ if (filter.accepts(o, archive, t, targetArchive)) {
+ addDep(o, t);
+ if (!requires.contains(targetArchive)) {
+ requires.add(targetArchive);
+ }
}
+ if (targetArchive instanceof JDKArchive) {
+ Profile p = Profile.getProfile(t.getPackageName());
+ if (profile == null || (p != null && p.compareTo(profile) > 0)) {
+ profile = p;
+ }
+ }
+ }
- @Override
- public boolean equals(Object o) {
- if (o instanceof Dep) {
- Dep d = (Dep)o;
- return this.archive == d.archive && this.target.equals(d.target);
- }
- return false;
+ private Dep curDep;
+ protected Dep addDep(Location o, Location t) {
+ String origin = getLocationName(o);
+ String target = getLocationName(t);
+ Archive targetArchive = findArchive(t);
+ if (curDep != null &&
+ curDep.origin().equals(origin) &&
+ curDep.originArchive() == archive &&
+ curDep.target().equals(target) &&
+ curDep.targetArchive() == targetArchive) {
+ return curDep;
}
- @Override
- public int hashCode() {
- int hash = 3;
- hash = 17 * hash + Objects.hashCode(this.archive);
- hash = 17 * hash + Objects.hashCode(this.target);
- return hash;
- }
-
- @Override
- public int compareTo(Dep o) {
- if (this.target.equals(o.target)) {
- if (this.archive == o.archive) {
- return 0;
- } else {
- return this.archive.getFileName().compareTo(o.archive.getFileName());
+ Dep e = new Dep(origin, archive, target, targetArchive);
+ if (deps.contains(e)) {
+ for (Dep e1 : deps) {
+ if (e.equals(e1)) {
+ curDep = e1;
}
}
- return this.target.compareTo(o.target);
+ } else {
+ deps.add(e);
+ curDep = e;
}
- }
- public abstract void visit(Location o, Location t);
- }
-
- private class ClassVisitor extends ArchiveDeps {
- ClassVisitor(Archive archive) {
- super(archive);
- }
- @Override
- public void visit(Location o, Location t) {
- Archive targetArchive =
- this.archive.getClasses().contains(t) ? this.archive : map.get(t);
- if (targetArchive == null) {
- map.put(t, targetArchive = NOT_FOUND);
- }
-
- String origin = o.getClassName();
- String target = t.getClassName();
- add(origin, target, targetArchive, t.getPackageName());
+ return curDep;
}
}
- private class PackageVisitor extends ArchiveDeps {
- PackageVisitor(Archive archive) {
- super(archive);
+ /*
+ * Class-level or package-level dependency
+ */
+ class Dep {
+ final String origin;
+ final Archive originArchive;
+ final String target;
+ final Archive targetArchive;
+
+ Dep(String origin, Archive originArchive, String target, Archive targetArchive) {
+ this.origin = origin;
+ this.originArchive = originArchive;
+ this.target = target;
+ this.targetArchive = targetArchive;
}
+
+ String origin() {
+ return origin;
+ }
+
+ Archive originArchive() {
+ return originArchive;
+ }
+
+ String target() {
+ return target;
+ }
+
+ Archive targetArchive() {
+ return targetArchive;
+ }
+
@Override
- public void visit(Location o, Location t) {
- Archive targetArchive =
- this.archive.getClasses().contains(t) ? this.archive : map.get(t);
- if (targetArchive == null) {
- map.put(t, targetArchive = NOT_FOUND);
+ @SuppressWarnings("unchecked")
+ public boolean equals(Object o) {
+ if (o instanceof Dep) {
+ Dep d = (Dep) o;
+ return this.origin.equals(d.origin) &&
+ this.originArchive == d.originArchive &&
+ this.target.equals(d.target) &&
+ this.targetArchive == d.targetArchive;
}
+ return false;
+ }
- String origin = packageOf(o);
- String target = packageOf(t);
- add(origin, target, targetArchive, t.getPackageName());
- }
- public String packageOf(Location o) {
- String pkg = o.getPackageName();
- return pkg.isEmpty() ? "<unnamed>" : pkg;
+ @Override
+ public int hashCode() {
+ int hash = 7;
+ hash = 67*hash + Objects.hashCode(this.origin)
+ + Objects.hashCode(this.originArchive)
+ + Objects.hashCode(this.target)
+ + Objects.hashCode(this.targetArchive);
+ return hash;
}
}
}
--- a/langtools/src/share/classes/com/sun/tools/jdeps/Archive.java Tue Jul 08 15:42:04 2014 +0100
+++ b/langtools/src/share/classes/com/sun/tools/jdeps/Archive.java Tue Jul 08 18:26:34 2014 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2014, 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
@@ -25,28 +25,34 @@
package com.sun.tools.jdeps;
import com.sun.tools.classfile.Dependency.Location;
+
+import java.io.IOException;
import java.nio.file.Path;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
/**
* Represents the source of the class files.
*/
public class Archive {
+ public static Archive getInstance(Path p) throws IOException {
+ return new Archive(p, ClassFileReader.newInstance(p));
+ }
+
private final Path path;
private final String filename;
private final ClassFileReader reader;
- private final Map<Location, Set<Location>> deps = new HashMap<>();
+ protected Map<Location, Set<Location>> deps = new ConcurrentHashMap<>();
- public Archive(String name) {
+ protected Archive(String name) {
this.path = null;
this.filename = name;
this.reader = null;
}
- public Archive(Path p, ClassFileReader reader) {
+ protected Archive(Path p, ClassFileReader reader) {
this.path = p;
this.filename = path.getFileName().toString();
this.reader = reader;
@@ -56,7 +62,7 @@
return reader;
}
- public String getFileName() {
+ public String getName() {
return filename;
}
@@ -89,6 +95,10 @@
}
}
+ public boolean isEmpty() {
+ return getClasses().isEmpty();
+ }
+
public String getPathName() {
return path != null ? path.toString() : filename;
}
--- a/langtools/src/share/classes/com/sun/tools/jdeps/ClassFileReader.java Tue Jul 08 15:42:04 2014 +0100
+++ b/langtools/src/share/classes/com/sun/tools/jdeps/ClassFileReader.java Tue Jul 08 18:26:34 2014 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2014, 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
@@ -68,7 +68,8 @@
protected final Path path;
protected final String baseFileName;
- private ClassFileReader(Path path) {
+ protected final List<String> skippedEntries = new ArrayList<>();
+ protected ClassFileReader(Path path) {
this.path = path;
this.baseFileName = path.getFileName() != null
? path.getFileName().toString()
@@ -79,6 +80,10 @@
return baseFileName;
}
+ public List<String> skippedEntries() {
+ return skippedEntries;
+ }
+
/**
* Returns the ClassFile matching the given binary name
* or a fully-qualified class name.
@@ -232,11 +237,12 @@
}
}
- private static class JarFileReader extends ClassFileReader {
- final JarFile jarfile;
+ static class JarFileReader extends ClassFileReader {
+ private final JarFile jarfile;
JarFileReader(Path path) throws IOException {
- this(path, new JarFile(path.toFile()));
+ this(path, new JarFile(path.toFile(), false));
}
+
JarFileReader(Path path, JarFile jf) throws IOException {
super(path);
this.jarfile = jf;
@@ -252,18 +258,18 @@
+ entryName.substring(i + 1, entryName.length()));
}
if (e != null) {
- return readClassFile(e);
+ return readClassFile(jarfile, e);
}
} else {
JarEntry e = jarfile.getJarEntry(name + ".class");
if (e != null) {
- return readClassFile(e);
+ return readClassFile(jarfile, e);
}
}
return null;
}
- private ClassFile readClassFile(JarEntry e) throws IOException {
+ protected ClassFile readClassFile(JarFile jarfile, JarEntry e) throws IOException {
InputStream is = null;
try {
is = jarfile.getInputStream(e);
@@ -277,60 +283,76 @@
}
public Iterable<ClassFile> getClassFiles() throws IOException {
- final Iterator<ClassFile> iter = new JarFileIterator();
+ final Iterator<ClassFile> iter = new JarFileIterator(this, jarfile);
return new Iterable<ClassFile>() {
public Iterator<ClassFile> iterator() {
return iter;
}
};
}
+ }
- class JarFileIterator implements Iterator<ClassFile> {
- private Enumeration<JarEntry> entries;
- private JarEntry nextEntry;
- JarFileIterator() {
- this.entries = jarfile.entries();
- while (entries.hasMoreElements()) {
- JarEntry e = entries.nextElement();
- String name = e.getName();
- if (name.endsWith(".class")) {
- this.nextEntry = e;
- break;
- }
+ class JarFileIterator implements Iterator<ClassFile> {
+ protected final JarFileReader reader;
+ protected Enumeration<JarEntry> entries;
+ protected JarFile jf;
+ protected JarEntry nextEntry;
+ protected ClassFile cf;
+ JarFileIterator(JarFileReader reader) {
+ this(reader, null);
+ }
+ JarFileIterator(JarFileReader reader, JarFile jarfile) {
+ this.reader = reader;
+ setJarFile(jarfile);
+ }
+
+ void setJarFile(JarFile jarfile) {
+ if (jarfile == null) return;
+
+ this.jf = jarfile;
+ this.entries = jf.entries();
+ this.nextEntry = nextEntry();
+ }
+
+ public boolean hasNext() {
+ if (nextEntry != null && cf != null) {
+ return true;
+ }
+ while (nextEntry != null) {
+ try {
+ cf = reader.readClassFile(jf, nextEntry);
+ return true;
+ } catch (ClassFileError | IOException ex) {
+ skippedEntries.add(nextEntry.getName());
+ }
+ nextEntry = nextEntry();
+ }
+ return false;
+ }
+
+ public ClassFile next() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+ ClassFile classFile = cf;
+ cf = null;
+ nextEntry = nextEntry();
+ return classFile;
+ }
+
+ protected JarEntry nextEntry() {
+ while (entries.hasMoreElements()) {
+ JarEntry e = entries.nextElement();
+ String name = e.getName();
+ if (name.endsWith(".class")) {
+ return e;
}
}
-
- public boolean hasNext() {
- return nextEntry != null;
- }
-
- public ClassFile next() {
- if (!hasNext()) {
- throw new NoSuchElementException();
- }
+ return null;
+ }
- ClassFile cf;
- try {
- cf = readClassFile(nextEntry);
- } catch (IOException ex) {
- throw new ClassFileError(ex);
- }
- JarEntry entry = nextEntry;
- nextEntry = null;
- while (entries.hasMoreElements()) {
- JarEntry e = entries.nextElement();
- String name = e.getName();
- if (name.endsWith(".class")) {
- nextEntry = e;
- break;
- }
- }
- return cf;
- }
-
- public void remove() {
- throw new UnsupportedOperationException("Not supported yet.");
- }
+ public void remove() {
+ throw new UnsupportedOperationException("Not supported yet.");
}
}
}
--- a/langtools/src/share/classes/com/sun/tools/jdeps/JdepsTask.java Tue Jul 08 15:42:04 2014 +0100
+++ b/langtools/src/share/classes/com/sun/tools/jdeps/JdepsTask.java Tue Jul 08 18:26:34 2014 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2014, 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
@@ -30,7 +30,9 @@
import com.sun.tools.classfile.Dependencies;
import com.sun.tools.classfile.Dependencies.ClassFileError;
import com.sun.tools.classfile.Dependency;
+import com.sun.tools.classfile.Dependency.Location;
import com.sun.tools.jdeps.PlatformClassPath.JDKArchive;
+import static com.sun.tools.jdeps.Analyzer.Type.*;
import java.io.*;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
@@ -110,7 +112,7 @@
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);
+ throw new BadArgs("err.invalid.path", arg);
}
task.options.dotOutputDir = arg;
}
@@ -118,25 +120,26 @@
new Option(false, "-s", "-summary") {
void process(JdepsTask task, String opt, String arg) {
task.options.showSummary = true;
- task.options.verbose = Analyzer.Type.SUMMARY;
+ task.options.verbose = SUMMARY;
}
},
new Option(false, "-v", "-verbose",
"-verbose:package",
- "-verbose:class")
- {
+ "-verbose:class") {
void process(JdepsTask task, String opt, String arg) throws BadArgs {
switch (opt) {
case "-v":
case "-verbose":
- task.options.verbose = Analyzer.Type.VERBOSE;
+ task.options.verbose = VERBOSE;
+ task.options.filterSameArchive = false;
+ task.options.filterSamePackage = false;
break;
case "-verbose:package":
- task.options.verbose = Analyzer.Type.PACKAGE;
- break;
+ task.options.verbose = PACKAGE;
+ break;
case "-verbose:class":
- task.options.verbose = Analyzer.Type.CLASS;
- break;
+ task.options.verbose = CLASS;
+ break;
default:
throw new BadArgs("err.invalid.arg.for.option", opt);
}
@@ -157,6 +160,32 @@
task.options.regex = arg;
}
},
+
+ new Option(true, "-f", "-filter") {
+ void process(JdepsTask task, String opt, String arg) {
+ task.options.filterRegex = arg;
+ }
+ },
+ new Option(false, "-filter:package",
+ "-filter:archive",
+ "-filter:none") {
+ void process(JdepsTask task, String opt, String arg) {
+ switch (opt) {
+ case "-filter:package":
+ task.options.filterSamePackage = true;
+ task.options.filterSameArchive = false;
+ break;
+ case "-filter:archive":
+ task.options.filterSameArchive = true;
+ task.options.filterSamePackage = false;
+ break;
+ case "-filter:none":
+ task.options.filterSameArchive = false;
+ task.options.filterSamePackage = false;
+ break;
+ }
+ }
+ },
new Option(true, "-include") {
void process(JdepsTask task, String opt, String arg) throws BadArgs {
task.options.includePattern = Pattern.compile(arg);
@@ -178,12 +207,15 @@
new Option(false, "-R", "-recursive") {
void process(JdepsTask task, String opt, String arg) {
task.options.depth = 0;
+ // turn off filtering
+ task.options.filterSameArchive = false;
+ task.options.filterSamePackage = false;
}
},
new Option(false, "-jdkinternals") {
void process(JdepsTask task, String opt, String arg) {
task.options.findJDKInternals = true;
- task.options.verbose = Analyzer.Type.CLASS;
+ task.options.verbose = CLASS;
if (task.options.includePattern == null) {
task.options.includePattern = Pattern.compile(".*");
}
@@ -262,7 +294,7 @@
showHelp();
return EXIT_CMDERR;
}
- if (options.showSummary && options.verbose != Analyzer.Type.SUMMARY) {
+ if (options.showSummary && options.verbose != SUMMARY) {
showHelp();
return EXIT_CMDERR;
}
@@ -283,9 +315,28 @@
private final List<Archive> sourceLocations = new ArrayList<>();
private boolean run() throws IOException {
+ // parse classfiles and find all dependencies
findDependencies();
- Analyzer analyzer = new Analyzer(options.verbose);
+
+ Analyzer analyzer = new Analyzer(options.verbose, new Analyzer.Filter() {
+ @Override
+ public boolean accepts(Location origin, Archive originArchive, Location target, Archive targetArchive) {
+ if (options.findJDKInternals) {
+ // accepts target that is JDK class but not exported
+ return isJDKArchive(targetArchive) &&
+ !((JDKArchive) targetArchive).isExported(target.getClassName());
+ } else if (options.filterSameArchive) {
+ // accepts origin and target that from different archive
+ return originArchive != targetArchive;
+ }
+ return true;
+ }
+ });
+
+ // analyze the dependencies
analyzer.run(sourceLocations);
+
+ // output result
if (options.dotOutputDir != null) {
Path dir = Paths.get(options.dotOutputDir);
Files.createDirectories(dir);
@@ -296,27 +347,34 @@
return true;
}
- private void generateDotFiles(Path dir, Analyzer analyzer) throws IOException {
+ private void generateSummaryDotFile(Path dir, Analyzer analyzer) throws IOException {
+ // If verbose mode (-v or -verbose option),
+ // the summary.dot file shows package-level dependencies.
+ Analyzer.Type summaryType =
+ (options.verbose == PACKAGE || options.verbose == SUMMARY) ? SUMMARY : PACKAGE;
Path summary = dir.resolve("summary.dot");
- boolean verbose = options.verbose == Analyzer.Type.VERBOSE;
- DotGraph<?> graph = verbose ? new DotSummaryForPackage()
- : new DotSummaryForArchive();
- for (Archive archive : sourceLocations) {
- analyzer.visitArchiveDependences(archive, graph);
- if (verbose || options.showLabel) {
- // traverse detailed dependences to generate package-level
- // summary or build labels for edges
- analyzer.visitDependences(archive, graph);
+ try (PrintWriter sw = new PrintWriter(Files.newOutputStream(summary));
+ SummaryDotFile dotfile = new SummaryDotFile(sw, summaryType)) {
+ for (Archive archive : sourceLocations) {
+ if (!archive.isEmpty()) {
+ if (options.verbose == PACKAGE || options.verbose == SUMMARY) {
+ if (options.showLabel) {
+ // build labels listing package-level dependencies
+ analyzer.visitDependences(archive, dotfile.labelBuilder(), PACKAGE);
+ }
+ }
+ analyzer.visitDependences(archive, dotfile, summaryType);
+ }
}
}
- try (PrintWriter sw = new PrintWriter(Files.newOutputStream(summary))) {
- graph.writeTo(sw);
- }
+ }
+
+ private void generateDotFiles(Path dir, Analyzer analyzer) throws IOException {
// output individual .dot file for each archive
- if (options.verbose != Analyzer.Type.SUMMARY) {
+ if (options.verbose != SUMMARY) {
for (Archive archive : sourceLocations) {
if (analyzer.hasDependences(archive)) {
- Path dotfile = dir.resolve(archive.getFileName() + ".dot");
+ Path dotfile = dir.resolve(archive.getName() + ".dot");
try (PrintWriter pw = new PrintWriter(Files.newOutputStream(dotfile));
DotFileFormatter formatter = new DotFileFormatter(pw, archive)) {
analyzer.visitDependences(archive, formatter);
@@ -324,17 +382,23 @@
}
}
}
+ // generate summary dot file
+ generateSummaryDotFile(dir, analyzer);
}
private void printRawOutput(PrintWriter writer, Analyzer analyzer) {
+ RawOutputFormatter depFormatter = new RawOutputFormatter(writer);
+ RawSummaryFormatter summaryFormatter = new RawSummaryFormatter(writer);
for (Archive archive : sourceLocations) {
- RawOutputFormatter formatter = new RawOutputFormatter(writer);
- analyzer.visitArchiveDependences(archive, formatter);
- if (options.verbose != Analyzer.Type.SUMMARY) {
- analyzer.visitDependences(archive, formatter);
+ if (!archive.isEmpty()) {
+ analyzer.visitDependences(archive, summaryFormatter, SUMMARY);
+ if (analyzer.hasDependences(archive) && options.verbose != SUMMARY) {
+ analyzer.visitDependences(archive, depFormatter);
+ }
}
}
}
+
private boolean isValidClassName(String name) {
if (!Character.isJavaIdentifierStart(name.charAt(0))) {
return false;
@@ -348,21 +412,54 @@
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());
- }
- };
+ /*
+ * Dep Filter configured based on the input jdeps option
+ * 1. -p and -regex to match target dependencies
+ * 2. -filter:package to filter out same-package dependencies
+ *
+ * This filter is applied when jdeps parses the class files
+ * and filtered dependencies are not stored in the Analyzer.
+ *
+ * -filter:archive is applied later in the Analyzer as the
+ * containing archive of a target class may not be known until
+ * the entire archive
+ */
+ class DependencyFilter implements Dependency.Filter {
+ final Dependency.Filter filter;
+ final Pattern filterPattern;
+ DependencyFilter() {
+ if (options.regex != null) {
+ this.filter = Dependencies.getRegexFilter(Pattern.compile(options.regex));
+ } else if (options.packageNames.size() > 0) {
+ this.filter = Dependencies.getPackageFilter(options.packageNames, false);
+ } else {
+ this.filter = null;
+ }
+
+ this.filterPattern =
+ options.filterRegex != null ? Pattern.compile(options.filterRegex) : null;
+ }
+ @Override
+ public boolean accepts(Dependency d) {
+ if (d.getOrigin().equals(d.getTarget())) {
+ return false;
+ }
+ String pn = d.getTarget().getPackageName();
+ if (options.filterSamePackage && d.getOrigin().getPackageName().equals(pn)) {
+ return false;
+ }
+
+ if (filterPattern != null && filterPattern.matcher(pn).matches()) {
+ return false;
+ }
+ return filter != null ? filter.accepts(d) : true;
}
}
+ /**
+ * Tests if the given class matches the pattern given in the -include option
+ * or if it's a public class if -apionly option is specified
+ */
private boolean matches(String classname, AccessFlags flags) {
if (options.apiOnly && !flags.is(AccessFlags.ACC_PUBLIC)) {
return false;
@@ -377,14 +474,14 @@
Dependency.Finder finder =
options.apiOnly ? Dependencies.getAPIFinder(AccessFlags.ACC_PROTECTED)
: Dependencies.getClassDependencyFinder();
- Dependency.Filter filter = getDependencyFilter();
+ Dependency.Filter filter = new DependencyFilter();
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)));
+ archives.add(Archive.getInstance(p));
} else {
if (isValidClassName(s)) {
roots.add(s);
@@ -421,19 +518,26 @@
throw new ClassFileError(e);
}
- if (matches(classFileName, cf.access_flags)) {
- if (!doneClasses.contains(classFileName)) {
- doneClasses.add(classFileName);
+ // tests if this class matches the -include or -apiOnly option if specified
+ if (!matches(classFileName, cf.access_flags)) {
+ continue;
+ }
+
+ 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());
}
- 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());
- }
- }
+ }
+ for (String name : a.reader().skippedEntries()) {
+ warning("warn.skipped.entry", name, a.getPathName());
}
}
}
@@ -462,6 +566,10 @@
// if name is a fully-qualified class name specified
// from command-line, this class might already be parsed
doneClasses.add(classFileName);
+ // process @jdk.Exported for JDK classes
+ if (isJDKArchive(a)) {
+ ((JDKArchive)a).processJdkExported(cf);
+ }
for (Dependency d : finder.findDependencies(cf)) {
if (depth == 0) {
// ignore the dependency
@@ -544,7 +652,7 @@
for (Option o : recognizedOptions) {
String name = o.aliases[0].substring(1); // there must always be at least one name
name = name.charAt(0) == '-' ? name.substring(1) : name;
- if (o.isHidden() || name.equals("h")) {
+ if (o.isHidden() || name.equals("h") || name.startsWith("filter:")) {
continue;
}
log.println(getMessage("main.opt." + name));
@@ -582,14 +690,18 @@
boolean fullVersion;
boolean showProfile;
boolean showSummary;
- boolean wildcard;
boolean apiOnly;
boolean showLabel;
boolean findJDKInternals;
+ // default is to show package-level dependencies
+ // and filter references from same package
+ Analyzer.Type verbose = PACKAGE;
+ boolean filterSamePackage = true;
+ boolean filterSameArchive = false;
+ String filterRegex;
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
@@ -613,19 +725,6 @@
}
}
- private List<Archive> getArchives(List<String> filenames) throws IOException {
- List<Archive> result = new ArrayList<>();
- 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()) {
@@ -648,7 +747,7 @@
}
for (Path f : files) {
if (Files.exists(f)) {
- result.add(new Archive(f, ClassFileReader.newInstance(f)));
+ result.add(Archive.getInstance(f));
}
}
}
@@ -656,81 +755,50 @@
return result;
}
- /**
- * If the given archive is JDK archive and non-null Profile,
- * this method returns the profile name only if -profile option is specified;
- * a null profile indicates it accesses a private JDK API and this method
- * will return "JDK internal API".
- *
- * For non-JDK archives, this method returns the file name of the archive.
- */
- private String getProfileArchiveInfo(Archive source, Profile profile) {
- if (options.showProfile && profile != null)
- return profile.toString();
-
- if (source instanceof JDKArchive) {
- return profile == null ? "JDK internal API (" + source.getFileName() + ")" : "";
- }
- return source.getFileName();
- }
-
- /**
- * Returns the profile name or "JDK internal API" for JDK archive;
- * otherwise empty string.
- */
- private String profileName(Archive archive, Profile profile) {
- if (archive instanceof JDKArchive) {
- return Objects.toString(profile, "JDK internal API");
- } else {
- return "";
- }
- }
-
class RawOutputFormatter implements Analyzer.Visitor {
private final PrintWriter writer;
+ private String pkg = "";
RawOutputFormatter(PrintWriter writer) {
this.writer = writer;
}
-
- private String pkg = "";
@Override
- public void visitDependence(String origin, Archive source,
- String target, Archive archive, Profile profile) {
- if (options.findJDKInternals &&
- !(archive instanceof JDKArchive && profile == null)) {
- // filter dependences other than JDK internal APIs
- return;
- }
- if (options.verbose == Analyzer.Type.VERBOSE) {
- writer.format(" %-50s -> %-50s %s%n",
- origin, target, getProfileArchiveInfo(archive, profile));
+ public void visitDependence(String origin, Archive originArchive,
+ String target, Archive targetArchive) {
+ String tag = toTag(target, targetArchive);
+ if (options.verbose == VERBOSE) {
+ writer.format(" %-50s -> %-50s %s%n", origin, target, tag);
} else {
if (!origin.equals(pkg)) {
pkg = origin;
- writer.format(" %s (%s)%n", origin, source.getFileName());
+ writer.format(" %s (%s)%n", origin, originArchive.getName());
}
- writer.format(" -> %-50s %s%n",
- target, getProfileArchiveInfo(archive, profile));
- }
- }
-
- @Override
- public void visitArchiveDependence(Archive origin, Archive target, Profile profile) {
- writer.format("%s -> %s", origin.getPathName(), target.getPathName());
- if (options.showProfile && profile != null) {
- writer.format(" (%s)%n", profile);
- } else {
- writer.format("%n");
+ writer.format(" -> %-50s %s%n", target, tag);
}
}
}
- class DotFileFormatter extends DotGraph<String> implements AutoCloseable {
+ class RawSummaryFormatter implements Analyzer.Visitor {
+ private final PrintWriter writer;
+ RawSummaryFormatter(PrintWriter writer) {
+ this.writer = writer;
+ }
+ @Override
+ public void visitDependence(String origin, Archive originArchive,
+ String target, Archive targetArchive) {
+ writer.format("%s -> %s", originArchive.getName(), targetArchive.getPathName());
+ if (options.showProfile && JDKArchive.isProfileArchive(targetArchive)) {
+ writer.format(" (%s)", target);
+ }
+ writer.format("%n");
+ }
+ }
+
+ class DotFileFormatter implements Analyzer.Visitor, AutoCloseable {
private final PrintWriter writer;
private final String name;
DotFileFormatter(PrintWriter writer, Archive archive) {
this.writer = writer;
- this.name = archive.getFileName();
+ this.name = archive.getName();
writer.format("digraph \"%s\" {%n", name);
writer.format(" // Path: %s%n", archive.getPathName());
}
@@ -741,173 +809,123 @@
}
@Override
- public void visitDependence(String origin, Archive source,
- String target, Archive archive, Profile profile) {
- if (options.findJDKInternals &&
- !(archive instanceof JDKArchive && profile == null)) {
- // filter dependences other than JDK internal APIs
- return;
- }
- // if -P option is specified, package name -> profile will
- // be shown and filter out multiple same edges.
- String name = getProfileArchiveInfo(archive, profile);
- writeEdge(writer, new Edge(origin, target, getProfileArchiveInfo(archive, profile)));
- }
- @Override
- public void visitArchiveDependence(Archive origin, Archive target, Profile profile) {
- throw new UnsupportedOperationException();
- }
- }
-
- class DotSummaryForArchive extends DotGraph<Archive> {
- @Override
- public void visitDependence(String origin, Archive source,
- String target, Archive archive, Profile profile) {
- Edge e = findEdge(source, archive);
- assert e != null;
- // add the dependency to the label if enabled and not compact1
- if (profile == Profile.COMPACT1) {
- return;
- }
- e.addLabel(origin, target, profileName(archive, profile));
- }
- @Override
- public void visitArchiveDependence(Archive origin, Archive target, Profile profile) {
- // add an edge with the archive's name with no tag
- // so that there is only one node for each JDK archive
- // while there may be edges to different profiles
- Edge e = addEdge(origin, target, "");
- if (target instanceof JDKArchive) {
- // add a label to print the profile
- if (profile == null) {
- e.addLabel("JDK internal API");
- } else if (options.showProfile && !options.showLabel) {
- e.addLabel(profile.toString());
- }
- }
+ public void visitDependence(String origin, Archive originArchive,
+ String target, Archive targetArchive) {
+ String tag = toTag(target, targetArchive);
+ writer.format(" %-50s -> \"%s\";%n",
+ String.format("\"%s\"", origin),
+ tag.isEmpty() ? target
+ : String.format("%s (%s)", target, tag));
}
}
- // DotSummaryForPackage generates the summary.dot file for verbose mode
- // (-v or -verbose option) that includes all class dependencies.
- // The summary.dot file shows package-level dependencies.
- class DotSummaryForPackage extends DotGraph<String> {
- private String packageOf(String cn) {
- int i = cn.lastIndexOf('.');
- return i > 0 ? cn.substring(0, i) : "<unnamed>";
+ class SummaryDotFile implements Analyzer.Visitor, AutoCloseable {
+ private final PrintWriter writer;
+ private final Analyzer.Type type;
+ private final Map<Archive, Map<Archive,StringBuilder>> edges = new HashMap<>();
+ SummaryDotFile(PrintWriter writer, Analyzer.Type type) {
+ this.writer = writer;
+ this.type = type;
+ writer.format("digraph \"summary\" {%n");
}
- @Override
- public void visitDependence(String origin, Archive source,
- String target, Archive archive, Profile profile) {
- // add a package dependency edge
- String from = packageOf(origin);
- String to = packageOf(target);
- Edge e = addEdge(from, to, getProfileArchiveInfo(archive, profile));
- // add the dependency to the label if enabled and not compact1
- if (!options.showLabel || profile == Profile.COMPACT1) {
- return;
- }
-
- // trim the package name of origin to shorten the label
- int i = origin.lastIndexOf('.');
- String n1 = i < 0 ? origin : origin.substring(i+1);
- e.addLabel(n1, target, profileName(archive, profile));
- }
@Override
- public void visitArchiveDependence(Archive origin, Archive target, Profile profile) {
- // nop
- }
- }
- abstract class DotGraph<T> implements Analyzer.Visitor {
- private final Set<Edge> edges = new LinkedHashSet<>();
- private Edge curEdge;
- public void writeTo(PrintWriter writer) {
- writer.format("digraph \"summary\" {%n");
- for (Edge e: edges) {
- writeEdge(writer, e);
- }
+ public void close() {
writer.println("}");
}
- void writeEdge(PrintWriter writer, Edge e) {
- writer.format(" %-50s -> \"%s\"%s;%n",
- String.format("\"%s\"", e.from.toString()),
- e.tag.isEmpty() ? e.to
- : String.format("%s (%s)", e.to, e.tag),
- getLabel(e));
+ @Override
+ public void visitDependence(String origin, Archive originArchive,
+ String target, Archive targetArchive) {
+ String targetName = type == PACKAGE ? target : targetArchive.getName();
+ if (type == PACKAGE) {
+ String tag = toTag(target, targetArchive, type);
+ if (!tag.isEmpty())
+ targetName += " (" + tag + ")";
+ } else if (options.showProfile && JDKArchive.isProfileArchive(targetArchive)) {
+ targetName += " (" + target + ")";
+ }
+ String label = getLabel(originArchive, targetArchive);
+ writer.format(" %-50s -> \"%s\"%s;%n",
+ String.format("\"%s\"", origin), targetName, label);
}
- Edge addEdge(T origin, T target, String tag) {
- Edge e = new Edge(origin, target, tag);
- if (e.equals(curEdge)) {
- return curEdge;
- }
+ String getLabel(Archive origin, Archive target) {
+ if (edges.isEmpty())
+ return "";
- if (edges.contains(e)) {
- for (Edge e1 : edges) {
- if (e.equals(e1)) {
- curEdge = e1;
- }
- }
- } else {
- edges.add(e);
- curEdge = e;
- }
- return curEdge;
- }
-
- Edge findEdge(T origin, T target) {
- for (Edge e : edges) {
- if (e.from.equals(origin) && e.to.equals(target)) {
- return e;
- }
- }
- return null;
- }
-
- String getLabel(Edge e) {
- String label = e.label.toString();
- return label.isEmpty() ? "" : String.format("[label=\"%s\",fontsize=9]", label);
+ StringBuilder label = edges.get(origin).get(target);
+ return label == null ? "" : String.format(" [label=\"%s\",fontsize=9]", label.toString());
}
- class Edge {
- final T from;
- final T to;
- final String tag; // optional tag
- final StringBuilder label = new StringBuilder();
- Edge(T from, T to, String tag) {
- this.from = from;
- this.to = to;
- this.tag = tag;
- }
- void addLabel(String s) {
- label.append(s).append("\\n");
- }
- void addLabel(String origin, String target, String profile) {
- label.append(origin).append(" -> ").append(target);
- if (!profile.isEmpty()) {
- label.append(" (" + profile + ")");
+ Analyzer.Visitor labelBuilder() {
+ // show the package-level dependencies as labels in the dot graph
+ return new Analyzer.Visitor() {
+ @Override
+ public void visitDependence(String origin, Archive originArchive, String target, Archive targetArchive) {
+ edges.putIfAbsent(originArchive, new HashMap<>());
+ edges.get(originArchive).putIfAbsent(targetArchive, new StringBuilder());
+ StringBuilder sb = edges.get(originArchive).get(targetArchive);
+ String tag = toTag(target, targetArchive, PACKAGE);
+ addLabel(sb, origin, target, tag);
}
- label.append("\\n");
- }
- @Override @SuppressWarnings("unchecked")
- public boolean equals(Object o) {
- if (o instanceof DotGraph<?>.Edge) {
- DotGraph<?>.Edge e = (DotGraph<?>.Edge)o;
- return this.from.equals(e.from) &&
- this.to.equals(e.to) &&
- this.tag.equals(e.tag);
+
+ void addLabel(StringBuilder label, String origin, String target, String tag) {
+ label.append(origin).append(" -> ").append(target);
+ if (!tag.isEmpty()) {
+ label.append(" (" + tag + ")");
+ }
+ label.append("\\n");
}
- return false;
- }
- @Override
- public int hashCode() {
- int hash = 7;
- hash = 67 * hash + Objects.hashCode(this.from) +
- Objects.hashCode(this.to) + Objects.hashCode(this.tag);
- return hash;
- }
+ };
}
}
+
+ /**
+ * Test if the given archive is part of the JDK
+ */
+ private boolean isJDKArchive(Archive archive) {
+ return JDKArchive.class.isInstance(archive);
+ }
+
+ /**
+ * If the given archive is JDK archive, this method returns the profile name
+ * only if -profile option is specified; it accesses a private JDK API and
+ * the returned value will have "JDK internal API" prefix
+ *
+ * For non-JDK archives, this method returns the file name of the archive.
+ */
+ private String toTag(String name, Archive source, Analyzer.Type type) {
+ if (!isJDKArchive(source)) {
+ return source.getName();
+ }
+
+ JDKArchive jdk = (JDKArchive)source;
+ boolean isExported = false;
+ if (type == CLASS || type == VERBOSE) {
+ isExported = jdk.isExported(name);
+ } else {
+ isExported = jdk.isExportedPackage(name);
+ }
+ Profile p = getProfile(name, type);
+ if (isExported) {
+ // exported API
+ return options.showProfile && p != null ? p.profileName() : "";
+ } else {
+ return "JDK internal API (" + source.getName() + ")";
+ }
+ }
+
+ private String toTag(String name, Archive source) {
+ return toTag(name, source, options.verbose);
+ }
+
+ private Profile getProfile(String name, Analyzer.Type type) {
+ String pn = name;
+ if (type == CLASS || type == VERBOSE) {
+ int i = name.lastIndexOf('.');
+ pn = i > 0 ? name.substring(0, i) : "";
+ }
+ return Profile.getProfile(pn);
+ }
}
--- a/langtools/src/share/classes/com/sun/tools/jdeps/Main.java Tue Jul 08 15:42:04 2014 +0100
+++ b/langtools/src/share/classes/com/sun/tools/jdeps/Main.java Tue Jul 08 18:26:34 2014 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2014, 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
@@ -63,4 +63,3 @@
return t.run(args);
}
}
-
--- a/langtools/src/share/classes/com/sun/tools/jdeps/PlatformClassPath.java Tue Jul 08 15:42:04 2014 +0100
+++ b/langtools/src/share/classes/com/sun/tools/jdeps/PlatformClassPath.java Tue Jul 08 18:26:34 2014 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2014, 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
@@ -24,6 +24,12 @@
*/
package com.sun.tools.jdeps;
+import com.sun.tools.classfile.Annotation;
+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 com.sun.tools.classfile.Dependencies.ClassFileError;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
@@ -33,11 +39,15 @@
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
+import static com.sun.tools.classfile.Attribute.*;
+
/**
* ClassPath for Java SE and JDK
*/
class PlatformClassPath {
- private final static List<Archive> javaHomeArchives = init();
+ private static final List<String> NON_PLATFORM_JARFILES =
+ Arrays.asList("alt-rt.jar", "jfxrt.jar", "ant-javafx.jar", "javafx-mx.jar");
+ private static final List<Archive> javaHomeArchives = init();
static List<Archive> getArchives() {
return javaHomeArchives;
@@ -50,12 +60,19 @@
if (home.endsWith("jre")) {
// jar files in <javahome>/jre/lib
result.addAll(addJarFiles(home.resolve("lib")));
+ if (home.getParent() != null) {
+ // add tools.jar and other JDK jar files
+ Path lib = home.getParent().resolve("lib");
+ if (Files.exists(lib)) {
+ result.addAll(addJarFiles(lib));
+ }
+ }
} else if (Files.exists(home.resolve("lib"))) {
// either a JRE or a jdk build image
Path classes = home.resolve("classes");
if (Files.isDirectory(classes)) {
// jdk build outputdir
- result.add(new JDKArchive(classes, ClassFileReader.newInstance(classes)));
+ result.add(new JDKArchive(classes));
}
// add other JAR files
result.addAll(addJarFiles(home.resolve("lib")));
@@ -91,9 +108,9 @@
if (fn.endsWith(".jar")) {
// JDK may cobundle with JavaFX that doesn't belong to any profile
// Treat jfxrt.jar as regular Archive
- result.add(fn.equals("jfxrt.jar")
- ? new Archive(p, ClassFileReader.newInstance(p))
- : new JDKArchive(p, ClassFileReader.newInstance(p)));
+ result.add(NON_PLATFORM_JARFILES.contains(fn)
+ ? Archive.getInstance(p)
+ : new JDKArchive(p));
}
return FileVisitResult.CONTINUE;
}
@@ -106,8 +123,91 @@
* or implementation classes (i.e. JDK internal API)
*/
static class JDKArchive extends Archive {
- JDKArchive(Path p, ClassFileReader reader) {
- super(p, reader);
+ private static List<String> PROFILE_JARS = Arrays.asList("rt.jar", "jce.jar");
+ public static boolean isProfileArchive(Archive archive) {
+ if (archive instanceof JDKArchive) {
+ return PROFILE_JARS.contains(archive.getName());
+ }
+ return false;
+ }
+
+ private final Map<String,Boolean> exportedPackages = new HashMap<>();
+ private final Map<String,Boolean> exportedTypes = new HashMap<>();
+ JDKArchive(Path p) throws IOException {
+ super(p, ClassFileReader.newInstance(p));
+ }
+
+ /**
+ * Tests if a given fully-qualified name is an exported type.
+ */
+ public boolean isExported(String cn) {
+ int i = cn.lastIndexOf('.');
+ String pn = i > 0 ? cn.substring(0, i) : "";
+
+ boolean isJdkExported = isExportedPackage(pn);
+ if (exportedTypes.containsKey(cn)) {
+ return exportedTypes.get(cn);
+ }
+ return isJdkExported;
+ }
+
+ /**
+ * Tests if a given package name is exported.
+ */
+ public boolean isExportedPackage(String pn) {
+ if (Profile.getProfile(pn) != null) {
+ return true;
+ }
+ return exportedPackages.containsKey(pn) ? exportedPackages.get(pn) : false;
+ }
+
+ private static final String JDK_EXPORTED_ANNOTATION = "Ljdk/Exported;";
+ private Boolean isJdkExported(ClassFile cf) throws ConstantPoolException {
+ RuntimeAnnotations_attribute attr = (RuntimeAnnotations_attribute)
+ cf.attributes.get(RuntimeVisibleAnnotations);
+ 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 (JDK_EXPORTED_ANNOTATION.equals(annType)) {
+ boolean isJdkExported = true;
+ for (int j = 0; j < ann.num_element_value_pairs; j++) {
+ Annotation.element_value_pair pair = ann.element_value_pairs[j];
+ Annotation.Primitive_element_value ev = (Annotation.Primitive_element_value) pair.value;
+ ConstantPool.CONSTANT_Integer_info info = (ConstantPool.CONSTANT_Integer_info)
+ cf.constant_pool.get(ev.const_value_index);
+ isJdkExported = info.value != 0;
+ }
+ return Boolean.valueOf(isJdkExported);
+ }
+ }
+ }
+ return null;
+ }
+
+ void processJdkExported(ClassFile cf) throws IOException {
+ try {
+ String cn = cf.getName();
+ String pn = cn.substring(0, cn.lastIndexOf('/')).replace('/', '.');
+
+ Boolean b = isJdkExported(cf);
+ if (b != null) {
+ exportedTypes.put(cn.replace('/', '.'), b);
+ }
+ if (!exportedPackages.containsKey(pn)) {
+ // check if package-info.class has @jdk.Exported
+ Boolean isJdkExported = null;
+ ClassFile pcf = reader().getClassFile(cn.substring(0, cn.lastIndexOf('/')+1) + "package-info");
+ if (pcf != null) {
+ isJdkExported = isJdkExported(pcf);
+ }
+ if (isJdkExported != null) {
+ exportedPackages.put(pn, isJdkExported);
+ }
+ }
+ } catch (ConstantPoolException e) {
+ throw new ClassFileError(e);
+ }
}
}
}
--- a/langtools/src/share/classes/com/sun/tools/jdeps/Profile.java Tue Jul 08 15:42:04 2014 +0100
+++ b/langtools/src/share/classes/com/sun/tools/jdeps/Profile.java Tue Jul 08 18:26:34 2014 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2014, 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
@@ -43,7 +43,6 @@
* Build the profile information from ct.sym if exists.
*/
enum Profile {
-
COMPACT1("compact1", 1),
COMPACT2("compact2", 2),
COMPACT3("compact3", 3),
@@ -61,8 +60,7 @@
this.proprietaryPkgs = new HashSet<>();
}
- @Override
- public String toString() {
+ public String profileName() {
return name;
}
@@ -77,7 +75,7 @@
public static Profile getProfile(String pn) {
Profile profile = PackageToProfile.map.get(pn);
return (profile != null && profile.packages.contains(pn))
- ? profile : null;
+ ? profile : null;
}
static class PackageToProfile {
--- a/langtools/src/share/classes/com/sun/tools/jdeps/resources/jdeps.properties Tue Jul 08 15:42:04 2014 +0100
+++ b/langtools/src/share/classes/com/sun/tools/jdeps/resources/jdeps.properties Tue Jul 08 18:26:34 2014 -0700
@@ -1,6 +1,6 @@
main.usage.summary=\
Usage: {0} <options> <classes...>\n\
-use -h, -? or --help for a list of possible options
+use -h, -? or -help for a list of possible options
main.usage=\
Usage: {0} <options> <classes...>\n\
@@ -18,20 +18,29 @@
main.opt.v=\
\ -v -verbose Print all class level dependencies\n\
+\ Equivalent to -verbose:class -filter:none.\n\
\ -verbose:package Print package-level dependencies excluding\n\
-\ dependencies within the same archive\n\
+\ dependencies within the same package by default\n\
\ -verbose:class Print class-level dependencies excluding\n\
-\ dependencies within the same archive
+\ dependencies within the same package by default
+
+main.opt.f=\
+\ -f <regex> -filter <regex> Filter dependences matching the given pattern\n\
+\ If given multiple times, the last one will be used.\n\
+\ -filter:package Filter dependences within the same package (default)\n\
+\ -filter:archive Filter dependences within the same archive\n\
+\ -filter:none No -filter:package and -filter:archive filtering\n\
+\ Filtering specified via the -filter option still applies.
main.opt.s=\
\ -s -summary Print dependency summary only
main.opt.p=\
-\ -p <pkgname> -package <pkgname> Finds dependences in the given package\n\
+\ -p <pkgname> -package <pkgname> Finds dependences matching the given package name\n\
\ (may be given multiple times)
main.opt.e=\
-\ -e <regex> -regex <regex> Finds dependences in packages matching pattern\n\
+\ -e <regex> -regex <regex> Finds dependences matching the given pattern\n\
\ (-p and -e are exclusive)
main.opt.include=\
@@ -47,7 +56,10 @@
\ -cp <path> -classpath <path> Specify where to find class files
main.opt.R=\
-\ -R -recursive Recursively traverse all dependencies
+\ -R -recursive Recursively traverse all dependencies.\n\
+\ The -R option implies -filter:none. If -p, -e, -f\n\
+\ option is specified, only the matching dependences\n\
+\ are analyzed.
main.opt.apionly=\
\ -apionly Restrict analysis to APIs i.e. dependences\n\
@@ -74,12 +86,11 @@
err.unknown.option=unknown option: {0}
err.missing.arg=no value given for {0}
-err.internal.error=internal error: {0} {1} {2}
err.invalid.arg.for.option=invalid argument for option: {0}
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}
+err.invalid.path=invalid path: {0}
warn.invalid.arg=Invalid classname or pathname not exist: {0}
warn.split.package=package {0} defined in {1} {2}
--- a/langtools/test/tools/jdeps/APIDeps.java Tue Jul 08 15:42:04 2014 +0100
+++ b/langtools/test/tools/jdeps/APIDeps.java Tue Jul 08 18:26:34 2014 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2014, 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
@@ -23,7 +23,7 @@
/*
* @test
- * @bug 8015912 8029216
+ * @bug 8015912 8029216 8048063
* @summary Test -apionly and -jdkinternals options
* @build m.Bar m.Foo m.Gee b.B c.C c.I d.D e.E f.F g.G
* @run main APIDeps
@@ -81,27 +81,39 @@
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"},
+ new String[] {testDirBasename},
+ new String[] {"-classpath", testDir.getPath(), "-verbose:class", "-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"});
+ new String[] {"-classpath", testDir.getPath(), "-verbose:class", "-filter:none", "-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"});
+ new String[] {"g.G", "sun.misc.Lock", "com.sun.tools.classfile.ClassFile",
+ "com.sun.management.ThreadMXBean", "com.sun.source.tree.BinaryTree"},
+ new String[] {testDirBasename, "JDK internal API", "compact3", ""},
+ new String[] {"-classpath", testDir.getPath(), "-verbose", "-P"});
// -jdkinternals
test(new File(mDir, "Gee.class"),
- new String[] {"sun.misc.Lock"},
+ new String[] {"sun.misc.Lock", "com.sun.tools.classfile.ClassFile"},
new String[] {"JDK internal API"},
new String[] {"-jdkinternals"});
// -jdkinternals parses all classes on -classpath and the input arguments
test(new File(mDir, "Gee.class"),
- new String[] {"sun.misc.Lock", "sun.misc.Unsafe"},
+ new String[] {"com.sun.tools.jdeps.Main", "com.sun.tools.classfile.ClassFile",
+ "sun.misc.Lock", "sun.misc.Unsafe"},
new String[] {"JDK internal API"},
new String[] {"-classpath", testDir.getPath(), "-jdkinternals"});
// parse only APIs
- // parse only APIs
+ test(mDir,
+ new String[] {"java.lang.Object", "java.lang.String",
+ "java.util.Set",
+ "c.C", "d.D", "c.I", "e.E"},
+ new String[] {"compact1", testDirBasename},
+ new String[] {"-classpath", testDir.getPath(), "-verbose:class", "-P", "-apionly"});
+
test(mDir,
new String[] {"java.lang.Object", "java.lang.String",
"java.util.Set",
--- a/langtools/test/tools/jdeps/Basic.java Tue Jul 08 15:42:04 2014 +0100
+++ b/langtools/test/tools/jdeps/Basic.java Tue Jul 08 18:26:34 2014 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2014, 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
@@ -23,7 +23,7 @@
/*
* @test
- * @bug 8003562 8005428 8015912 8027481
+ * @bug 8003562 8005428 8015912 8027481 8048063
* @summary Basic tests for jdeps tool
* @build Test p.Foo p.Bar javax.activity.NotCompactProfile
* @run main Basic
@@ -86,6 +86,16 @@
new String[] {"java.lang.Object", "java.lang.String", "p.Foo", "p.Bar"},
new String[] {"compact1", "compact1", "not found", "not found"},
new String[] {"-verbose:class"});
+ // test -filter:none option
+ test(new File(testDir, "p"),
+ new String[] {"java.lang", "java.util", "java.lang.management", "javax.activity", "javax.crypto", "p"},
+ new String[] {"compact1", "compact1", "compact3", testDir.getName(), "compact1", "p"},
+ new String[] {"-classpath", testDir.getPath(), "-verbose:package", "-filter:none"});
+ // test -filter:archive option
+ test(new File(testDir, "p"),
+ new String[] {"java.lang", "java.util", "java.lang.management", "javax.activity", "javax.crypto"},
+ new String[] {"compact1", "compact1", "compact3", testDir.getName(), "compact1"},
+ new String[] {"-classpath", testDir.getPath(), "-verbose:package", "-filter:archive"});
// test -p option
test(new File(testDir, "Test.class"),
new String[] {"p.Foo", "p.Bar"},
@@ -100,11 +110,12 @@
new String[] {"java.lang"},
new String[] {"compact1"},
new String[] {"-verbose:package", "-e", "java\\.lang\\..*"});
+
// test -classpath and -include options
test(null,
- new String[] {"java.lang", "java.util",
- "java.lang.management", "javax.crypto"},
- new String[] {"compact1", "compact1", "compact3", "compact1"},
+ new String[] {"java.lang", "java.util", "java.lang.management",
+ "javax.activity", "javax.crypto"},
+ new String[] {"compact1", "compact1", "compact3", testDir.getName(), "compact1"},
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", "p.Bar"},
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/jdeps/DotFileTest.java Tue Jul 08 18:26:34 2014 -0700
@@ -0,0 +1,272 @@
+/*
+ * Copyright (c) 2014, 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 8003562
+ * @summary Basic tests for jdeps -dotoutput option
+ * @build Test p.Foo p.Bar javax.activity.NotCompactProfile
+ * @run main DotFileTest
+ */
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.*;
+import java.util.regex.*;
+
+public class DotFileTest {
+ 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 DotFileTest().run();
+ if (errors > 0)
+ throw new Exception(errors + " errors found");
+ }
+
+ final Path dir;
+ final Path dotoutput;
+ DotFileTest() {
+ this.dir = Paths.get(System.getProperty("test.classes", "."));
+ this.dotoutput = dir.resolve("dots");
+ }
+
+ int run() throws IOException {
+ File testDir = dir.toFile();
+ // test a .class file
+ test(new File(testDir, "Test.class"),
+ new String[] {"java.lang", "p"},
+ new String[] {"compact1", "not found"});
+ // test a directory
+ // also test non-SE javax.activity class dependency
+ test(new File(testDir, "p"),
+ new String[] {"java.lang", "java.util", "java.lang.management", "javax.activity", "javax.crypto"},
+ new String[] {"compact1", "compact1", "compact3", testDir.getName(), "compact1"},
+ new String[] {"-classpath", testDir.getPath()});
+ // test class-level dependency output
+ test(new File(testDir, "Test.class"),
+ new String[] {"java.lang.Object", "java.lang.String", "p.Foo", "p.Bar"},
+ new String[] {"compact1", "compact1", "not found", "not found"},
+ new String[] {"-verbose:class"});
+ // test -filter:none option
+ test(new File(testDir, "p"),
+ new String[] {"java.lang", "java.util", "java.lang.management", "javax.activity", "javax.crypto", "p"},
+ new String[] {"compact1", "compact1", "compact3", testDir.getName(), "compact1", "p"},
+ new String[] {"-classpath", testDir.getPath(), "-verbose:package", "-filter:none"});
+ // test -filter:archive option
+ test(new File(testDir, "p"),
+ new String[] {"java.lang", "java.util", "java.lang.management", "javax.activity", "javax.crypto"},
+ new String[] {"compact1", "compact1", "compact3", testDir.getName(), "compact1"},
+ new String[] {"-classpath", testDir.getPath(), "-verbose:package", "-filter:archive"});
+ // test -p option
+ test(new File(testDir, "Test.class"),
+ new String[] {"p.Foo", "p.Bar"},
+ new String[] {"not found", "not found"},
+ new String[] {"-verbose:class", "-p", "p"});
+ // test -e option
+ test(new File(testDir, "Test.class"),
+ new String[] {"p.Foo", "p.Bar"},
+ new String[] {"not found", "not found"},
+ new String[] {"-verbose:class", "-e", "p\\..*"});
+ test(new File(testDir, "Test.class"),
+ new String[] {"java.lang"},
+ new String[] {"compact1"},
+ new String[] {"-verbose:package", "-e", "java\\.lang\\..*"});
+ // test -classpath options
+ test(new File(testDir, "Test.class"),
+ new String[] {"java.lang.Object", "java.lang.String", "p.Foo", "p.Bar"},
+ new String[] {"compact1", "compact1", testDir.getName(), testDir.getName()},
+ new String[] {"-v", "-classpath", testDir.getPath()});
+
+ testSummary(new File(testDir, "Test.class"),
+ new String[] {"rt.jar", testDir.getName()},
+ new String[] {"compact1", ""},
+ new String[] {"-classpath", testDir.getPath()});
+ testSummary(new File(testDir, "Test.class"),
+ new String[] {"java.lang", "p"},
+ new String[] {"compact1", testDir.getName()},
+ new String[] {"-v", "-classpath", testDir.getPath()});
+ return errors;
+ }
+
+ void test(File file, String[] expect, String[] profiles) throws IOException {
+ test(file, expect, profiles, new String[0]);
+ }
+
+ void test(File file, String[] expect, String[] profiles, String[] options)
+ throws IOException
+ {
+ Path dotfile = dotoutput.resolve(file.toPath().getFileName().toString() + ".dot");
+
+ List<String> args = new ArrayList<>(Arrays.asList(options));
+ args.add("-dotoutput");
+ args.add(dotoutput.toString());
+ if (file != null) {
+ args.add(file.getPath());
+ }
+
+ Map<String,String> result = jdeps(args, dotfile);
+ checkResult("dependencies", expect, result.keySet());
+
+ // with -P option
+ List<String> argsWithDashP = new ArrayList<>();
+ argsWithDashP.add("-dotoutput");
+ argsWithDashP.add(dotoutput.toString());
+ argsWithDashP.add("-P");
+ argsWithDashP.addAll(args);
+
+ result = jdeps(argsWithDashP, dotfile);
+ checkResult("profiles", expect, profiles, result);
+ }
+
+ void testSummary(File file, String[] expect, String[] profiles, String[] options)
+ throws IOException
+ {
+ Path dotfile = dotoutput.resolve("summary.dot");
+
+ List<String> args = new ArrayList<>(Arrays.asList(options));
+ args.add("-dotoutput");
+ args.add(dotoutput.toString());
+ if (file != null) {
+ args.add(file.getPath());
+ }
+
+ Map<String,String> result = jdeps(args, dotfile);
+ checkResult("dependencies", expect, result.keySet());
+
+ // with -P option
+ List<String> argsWithDashP = new ArrayList<>();
+ argsWithDashP.add("-dotoutput");
+ argsWithDashP.add(dotoutput.toString());
+ argsWithDashP.add("-P");
+ argsWithDashP.addAll(args);
+
+ result = jdeps(argsWithDashP, dotfile);
+ checkResult("profiles", expect, profiles, result);
+ }
+
+ Map<String,String> jdeps(List<String> args, Path dotfile) throws IOException {
+ if (Files.exists(dotoutput)) {
+ try (DirectoryStream<Path> stream = Files.newDirectoryStream(dotoutput)) {
+ for (Path p : stream) {
+ Files.delete(p);
+ }
+ }
+ Files.delete(dotoutput);
+ }
+ // invoke jdeps
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ System.err.println("jdeps " + args);
+ int rc = com.sun.tools.jdeps.Main.run(args.toArray(new String[0]), pw);
+ pw.close();
+ String out = sw.toString();
+ if (!out.isEmpty())
+ System.err.println(out);
+ if (rc != 0)
+ throw new Error("jdeps failed: rc=" + rc);
+
+ // check output files
+ if (Files.notExists(dotfile)) {
+ throw new RuntimeException(dotfile + " doesn't exist");
+ }
+ return parse(dotfile);
+ }
+ private static Pattern pattern = Pattern.compile("(.*) -> +([^ ]*) (.*)");
+ private Map<String,String> parse(Path outfile) throws IOException {
+ Map<String,String> result = new LinkedHashMap<>();
+ for (String line : Files.readAllLines(outfile)) {
+ line = line.replace('"', ' ').replace(';', ' ');
+ Matcher pm = pattern.matcher(line);
+ if (pm.find()) {
+ String origin = pm.group(1).trim();
+ String target = pm.group(2).trim();
+ String module = pm.group(3).replace('(', ' ').replace(')', ' ').trim();
+ result.put(target, module);
+ }
+ }
+ return result;
+ }
+
+ void checkResult(String label, String[] expect, Collection<String> found) {
+ List<String> list = Arrays.asList(expect);
+ if (!isEqual(list, found))
+ error("Unexpected " + label + " found: '" + found + "', expected: '" + list + "'");
+ }
+
+ void checkResult(String label, String[] expect, String[] profiles, Map<String,String> result) {
+ if (expect.length != profiles.length)
+ error("Invalid expected names and profiles");
+
+ // check the dependencies
+ checkResult(label, expect, result.keySet());
+ // check profile information
+ checkResult(label, profiles, result.values());
+ for (int i=0; i < expect.length; i++) {
+ String profile = result.get(expect[i]);
+ if (!profile.equals(profiles[i]))
+ error("Unexpected profile: '" + profile + "', expected: '" + profiles[i] + "'");
+ }
+ }
+
+ boolean isEqual(List<String> expected, Collection<String> found) {
+ if (expected.size() != found.size())
+ return false;
+
+ List<String> list = new ArrayList<>(found);
+ list.removeAll(expected);
+ return list.isEmpty();
+ }
+
+ void error(String msg) {
+ System.err.println("Error: " + msg);
+ errors++;
+ }
+
+ int errors;
+}
--- a/langtools/test/tools/jdeps/m/Gee.java Tue Jul 08 15:42:04 2014 +0100
+++ b/langtools/test/tools/jdeps/m/Gee.java Tue Jul 08 18:26:34 2014 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2014, 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
@@ -26,5 +26,7 @@
class Gee extends g.G {
public sun.misc.Lock lock;
+ public com.sun.tools.classfile.ClassFile cf; // @jdk.Exported(false)
+ public com.sun.source.tree.BinaryTree tree; // @jdk.Exported
+ public com.sun.management.ThreadMXBean mxbean; // @jdk.Exported on package-info
}
-
--- a/langtools/test/tools/jdeps/p/Bar.java Tue Jul 08 15:42:04 2014 +0100
+++ b/langtools/test/tools/jdeps/p/Bar.java Tue Jul 08 18:26:34 2014 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2014, 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
@@ -30,4 +30,8 @@
public javax.crypto.Cipher getCiper() {
return null;
}
+
+ public Foo foo() {
+ return new Foo();
+ }
}