changeset 3733:117bd6e52035

8167057: jdeps option to list modules and internal APIs for @modules for test dev Reviewed-by: dfuchs
author mchung
date Mon, 31 Oct 2016 18:06:03 -0700
parents e0798bbe23da
children 11ab0f581f11
files src/jdk.jdeps/share/classes/com/sun/tools/jdeps/DepsAnalyzer.java src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsFilter.java src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsTask.java src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsWriter.java src/jdk.jdeps/share/classes/com/sun/tools/jdeps/Module.java src/jdk.jdeps/share/classes/com/sun/tools/jdeps/ModuleAnalyzer.java src/jdk.jdeps/share/classes/com/sun/tools/jdeps/ModuleExportsAnalyzer.java src/jdk.jdeps/share/classes/com/sun/tools/jdeps/ModuleGraphBuilder.java src/jdk.jdeps/share/classes/com/sun/tools/jdeps/resources/jdeps.properties test/tools/jdeps/lib/JdepsRunner.java test/tools/jdeps/listdeps/ListModuleDeps.java test/tools/jdeps/listdeps/src/lib/Lib.java test/tools/jdeps/listdeps/src/z/Bar.java test/tools/jdeps/listdeps/src/z/Foo.java test/tools/jdeps/listdeps/src/z/UseUnsafe.java
diffstat 15 files changed, 729 insertions(+), 100 deletions(-) [+]
line wrap: on
line diff
--- a/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/DepsAnalyzer.java	Fri Oct 28 10:17:56 2016 +0100
+++ b/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/DepsAnalyzer.java	Mon Oct 31 18:06:03 2016 -0700
@@ -50,7 +50,7 @@
  *
  * Type of filters:
  * source filter: -include <pattern>
- * target filter: -package, -regex, -requires
+ * target filter: -package, -regex, --require
  *
  * The initial archive set for analysis includes
  * 1. archives specified in the command line arguments
@@ -146,7 +146,9 @@
             // analyze the dependencies collected
             analyzer.run(archives, finder.locationToArchive());
 
-            writer.generateOutput(archives, analyzer);
+            if (writer != null) {
+                writer.generateOutput(archives, analyzer);
+            }
         } finally {
             finder.shutdown();
         }
