Path: blob/master/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/ModuleDotGraph.java
41161 views
/*1* Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/24package com.sun.tools.jdeps;2526import static java.lang.module.ModuleDescriptor.Requires.Modifier.*;27import static java.util.stream.Collectors.*;2829import java.io.BufferedWriter;30import java.io.IOException;31import java.io.PrintWriter;32import java.lang.module.Configuration;33import java.lang.module.ModuleDescriptor;34import java.lang.module.ModuleDescriptor.*;35import java.lang.module.ModuleFinder;36import java.lang.module.ModuleReference;37import java.lang.module.ResolvedModule;38import java.nio.file.Files;39import java.nio.file.Path;40import java.util.ArrayDeque;41import java.util.ArrayList;42import java.util.Collections;43import java.util.Deque;44import java.util.HashSet;45import java.util.List;46import java.util.Locale;47import java.util.Map;48import java.util.Objects;49import java.util.Optional;50import java.util.Set;51import java.util.TreeSet;52import java.util.function.Function;53import java.util.stream.Collectors;54import java.util.stream.Stream;5556/**57* Generate dot graph for modules58*/59public class ModuleDotGraph {60private final JdepsConfiguration config;61private final Map<String, Configuration> configurations;62private final boolean apiOnly;63public ModuleDotGraph(JdepsConfiguration config, boolean apiOnly) {64this(config,65config.rootModules().stream()66.map(Module::name)67.sorted()68.collect(toMap(Function.identity(), mn -> config.resolve(Set.of(mn)))),69apiOnly);70}7172public ModuleDotGraph(Map<String, Configuration> configurations, boolean apiOnly) {73this(null, configurations, apiOnly);74}7576private ModuleDotGraph(JdepsConfiguration config,77Map<String, Configuration> configurations,78boolean apiOnly) {79this.configurations = configurations;80this.apiOnly = apiOnly;81this.config = config;82}8384/**85* Generate dotfile for all modules86*87* @param dir output directory88*/89public boolean genDotFiles(Path dir) throws IOException {90return genDotFiles(dir, DotGraphAttributes.DEFAULT);91}9293public boolean genDotFiles(Path dir, Attributes attributes)94throws IOException95{96Files.createDirectories(dir);97for (String mn : configurations.keySet()) {98Path path = dir.resolve(toDotFileBaseName(mn) + ".dot");99genDotFile(path, mn, configurations.get(mn), attributes);100}101return true;102}103104private String toDotFileBaseName(String mn) {105if (config == null)106return mn;107108Optional<Path> path = config.findModule(mn).flatMap(Module::path);109if (path.isPresent())110return path.get().getFileName().toString();111else112return mn;113}114/**115* Generate dotfile of the given path116*/117public void genDotFile(Path path, String name,118Configuration configuration,119Attributes attributes)120throws IOException121{122// transitive reduction123Graph<String> graph = apiOnly124? requiresTransitiveGraph(configuration, Set.of(name))125: gengraph(configuration);126127DotGraphBuilder builder = new DotGraphBuilder(name, graph, attributes);128builder.subgraph("se", "java", attributes.javaSubgraphColor(),129DotGraphBuilder.JAVA_SE_SUBGRAPH)130.subgraph("jdk", "jdk", attributes.jdkSubgraphColor(),131DotGraphBuilder.JDK_SUBGRAPH)132.modules(graph.nodes().stream()133.map(mn -> configuration.findModule(mn).get()134.reference().descriptor()));135// build dot file136builder.build(path);137}138139/**140* Returns a Graph of the given Configuration after transitive reduction.141*142* Transitive reduction of requires transitive edge and requires edge have143* to be applied separately to prevent the requires transitive edges144* (e.g. U -> V) from being reduced by a path (U -> X -> Y -> V)145* in which V would not be re-exported from U.146*/147private Graph<String> gengraph(Configuration cf) {148Graph.Builder<String> builder = new Graph.Builder<>();149cf.modules().stream()150.forEach(rm -> {151String mn = rm.name();152builder.addNode(mn);153rm.reads().stream()154.map(ResolvedModule::name)155.forEach(target -> builder.addEdge(mn, target));156});157158Graph<String> rpg = requiresTransitiveGraph(cf, builder.nodes);159return builder.build().reduce(rpg);160}161162163/**164* Returns a Graph containing only requires transitive edges165* with transitive reduction.166*/167public Graph<String> requiresTransitiveGraph(Configuration cf,168Set<String> roots)169{170Deque<String> deque = new ArrayDeque<>(roots);171Set<String> visited = new HashSet<>();172Graph.Builder<String> builder = new Graph.Builder<>();173174while (deque.peek() != null) {175String mn = deque.pop();176if (visited.contains(mn))177continue;178179visited.add(mn);180builder.addNode(mn);181cf.findModule(mn).get()182.reference().descriptor().requires().stream()183.filter(d -> d.modifiers().contains(TRANSITIVE)184|| d.name().equals("java.base"))185.map(Requires::name)186.forEach(d -> {187deque.add(d);188builder.addEdge(mn, d);189});190}191192return builder.build().reduce();193}194195public interface Attributes {196static final String ORANGE = "#e76f00";197static final String BLUE = "#437291";198static final String BLACK = "#000000";199static final String DARK_GRAY = "#999999";200static final String LIGHT_GRAY = "#dddddd";201202int fontSize();203String fontName();204String fontColor();205206int arrowSize();207int arrowWidth();208String arrowColor();209210default double rankSep() {211return 1;212}213214default List<Set<String>> ranks() {215return Collections.emptyList();216}217218default int weightOf(String s, String t) {219return 1;220}221222default String requiresMandatedColor() {223return LIGHT_GRAY;224}225226default String javaSubgraphColor() {227return ORANGE;228}229230default String jdkSubgraphColor() {231return BLUE;232}233}234235static class DotGraphAttributes implements Attributes {236static final DotGraphAttributes DEFAULT = new DotGraphAttributes();237238static final String FONT_NAME = "DejaVuSans";239static final int FONT_SIZE = 12;240static final int ARROW_SIZE = 1;241static final int ARROW_WIDTH = 2;242243@Override244public int fontSize() {245return FONT_SIZE;246}247248@Override249public String fontName() {250return FONT_NAME;251}252253@Override254public String fontColor() {255return BLACK;256}257258@Override259public int arrowSize() {260return ARROW_SIZE;261}262263@Override264public int arrowWidth() {265return ARROW_WIDTH;266}267268@Override269public String arrowColor() {270return DARK_GRAY;271}272}273274private static class DotGraphBuilder {275static final String REEXPORTS = "";276static final String REQUIRES = "style=\"dashed\"";277278static final Set<String> JAVA_SE_SUBGRAPH = javaSE();279static final Set<String> JDK_SUBGRAPH = jdk();280281private static Set<String> javaSE() {282String root = "java.se";283ModuleFinder system = ModuleFinder.ofSystem();284if (system.find(root).isPresent()) {285return Stream.concat(Stream.of(root),286Configuration.empty().resolve(system,287ModuleFinder.of(),288Set.of(root))289.findModule(root).get()290.reads().stream()291.map(ResolvedModule::name))292.collect(toSet());293} else {294// approximation295return system.findAll().stream()296.map(ModuleReference::descriptor)297.map(ModuleDescriptor::name)298.filter(name -> name.startsWith("java.") &&299!name.equals("java.smartcardio"))300.collect(Collectors.toSet());301}302}303304private static Set<String> jdk() {305return ModuleFinder.ofSystem().findAll().stream()306.map(ModuleReference::descriptor)307.map(ModuleDescriptor::name)308.filter(name -> !JAVA_SE_SUBGRAPH.contains(name) &&309(name.startsWith("java.") || name.startsWith("jdk.")))310.collect(Collectors.toSet());311}312313static class SubGraph {314final String name;315final String group;316final String color;317final Set<String> nodes;318SubGraph(String name, String group, String color, Set<String> nodes) {319this.name = Objects.requireNonNull(name);320this.group = Objects.requireNonNull(group);321this.color = Objects.requireNonNull(color);322this.nodes = Objects.requireNonNull(nodes);323}324}325326private final String name;327private final Graph<String> graph;328private final Set<ModuleDescriptor> descriptors = new TreeSet<>();329private final List<SubGraph> subgraphs = new ArrayList<>();330private final Attributes attributes;331public DotGraphBuilder(String name,332Graph<String> graph,333Attributes attributes) {334this.name = name;335this.graph = graph;336this.attributes = attributes;337}338339public DotGraphBuilder modules(Stream<ModuleDescriptor> descriptors) {340descriptors.forEach(this.descriptors::add);341return this;342}343344public void build(Path filename) throws IOException {345try (BufferedWriter writer = Files.newBufferedWriter(filename);346PrintWriter out = new PrintWriter(writer)) {347348out.format("digraph \"%s\" {%n", name);349out.format(" nodesep=.5;%n");350out.format((Locale)null, " ranksep=%f;%n", attributes.rankSep());351out.format(" pencolor=transparent;%n");352out.format(" node [shape=plaintext, fontcolor=\"%s\", fontname=\"%s\","353+ " fontsize=%d, margin=\".2,.2\"];%n",354attributes.fontColor(),355attributes.fontName(),356attributes.fontSize());357out.format(" edge [penwidth=%d, color=\"%s\", arrowhead=open, arrowsize=%d];%n",358attributes.arrowWidth(),359attributes.arrowColor(),360attributes.arrowSize());361362// same RANKS363attributes.ranks().stream()364.map(nodes -> descriptors.stream()365.map(ModuleDescriptor::name)366.filter(nodes::contains)367.map(mn -> "\"" + mn + "\"")368.collect(joining(",")))369.filter(group -> group.length() > 0)370.forEach(group -> out.format(" {rank=same %s}%n", group));371372subgraphs.forEach(subgraph -> {373out.format(" subgraph %s {%n", subgraph.name);374descriptors.stream()375.map(ModuleDescriptor::name)376.filter(subgraph.nodes::contains)377.forEach(mn -> printNode(out, mn, subgraph.color, subgraph.group));378out.format(" }%n");379});380381descriptors.stream()382.filter(md -> graph.contains(md.name()) &&383!graph.adjacentNodes(md.name()).isEmpty())384.forEach(md -> printNode(out, md, graph.adjacentNodes(md.name())));385386out.println("}");387}388}389390public DotGraphBuilder subgraph(String name, String group, String color,391Set<String> nodes) {392subgraphs.add(new SubGraph(name, group, color, nodes));393return this;394}395396public void printNode(PrintWriter out, String node, String color, String group) {397out.format(" \"%s\" [fontcolor=\"%s\", group=%s];%n",398node, color, group);399}400401public void printNode(PrintWriter out, ModuleDescriptor md, Set<String> edges) {402Set<String> requiresTransitive = md.requires().stream()403.filter(d -> d.modifiers().contains(TRANSITIVE))404.map(d -> d.name())405.collect(toSet());406407String mn = md.name();408edges.stream().forEach(dn -> {409String attr;410if (dn.equals("java.base")) {411attr = "color=\"" + attributes.requiresMandatedColor() + "\"";412} else {413attr = (requiresTransitive.contains(dn) ? REEXPORTS : REQUIRES);414}415416int w = attributes.weightOf(mn, dn);417if (w > 1) {418if (!attr.isEmpty())419attr += ", ";420421attr += "weight=" + w;422}423out.format(" \"%s\" -> \"%s\" [%s];%n", mn, dn, attr);424});425}426427}428}429430431