--- old/jdk/make/CompileModuleTools.gmk 2017-02-15 11:31:23.000000000 -0800 +++ new/jdk/make/CompileModuleTools.gmk 2017-02-15 11:31:23.000000000 -0800 @@ -37,5 +37,7 @@ build/tools/jigsaw, \ BIN := $(TOOLS_CLASSES_DIR), \ ADD_JAVAC_FLAGS := \ + --add-modules jdk.jdeps \ --add-exports java.base/jdk.internal.module=ALL-UNNAMED \ + --add-exports jdk.jdeps/com.sun.tools.jdeps=ALL-UNNAMED \ )) --- old/jdk/make/GenerateModuleSummary.gmk 2017-02-15 11:31:24.000000000 -0800 +++ new/jdk/make/GenerateModuleSummary.gmk 2017-02-15 11:31:24.000000000 -0800 @@ -31,11 +31,16 @@ include ModuleTools.gmk GENGRAPHS_DIR := $(IMAGES_OUTPUTDIR)/gengraphs +SPEC_DOTFILES_DIR := $(IMAGES_OUTPUTDIR)/spec-dotfiles TOOLS_MODULE_SRCDIR := $(JDK_TOPDIR)/make/src/classes/build/tools/jigsaw $(GENGRAPHS_DIR)/jdk.dot: $(BUILD_JIGSAW_TOOLS) $(MKDIR) -p $(@D) - $(TOOL_GENGRAPHS) $(GENGRAPHS_DIR) + $(TOOL_GENGRAPHS) --output $(GENGRAPHS_DIR) + +$(SPEC_DOTFILES_DIR)/java.se.dot: $(BUILD_JIGSAW_TOOLS) + $(MKDIR) -p $(@D) + $(TOOL_GENGRAPHS) --spec --output $(SPEC_DOTFILES_DIR) $(GENGRAPHS_DIR)/technology-summary.html: $(TOOLS_MODULE_SRCDIR)/technology-summary.html $(install-file) @@ -44,4 +49,4 @@ $(MKDIR) -p $(@D) $(TOOL_MODULESUMMARY) -o $@ --module-path $(IMAGES_OUTPUTDIR)/jmods -all: $(GENGRAPHS_DIR)/jdk.dot $(GENGRAPHS_DIR)/module-summary.html +all: $(GENGRAPHS_DIR)/jdk.dot $(GENGRAPHS_DIR)/module-summary.html $(SPEC_DOTFILES_DIR)/java.se.dot --- old/jdk/make/ModuleTools.gmk 2017-02-15 11:31:25.000000000 -0800 +++ new/jdk/make/ModuleTools.gmk 2017-02-15 11:31:24.000000000 -0800 @@ -36,6 +36,8 @@ BUILD_JIGSAW_TOOLS, $(TOOLS_CLASSES_DIR)) TOOL_GENGRAPHS := $(BUILD_JAVA) -esa -ea -cp $(TOOLS_CLASSES_DIR) \ + --add-modules jdk.jdeps \ + --add-exports jdk.jdeps/com.sun.tools.jdeps=ALL-UNNAMED \ build.tools.jigsaw.GenGraphs TOOL_MODULESUMMARY := $(BUILD_JAVA) -esa -ea -cp $(TOOLS_CLASSES_DIR) \ --- old/jdk/make/src/classes/build/tools/jigsaw/GenGraphs.java 2017-02-15 11:31:25.000000000 -0800 +++ new/jdk/make/src/classes/build/tools/jigsaw/GenGraphs.java 2017-02-15 11:31:25.000000000 -0800 @@ -25,29 +25,21 @@ package build.tools.jigsaw; +import com.sun.tools.jdeps.ModuleDotGraph; +import com.sun.tools.jdeps.ModuleDotGraph.DotGraphBuilder; + import java.io.IOException; -import java.io.OutputStream; -import java.io.PrintStream; import java.lang.module.Configuration; import java.lang.module.ModuleDescriptor; import java.lang.module.ModuleFinder; import java.lang.module.ModuleReference; -import java.lang.module.ResolvedModule; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; -import java.util.TreeSet; -import java.util.function.Function; - -import static java.util.stream.Collectors.*; -import static java.lang.module.ModuleDescriptor.Requires.Modifier.TRANSITIVE; /** * Generate the DOT file for a module graph for each module in the JDK @@ -56,238 +48,100 @@ public class GenGraphs { public static void main(String[] args) throws Exception { + Path dir = null; + boolean spec = false; + for (int i=0; i < args.length; i++) { + String arg = args[i]; + if (arg.equals("--spec")) { + spec = true; + } else if (arg.equals("--output")) { + i++; + dir = i < args.length ? Paths.get(args[i]) : null; + } else if (arg.startsWith("-")) { + throw new IllegalArgumentException("Invalid option: " + arg); + } + } - if (args.length != 1) { - System.err.println("ERROR: specify the output directory"); + if (dir == null) { + System.err.println("ERROR: must specify --output argument"); System.exit(1); } - Path dir = Paths.get(args[0]); + + // setup and configure the dot graph attributes + initDotGraphAttributes(); Files.createDirectories(dir); - ModuleFinder finder = ModuleFinder.ofSystem(); + GenGraphs genGraphs = new GenGraphs(dir, spec); - Set javaSEModules - = new TreeSet<>(finder.findAll().stream() - .map(ModuleReference::descriptor) - .filter(m -> (m.name().startsWith("java.") && - !m.name().equals("java.smartcardio"))) - .collect(toSet())); - Set jdkModules - = new TreeSet<>(finder.findAll().stream() - .map(ModuleReference::descriptor) - .filter(m -> !javaSEModules.contains(m)) - .collect(toSet())); - - GenGraphs genGraphs = new GenGraphs(dir, javaSEModules, jdkModules); - Set mods = new HashSet<>(); - for (ModuleReference mref: finder.findAll()) { - mods.add(mref.descriptor().name()); - genGraphs.genDotFile(mref); + // print dot file for each module + Map configurations = new HashMap<>(); + Set modules = new HashSet<>(); + ModuleFinder finder = ModuleFinder.ofSystem(); + for (ModuleReference mref : finder.findAll()) { + String name = (mref.descriptor().name()); + modules.add(name); + if (genGraphs.accept(name, mref.descriptor())) { + configurations.put(name, Configuration.empty() + .resolve(finder, + ModuleFinder.of(), + Set.of(name))); + } } - // all modules - genGraphs.genDotFile("jdk", mods); - - } - - private static final String ORANGE = "#e76f00"; - private static final String BLUE = "#437291"; - private static final String GRAY = "#dddddd"; - - private static final String REEXPORTS = ""; - private static final String REQUIRES = "style=\"dashed\""; - private static final String REQUIRES_BASE = "color=\"" + GRAY + "\""; - - private static final Map weights = new HashMap<>(); - private static final List> ranks = new ArrayList<>(); - - private static void weight(String s, String t, int w) { - weights.put(s + ":" + t, w); - } + if (genGraphs.accept("jdk", null)) { + // print a graph of all JDK modules + configurations.put("jdk", Configuration.empty() + .resolve(finder, + ModuleFinder.of(), + modules)); + } - private static int weightOf(String s, String t) { - int w = weights.getOrDefault(s + ":" + t, 1); - if (w != 1) - return w; - if (s.startsWith("java.") && t.startsWith("java.")) - return 10; - return 1; + genGraphs.genDotFiles(configurations); } - static { + static void initDotGraphAttributes() { int h = 1000; - weight("java.se", "java.sql.rowset", h * 10); - weight("java.sql.rowset", "java.sql", h * 10); - weight("java.sql", "java.xml", h * 10); - weight("java.xml", "java.base", h * 10); - - ranks.add(Set.of("java.logging", "java.scripting", "java.xml")); - ranks.add(Set.of("java.sql")); - ranks.add(Set.of("java.compiler", "java.instrument")); - ranks.add(Set.of("java.desktop", "java.management")); - ranks.add(Set.of("java.corba", "java.xml.ws")); - ranks.add(Set.of("java.xml.bind", "java.xml.ws.annotation")); - + DotGraphBuilder.weight("java.se", "java.sql.rowset", h * 10); + DotGraphBuilder.weight("java.sql.rowset", "java.sql", h * 10); + DotGraphBuilder.weight("java.sql", "java.xml", h * 10); + DotGraphBuilder.weight("java.xml", "java.base", h * 10); + + DotGraphBuilder.sameRankNodes(Set.of("java.logging", "java.scripting", "java.xml")); + DotGraphBuilder.sameRankNodes(Set.of("java.sql")); + DotGraphBuilder.sameRankNodes(Set.of("java.compiler", "java.instrument")); + DotGraphBuilder.sameRankNodes(Set.of("java.desktop", "java.management")); + DotGraphBuilder.sameRankNodes(Set.of("java.corba", "java.xml.ws")); + DotGraphBuilder.sameRankNodes(Set.of("java.xml.bind", "java.xml.ws.annotation")); + DotGraphBuilder.setRankSep(0.7); + DotGraphBuilder.setFontSize(12); + DotGraphBuilder.setArrowSize(1); + DotGraphBuilder.setArrowWidth(2); } private final Path dir; - private final Set javaGroup; - private final Set jdkGroup; - - GenGraphs(Path dir, Set javaGroup, Set jdkGroup) { + private final boolean spec; + GenGraphs(Path dir, boolean spec) { this.dir = dir; - this.javaGroup = Collections.unmodifiableSet(javaGroup); - this.jdkGroup = Collections.unmodifiableSet(jdkGroup); + this.spec = spec; } - /** - * Generates a dot file for the given module reference as the root. - */ - void genDotFile(ModuleReference mref) throws IOException { - String name = mref.descriptor().name(); - genDotFile(name, Set.of(name)); + void genDotFiles(Map configurations) throws IOException { + ModuleDotGraph dotGraph = new ModuleDotGraph(configurations, spec); + dotGraph.genDotFiles(dir); } - /** - * Generates a dot file for the given set of root modules. - */ - void genDotFile(String name, Set roots) throws IOException { - Configuration cf = - Configuration.empty().resolve(ModuleFinder.ofSystem(), - ModuleFinder.of(), - roots); - - Set mds = cf.modules().stream() - .map(ResolvedModule::reference) - .map(ModuleReference::descriptor) - .collect(toSet()); - - // generate a dot file for the resolved graph - try (OutputStream os = Files.newOutputStream(dir.resolve(name + ".dot")); - PrintStream out = new PrintStream(os)) { - printGraph(out, name, gengraph(cf), - mds.stream() - .collect(toMap(ModuleDescriptor::name, Function.identity())) - ); - } + boolean accept(String name, ModuleDescriptor descriptor) { + if (!spec) return true; - if (name.equals("java.se") || name.equals("java.se.ee")) { - // generate a dot file for Java SE module graph - try (OutputStream os = Files.newOutputStream(dir.resolve(name + "-spec.dot")); - PrintStream out = new PrintStream(os)) { - // transitive reduction on the graph of `requires transitive` edges - // filter out jdk.* modules which are implementation dependences - Graph graph = requiresTransitiveGraph(cf, true); - printGraph(out, name, graph, - mds.stream() - .filter(md -> !md.name().startsWith("jdk.") && - graph.nodes().contains(md.name())) - .collect(toMap(ModuleDescriptor::name, Function.identity())) - ); - } - } - } + if (name.equals("jdk")) + return false; - private void printGraph(PrintStream out, - String name, - Graph graph, - Map nameToModule) - throws IOException - { - Set descriptors = new TreeSet<>(nameToModule.values()); - - out.format("digraph \"%s\" {%n", name); - out.format("size=\"25,25\";"); - out.format("nodesep=.5;%n"); - out.format("ranksep=1.5;%n"); - out.format("pencolor=transparent;%n"); - out.format("node [shape=plaintext, fontname=\"DejaVuSans\", fontsize=36, margin=\".2,.2\"];%n"); - out.format("edge [penwidth=4, color=\"#999999\", arrowhead=open, arrowsize=2];%n"); - - out.format("subgraph %sse {%n", name.equals("jdk") ? "cluster_" : ""); - descriptors.stream() - .filter(javaGroup::contains) - .map(ModuleDescriptor::name) - .forEach(mn -> out.format(" \"%s\" [fontcolor=\"%s\", group=%s];%n", - mn, ORANGE, "java")); - out.format("}%n"); - - // same ranks - ranks.stream() - .map(group -> descriptors.stream() - .map(ModuleDescriptor::name) - .filter(group::contains) - .map(mn -> "\"" + mn + "\"") - .collect(joining(","))) - .filter(group -> group.length() > 0) - .forEach(group -> out.format("{rank=same %s}%n", group)); - - descriptors.stream() - .filter(jdkGroup::contains) - .map(ModuleDescriptor::name) - .forEach(mn -> out.format(" \"%s\" [fontcolor=\"%s\", group=%s];%n", - mn, BLUE, "jdk")); - - descriptors.stream() - .forEach(md -> { - String mn = md.name(); - Set requiresTransitive = md.requires().stream() - .filter(d -> d.modifiers().contains(TRANSITIVE)) - .map(d -> d.name()) - .collect(toSet()); - - graph.adjacentNodes(mn) - .stream() - .filter(nameToModule::containsKey) - .forEach(dn -> { - String attr = dn.equals("java.base") ? REQUIRES_BASE - : (requiresTransitive.contains(dn) ? REEXPORTS : REQUIRES); - int w = weightOf(mn, dn); - if (w > 1) - attr += "weight=" + w; - out.format(" \"%s\" -> \"%s\" [%s];%n", mn, dn, attr); - }); - }); + if (name.equals("java.se") || name.equals("java.se.ee")) + return true; - out.println("}"); - } - - /** - * Returns a Graph of the given Configuration after transitive reduction. - * - * Transitive reduction of requires transitive edge and requires edge have - * to be applied separately to prevent the requires transitive edges - * (e.g. U -> V) from being reduced by a path (U -> X -> Y -> V) - * in which V would not be re-exported from U. - */ - private Graph gengraph(Configuration cf) { - Graph.Builder builder = new Graph.Builder<>(); - for (ResolvedModule resolvedModule : cf.modules()) { - String mn = resolvedModule.reference().descriptor().name(); - builder.addNode(mn); - resolvedModule.reads().stream() - .map(ResolvedModule::name) - .forEach(target -> builder.addEdge(mn, target)); - } - Graph rpg = requiresTransitiveGraph(cf, false); - return builder.build().reduce(rpg); - } - - /** - * Returns a Graph containing only requires transitive edges - * with transitive reduction. - */ - private Graph requiresTransitiveGraph(Configuration cf, boolean includeBase) { - Graph.Builder builder = new Graph.Builder<>(); - for (ResolvedModule resolvedModule : cf.modules()) { - ModuleDescriptor descriptor = resolvedModule.reference().descriptor(); - String mn = descriptor.name(); - descriptor.requires().stream() - .filter(d -> d.modifiers().contains(TRANSITIVE) - || (includeBase && d.name().equals("java.base"))) - .map(d -> d.name()) - .forEach(d -> builder.addEdge(mn, d)); - } - return builder.build().reduce(); + // only the module that has exported API + return descriptor.exports().stream() + .filter(e -> !e.isQualified()) + .findAny().isPresent(); } -} +} \ No newline at end of file --- old/langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/Graph.java 2017-02-15 11:31:26.000000000 -0800 +++ new/langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/Graph.java 2017-02-15 11:31:26.000000000 -0800 @@ -116,7 +116,7 @@ .forEach(u -> g.adjacentNodes(u).stream() .filter(v -> isAdjacent(u, v)) .forEach(v -> builder.addEdge(u, v))); - return builder.build(); + return builder.build().reduce(); } /** @@ -274,7 +274,7 @@ } public void addNodes(Set nodes) { - nodes.addAll(nodes); + this.nodes.addAll(nodes); } public void addEdge(T u, T v) { @@ -335,67 +335,4 @@ result.addLast(node); } } - - public static class DotGraph { - static final String ORANGE = "#e76f00"; - static final String BLUE = "#437291"; - static final String GRAY = "#dddddd"; - - static final String REEXPORTS = ""; - static final String REQUIRES = "style=\"dashed\""; - static final String REQUIRES_BASE = "color=\"" + GRAY + "\""; - - static final Set javaModules = modules(name -> - (name.startsWith("java.") && !name.equals("java.smartcardio"))); - static final Set jdkModules = modules(name -> - (name.startsWith("java.") || - name.startsWith("jdk.") || - name.startsWith("javafx.")) && !javaModules.contains(name)); - - private static Set modules(Predicate predicate) { - return ModuleFinder.ofSystem().findAll() - .stream() - .map(ModuleReference::descriptor) - .map(ModuleDescriptor::name) - .filter(predicate) - .collect(Collectors.toSet()); - } - - static void printAttributes(PrintWriter out) { - out.format(" size=\"25,25\";%n"); - out.format(" nodesep=.5;%n"); - out.format(" ranksep=1.5;%n"); - out.format(" pencolor=transparent;%n"); - out.format(" node [shape=plaintext, fontname=\"DejaVuSans\", fontsize=36, margin=\".2,.2\"];%n"); - out.format(" edge [penwidth=4, color=\"#999999\", arrowhead=open, arrowsize=2];%n"); - } - - static void printNodes(PrintWriter out, Graph graph) { - out.format(" subgraph se {%n"); - graph.nodes().stream() - .filter(javaModules::contains) - .forEach(mn -> out.format(" \"%s\" [fontcolor=\"%s\", group=%s];%n", - mn, ORANGE, "java")); - out.format(" }%n"); - graph.nodes().stream() - .filter(jdkModules::contains) - .forEach(mn -> out.format(" \"%s\" [fontcolor=\"%s\", group=%s];%n", - mn, BLUE, "jdk")); - - graph.nodes().stream() - .filter(mn -> !javaModules.contains(mn) && !jdkModules.contains(mn)) - .forEach(mn -> out.format(" \"%s\";%n", mn)); - } - - static void printEdges(PrintWriter out, Graph graph, - String node, Set requiresTransitive) { - graph.adjacentNodes(node).forEach(dn -> { - String attr = dn.equals("java.base") ? REQUIRES_BASE - : (requiresTransitive.contains(dn) ? REEXPORTS : REQUIRES); - out.format(" \"%s\" -> \"%s\" [%s];%n", node, dn, attr); - }); - } - } - - } --- old/langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsConfiguration.java 2017-02-15 11:31:27.000000000 -0800 +++ new/langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsConfiguration.java 2017-02-15 11:31:27.000000000 -0800 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2017, 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 @@ -207,16 +207,6 @@ } /** - * Returns the modules that the given module can read - */ - public Stream reads(Module module) { - return configuration.findModule(module.name()).get() - .reads().stream() - .map(ResolvedModule::name) - .map(nameToModule::get); - } - - /** * Returns the list of packages that split between resolved module and * unnamed module */ @@ -267,16 +257,15 @@ return nameToModule; } - public Stream resolve(Set roots) { - if (roots.isEmpty()) { - return nameToModule.values().stream(); - } else { - return Configuration.empty() - .resolve(finder, ModuleFinder.of(), roots) - .modules().stream() - .map(ResolvedModule::name) - .map(nameToModule::get); - } + /** + * Returns Configuration with the given roots + */ + public Configuration resolve(Set roots) { + if (roots.isEmpty()) + throw new IllegalArgumentException("empty roots"); + + return Configuration.empty() + .resolve(finder, ModuleFinder.of(), roots); } public List classPathArchives() { --- old/langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsTask.java 2017-02-15 11:31:27.000000000 -0800 +++ new/langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsTask.java 2017-02-15 11:31:27.000000000 -0800 @@ -721,9 +721,9 @@ return run(config, writer, type); } - boolean run(JdepsConfiguration config, JdepsWriter writer, Type type) throws IOException { - - + boolean run(JdepsConfiguration config, JdepsWriter writer, Type type) + throws IOException + { // analyze the dependencies DepsAnalyzer analyzer = new DepsAnalyzer(config, dependencyFilter(config), @@ -1024,8 +1024,10 @@ boolean run(JdepsConfiguration config) throws IOException { if ((options.showSummary || options.verbose == MODULE) && !options.addmods.isEmpty() && inputArgs.isEmpty()) { - // print module descriptor - return new ModuleAnalyzer(config, log).genDotFiles(dotOutputDir); + // generate dot graph from the resolved graph from module + // resolution. No class dependency analysis is performed. + return new ModuleDotGraph(config, options.apiOnly) + .genDotFiles(dotOutputDir); } Type type = getAnalyzerType(); --- old/langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/ModuleAnalyzer.java 2017-02-15 11:31:28.000000000 -0800 +++ new/langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/ModuleAnalyzer.java 2017-02-15 11:31:28.000000000 -0800 @@ -59,15 +59,10 @@ private final JdepsConfiguration configuration; private final PrintWriter log; - private final DependencyFinder dependencyFinder; private final Map modules; public ModuleAnalyzer(JdepsConfiguration config, - PrintWriter log) { - this(config, log, Collections.emptySet()); - } - public ModuleAnalyzer(JdepsConfiguration config, PrintWriter log, Set names) { this.configuration = config; @@ -333,88 +328,6 @@ return true; } - /** - * Generate dotfile from module descriptor - * - * @param dir output directory - */ - public boolean genDotFiles(Path dir) throws IOException { - Files.createDirectories(dir); - for (Module m : modules.keySet()) { - genDotFile(dir, m.name()); - } - return true; - } - - - private void genDotFile(Path dir, String name) throws IOException { - try (OutputStream os = Files.newOutputStream(dir.resolve(name + ".dot")); - PrintWriter out = new PrintWriter(os)) { - Set modules = configuration.resolve(Set.of(name)) - .collect(Collectors.toSet()); - - // transitive reduction - Graph graph = gengraph(modules); - - out.format("digraph \"%s\" {%n", name); - DotGraph.printAttributes(out); - DotGraph.printNodes(out, graph); - - modules.stream() - .map(Module::descriptor) - .sorted(Comparator.comparing(ModuleDescriptor::name)) - .forEach(md -> { - String mn = md.name(); - Set requiresTransitive = md.requires().stream() - .filter(d -> d.modifiers().contains(TRANSITIVE)) - .map(d -> d.name()) - .collect(toSet()); - - DotGraph.printEdges(out, graph, mn, requiresTransitive); - }); - - out.println("}"); - } - } - - /** - * Returns a Graph of the given Configuration after transitive reduction. - * - * Transitive reduction of requires transitive edge and requires edge have - * to be applied separately to prevent the requires transitive edges - * (e.g. U -> V) from being reduced by a path (U -> X -> Y -> V) - * in which V would not be re-exported from U. - */ - private Graph gengraph(Set modules) { - // build a Graph containing only requires transitive edges - // with transitive reduction. - Graph.Builder rpgbuilder = new Graph.Builder<>(); - for (Module module : modules) { - ModuleDescriptor md = module.descriptor(); - String mn = md.name(); - md.requires().stream() - .filter(d -> d.modifiers().contains(TRANSITIVE)) - .map(d -> d.name()) - .forEach(d -> rpgbuilder.addEdge(mn, d)); - } - - Graph rpg = rpgbuilder.build().reduce(); - - // build the readability graph - Graph.Builder builder = new Graph.Builder<>(); - for (Module module : modules) { - ModuleDescriptor md = module.descriptor(); - String mn = md.name(); - builder.addNode(mn); - configuration.reads(module) - .map(Module::name) - .forEach(d -> builder.addEdge(mn, d)); - } - - // transitive reduction of requires edges - return builder.build().reduce(rpg); - } - // ---- for testing purpose public ModuleDescriptor[] descriptors(String name) { ModuleDeps moduleDeps = modules.keySet().stream() --- /dev/null 2017-02-15 11:31:29.000000000 -0800 +++ new/langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/ModuleDotGraph.java 2017-02-15 11:31:28.000000000 -0800 @@ -0,0 +1,367 @@ +/* + * Copyright (c) 2017, 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 java.lang.module.ModuleDescriptor.Requires.Modifier.*; +import static java.util.stream.Collectors.*; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.module.Configuration; +import java.lang.module.ModuleDescriptor; +import java.lang.module.ModuleFinder; +import java.lang.module.ModuleReference; +import java.lang.module.ResolvedModule; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +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.TreeSet; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Generate dot graph for modules + */ +public class ModuleDotGraph { + private final Map configurations; + private final boolean apiOnly; + public ModuleDotGraph(JdepsConfiguration config, boolean apiOnly) { + this(config.rootModules().stream() + .map(Module::name) + .sorted() + .collect(toMap(Function.identity(), mn -> config.resolve(Set.of(mn)))), + apiOnly); + } + + public ModuleDotGraph(Map configurations, boolean apiOnly) { + this.configurations = configurations; + this.apiOnly = apiOnly; + } + + /** + * Generate dotfile for all modules + * + * @param dir output directory + */ + public boolean genDotFiles(Path dir) throws IOException { + Files.createDirectories(dir); + for (String mn : configurations.keySet()) { + Path path = dir.resolve(mn + ".dot"); + genDotFile(path, mn, configurations.get(mn)); + } + return true; + } + + /** + * Generate dotfile of the given path + */ + public void genDotFile(Path path, String name, Configuration configuration) + throws IOException + { + // transitive reduction + Graph graph = apiOnly + ? requiresTransitiveGraph(configuration, Set.of(name)) + : gengraph(configuration); + + DotGraphBuilder builder = new DotGraphBuilder(name, graph); + builder.descriptors(graph.nodes().stream() + .map(mn -> configuration.findModule(mn).get() + .reference().descriptor())); + builder.subgraph("se", "java", DotGraphBuilder.ORANGE, + DotGraphBuilder.JAVA_SE_SUBGRAPH); + builder.subgraph("jdk", "jdk", + DotGraphBuilder.BLUE, DotGraphBuilder.JDK_SUBGRAPH); + builder.build(path); + } + + /** + * Returns a Graph of the given Configuration after transitive reduction. + * + * Transitive reduction of requires transitive edge and requires edge have + * to be applied separately to prevent the requires transitive edges + * (e.g. U -> V) from being reduced by a path (U -> X -> Y -> V) + * in which V would not be re-exported from U. + */ + private Graph gengraph(Configuration cf) { + Graph.Builder builder = new Graph.Builder<>(); + cf.modules().stream() + .forEach(resolvedModule -> { + String mn = resolvedModule.reference().descriptor().name(); + builder.addNode(mn); + resolvedModule.reads().stream() + .map(ResolvedModule::name) + .forEach(target -> builder.addEdge(mn, target)); + }); + + Graph rpg = requiresTransitiveGraph(cf, builder.nodes); + return builder.build().reduce(rpg); + } + + + /** + * Returns a Graph containing only requires transitive edges + * with transitive reduction. + */ + public Graph requiresTransitiveGraph(Configuration cf, + Set roots) + { + Deque deque = new ArrayDeque<>(roots); + Set visited = new HashSet<>(); + Graph.Builder builder = new Graph.Builder<>(); + + while (deque.peek() != null) { + String mn = deque.pop(); + if (visited.contains(mn)) + continue; + + visited.add(mn); + builder.addNode(mn); + ModuleDescriptor descriptor = cf.findModule(mn).get() + .reference().descriptor(); + descriptor.requires().stream() + .filter(d -> d.modifiers().contains(TRANSITIVE) + || d.name().equals("java.base")) + .map(d -> d.name()) + .forEach(d -> { + deque.add(d); + builder.addEdge(mn, d); + }); + } + + return builder.build().reduce(); + } + + public static class DotGraphBuilder { + static final Set JAVA_SE_SUBGRAPH = javaSE(); + static final Set JDK_SUBGRAPH = jdk(); + + private static Set javaSE() { + String root = "java.se.ee"; + ModuleFinder system = ModuleFinder.ofSystem(); + if (system.find(root).isPresent()) { + return Stream.concat(Stream.of(root), + Configuration.empty().resolve(system, + ModuleFinder.of(), + Set.of(root)) + .findModule(root).get() + .reads().stream() + .map(ResolvedModule::name)) + .collect(toSet()); + } else { + // approximation + return system.findAll().stream() + .map(ModuleReference::descriptor) + .map(ModuleDescriptor::name) + .filter(name -> name.startsWith("java.") && + !name.equals("java.smartcardio")) + .collect(Collectors.toSet()); + } + } + + private static Set jdk() { + return ModuleFinder.ofSystem().findAll().stream() + .map(ModuleReference::descriptor) + .map(ModuleDescriptor::name) + .filter(name -> !JAVA_SE_SUBGRAPH.contains(name) && + (name.startsWith("java.") || + name.startsWith("jdk.") || + name.startsWith("javafx."))) + .collect(Collectors.toSet()); + } + + static class SubGraph { + final String name; + final String group; + final String color; + final Set nodes; + SubGraph(String name, String group, String color, Set nodes) { + this.name = Objects.requireNonNull(name); + this.group = Objects.requireNonNull(group); + this.color = Objects.requireNonNull(color); + this.nodes = Objects.requireNonNull(nodes); + } + } + + static final String ORANGE = "#e76f00"; + static final String BLUE = "#437291"; + static final String GRAY = "#dddddd"; + static final String BLACK = "#000000"; + + static final String FONT_NAME = "DejaVuSans"; + static final int FONT_SIZE = 12; + static final int ARROW_SIZE = 1; + static final int ARROW_WIDTH = 2; + static final int RANK_SEP = 1; + + static final String REEXPORTS = ""; + static final String REQUIRES = "style=\"dashed\""; + static final String REQUIRES_BASE = "color=\"" + GRAY + "\""; + + // can be configured + static double rankSep = RANK_SEP; + static String fontColor = BLACK; + static String fontName = FONT_NAME; + static int fontsize = FONT_SIZE; + static int arrowWidth = ARROW_WIDTH; + static int arrowSize = ARROW_SIZE; + static final Map weights = new HashMap<>(); + static final List> ranks = new ArrayList<>(); + + private final String name; + private final Graph graph; + private final Set descriptors = new TreeSet<>(); + private final List subgraphs = new ArrayList<>(); + public DotGraphBuilder(String name, Graph graph) { + this.name = name; + this.graph = graph; + } + + public DotGraphBuilder descriptors(Stream descriptors) { + descriptors.forEach(this.descriptors::add); + return this; + } + + public void build(Path filename) throws IOException { + try (BufferedWriter writer = Files.newBufferedWriter(filename); + PrintWriter out = new PrintWriter(writer)) { + + out.format("digraph \"%s\" {%n", name); + out.format(" nodesep=.5;%n"); + out.format(" ranksep=%f;%n", rankSep); + out.format(" pencolor=transparent;%n"); + out.format(" node [shape=plaintext, fontname=\"%s\", fontsize=%d, margin=\".2,.2\"];%n", + fontName, fontsize); + out.format(" edge [penwidth=%d, color=\"#999999\", arrowhead=open, arrowsize=%d];%n", + arrowWidth, arrowSize); + + // same RANKS + ranks.stream() + .map(nodes -> descriptors.stream() + .map(ModuleDescriptor::name) + .filter(nodes::contains) + .map(mn -> "\"" + mn + "\"") + .collect(joining(","))) + .filter(group -> group.length() > 0) + .forEach(group -> out.format(" {rank=same %s}%n", group)); + + subgraphs.forEach(subgraph -> { + out.format(" subgraph %s {%n", subgraph.name); + descriptors.stream() + .map(ModuleDescriptor::name) + .filter(subgraph.nodes::contains) + .forEach(mn -> printNode(out, mn, subgraph.color, subgraph.group)); + out.format(" }%n"); + }); + + descriptors.stream() + .filter(md -> graph.contains(md.name()) && + !graph.adjacentNodes(md.name()).isEmpty()) + .forEach(md -> printNode(out, md, graph.adjacentNodes(md.name()))); + + out.println("}"); + } + } + + public DotGraphBuilder subgraph(String name, String group, String color, + Set nodes) { + subgraphs.add(new SubGraph(name, group, color, nodes)); + return this; + } + + public void printNode(PrintWriter out, String node, String color, String group) { + out.format(" \"%s\" [fontcolor=\"%s\", group=%s];%n", + node, color, group); + } + + public void printNode(PrintWriter out, ModuleDescriptor md, Set edges) { + Set requiresTransitive = md.requires().stream() + .filter(d -> d.modifiers().contains(TRANSITIVE)) + .map(d -> d.name()) + .collect(toSet()); + + String mn = md.name(); + edges.stream().forEach(dn -> { + String attr = dn.equals("java.base") ? REQUIRES_BASE + : (requiresTransitive.contains(dn) ? REEXPORTS : REQUIRES); + + int w = weightOf(mn, dn); + if (w > 1) { + if (!attr.isEmpty()) + attr += ", "; + + attr += "weight=" + w; + } + out.format(" \"%s\" -> \"%s\" [%s];%n", mn, dn, attr); + }); + } + + public int weightOf(String s, String t) { + int w = weights.getOrDefault(s + ":" + t, 1); + if (w != 1) + return w; + if (s.startsWith("java.") && t.startsWith("java.")) + return 10; + return 1; + } + + public static void sameRankNodes(Set nodes) { + ranks.add(nodes); + } + + public static void weight(String s, String t, int w) { + weights.put(s + ":" + t, w); + } + + public static void setRankSep(double value) { + rankSep = value; + } + + public static void setFontSize(int size) { + fontsize = size; + } + + public static void setFontColor(String color) { + fontColor = color; + } + + public static void setArrowSize(int size) { + arrowSize = size; + } + + public static void setArrowWidth(int width) { + arrowWidth = width; + } + } +} --- /dev/null 2017-02-15 11:31:29.000000000 -0800 +++ new/langtools/test/tools/jdeps/modules/DotFileTest.java 2017-02-15 11:31:29.000000000 -0800 @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2017, 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 8173374 + * @summary Tests module dot graph + * @modules java.desktop + * java.sql + * jdk.jdeps/com.sun.tools.jdeps + * @run testng DotFileTest + */ + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Set; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.spi.ToolProvider; +import java.util.stream.Collectors; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.assertEquals; + +public class DotFileTest { + private static final ToolProvider JDEPS = ToolProvider.findFirst("jdeps") + .orElseThrow(() -> new RuntimeException("jdeps not found")); + + private static final Path DOTS_DIR = Paths.get("dots"); + private static final Path SPEC_DIR = Paths.get("spec"); + + @DataProvider(name = "modules") + public Object[][] modules() { + return new Object[][]{ + {"java.desktop", Set.of("java.datatransfer -> java.base", + "java.desktop -> java.datatransfer", + "java.desktop -> java.prefs", + "java.prefs -> java.xml", + "java.xml -> java.base" ) + }, + { "java.sql", Set.of("java.logging -> java.base", + "java.sql -> java.logging", + "java.sql -> java.xml", + "java.xml -> java.base" ) + } + }; + } + @DataProvider(name = "specVersion") + public Object[][] specVersion() { + return new Object[][]{ + {"java.desktop", Set.of("java.datatransfer -> java.base", + "java.desktop -> java.datatransfer", + "java.desktop -> java.xml", + "java.xml -> java.base") + }, + { "java.sql", Set.of("java.logging -> java.base", + "java.sql -> java.logging", + "java.sql -> java.xml", + "java.xml -> java.base" ) + } + }; + } + + @Test(dataProvider = "modules") + public void test(String name, Set edges) throws Exception { + String[] options = new String[] { + "-dotoutput", DOTS_DIR.toString(), + "-s", "-m", name + }; + assertTrue(JDEPS.run(System.out, System.out, options) == 0); + + Path path = DOTS_DIR.resolve(name + ".dot"); + assertTrue(Files.exists(path)); + Set lines = Files.readAllLines(path).stream() + .filter(l -> l.contains(" -> ")) + .map(this::split) + .collect(Collectors.toSet()); + assertEquals(lines, edges); + } + + @Test(dataProvider = "specVersion") + public void testAPIOnly(String name, Set edges) throws Exception { + String[] options = new String[]{ + "-dotoutput", SPEC_DIR.toString(), + "-s", "-apionly", + "-m", name + }; + assertTrue(JDEPS.run(System.out, System.out, options) == 0); + + Path path = SPEC_DIR.resolve(name + ".dot"); + assertTrue(Files.exists(path)); + Set lines = Files.readAllLines(path).stream() + .filter(l -> l.contains(" -> ")) + .map(this::split) + .collect(Collectors.toSet()); + assertEquals(lines, edges); + } + + static Pattern PATTERN = Pattern.compile(" *\"(\\S+)\" -> \"(\\S+)\" .*"); + String split(String line) { + Matcher pm = PATTERN.matcher(line); + assertTrue(pm.find()); + return String.format("%s -> %s", pm.group(1), pm.group(2)); + } +} --- old/jdk/make/src/classes/build/tools/jigsaw/Graph.java 2017-02-15 11:31:30.000000000 -0800 +++ /dev/null 2017-02-15 11:31:30.000000000 -0800 @@ -1,171 +0,0 @@ -/* - * 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. 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 build.tools.jigsaw; - -import java.io.PrintStream; -import java.util.Deque; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.Map; -import java.util.Set; - -public class Graph { - private static boolean traceOn = Boolean.getBoolean("build.tools.module.trace"); - private final Set nodes; - private final Map> edges; - private Graph(Set nodes, Map> edges) { - this.nodes = nodes; - this.edges = edges; - } - - public Set nodes() { - return nodes; - } - - public Map> edges() { - return edges; - } - - public Set adjacentNodes(T u) { - return edges.get(u); - } - - /** - * Returns a new Graph after transitive reduction - */ - public Graph reduce() { - Graph.Builder builder = new Builder<>(); - nodes.stream() - .forEach(u -> { - builder.addNode(u); - edges.get(u).stream() - .filter(v -> !pathExists(u, v, false)) - .forEach(v -> builder.addEdge(u, v)); - }); - return builder.build(); - } - - /** - * Returns a new Graph after transitive reduction. All edges in - * the given g takes precedence over this graph. - * - * @throw IllegalArgumentException g must be a subgraph this graph - */ - public Graph reduce(Graph g) { - boolean subgraph = nodes.containsAll(g.nodes) && g.edges.keySet().stream() - .allMatch(u -> adjacentNodes(u).containsAll(g.adjacentNodes(u))); - if (!subgraph) { - throw new IllegalArgumentException("the given argument is not a subgraph of this graph"); - } - - Graph.Builder builder = new Builder<>(); - nodes.stream() - .forEach(u -> { - builder.addNode(u); - // filter the edge if there exists a path from u to v in the given g - // or there exists another path from u to v in this graph - edges.get(u).stream() - .filter(v -> !g.pathExists(u, v) && !pathExists(u, v, false)) - .forEach(v -> builder.addEdge(u, v)); - }); - - // add the overlapped edges from this graph and the given g - g.edges().keySet().stream() - .forEach(u -> g.adjacentNodes(u).stream() - .filter(v -> isAdjacent(u, v)) - .forEach(v -> builder.addEdge(u, v))); - return builder.build(); - } - - private boolean isAdjacent(T u, T v) { - return edges.containsKey(u) && edges.get(u).contains(v); - } - - private boolean pathExists(T u, T v) { - return pathExists(u, v, true); - } - - /** - * Returns true if there exists a path from u to v in this graph. - * If includeAdjacent is false, it returns true if there exists - * another path from u to v of distance > 1 - */ - private boolean pathExists(T u, T v, boolean includeAdjacent) { - if (!nodes.contains(u) || !nodes.contains(v)) { - return false; - } - if (includeAdjacent && isAdjacent(u, v)) { - return true; - } - Deque stack = new LinkedList<>(); - Set visited = new HashSet<>(); - stack.push(u); - while (!stack.isEmpty()) { - T node = stack.pop(); - if (node.equals(v)) { - if (traceOn) { - System.out.format("Edge %s -> %s removed%n", u, v); - } - return true; - } - if (!visited.contains(node)) { - visited.add(node); - edges.get(node).stream() - .filter(e -> includeAdjacent || !node.equals(u) || !e.equals(v)) - .forEach(e -> stack.push(e)); - } - } - assert !visited.contains(v); - return false; - } - - void printGraph(PrintStream out) { - nodes.stream() - .forEach(u -> adjacentNodes(u).stream() - .forEach(v -> out.format("%s -> %s%n", u, v))); - } - - public static class Builder { - final Set nodes = new HashSet<>(); - final Map> edges = new HashMap<>(); - public void addNode(T node) { - if (nodes.contains(node)) { - return; - } - nodes.add(node); - edges.computeIfAbsent(node, _e -> new HashSet<>()); - } - public void addEdge(T u, T v) { - addNode(u); - addNode(v); - edges.get(u).add(v); - } - public Graph build() { - return new Graph<>(nodes, edges); - } - } -}