--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/InverseDepsAnalyzer.java Thu May 19 11:01:26 2016 -0700
@@ -0,0 +1,250 @@
+/*
+ * Copyright (c) 2016, 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 static com.sun.tools.jdeps.JdepsFilter.DEFAULT_FILTER;
+import static com.sun.tools.jdeps.Module.trace;
+import static com.sun.tools.jdeps.Graph.*;
+
+import java.lang.module.ModuleDescriptor.Requires;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Inverse transitive dependency analysis (compile-time view)
+ */
+public class InverseDepsAnalyzer extends DepsAnalyzer {
+ // the end points for the resulting paths to be reported
+ private final Map<Archive, Set<Archive>> endPoints = new HashMap<>();
+ // target archives for inverse transitive dependence analysis
+ private final Set<Archive> targets = new HashSet<>();
+
+ public InverseDepsAnalyzer(JdepsConfiguration config,
+ JdepsFilter filter,
+ JdepsWriter writer,
+ Analyzer.Type verbose,
+ boolean apiOnly) {
+ super(config, filter, writer, verbose, apiOnly);
+ }
+
+ public boolean run() throws IOException {
+ try {
+ if (apiOnly) {
+ finder.parseExportedAPIs(rootArchives.stream());
+ } else {
+ finder.parse(rootArchives.stream());
+ }
+ archives.addAll(rootArchives);
+
+ Set<Archive> archives = archives();
+
+ // If -package or -regex is specified, the archives that reference
+ // the matching types are used as the targets for inverse
+ // transitive analysis. If -requires is specified, the
+ // specified modules are the targets.
+
+ if (filter.requiresFilter().isEmpty()) {
+ targets.addAll(archives);
+ } else {
+ filter.requiresFilter().stream()
+ .map(configuration::findModule)
+ .flatMap(Optional::stream)
+ .forEach(targets::add);
+ }
+
+ // If -package or -regex is specified, the end points are
+ // the matching archives. If -requires is specified,
+ // the end points are the modules specified in -requires.
+ if (filter.requiresFilter().isEmpty()) {
+ Map<Archive, Set<Archive>> dependences = finder.dependences();
+ targets.forEach(source -> endPoints.put(source, dependences.get(source)));
+ } else {
+ targets.forEach(t -> endPoints.put(t, Collections.emptySet()));
+ }
+
+ analyzer.run(archives, finder.locationToArchive());
+
+ // print the first-level of dependencies
+ if (writer != null) {
+ writer.generateOutput(archives, analyzer);
+ }
+
+ } finally {
+ finder.shutdown();
+ }
+ return true;
+ }
+
+ /**
+ * Returns the target archives determined from the dependency analysis.
+ *
+ * Inverse transitive dependency will find all nodes that depend
+ * upon the returned set of archives directly and indirectly.
+ */
+ public Set<Archive> targets() {
+ return Collections.unmodifiableSet(targets);
+ }
+
+ /**
+ * Finds all inverse transitive dependencies using the given requires set
+ * as the targets, if non-empty. If the given requires set is empty,
+ * use the archives depending the packages specified in -regex or -p options.
+ */
+ public Set<Deque<Archive>> inverseDependences() throws IOException {
+ // create a new dependency finder to do the analysis
+ DependencyFinder dependencyFinder = new DependencyFinder(configuration, DEFAULT_FILTER);
+ try {
+ // parse all archives in unnamed module to get compile-time dependences
+ Stream<Archive> archives =
+ Stream.concat(configuration.initialArchives().stream(),
+ configuration.classPathArchives().stream());
+ if (apiOnly) {
+ dependencyFinder.parseExportedAPIs(archives);
+ } else {
+ dependencyFinder.parse(archives);
+ }
+
+ Graph.Builder<Archive> builder = new Graph.Builder<>();
+ // include all target nodes
+ targets().forEach(builder::addNode);
+
+ // transpose the module graph - may filter JDK module
+ configuration.getModules().values().stream()
+ .filter(filter::include)
+ .forEach(m -> {
+ builder.addNode(m);
+ m.descriptor().requires().stream()
+ .map(Requires::name)
+ .map(configuration::findModule) // must be present
+ .forEach(v -> builder.addEdge(v.get(), m));
+ });
+
+ // add the dependences from the analysis
+ Map<Archive, Set<Archive>> dependences = dependencyFinder.dependences();
+ dependences.entrySet().stream()
+ .forEach(e -> {
+ Archive u = e.getKey();
+ builder.addNode(u);
+ e.getValue().forEach(v -> builder.addEdge(v, u));
+ });
+
+ // transposed dependence graph.
+ Graph<Archive> graph = builder.build();
+ trace("targets: %s%n", targets());
+
+ // Traverse from the targets and find all paths
+ // rebuild a graph with all nodes that depends on targets
+ // targets directly and indirectly
+ return targets().stream()
+ .map(t -> findPaths(graph, t))
+ .flatMap(Set::stream)
+ .collect(Collectors.toSet());
+ } finally {
+ dependencyFinder.shutdown();
+ }
+ }
+
+ /**
+ * Returns all paths reachable from the given targets.
+ */
+ private Set<Deque<Archive>> findPaths(Graph<Archive> graph, Archive target) {
+
+ // path is in reversed order
+ Deque<Archive> path = new LinkedList<>();
+ path.push(target);
+
+ Set<Edge<Archive>> visited = new HashSet<>();
+
+ Deque<Edge<Archive>> deque = new LinkedList<>();
+ deque.addAll(graph.edgesFrom(target));
+ if (deque.isEmpty()) {
+ return makePaths(path).collect(Collectors.toSet());
+ }
+
+ Set<Deque<Archive>> allPaths = new HashSet<>();
+ while (!deque.isEmpty()) {
+ Edge<Archive> edge = deque.pop();
+
+ if (visited.contains(edge))
+ continue;
+
+ Archive node = edge.v;
+ path.addLast(node);
+ visited.add(edge);
+
+ Set<Edge<Archive>> unvisitedDeps = graph.edgesFrom(node)
+ .stream()
+ .filter(e -> !visited.contains(e))
+ .collect(Collectors.toSet());
+
+ trace("visiting %s %s (%s)%n", edge, path, unvisitedDeps);
+ if (unvisitedDeps.isEmpty()) {
+ makePaths(path).forEach(allPaths::add);
+ path.removeLast();
+ }
+
+ // push unvisited adjacent edges
+ unvisitedDeps.stream().forEach(deque::push);
+
+
+ // when the adjacent edges of a node are visited, pop it from the path
+ while (!path.isEmpty()) {
+ if (visited.containsAll(graph.edgesFrom(path.peekLast())))
+ path.removeLast();
+ else
+ break;
+ }
+ }
+
+ return allPaths;
+ }
+
+ /**
+ * Prepend end point to the path
+ */
+ private Stream<Deque<Archive>> makePaths(Deque<Archive> path) {
+ Set<Archive> nodes = endPoints.get(path.peekFirst());
+ if (nodes == null || nodes.isEmpty()) {
+ return Stream.of(new LinkedList<>(path));
+ } else {
+ return nodes.stream().map(n -> {
+ Deque<Archive> newPath = new LinkedList<>();
+ newPath.addFirst(n);
+ newPath.addAll(path);
+ return newPath;
+ });
+ }
+ }
+}
--- a/langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsConfiguration.java Thu May 19 10:55:33 2016 -0700
+++ b/langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsConfiguration.java Thu May 19 11:01:26 2016 -0700
@@ -65,7 +65,10 @@
import java.util.stream.Stream;
public class JdepsConfiguration {
- private static final String MODULE_INFO = "module-info.class";
+ // the token for "all modules on the module path"
+ public static final String ALL_MODULE_PATH = "ALL-MODULE-PATH";
+ public static final String ALL_DEFAULT = "ALL-DEFAULT";
+ public static final String MODULE_INFO = "module-info.class";
private final SystemModuleFinder system;
private final ModuleFinder finder;
@@ -83,7 +86,8 @@
ModuleFinder finder,
Set<String> roots,
List<Path> classpaths,
- List<Archive> initialArchives)
+ List<Archive> initialArchives,
+ boolean allDefaultModules)
throws IOException
{
trace("root: %s%n", roots);
@@ -91,15 +95,13 @@
this.system = systemModulePath;
this.finder = finder;
- // build root set
- Set<String> mods = new HashSet<>();
+ // build root set for resolution
+ Set<String> mods = new HashSet<>(roots);
- if (initialArchives.isEmpty() && classpaths.isEmpty() && !roots.isEmpty()) {
- // named module as root. No initial unnamed module
- mods.addAll(roots);
- } else {
- // unnamed module
- mods.addAll(roots);
+ // add default modules to the root set
+ // unnamed module
+ if (!initialArchives.isEmpty() || !classpaths.isEmpty() ||
+ roots.isEmpty() || allDefaultModules) {
mods.addAll(systemModulePath.defaultSystemRoots());
}
@@ -190,6 +192,10 @@
return system.find(m.name()).isPresent();
}
+ boolean isValidToken(String name) {
+ return ALL_MODULE_PATH.equals(name) || ALL_DEFAULT.equals(name);
+ }
+
/**
* Returns the modules that the given module can read
*/
@@ -299,6 +305,7 @@
}
static class SystemModuleFinder implements ModuleFinder {
+ private static final String JAVA_HOME = System.getProperty("java.home");
private static final String JAVA_SE = "java.se";
private final FileSystem fileSystem;
@@ -306,41 +313,50 @@
private final Map<String, ModuleReference> systemModules;
SystemModuleFinder() {
- this(System.getProperty("java.home"));
+ if (Files.isRegularFile(Paths.get(JAVA_HOME, "lib", "modules"))) {
+ // jrt file system
+ this.fileSystem = FileSystems.getFileSystem(URI.create("jrt:/"));
+ this.root = fileSystem.getPath("/modules");
+ this.systemModules = walk(root);
+ } else {
+ // exploded image
+ this.fileSystem = FileSystems.getDefault();
+ root = Paths.get(JAVA_HOME, "modules");
+ this.systemModules = ModuleFinder.ofSystem().findAll().stream()
+ .collect(toMap(mref -> mref.descriptor().name(), Function.identity()));
+ }
}
- SystemModuleFinder(String javaHome) {
- final FileSystem fs;
- final Path root;
- final Map<String, ModuleReference> systemModules;
- if (javaHome != null) {
- if (Files.isRegularFile(Paths.get(javaHome, "lib", "modules"))) {
- try {
- // jrt file system
- fs = FileSystems.getFileSystem(URI.create("jrt:/"));
- root = fs.getPath("/modules");
- systemModules = Files.walk(root, 1)
- .filter(path -> !path.equals(root))
- .map(this::toModuleReference)
- .collect(toMap(mref -> mref.descriptor().name(),
- Function.identity()));
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- } else {
- // exploded image
- fs = FileSystems.getDefault();
- root = Paths.get(javaHome, "modules");
- systemModules = ModuleFinder.ofSystem().findAll().stream()
- .collect(toMap(mref -> mref.descriptor().name(), Function.identity()));
- }
+
+ SystemModuleFinder(String javaHome) throws IOException {
+ if (javaHome == null) {
+ // -system none
+ this.fileSystem = null;
+ this.root = null;
+ this.systemModules = Collections.emptyMap();
} else {
- fs = null;
- root = null;
- systemModules = Collections.emptyMap();
+ if (Files.isRegularFile(Paths.get(javaHome, "lib", "modules")))
+ throw new IllegalArgumentException("Invalid java.home: " + javaHome);
+
+ // alternate java.home
+ Map<String, String> env = new HashMap<>();
+ env.put("java.home", javaHome);
+ // a remote run-time image
+ this.fileSystem = FileSystems.newFileSystem(URI.create("jrt:/"), env);
+ this.root = fileSystem.getPath("/modules");
+ this.systemModules = walk(root);
}
- this.fileSystem = fs;
- this.root = root;
- this.systemModules = systemModules;
+ }
+
+ private Map<String, ModuleReference> walk(Path root) {
+ try {
+ return Files.walk(root, 1)
+ .filter(path -> !path.equals(root))
+ .map(this::toModuleReference)
+ .collect(toMap(mref -> mref.descriptor().name(),
+ Function.identity()));
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
}
private ModuleReference toModuleReference(Path path) {
@@ -437,8 +453,6 @@
}
public static class Builder {
- // the token for "all modules on the module path"
- private static final String ALL_MODULE_PATH = "ALL-MODULE-PATH";
final SystemModuleFinder systemModulePath;
final Set<String> rootModules = new HashSet<>();
@@ -449,13 +463,16 @@
ModuleFinder upgradeModulePath;
ModuleFinder appModulePath;
boolean addAllApplicationModules;
+ boolean addAllDefaultModules;
public Builder() {
- this(System.getProperty("java.home"));
+ this.systemModulePath = new SystemModuleFinder();
}
- public Builder(String systemModulePath) {
- this.systemModulePath = new SystemModuleFinder(systemModulePath);;
+ public Builder(String javaHome) throws IOException {
+ this.systemModulePath = SystemModuleFinder.JAVA_HOME.equals(javaHome)
+ ? new SystemModuleFinder()
+ : new SystemModuleFinder(javaHome);
}
public Builder upgradeModulePath(String upgradeModulePath) {
@@ -474,6 +491,9 @@
case ALL_MODULE_PATH:
this.addAllApplicationModules = true;
break;
+ case ALL_DEFAULT:
+ this.addAllDefaultModules = true;
+ break;
default:
this.rootModules.add(mn);
}
@@ -531,11 +551,13 @@
.map(mref -> mref.descriptor().name())
.forEach(rootModules::add);
}
+
return new JdepsConfiguration(systemModulePath,
finder,
rootModules,
classPaths,
- initialArchives);
+ initialArchives,
+ addAllDefaultModules);
}
private static ModuleFinder createModulePathFinder(String mpaths) {
--- a/langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsTask.java Thu May 19 10:55:33 2016 -0700
+++ b/langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsTask.java Thu May 19 11:01:26 2016 -0700
@@ -28,6 +28,7 @@
import static com.sun.tools.jdeps.Analyzer.NOT_FOUND;
import static com.sun.tools.jdeps.Analyzer.Type.*;
import static com.sun.tools.jdeps.JdepsWriter.*;
+import static com.sun.tools.jdeps.JdepsConfiguration.ALL_MODULE_PATH;
import java.io.IOException;
import java.io.PrintWriter;
@@ -37,6 +38,8 @@
import java.nio.file.Paths;
import java.text.MessageFormat;
import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
@@ -246,6 +249,25 @@
task.options.upgradeModulePath = arg;
}
},
+ new Option(true, "-system") {
+ void process(JdepsTask task, String opt, String arg) throws BadArgs {
+ if (arg.equals("none")) {
+ task.options.systemModulePath = null;
+ } else {
+ Path path = Paths.get(arg);
+ if (Files.isRegularFile(path.resolve("lib").resolve("modules")))
+ task.options.systemModulePath = arg;
+ else
+ throw new BadArgs("err.invalid.path", arg);
+ }
+ }
+ },
+ new Option(true, "-addmods") {
+ void process(JdepsTask task, String opt, String arg) throws BadArgs {
+ Set<String> mods = Set.of(arg.split(","));
+ task.options.addmods.addAll(mods);
+ }
+ },
new Option(true, "-m") {
void process(JdepsTask task, String opt, String arg) throws BadArgs {
task.options.rootModule = arg;
@@ -264,7 +286,7 @@
task.options.regex = Pattern.compile(arg);
}
},
- new Option(true, "-module") {
+ new Option(true, "-requires") {
void process(JdepsTask task, String opt, String arg) {
task.options.requires.add(arg);
}
@@ -315,6 +337,7 @@
task.options.showProfile = true;
}
},
+
new Option(false, "-R", "-recursive") {
void process(JdepsTask task, String opt, String arg) {
task.options.depth = 0;
@@ -323,6 +346,17 @@
task.options.filterSamePackage = false;
}
},
+
+ new Option(false, "-I", "-inverse") {
+ void process(JdepsTask task, String opt, String arg) {
+ task.options.inverse = true;
+ // equivalent to the inverse of compile-time view analysis
+ task.options.compileTimeView = true;
+ task.options.filterSamePackage = true;
+ task.options.filterSameArchive = true;
+ }
+ },
+
new Option(false, "-ct", "-compile-time") {
void process(JdepsTask task, String opt, String arg) {
task.options.compileTimeView = true;
@@ -423,6 +457,16 @@
return EXIT_CMDERR;
}
+ if (options.inverse && options.depth != 1) {
+ reportError("err.invalid.inverse.option", "-R");
+ return EXIT_CMDERR;
+ }
+
+ if (options.inverse && options.numFilters() == 0) {
+ reportError("err.invalid.filters");
+ return EXIT_CMDERR;
+ }
+
if ((options.findJDKInternals) && (options.hasFilter() || options.showSummary)) {
showHelp();
return EXIT_CMDERR;
@@ -463,8 +507,9 @@
.forEach(e -> System.out.format("split package: %s %s%n", e.getKey(),
e.getValue().toString()));
- // check if any module specified in -module is missing
+ // check if any module specified in -requires is missing
Stream.concat(options.addmods.stream(), options.requires.stream())
+ .filter(mn -> !config.isValidToken(mn))
.forEach(mn -> config.findModule(mn).orElseThrow(() ->
new UncheckedBadArgs(new BadArgs("err.module.not.found", mn))));
@@ -484,7 +529,11 @@
return new ModuleAnalyzer(config, log).genDotFiles(options.dotOutputDir);
}
- return analyzeDeps(config);
+ if (options.inverse) {
+ return analyzeInverseDeps(config);
+ } else {
+ return analyzeDeps(config);
+ }
}
private JdepsConfiguration buildConfig() throws IOException {
@@ -540,7 +589,7 @@
boolean ok = analyzer.run(options.compileTimeView, options.depth);
// print skipped entries, if any
- analyzer.analyzer.archives()
+ analyzer.archives()
.forEach(archive -> archive.reader()
.skippedEntries().stream()
.forEach(name -> warning("warn.skipped.entry",
@@ -569,6 +618,40 @@
return ok;
}
+ private boolean analyzeInverseDeps(JdepsConfiguration config) throws IOException {
+ JdepsWriter writer = new SimpleWriter(log,
+ options.verbose,
+ options.showProfile,
+ options.showModule);
+
+ InverseDepsAnalyzer analyzer = new InverseDepsAnalyzer(config,
+ dependencyFilter(config),
+ writer,
+ options.verbose,
+ options.apiOnly);
+ boolean ok = analyzer.run();
+
+ log.println();
+ if (!options.requires.isEmpty())
+ log.format("Inverse transitive dependences on %s%n", options.requires);
+ else
+ log.format("Inverse transitive dependences matching %s%n",
+ options.regex != null
+ ? options.regex.toString()
+ : "packages " + options.packageNames);
+
+ analyzer.inverseDependences().stream()
+ .sorted(Comparator.comparing(this::sortPath))
+ .forEach(path -> log.println(path.stream()
+ .map(Archive::getName)
+ .collect(Collectors.joining(" <- "))));
+ return ok;
+ }
+
+ private String sortPath(Deque<Archive> path) {
+ return path.peekFirst().getName();
+ }
+
private boolean genModuleInfo(JdepsConfiguration config) throws IOException {
ModuleInfoBuilder builder
= new ModuleInfoBuilder(config, inputArgs, options.genModuleInfo);
@@ -603,7 +686,7 @@
* Returns a filter used during dependency analysis
*/
private JdepsFilter dependencyFilter(JdepsConfiguration config) {
- // Filter specified by -filter, -package, -regex, and -module options
+ // Filter specified by -filter, -package, -regex, and -requires options
JdepsFilter.Builder builder = new JdepsFilter.Builder();
// source filters
@@ -613,7 +696,7 @@
builder.filter(options.filterSamePackage, options.filterSameArchive);
builder.findJDKInternals(options.findJDKInternals);
- // -module
+ // -requires
if (!options.requires.isEmpty()) {
options.requires.stream()
.forEach(mn -> {
@@ -753,6 +836,7 @@
Pattern regex; // apply to the dependences
Pattern includePattern;
Pattern includeSystemModulePattern;
+ boolean inverse = false;
boolean compileTimeView = false;
Set<String> checkModuleDeps;
String systemModulePath = System.getProperty("java.home");
--- a/langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/resources/jdeps.properties Thu May 19 10:55:33 2016 -0700
+++ b/langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/resources/jdeps.properties Thu May 19 11:01:26 2016 -0700
@@ -48,10 +48,10 @@
main.opt.e=\
\ -e <regex> -regex <regex> Finds dependences matching the given pattern.
-main.opt.module=\
-\ -module <module-name> Finds dependences matching the given module\n\
+main.opt.requires=\
+\ -requires <module-name> Finds dependences matching the given module\n\
\ name (may be given multiple times).\n\
-\ -package, -regex, -module are mutual exclusive.
+\ -package, -regex, -requires are mutual exclusive.
main.opt.include=\n\
\Options to filter classes to be analyzed:\n\
@@ -73,6 +73,13 @@
main.opt.upgrademodulepath=\
\ -upgrademodulepath <module path>... Specify upgrade module path
+main.opt.system=\
+\ -system <java-home> Specify an alternate system module path
+
+main.opt.addmods=\
+\ -addmods <module-name>[,<module-name>...]\n\
+\ Adds modules to the root set for analysis
+
main.opt.m=\
\ -m <module-name> Specify the root module for analysis
@@ -82,6 +89,15 @@
\ -e, -foption is specified, only the matching\n\
\ dependences are analyzed.
+main.opt.I=\
+\ -I -inverse Analyzes the dependences per other given options\n\
+\ and then find all artifacts that directly\n\
+\ and indirectly depend on the matching nodes.\n\
+\ This is equivalent to the inverse of\n\
+\ compile-time view analysis and print\n\
+\ dependency summary. This option must use\n\
+\ with -requires, -package or -regex option.
+
main.opt.ct=\
\ -ct -compile-time Compile-time view of transitive dependencies\n\
\ i.e. compile-time view of -R option.\n\
@@ -140,9 +156,11 @@
err.exception.message={0}
err.invalid.path=invalid path: {0}
err.invalid.module.option=Cannot set {0} with {1} option.
-err.invalid.filters=Only one of -package (-p), -regex (-e), -module option can be set
+err.invalid.filters=Only one of -package (-p), -regex (-e), -requires option can be set
err.module.not.found=module not found: {0}
err.root.module.not.set=root module set empty
+err.invalid.inverse.option={0} cannot be used with -inverse option
+err.inverse.filter.not.set={0} cannot be used with -inverse option
warn.invalid.arg=Path not exist: {0}
warn.split.package=package {0} defined in {1} {2}
warn.replace.useJDKInternals=\
--- a/langtools/test/tools/jdeps/lib/JdepsUtil.java Thu May 19 10:55:33 2016 -0700
+++ b/langtools/test/tools/jdeps/lib/JdepsUtil.java Thu May 19 11:01:26 2016 -0700
@@ -23,6 +23,7 @@
import com.sun.tools.jdeps.Analyzer;
import com.sun.tools.jdeps.DepsAnalyzer;
+import com.sun.tools.jdeps.InverseDepsAnalyzer;
import com.sun.tools.jdeps.JdepsConfiguration;
import com.sun.tools.jdeps.JdepsFilter;
import com.sun.tools.jdeps.JdepsWriter;
@@ -199,6 +200,11 @@
return new ModuleAnalyzer(configuration(), pw, mods);
}
+ public InverseDepsAnalyzer getInverseDepsAnalyzer() throws IOException {
+ return new InverseDepsAnalyzer(configuration(), filter.build(), writer(),
+ verbose, false);
+ }
+
public void dumpOutput(PrintStream out) {
out.println(sw.toString());
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/jdeps/modules/InverseDeps.java Thu May 19 11:01:26 2016 -0700
@@ -0,0 +1,295 @@
+/*
+ * Copyright (c) 2016, 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
+ * @summary Tests split packages
+ * @library ../lib
+ * @build CompilerUtils JdepsUtil
+ * @modules jdk.jdeps/com.sun.tools.jdeps
+ * @run testng InverseDeps
+ */
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import com.sun.tools.jdeps.Archive;
+import com.sun.tools.jdeps.InverseDepsAnalyzer;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertEquals;
+
+
+public class InverseDeps {
+ private static final String TEST_SRC = System.getProperty("test.src");
+ private static final String TEST_CLASSES = System.getProperty("test.classes");
+
+ private static final Path SRC_DIR = Paths.get(TEST_SRC, "src");
+ private static final Path MODS_DIR = Paths.get("mods");
+ private static final Path LIBS_DIR = Paths.get("libs");
+
+ private static final Set<String> modules = new LinkedHashSet(
+ List.of("unsafe", "m4", "m5", "m6", "m7")
+ );
+
+ /**
+ * Compiles classes used by the test
+ */
+ @BeforeTest
+ public void compileAll() throws Exception {
+ CompilerUtils.cleanDir(MODS_DIR);
+
+ for (String mn : modules) {
+ // compile a module
+ assertTrue(CompilerUtils.compileModule(SRC_DIR, MODS_DIR, mn));
+
+ // create JAR files with no module-info.class
+ Path root = MODS_DIR.resolve(mn);
+ JdepsUtil.createJar(LIBS_DIR.resolve(mn + ".jar"), root,
+ Files.walk(root, Integer.MAX_VALUE)
+ .filter(f -> {
+ String fn = f.getFileName().toString();
+ return fn.endsWith(".class") && !fn.equals("module-info.class");
+ }));
+ }
+ }
+
+ @DataProvider(name = "testrequires")
+ public Object[][] expected1() {
+ return new Object[][] {
+ // -requires and result
+ { "java.sql", new String[][] {
+ new String[] { "java.sql", "m5" },
+ }
+ },
+ { "java.compiler", new String[][] {
+ new String[] { "java.compiler", "m5" },
+ new String[] { "java.compiler", "m4", "m5" },
+ }
+ },
+ { "java.logging", new String[][]{
+ new String[] {"java.logging", "m5"},
+ new String[] {"java.logging", "m4", "m5"},
+ new String[] {"java.logging", "java.sql", "m5"},
+ }
+ },
+ { "jdk.unsupported", new String[][] {
+ new String[] {"jdk.unsupported", "unsafe", "m6", "m7"},
+ new String[] {"jdk.unsupported", "unsafe", "m7"}
+ }
+ },
+ };
+ }
+
+ @Test(dataProvider = "testrequires")
+ public void testrequires(String name, String[][] expected) throws Exception {
+ JdepsUtil.Command jdeps = JdepsUtil.newCommand(
+ String.format("jdeps -inverse -modulepath %s -requires %s -addmods %s%n",
+ MODS_DIR, name, modules.stream().collect(Collectors.joining(","))
+ ));
+ jdeps.appModulePath(MODS_DIR.toString())
+ .addmods(modules)
+ .requires(Set.of(name));
+
+ runJdeps(jdeps, expected);
+
+ // automatic module
+ jdeps = JdepsUtil.newCommand(
+ String.format("jdeps -inverse -modulepath %s -requires %s -addmods ALL-MODULE-PATH%n",
+ LIBS_DIR, name)
+ );
+ jdeps.appModulePath(MODS_DIR.toString())
+ .addmods(Set.of("ALL-MODULE-PATH"))
+ .requires(Set.of(name));
+
+ runJdeps(jdeps, expected);
+ }
+
+ @DataProvider(name = "testpackage")
+ public Object[][] expected2() {
+ return new Object[][] {
+ // -package and result
+ { "p4", new String[][] {
+ new String[] { "m4", "m5"},
+ }
+ },
+ { "javax.tools", new String[][] {
+ new String[] {"java.compiler", "m5"},
+ new String[] {"java.compiler", "m4", "m5"},
+ }
+ },
+ { "sun.misc", new String[][] {
+ new String[] {"jdk.unsupported", "unsafe", "m6", "m7"},
+ new String[] {"jdk.unsupported", "unsafe", "m7"}
+ }
+ }
+ };
+ }
+
+ @Test(dataProvider = "testpackage")
+ public void testpackage(String name, String[][] expected) throws Exception {
+ JdepsUtil.Command jdeps = JdepsUtil.newCommand(
+ String.format("jdeps -inverse -modulepath %s -package %s -addmods %s%n",
+ MODS_DIR, name, modules.stream().collect(Collectors.joining(","))
+ ));
+ jdeps.appModulePath(MODS_DIR.toString())
+ .addmods(modules)
+ .matchPackages(Set.of(name));
+
+ runJdeps(jdeps, expected);
+ }
+
+ @DataProvider(name = "testregex")
+ public Object[][] expected3() {
+ return new Object[][] {
+ // -regex and result
+ { "org.safe.Lib", new String[][] {
+ new String[] { "unsafe", "m7"},
+ new String[] { "unsafe", "m6", "m7"},
+ }
+ },
+ { "java.util.logging.*|org.safe.Lib", new String[][] {
+ new String[] { "unsafe", "m7"},
+ new String[] { "unsafe", "m6", "m7"},
+ new String[] { "java.logging", "m5"},
+ }
+ }
+ };
+ }
+
+ @Test(dataProvider = "testregex")
+ public void testregex(String name, String[][] expected) throws Exception {
+ JdepsUtil.Command jdeps = JdepsUtil.newCommand(
+ String.format("jdeps -inverse -modulepath %s -regex %s -addmods %s%n",
+ MODS_DIR, name, modules.stream().collect(Collectors.joining(",")))
+ );
+
+ jdeps.appModulePath(MODS_DIR.toString())
+ .addmods(modules)
+ .regex(name);
+
+ runJdeps(jdeps, expected);
+ }
+
+ @DataProvider(name = "classpath")
+ public Object[][] expected4() {
+ return new Object[][] {
+ // -regex and result
+ { "sun.misc.Unsafe", new String[][] {
+ new String[] {"jdk.unsupported", "unsafe.jar", "m6.jar", "m7.jar"},
+ new String[] {"jdk.unsupported", "unsafe.jar", "m7.jar"}
+ }
+ },
+ { "org.safe.Lib", new String[][] {
+ new String[] { "unsafe.jar", "m7.jar"},
+ new String[] { "unsafe.jar", "m6.jar", "m7.jar"},
+ }
+ },
+ { "java.util.logging.*|org.safe.Lib", new String[][] {
+ new String[] { "unsafe.jar", "m7.jar"},
+ new String[] { "unsafe.jar", "m6.jar", "m7.jar"},
+ new String[] { "java.logging", "m5.jar"},
+ }
+ }
+ };
+ }
+
+ @Test(dataProvider = "classpath")
+ public void testClassPath(String name, String[][] expected) throws Exception {
+ // -classpath
+ String cpath = modules.stream()
+ .filter(mn -> !mn.equals("m7"))
+ .map(mn -> LIBS_DIR.resolve(mn + ".jar").toString())
+ .collect(Collectors.joining(File.pathSeparator));
+
+ Path jarfile = LIBS_DIR.resolve("m7.jar");
+ JdepsUtil.Command jdeps = JdepsUtil.newCommand(
+ String.format("jdeps -inverse -classpath %s -regex %s %s%n",
+ cpath, name, jarfile)
+ );
+ jdeps.verbose("-verbose:class")
+ .addClassPath(cpath)
+ .regex(name).addRoot(jarfile);
+ runJdeps(jdeps, expected);
+
+ // all JAR files on the command-line arguments
+ Set<Path> paths = modules.stream()
+ .map(mn -> LIBS_DIR.resolve(mn + ".jar"))
+ .collect(Collectors.toSet());
+ jdeps = JdepsUtil.newCommand(
+ String.format("jdeps -inverse -regex %s %s%n", name, paths)
+ );
+ jdeps.verbose("-verbose:class").regex(name);
+ paths.forEach(jdeps::addRoot);
+ runJdeps(jdeps, expected);
+
+ }
+
+ private void runJdeps(JdepsUtil.Command jdeps, String[][] expected) throws Exception {
+ InverseDepsAnalyzer analyzer = jdeps.getInverseDepsAnalyzer();
+
+ assertTrue(analyzer.run());
+
+ // get the inverse transitive dependences
+ List<String[]> paths = analyzer.inverseDependences().stream()
+ .map(deque -> deque.stream()
+ .map(Archive::getName)
+ .collect(Collectors.toList()).toArray(new String[0]))
+ .collect(Collectors.toList());
+
+ jdeps.dumpOutput(System.err);
+ paths.forEach(path -> System.err.println(Arrays.stream(path)
+ .collect(Collectors.joining(" <- "))));
+
+ // verify the dependences
+ assertEquals(paths.size(), expected.length);
+
+ for (int i=0; i < paths.size(); i++) {
+ String[] path = paths.get(i);
+ boolean noneMatched = Arrays.stream(expected)
+ .filter(array -> array.length == path.length)
+ .noneMatch(array -> Arrays.equals(array, path));
+ if (noneMatched)
+ System.err.format("Expected: %s found: %s%n",
+ Arrays.stream(expected)
+ .map(Arrays::toString)
+ .collect(Collectors.joining(", ")),
+ Arrays.toString(path));
+
+ assertFalse(noneMatched);
+ }
+
+ }
+
+}