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 } |