@@ -156,7 +158,7 @@
     /**
      * Returns the archives for reporting that has matching dependences.
      *
-     * If -requires is set, they should be excluded.
+     * If --require is set, they should be excluded.
      */
     Set<Archive> archives() {
         if (filter.requiresFilter().isEmpty()) {
@@ -165,7 +167,7 @@
                 .filter(Archive::hasDependences)
                 .collect(Collectors.toSet());
         } else {
-            // use the archives that have dependences and not specified in -requires
+            // use the archives that have dependences and not specified in --require
             return archives.stream()
                 .filter(filter::include)
                 .filter(source -> !filter.requiresFilter().contains(source))
@@ -360,16 +362,17 @@
         Archive source = dep.originArchive();
         Archive target = dep.targetArchive();
         String pn = dep.target();
-        if ((verbose == CLASS || verbose == VERBOSE)) {
+        if (verbose == CLASS || verbose == VERBOSE) {
             int i = dep.target().lastIndexOf('.');
             pn = i > 0 ? dep.target().substring(0, i) : "";
         }
         final Info info;
+        Module targetModule = target.getModule();
         if (source == target) {
             info = Info.MODULE_PRIVATE;
-        } else if (!target.getModule().isNamed()) {
+        } else if (!targetModule.isNamed()) {
             info = Info.EXPORTED_API;
-        } else if (target.getModule().isExported(pn)) {
+        } else if (targetModule.isExported(pn) && !targetModule.isJDKUnsupported()) {
             info = Info.EXPORTED_API;
         } else {
             Module module = target.getModule();
--- a/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsFilter.java	Fri Oct 28 10:17:56 2016 +0100
+++ b/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsFilter.java	Mon Oct 31 18:06:03 2016 -0700
@@ -41,7 +41,7 @@
  * 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.
- * 3. -requires specifies to match target dependence from the given module
+ * 3. --require specifies to match target dependence from the given module
  *    This gets expanded into package lists to be filtered.
  * 4. -filter:archive to filter out same-archive dependencies
  *    This filter is applied later in the Analyzer as the
@@ -166,7 +166,7 @@
             // accepts target that is JDK class but not exported
             Module module = targetArchive.getModule();
             return originArchive != targetArchive &&
-                    module.isJDK() && !module.isExported(target.getPackageName());
+                    isJDKInternalPackage(module, target.getPackageName());
         } else if (filterSameArchive) {
             // accepts origin and target that from different archive
             return originArchive != targetArchive;
@@ -174,6 +174,18 @@
         return true;
     }
 
+    /**
+     * Tests if the package is an internal package of the given module.
+     */
+    public boolean isJDKInternalPackage(Module module, String pn) {
+        if (module.isJDKUnsupported()) {
+            // its exported APIs are unsupported
+            return true;
+        }
+
+        return module.isJDK() && !module.isExported(pn);
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
--- a/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsTask.java	Fri Oct 28 10:17:56 2016 +0100
+++ b/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsTask.java	Mon Oct 31 18:06:03 2016 -0700
@@ -221,6 +221,12 @@
                 }
             }
         },
+        new Option(false, "--list-deps", "--list-reduced-deps") {
+            void process(JdepsTask task, String opt, String arg) {
+                task.options.showModulesAddExports = true;
+                task.options.reduced = opt.equals("--list-reduced-deps");
+            }
+        },
 
         // ---- paths option ----
         new Option(true, "-cp", "-classpath", "--class-path") {
@@ -517,13 +523,13 @@
                 .forEach(e -> System.out.format("split package: %s %s%n", e.getKey(),
                     e.getValue().toString()));
 
-            // check if any module specified in -requires is missing
+            // check if any module specified in --require 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))));
 
-            // --gen-module-info
+            // --generate-module-info
             if (options.genModuleInfo != null) {
                 return genModuleInfo(config);
             }
@@ -533,6 +539,13 @@
                 return new ModuleAnalyzer(config, log, options.checkModuleDeps).run();
             }
 
+            if (options.showModulesAddExports) {
+                return new ModuleExportsAnalyzer(config,
+                                                 dependencyFilter(config),
+                                                 options.reduced,
+                                                 log).run();
+            }
+
             if (options.dotOutputDir != null &&
                 (options.verbose == SUMMARY || options.verbose == MODULE) &&
                 !options.addmods.isEmpty() && inputArgs.isEmpty()) {
@@ -555,7 +568,7 @@
                .appModulePath(options.modulePath)
                .addmods(options.addmods);
 
-        if (options.checkModuleDeps != null) {
+        if (options.checkModuleDeps != null || options.showModulesAddExports) {
             // check all system modules in the image
             builder.allModules();
         }
@@ -597,10 +610,10 @@
 
         // analyze the dependencies
         DepsAnalyzer analyzer = new DepsAnalyzer(config,
-                                        dependencyFilter(config),
-                                        writer,
-                                        options.verbose,
-                                        options.apiOnly);
+                                                 dependencyFilter(config),
+                                                 writer,
+                                                 options.verbose,
+                                                 options.apiOnly);
 
         boolean ok = analyzer.run(options.compileTimeView, options.depth);
 
@@ -727,7 +740,7 @@
      * Returns a filter used during dependency analysis
      */
     private JdepsFilter dependencyFilter(JdepsConfiguration config) {
-        // Filter specified by -filter, -package, -regex, and -requires options
+        // Filter specified by -filter, -package, -regex, and --require options
         JdepsFilter.Builder builder = new JdepsFilter.Builder();
 
         // source filters
@@ -737,7 +750,7 @@
         builder.filter(options.filterSamePackage, options.filterSameArchive);
         builder.findJDKInternals(options.findJDKInternals);
 
-        // -requires
+        // --require
         if (!options.requires.isEmpty()) {
             options.requires.stream()
                 .forEach(mn -> {
@@ -757,8 +770,8 @@
 
         // check if system module is set
         config.rootModules().stream()
-            .map(Module::name)
-            .forEach(builder::includeIfSystemModule);
+              .map(Module::name)
+              .forEach(builder::includeIfSystemModule);
 
         return builder.build();
     }
@@ -886,6 +899,8 @@
         String rootModule;
         Set<String> addmods = new HashSet<>();
         Runtime.Version multiRelease;
+        boolean showModulesAddExports;
+        boolean reduced;
 
         boolean hasFilter() {
             return numFilters() > 0;
--- a/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsWriter.java	Fri Oct 28 10:17:56 2016 +0100
+++ b/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsWriter.java	Mon Oct 31 18:06:03 2016 -0700
@@ -50,7 +50,7 @@
     final boolean showProfile;
     final boolean showModule;
 
-    private JdepsWriter(Analyzer.Type type, boolean showProfile, boolean showModule) {
+    JdepsWriter(Analyzer.Type type, boolean showProfile, boolean showModule) {
         this.type = type;
         this.showProfile = showProfile;
         this.showModule = showModule;
@@ -318,8 +318,7 @@
         }
 
         // exported API
-        boolean jdkunsupported = Module.JDK_UNSUPPORTED.equals(module.name());
-        if (module.isExported(pn) && !jdkunsupported) {
+        if (module.isExported(pn) && !module.isJDKUnsupported()) {
             return showProfileOrModule(module);
         }
 
--- a/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/Module.java	Fri Oct 28 10:17:56 2016 +0100
+++ b/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/Module.java	Mon Oct 31 18:06:03 2016 -0700
@@ -126,12 +126,13 @@
      * Tests if the package of the given name is exported.
      */
     public boolean isExported(String pn) {
-        if (JDK_UNSUPPORTED.equals(this.name())) {
-            return false;
-        }
         return exports.containsKey(pn) ? exports.get(pn).isEmpty() : false;
     }
 
+    public boolean isJDKUnsupported() {
+        return JDK_UNSUPPORTED.equals(this.name());
+    }
+
     /**
      * Converts this module to a strict module with the given dependences
      *
--- a/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/ModuleAnalyzer.java	Fri Oct 28 10:17:56 2016 +0100
+++ b/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/ModuleAnalyzer.java	Mon Oct 31 18:06:03 2016 -0700
@@ -41,10 +41,8 @@
 import java.nio.file.Path;
 import java.util.Collections;
 import java.util.Comparator;
-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;
@@ -179,50 +177,43 @@
             return builder.build();
         }
 
-        ModuleDescriptor reduced() {
-            Graph.Builder<Module> bd = new Graph.Builder<>();
+        private Graph<Module> buildReducedGraph() {
+            ModuleGraphBuilder rpBuilder = new ModuleGraphBuilder(configuration);
+            rpBuilder.addModule(root);
             requiresPublic.stream()
-                .forEach(m -> {
-                    bd.addNode(m);
-                    bd.addEdge(root, m);
-                });
+                          .forEach(m -> rpBuilder.addEdge(root, m));
 
             // requires public graph
-            Graph<Module> rbg = bd.build().reduce();
+            Graph<Module> rbg = rpBuilder.build().reduce();
+
+            ModuleGraphBuilder gb = new ModuleGraphBuilder(configuration);
+            gb.addModule(root);
+            requires.stream()
+                    .forEach(m -> gb.addEdge(root, m));
 
             // transitive reduction
-            Graph<Module> newGraph = buildGraph(requires).reduce(rbg);
+            Graph<Module> newGraph = gb.buildGraph().reduce(rbg);
             if (DEBUG) {
                 System.err.println("after transitive reduction: ");
                 newGraph.printGraph(log);
             }
-
-            return descriptor(requiresPublic, newGraph.adjacentNodes(root));
+            return newGraph;
         }
 
+        /**
+         * Apply the transitive reduction on the module graph
+         * and returns the corresponding ModuleDescriptor
+         */
+        ModuleDescriptor reduced() {
+            Graph<Module> g = buildReducedGraph();
+            return descriptor(requiresPublic, g.adjacentNodes(root));
+        }
 
         /**
          * Apply transitive reduction on the resulting graph and reports
          * recommended requires.
          */
         private void analyzeDeps() {
-            Graph.Builder<Module> builder = new Graph.Builder<>();
-            requiresPublic.stream()
-                .forEach(m -> {
-                    builder.addNode(m);
-                    builder.addEdge(root, m);
-                });
-
-            // requires public graph
-            Graph<Module> rbg = buildGraph(requiresPublic).reduce();
-
-            // transitive reduction
-            Graph<Module> newGraph = buildGraph(requires).reduce(builder.build().reduce());
-            if (DEBUG) {
-                System.err.println("after transitive reduction: ");
-                newGraph.printGraph(log);
-            }
-
             printModuleDescriptor(log, root);
 
             ModuleDescriptor analyzedDescriptor = descriptor();
@@ -277,45 +268,6 @@
 
 
         /**
-         * Returns a graph of modules required by the specified module.
-         *
-         * Requires public edges of the dependences are added to the graph.
-         */
-        private Graph<Module> buildGraph(Set<Module> deps) {
-            Graph.Builder<Module> builder = new Graph.Builder<>();
-            builder.addNode(root);
-            Set<Module> visited = new HashSet<>();
-            visited.add(root);
-            Deque<Module> deque = new LinkedList<>();
-            deps.stream()
-                .forEach(m -> {
-                    deque.add(m);
-                    builder.addEdge(root, m);
-                });
-
-            // read requires public from ModuleDescription
-            Module source;
-            while ((source = deque.poll()) != null) {
-                if (visited.contains(source))
-                    continue;
-
-                visited.add(source);
-                builder.addNode(source);
-                Module from = source;
-                source.descriptor().requires().stream()
-                    .filter(req -> req.modifiers().contains(PUBLIC))
-                    .map(ModuleDescriptor.Requires::name)
-                    .map(configuration::findModule)
-                    .flatMap(Optional::stream)
-                    .forEach(m -> {
-                        deque.add(m);
-                        builder.addEdge(from, m);
-                    });
-            }
-            return builder.build();
-        }
-
-        /**
          * Detects any qualified exports not used by the target module.
          */
         private Map<String, Set<String>> unusedQualifiedExports() {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/ModuleExportsAnalyzer.java	Mon Oct 31 18:06:03 2016 -0700
@@ -0,0 +1,176 @@
+/*
+ * 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 java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.module.ModuleDescriptor;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import static com.sun.tools.jdeps.Analyzer.NOT_FOUND;
+
+/**
+ * Analyze module dependences and any reference to JDK internal APIs.
+ * It can apply transition reduction on the resulting module graph.
+ *
+ * The result prints one line per module it depends on
+ * one line per JDK internal API package it references:
+ *     $MODULE[/$PACKAGE]
+ *
+ */
+public class ModuleExportsAnalyzer extends DepsAnalyzer {
+    // source archive to its dependences and JDK internal APIs it references
+    private final Map<Archive, Map<Archive,Set<String>>> deps = new HashMap<>();
+    private final boolean reduced;
+    private final PrintWriter writer;
+    public ModuleExportsAnalyzer(JdepsConfiguration config,
+                                 JdepsFilter filter,
+                                 boolean reduced,
+                                 PrintWriter writer) {
+        super(config, filter, null,
+              Analyzer.Type.PACKAGE,
+              false /* all classes */);
+        this.reduced = reduced;
+        this.writer = writer;
+    }
+
+    @Override
+    public boolean run() throws IOException {
+        // analyze dependences
+        boolean rc = super.run();
+
+        // A visitor to record the module-level dependences as well as
+        // use of JDK internal APIs
+        Analyzer.Visitor visitor = new Analyzer.Visitor() {
+            @Override
+            public void visitDependence(String origin, Archive originArchive,
+                                        String target, Archive targetArchive)
+            {
+                Set<String> jdkInternals =
+                    deps.computeIfAbsent(originArchive, _k -> new HashMap<>())
+                        .computeIfAbsent(targetArchive, _k -> new HashSet<>());
+
+                Module module = targetArchive.getModule();
+                if (originArchive.getModule() != module &&
+                        module.isJDK() && !module.isExported(target)) {
+                    // use of JDK internal APIs
+                    jdkInternals.add(target);
+                }
+            }
+        };
+
+        // visit the dependences
+        archives.stream()
+            .filter(analyzer::hasDependences)
+            .sorted(Comparator.comparing(Archive::getName))
+            .forEach(archive -> analyzer.visitDependences(archive, visitor));
+
+
+        // print the dependences on named modules
+        printDependences();
+
+        // print the dependences on unnamed module
+        deps.values().stream()
+            .flatMap(map -> map.keySet().stream())
+            .filter(archive -> !archive.getModule().isNamed())
+            .map(archive -> archive != NOT_FOUND
+                                ? "unnamed module: " + archive.getPathName()
+                                : archive.getPathName())
+            .distinct()
+            .sorted()
+            .forEach(archive -> writer.format("   %s%n", archive));
+
+        return rc;
+    }
+
+    private void printDependences() {
+        // find use of JDK internals
+        Map<Module, Set<String>> jdkinternals = new HashMap<>();
+        deps.keySet().stream()
+            .filter(source -> !source.getModule().isNamed())
+            .map(deps::get)
+            .flatMap(map -> map.entrySet().stream())
+            .filter(e -> e.getValue().size() > 0)
+            .forEach(e -> jdkinternals.computeIfAbsent(e.getKey().getModule(),
+                                                       _k -> new HashSet<>())
+                                      .addAll(e.getValue()));
+
+
+        // build module graph
+        ModuleGraphBuilder builder = new ModuleGraphBuilder(configuration);
+        Module root = new RootModule("root");
+        builder.addModule(root);
+        // find named module dependences
+        deps.keySet().stream()
+            .filter(source -> !source.getModule().isNamed())
+            .map(deps::get)
+            .flatMap(map -> map.keySet().stream())
+            .filter(m -> m.getModule().isNamed())
+            .map(Archive::getModule)
+            .forEach(m -> builder.addEdge(root, m));
+
+        // module dependences
+        Set<Module> modules = builder.build().adjacentNodes(root);
+
+        // if reduced is set, apply transition reduction
+        Set<Module> reducedSet = reduced ? builder.reduced().adjacentNodes(root)
+                                         : modules;
+
+        modules.stream()
+               .sorted(Comparator.comparing(Module::name))
+               .forEach(m -> {
+                if (jdkinternals.containsKey(m)) {
+                    jdkinternals.get(m).stream()
+                        .sorted()
+                        .forEach(pn -> writer.format("   %s/%s%n", m, pn));
+                } else if (reducedSet.contains(m)){
+                    // if the transition reduction is applied, show the reduced graph
+                    writer.format("   %s%n", m);
+                }
+            });
+    }
+
+
+    private class RootModule extends Module {
+        final ModuleDescriptor descriptor;
+        RootModule(String name) {
+            super(name);
+
+            ModuleDescriptor.Builder builder = new ModuleDescriptor.Builder(name);
+            this.descriptor = builder.build();
+        }
+
+        @Override
+        public ModuleDescriptor descriptor() {
+            return descriptor;
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/ModuleGraphBuilder.java	Mon Oct 31 18:06:03 2016 -0700
@@ -0,0 +1,130 @@
+/*
+ * 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 java.io.PrintWriter;
+import java.lang.module.ModuleDescriptor;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import static java.lang.module.ModuleDescriptor.Requires.Modifier.*;
+import static com.sun.tools.jdeps.Module.*;
+
+/**
+ * A builder to create a Graph<Module>
+ */
+public class ModuleGraphBuilder extends Graph.Builder<Module> {
+    final JdepsConfiguration config;
+
+    ModuleGraphBuilder(JdepsConfiguration config) {
+        this.config = config;
+    }
+
+    /**
+     * Adds a module to the graph.
+     */
+    ModuleGraphBuilder addModule(Module module) {
+        addNode(module);
+        return this;
+    }
+
+    /**
+     * Apply transitive reduction on the resulting graph
+     */
+    public Graph<Module> reduced() {
+        Graph<Module> graph = build();
+        // transitive reduction
+        Graph<Module> newGraph = buildGraph(graph.edges()).reduce();
+
+        if (DEBUG) {
+            PrintWriter log = new PrintWriter(System.err);
+            System.err.println("before transitive reduction: ");
+            graph.printGraph(log);
+            System.err.println("after transitive reduction: ");
+            newGraph.printGraph(log);
+        }
+
+        return newGraph;
+    }
+
+    public Graph<Module> buildGraph() {
+        Graph<Module> graph = build();
+        return buildGraph(graph.edges());
+    }
+
+    /**
+     * Build a graph of module from the given dependences.
+     *
+     * It transitively includes all implied read edges.
+     */
+    private Graph<Module> buildGraph(Map<Module, Set<Module>> edges) {
+        Graph.Builder<Module> builder = new Graph.Builder<>();
+        Set<Module> visited = new HashSet<>();
+        Deque<Module> deque = new LinkedList<>();
+        edges.entrySet().stream().forEach(e -> {
+            Module m = e.getKey();
+            deque.add(m);
+            e.getValue().stream().forEach(v -> {
+                deque.add(v);
+                builder.addEdge(m, v);
+            });
+        });
+
+        // read requires public from ModuleDescriptor
+        Module source;
+        while ((source = deque.poll()) != null) {
+            if (visited.contains(source))
+                continue;
+
+            visited.add(source);
+            builder.addNode(source);
+            Module from = source;
+            requiresPublic(from).forEach(m -> {
+                deque.add(m);
+                builder.addEdge(from, m);
+            });
+        }
+        return builder.build();
+    }
+
+    /*
+     * Returns a stream of modules upon which the given module `requires public`
+     */
+    public Stream<Module> requiresPublic(Module m) {
+        // find requires public
+        return m.descriptor()
+                .requires().stream()
+                .filter(req -> req.modifiers().contains(PUBLIC))
+                .map(ModuleDescriptor.Requires::name)
+                .map(config::findModule)
+                .flatMap(Optional::stream);
+    }
+}
--- a/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/resources/jdeps.properties	Fri Oct 28 10:17:56 2016 +0100
+++ b/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/resources/jdeps.properties	Mon Oct 31 18:06:03 2016 -0700
@@ -149,6 +149,15 @@
 \                                used with -p, -e and -s options.\n\
 \                                WARNING: JDK internal APIs are inaccessible.
 
+main.opt.list-deps=\
+\  --list-deps                   Lists the dependences and use of JDK internal\n\
+\                                APIs.\n\
+\  --list-reduced-deps           Same as --list-deps with not listing\n\
+\                                the implied reads edges from the module graph\n\
+\                                If module M1 depends on M2 and M3,\n\
+\                                M2 requires public on M3, then M1 reading M3 is\n\
+\                                implied and removed from the module graph.
+
 main.opt.depth=\
 \  -depth=<depth>                Specify the depth of the transitive\n\
 \                                dependency analysis
--- a/test/tools/jdeps/lib/JdepsRunner.java	Fri Oct 28 10:17:56 2016 +0100
+++ b/test/tools/jdeps/lib/JdepsRunner.java	Mon Oct 31 18:06:03 2016 -0700
@@ -27,16 +27,19 @@
 import java.io.StringWriter;
 import java.util.Arrays;
 import java.util.List;
+import java.util.spi.ToolProvider;
 import java.util.stream.Collectors;
 
 /**
  * JdepsRunner class to invoke jdeps with the given command line argument
  */
 public class JdepsRunner {
+    private static final ToolProvider JDEPS_TOOL = ToolProvider.findFirst("jdeps")
+        .orElseThrow(() -> new RuntimeException("jdeps tool not found"));
+
     public static JdepsRunner run(String... args) {
         JdepsRunner jdeps = new JdepsRunner(args);
-        int rc = jdeps.run();
-        jdeps.printStdout(System.err);
+        int rc = jdeps.run(true);
         if (rc != 0)
            throw new Error("jdeps failed: rc=" + rc);
         return jdeps;
@@ -46,7 +49,7 @@
     final StringWriter stderr = new StringWriter();
     final String[] args;
     public JdepsRunner(String... args) {
-        System.err.println("jdeps " + Arrays.stream(args)
+        System.out.println("jdeps " + Arrays.stream(args)
                                             .collect(Collectors.joining(" ")));
         this.args = args;
     }
@@ -60,10 +63,12 @@
     }
 
     public int run(boolean showOutput) {
-        try (PrintWriter pw = new PrintWriter(stdout)) {
-            int rc = com.sun.tools.jdeps.Main.run(args, pw);
+        try (PrintWriter pwout = new PrintWriter(stdout);
+             PrintWriter pwerr = new PrintWriter(stderr)) {
+            int rc = JDEPS_TOOL.run(pwout, pwerr, args);
             if (showOutput) {
-                System.err.println(stdout.toString());
+                printStdout(System.out);
+                printStderr(System.out);
             }
             return rc;
         }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/tools/jdeps/listdeps/ListModuleDeps.java	Mon Oct 31 18:06:03 2016 -0700
@@ -0,0 +1,180 @@
+/*
+ * 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
+ * @bug 8167057
+ * @summary Tests split packages
+ * @modules java.logging
+ *          java.xml
+ *          jdk.compiler
+ *          jdk.jdeps
+ *          jdk.unsupported
+ * @library ../lib
+ * @build CompilerUtils JdepsRunner
+ * @run testng ListModuleDeps
+ */
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+public class ListModuleDeps {
+    private static final String TEST_SRC = System.getProperty("test.src");
+
+    private static final Path SRC_DIR = Paths.get(TEST_SRC, "src");
+    private static final Path CLASSES_DIR = Paths.get("classes");
+    private static final Path LIB_DIR = Paths.get("lib");
+
+    private static final Path FOO_CLASS =
+        CLASSES_DIR.resolve("z").resolve("Foo.class");
+    private static final Path BAR_CLASS =
+        CLASSES_DIR.resolve("z").resolve("Bar.class");
+    private static final Path UNSAFE_CLASS =
+        CLASSES_DIR.resolve("z").resolve("UseUnsafe.class");
+
+    /**
+     * Compiles classes used by the test
+     */
+    @BeforeTest
+    public void compileAll() throws Exception {
+        // compile library
+        assertTrue(CompilerUtils.compile(Paths.get(TEST_SRC, "src", "lib"), LIB_DIR));
+
+        // compile classes in unnamed module
+        assertTrue(CompilerUtils.compile(Paths.get(TEST_SRC, "src", "z"),
+            CLASSES_DIR,
+            "-cp", LIB_DIR.toString(),
+            "--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED",
+            "--add-exports=java.base/sun.security.util=ALL-UNNAMED",
+            "--add-exports=java.xml/jdk.xml.internal=ALL-UNNAMED"
+        ));
+    }
+
+    @Test(dataProvider = "listdeps")
+    public void testListDeps(Path classes, String[] expected) {
+        JdepsRunner jdeps = JdepsRunner.run(
+            "--class-path", LIB_DIR.toString(),
+            "--list-deps", classes.toString()
+        );
+        String[] output = Arrays.stream(jdeps.output())
+                                .map(s -> s.trim())
+                                .toArray(String[]::new);
+        assertEquals(output, expected);
+    }
+
+    @Test(dataProvider = "reduceddeps")
+    public void testListReducedDeps(Path classes, String[]  expected) {
+        JdepsRunner jdeps = JdepsRunner.run(
+            "--class-path", LIB_DIR.toString(),
+            "--list-reduced-deps", classes.toString()
+        );
+        String[] output = Arrays.stream(jdeps.output())
+                                .map(s -> s.trim())
+                                .toArray(String[]::new);
+        assertEquals(output, expected);
+    }
+
+
+    @DataProvider(name = "listdeps")
+    public Object[][] listdeps() {
+        return new Object[][] {
+            { CLASSES_DIR,  new String[] {
+                                "java.base/jdk.internal.misc",
+                                "java.base/sun.security.util",
+                                "java.logging",
+                                "java.sql",
+                                "java.xml/jdk.xml.internal",
+                                "jdk.unsupported",
+                                "unnamed module: lib"
+                            }
+            },
+
+            { FOO_CLASS,    new String[] {
+                                "java.base",
+                                "java.logging",
+                                "java.sql",
+                                "java.xml",
+                                "unnamed module: lib"
+                            }
+            },
+
+            { BAR_CLASS,    new String[] {
+                                "java.base/sun.security.util",
+                                "java.xml/jdk.xml.internal",
+                            }
+            },
+
+            { UNSAFE_CLASS, new String[] {
+                                "java.base/jdk.internal.misc",
+                                "jdk.unsupported",
+                            }
+            },
+        };
+    }
+
+    @DataProvider(name = "reduceddeps")
+    public Object[][] reduceddeps() {
+        Path barClass = CLASSES_DIR.resolve("z").resolve("Bar.class");
+
+        return new Object[][] {
+            { CLASSES_DIR,  new String[] {
+                                "java.base/jdk.internal.misc",
+                                "java.base/sun.security.util",
+                                "java.sql",
+                                "java.xml/jdk.xml.internal",
+                                "jdk.unsupported",
+                                "unnamed module: lib"
+                            }
+            },
+
+
+            { FOO_CLASS,    new String[] {
+                                "java.base",
+                                "java.sql",
+                                "unnamed module: lib"
+                            }
+            },
+
+            { BAR_CLASS,    new String[] {
+                                "java.base/sun.security.util",
+                                "java.xml/jdk.xml.internal",
+                            }
+            },
+
+            { UNSAFE_CLASS, new String[] {
+                                "java.base/jdk.internal.misc",
+                                "jdk.unsupported",
+                            }
+            },
+        };
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/tools/jdeps/listdeps/src/lib/Lib.java	Mon Oct 31 18:06:03 2016 -0700
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+package lib;
+
+import javax.xml.stream.XMLInputFactory;
+
+public class Lib {
+    public static final String isCoalescing = XMLInputFactory.IS_COALESCING;
+    public static boolean check() { return true; }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/tools/jdeps/listdeps/src/z/Bar.java	Mon Oct 31 18:06:03 2016 -0700
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+package z;
+
+import sun.security.util.HostnameChecker;
+import jdk.xml.internal.JdkXmlUtils;
+
+public class Bar {
+    // internal API from java.xml
+    private static final String name = JdkXmlUtils.USE_CATALOG;
+
+    public static void main(String[] argv) throws Exception {
+        HostnameChecker hc = HostnameChecker.getInstance(HostnameChecker.TYPE_LDAP);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/tools/jdeps/listdeps/src/z/Foo.java	Mon Oct 31 18:06:03 2016 -0700
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+package z;
+
+import java.sql.Driver;
+import java.util.logging.Logger;
+import javax.xml.stream.XMLInputFactory;
+/*
+ * Dependences on java.sql and java.logging which can be reduced.
+ */
+public class Foo {
+    // dependence to java.logging
+    static Logger log = Logger.getLogger("foo");
+    static final String isCoalescing = XMLInputFactory.IS_COALESCING;
+
+    // dependence to java.sql
+    public Driver getDriver() { return null; }
+
+    // dependence to same package
+    public Bar getBar() { return new Bar(); }
+
+    // dependence to module m
+    public String isCoalescing() { return lib.Lib.isCoalescing; }
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/tools/jdeps/listdeps/src/z/UseUnsafe.java	Mon Oct 31 18:06:03 2016 -0700
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+package z;
+
+import sun.misc.Unsafe;
+import jdk.internal.misc.VM;
+
+public class UseUnsafe {
+    private static Unsafe unsafe = Unsafe.getUnsafe();
+
+    private static boolean booted = VM.isBooted();
+}