langtools/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/ModuleDotGraph.java
changeset 44452 93f6470b1045
parent 43873 705d732d3715
equal deleted inserted replaced
44451:1619c00af9df 44452:93f6470b1045
    30 import java.io.BufferedWriter;
    30 import java.io.BufferedWriter;
    31 import java.io.IOException;
    31 import java.io.IOException;
    32 import java.io.PrintWriter;
    32 import java.io.PrintWriter;
    33 import java.lang.module.Configuration;
    33 import java.lang.module.Configuration;
    34 import java.lang.module.ModuleDescriptor;
    34 import java.lang.module.ModuleDescriptor;
       
    35 import java.lang.module.ModuleDescriptor.*;
    35 import java.lang.module.ModuleFinder;
    36 import java.lang.module.ModuleFinder;
    36 import java.lang.module.ModuleReference;
    37 import java.lang.module.ModuleReference;
    37 import java.lang.module.ResolvedModule;
    38 import java.lang.module.ResolvedModule;
    38 import java.nio.file.Files;
    39 import java.nio.file.Files;
    39 import java.nio.file.Path;
    40 import java.nio.file.Path;
    40 import java.util.ArrayDeque;
    41 import java.util.ArrayDeque;
    41 import java.util.ArrayList;
    42 import java.util.ArrayList;
       
    43 import java.util.Collections;
    42 import java.util.Deque;
    44 import java.util.Deque;
    43 import java.util.HashMap;
       
    44 import java.util.HashSet;
    45 import java.util.HashSet;
    45 import java.util.List;
    46 import java.util.List;
    46 import java.util.Map;
    47 import java.util.Map;
    47 import java.util.Objects;
    48 import java.util.Objects;
    48 import java.util.Set;
    49 import java.util.Set;
    74      * Generate dotfile for all modules
    75      * Generate dotfile for all modules
    75      *
    76      *
    76      * @param dir output directory
    77      * @param dir output directory
    77      */
    78      */
    78     public boolean genDotFiles(Path dir) throws IOException {
    79     public boolean genDotFiles(Path dir) throws IOException {
       
    80         return genDotFiles(dir, DotGraphAttributes.DEFAULT);
       
    81     }
       
    82 
       
    83     public boolean genDotFiles(Path dir, Attributes attributes)
       
    84         throws IOException
       
    85     {
    79         Files.createDirectories(dir);
    86         Files.createDirectories(dir);
    80         for (String mn : configurations.keySet()) {
    87         for (String mn : configurations.keySet()) {
    81             Path path = dir.resolve(mn + ".dot");
    88             Path path = dir.resolve(mn + ".dot");
    82             genDotFile(path, mn, configurations.get(mn));
    89             genDotFile(path, mn, configurations.get(mn), attributes);
    83         }
    90         }
    84         return true;
    91         return true;
    85     }
    92     }
    86 
    93 
    87     /**
    94     /**
    88      * Generate dotfile of the given path
    95      * Generate dotfile of the given path
    89      */
    96      */
    90     public void genDotFile(Path path, String name, Configuration configuration)
    97     public void genDotFile(Path path, String name,
       
    98                            Configuration configuration,
       
    99                            Attributes attributes)
    91         throws IOException
   100         throws IOException
    92     {
   101     {
    93         // transitive reduction
   102         // transitive reduction
    94         Graph<String> graph = apiOnly
   103         Graph<String> graph = apiOnly
    95                 ? requiresTransitiveGraph(configuration, Set.of(name))
   104                 ? requiresTransitiveGraph(configuration, Set.of(name))
    96                 : gengraph(configuration);
   105                 : gengraph(configuration);
    97 
   106 
    98         DotGraphBuilder builder = new DotGraphBuilder(name, graph);
   107         DotGraphBuilder builder = new DotGraphBuilder(name, graph, attributes);
    99         builder.subgraph("se", "java", DotGraphBuilder.ORANGE,
   108         builder.subgraph("se", "java", attributes.javaSubgraphColor(),
   100                          DotGraphBuilder.JAVA_SE_SUBGRAPH)
   109                          DotGraphBuilder.JAVA_SE_SUBGRAPH)
   101                .subgraph("jdk", "jdk", DotGraphBuilder.BLUE,
   110                .subgraph("jdk", "jdk", attributes.jdkSubgraphColor(),
   102                          DotGraphBuilder.JDK_SUBGRAPH)
   111                          DotGraphBuilder.JDK_SUBGRAPH)
   103                .descriptors(graph.nodes().stream()
   112                .modules(graph.nodes().stream()
   104                                  .map(mn -> configuration.findModule(mn).get()
   113                                  .map(mn -> configuration.findModule(mn).get()
   105                                                 .reference().descriptor()));
   114                                                 .reference().descriptor()));
   106         // build dot file
   115         // build dot file
   107         builder.build(path);
   116         builder.build(path);
   108     }
   117     }
   116      * in which  V would not be re-exported from U.
   125      * in which  V would not be re-exported from U.
   117      */
   126      */
   118     private Graph<String> gengraph(Configuration cf) {
   127     private Graph<String> gengraph(Configuration cf) {
   119         Graph.Builder<String> builder = new Graph.Builder<>();
   128         Graph.Builder<String> builder = new Graph.Builder<>();
   120         cf.modules().stream()
   129         cf.modules().stream()
   121             .forEach(resolvedModule -> {
   130             .forEach(rm -> {
   122                 String mn = resolvedModule.reference().descriptor().name();
   131                 String mn = rm.name();
   123                 builder.addNode(mn);
   132                 builder.addNode(mn);
   124                 resolvedModule.reads().stream()
   133                 rm.reads().stream()
   125                     .map(ResolvedModule::name)
   134                   .map(ResolvedModule::name)
   126                     .forEach(target -> builder.addEdge(mn, target));
   135                   .forEach(target -> builder.addEdge(mn, target));
   127             });
   136             });
   128 
   137 
   129         Graph<String> rpg = requiresTransitiveGraph(cf, builder.nodes);
   138         Graph<String> rpg = requiresTransitiveGraph(cf, builder.nodes);
   130         return builder.build().reduce(rpg);
   139         return builder.build().reduce(rpg);
   131     }
   140     }
   147             if (visited.contains(mn))
   156             if (visited.contains(mn))
   148                 continue;
   157                 continue;
   149 
   158 
   150             visited.add(mn);
   159             visited.add(mn);
   151             builder.addNode(mn);
   160             builder.addNode(mn);
   152             ModuleDescriptor descriptor = cf.findModule(mn).get()
   161             cf.findModule(mn).get()
   153                 .reference().descriptor();
   162               .reference().descriptor().requires().stream()
   154             descriptor.requires().stream()
   163               .filter(d -> d.modifiers().contains(TRANSITIVE)
   155                 .filter(d -> d.modifiers().contains(TRANSITIVE)
       
   156                                 || d.name().equals("java.base"))
   164                                 || d.name().equals("java.base"))
   157                 .map(d -> d.name())
   165               .map(Requires::name)
   158                 .forEach(d -> {
   166               .forEach(d -> {
   159                     deque.add(d);
   167                   deque.add(d);
   160                     builder.addEdge(mn, d);
   168                   builder.addEdge(mn, d);
   161                 });
   169               });
   162         }
   170         }
   163 
   171 
   164         return builder.build().reduce();
   172         return builder.build().reduce();
   165     }
   173     }
   166 
   174 
   167     public static class DotGraphBuilder {
   175     public interface Attributes {
       
   176         static final String ORANGE = "#e76f00";
       
   177         static final String BLUE = "#437291";
       
   178         static final String BLACK = "#000000";
       
   179         static final String DARK_GRAY = "#999999";
       
   180         static final String LIGHT_GRAY = "#dddddd";
       
   181 
       
   182         int fontSize();
       
   183         String fontName();
       
   184         String fontColor();
       
   185 
       
   186         int arrowSize();
       
   187         int arrowWidth();
       
   188         String arrowColor();
       
   189 
       
   190         default double rankSep() {
       
   191             return 1;
       
   192         }
       
   193 
       
   194         default List<Set<String>> ranks() {
       
   195             return Collections.emptyList();
       
   196         }
       
   197 
       
   198         default int weightOf(String s, String t) {
       
   199             return 1;
       
   200         }
       
   201 
       
   202         default String requiresMandatedColor() {
       
   203             return LIGHT_GRAY;
       
   204         }
       
   205 
       
   206         default String javaSubgraphColor() {
       
   207             return ORANGE;
       
   208         }
       
   209 
       
   210         default String jdkSubgraphColor() {
       
   211             return BLUE;
       
   212         }
       
   213     }
       
   214 
       
   215     static class DotGraphAttributes implements Attributes {
       
   216         static final DotGraphAttributes DEFAULT = new DotGraphAttributes();
       
   217 
       
   218         static final String FONT_NAME = "DejaVuSans";
       
   219         static final int FONT_SIZE = 12;
       
   220         static final int ARROW_SIZE = 1;
       
   221         static final int ARROW_WIDTH = 2;
       
   222 
       
   223         @Override
       
   224         public int fontSize() {
       
   225             return FONT_SIZE;
       
   226         }
       
   227 
       
   228         @Override
       
   229         public String fontName() {
       
   230             return FONT_NAME;
       
   231         }
       
   232 
       
   233         @Override
       
   234         public String fontColor() {
       
   235             return BLACK;
       
   236         }
       
   237 
       
   238         @Override
       
   239         public int arrowSize() {
       
   240             return ARROW_SIZE;
       
   241         }
       
   242 
       
   243         @Override
       
   244         public int arrowWidth() {
       
   245             return ARROW_WIDTH;
       
   246         }
       
   247 
       
   248         @Override
       
   249         public String arrowColor() {
       
   250             return DARK_GRAY;
       
   251         }
       
   252     }
       
   253 
       
   254     private static class DotGraphBuilder {
       
   255         static final String REEXPORTS = "";
       
   256         static final String REQUIRES = "style=\"dashed\"";
       
   257 
   168         static final Set<String> JAVA_SE_SUBGRAPH = javaSE();
   258         static final Set<String> JAVA_SE_SUBGRAPH = javaSE();
   169         static final Set<String> JDK_SUBGRAPH = jdk();
   259         static final Set<String> JDK_SUBGRAPH = jdk();
   170 
   260 
   171         private static Set<String> javaSE() {
   261         private static Set<String> javaSE() {
   172             String root = "java.se.ee";
   262             String root = "java.se.ee";
   213                 this.color = Objects.requireNonNull(color);
   303                 this.color = Objects.requireNonNull(color);
   214                 this.nodes = Objects.requireNonNull(nodes);
   304                 this.nodes = Objects.requireNonNull(nodes);
   215             }
   305             }
   216         }
   306         }
   217 
   307 
   218         static final String ORANGE = "#e76f00";
       
   219         static final String BLUE = "#437291";
       
   220         static final String GRAY = "#dddddd";
       
   221         static final String BLACK = "#000000";
       
   222 
       
   223         static final String FONT_NAME = "DejaVuSans";
       
   224         static final int FONT_SIZE = 12;
       
   225         static final int ARROW_SIZE = 1;
       
   226         static final int ARROW_WIDTH = 2;
       
   227         static final int RANK_SEP = 1;
       
   228 
       
   229         static final String REEXPORTS = "";
       
   230         static final String REQUIRES = "style=\"dashed\"";
       
   231         static final String REQUIRES_BASE = "color=\"" + GRAY + "\"";
       
   232 
       
   233         // can be configured
       
   234         static double rankSep   = RANK_SEP;
       
   235         static String fontColor = BLACK;
       
   236         static String fontName  = FONT_NAME;
       
   237         static int fontsize     = FONT_SIZE;
       
   238         static int arrowWidth   = ARROW_WIDTH;
       
   239         static int arrowSize    = ARROW_SIZE;
       
   240         static final Map<String, Integer> weights = new HashMap<>();
       
   241         static final List<Set<String>> ranks = new ArrayList<>();
       
   242 
       
   243         private final String name;
   308         private final String name;
   244         private final Graph<String> graph;
   309         private final Graph<String> graph;
   245         private final Set<ModuleDescriptor> descriptors = new TreeSet<>();
   310         private final Set<ModuleDescriptor> descriptors = new TreeSet<>();
   246         private final List<SubGraph> subgraphs = new ArrayList<>();
   311         private final List<SubGraph> subgraphs = new ArrayList<>();
   247         public DotGraphBuilder(String name, Graph<String> graph) {
   312         private final Attributes attributes;
       
   313         public DotGraphBuilder(String name,
       
   314                                Graph<String> graph,
       
   315                                Attributes attributes) {
   248             this.name = name;
   316             this.name = name;
   249             this.graph = graph;
   317             this.graph = graph;
   250         }
   318             this.attributes = attributes;
   251 
   319         }
   252         public DotGraphBuilder descriptors(Stream<ModuleDescriptor> descriptors) {
   320 
       
   321         public DotGraphBuilder modules(Stream<ModuleDescriptor> descriptors) {
   253             descriptors.forEach(this.descriptors::add);
   322             descriptors.forEach(this.descriptors::add);
   254             return this;
   323             return this;
   255         }
   324         }
   256 
   325 
   257         public void build(Path filename) throws IOException {
   326         public void build(Path filename) throws IOException {
   258             try (BufferedWriter writer = Files.newBufferedWriter(filename);
   327             try (BufferedWriter writer = Files.newBufferedWriter(filename);
   259                  PrintWriter out = new PrintWriter(writer)) {
   328                  PrintWriter out = new PrintWriter(writer)) {
   260 
   329 
   261                 out.format("digraph \"%s\" {%n", name);
   330                 out.format("digraph \"%s\" {%n", name);
   262                 out.format("  nodesep=.5;%n");
   331                 out.format("  nodesep=.5;%n");
   263                 out.format("  ranksep=%f;%n", rankSep);
   332                 out.format("  ranksep=%f;%n", attributes.rankSep());
   264                 out.format("  pencolor=transparent;%n");
   333                 out.format("  pencolor=transparent;%n");
   265                 out.format("  node [shape=plaintext, fontname=\"%s\", fontsize=%d, margin=\".2,.2\"];%n",
   334                 out.format("  node [shape=plaintext, fontcolor=\"%s\", fontname=\"%s\","
   266                            fontName, fontsize);
   335                                 + " fontsize=%d, margin=\".2,.2\"];%n",
   267                 out.format("  edge [penwidth=%d, color=\"#999999\", arrowhead=open, arrowsize=%d];%n",
   336                            attributes.fontColor(),
   268                            arrowWidth, arrowSize);
   337                            attributes.fontName(),
       
   338                            attributes.fontSize());
       
   339                 out.format("  edge [penwidth=%d, color=\"%s\", arrowhead=open, arrowsize=%d];%n",
       
   340                            attributes.arrowWidth(),
       
   341                            attributes.arrowColor(),
       
   342                            attributes.arrowSize());
   269 
   343 
   270                 // same RANKS
   344                 // same RANKS
   271                 ranks.stream()
   345                 attributes.ranks().stream()
   272                      .map(nodes -> descriptors.stream()
   346                     .map(nodes -> descriptors.stream()
   273                                         .map(ModuleDescriptor::name)
   347                                         .map(ModuleDescriptor::name)
   274                                         .filter(nodes::contains)
   348                                         .filter(nodes::contains)
   275                                         .map(mn -> "\"" + mn + "\"")
   349                                         .map(mn -> "\"" + mn + "\"")
   276                                         .collect(joining(",")))
   350                                         .collect(joining(",")))
   277                      .filter(group -> group.length() > 0)
   351                     .filter(group -> group.length() > 0)
   278                      .forEach(group -> out.format("  {rank=same %s}%n", group));
   352                     .forEach(group -> out.format("  {rank=same %s}%n", group));
   279 
   353 
   280                 subgraphs.forEach(subgraph -> {
   354                 subgraphs.forEach(subgraph -> {
   281                     out.format("  subgraph %s {%n", subgraph.name);
   355                     out.format("  subgraph %s {%n", subgraph.name);
   282                     descriptors.stream()
   356                     descriptors.stream()
   283                         .map(ModuleDescriptor::name)
   357                         .map(ModuleDescriptor::name)
   312                 .map(d -> d.name())
   386                 .map(d -> d.name())
   313                 .collect(toSet());
   387                 .collect(toSet());
   314 
   388 
   315             String mn = md.name();
   389             String mn = md.name();
   316             edges.stream().forEach(dn -> {
   390             edges.stream().forEach(dn -> {
   317                 String attr = dn.equals("java.base") ? REQUIRES_BASE
   391                 String attr;
   318                     : (requiresTransitive.contains(dn) ? REEXPORTS : REQUIRES);
   392                 if (dn.equals("java.base")) {
   319 
   393                     attr = "color=\"" + attributes.requiresMandatedColor() + "\"";
   320                 int w = weightOf(mn, dn);
   394                 } else {
       
   395                     attr = (requiresTransitive.contains(dn) ? REEXPORTS : REQUIRES);
       
   396                 }
       
   397 
       
   398                 int w = attributes.weightOf(mn, dn);
   321                 if (w > 1) {
   399                 if (w > 1) {
   322                     if (!attr.isEmpty())
   400                     if (!attr.isEmpty())
   323                         attr += ", ";
   401                         attr += ", ";
   324 
   402 
   325                     attr += "weight=" + w;
   403                     attr += "weight=" + w;
   326                 }
   404                 }
   327                 out.format("  \"%s\" -> \"%s\" [%s];%n", mn, dn, attr);
   405                 out.format("  \"%s\" -> \"%s\" [%s];%n", mn, dn, attr);
   328             });
   406             });
   329         }
   407         }
   330 
   408 
   331         public int weightOf(String s, String t) {
       
   332             int w = weights.getOrDefault(s + ":" + t, 1);
       
   333             if (w != 1)
       
   334                 return w;
       
   335             if (s.startsWith("java.") && t.startsWith("java."))
       
   336                 return 10;
       
   337             return 1;
       
   338         }
       
   339 
       
   340         public static void sameRankNodes(Set<String> nodes) {
       
   341             ranks.add(nodes);
       
   342         }
       
   343 
       
   344         public static void weight(String s, String t, int w) {
       
   345             weights.put(s + ":" + t, w);
       
   346         }
       
   347 
       
   348         public static void setRankSep(double value) {
       
   349             rankSep = value;
       
   350         }
       
   351 
       
   352         public static void setFontSize(int size) {
       
   353             fontsize = size;
       
   354         }
       
   355 
       
   356         public static void setFontColor(String color) {
       
   357             fontColor = color;
       
   358         }
       
   359 
       
   360         public static void setArrowSize(int size) {
       
   361             arrowSize = size;
       
   362         }
       
   363 
       
   364         public static void setArrowWidth(int width) {
       
   365             arrowWidth = width;
       
   366         }
       
   367     }
   409     }
   368 }
   410 }