Skip to content

Commit 44ab513

Browse files
committed
Semi-generated dependency graph
1 parent 3753743 commit 44ab513

File tree

4 files changed

+285
-842
lines changed

4 files changed

+285
-842
lines changed

pom.xml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -855,6 +855,50 @@ under the License.</licenseText>
855855
</execution>
856856
</executions>
857857
</plugin>
858+
<plugin>
859+
<groupId>org.fusesource.mvnplugins</groupId>
860+
<artifactId>maven-graph-plugin</artifactId>
861+
<version>1.45</version>
862+
<inherited>false</inherited>
863+
<executions>
864+
<execution>
865+
<id>graph</id>
866+
<goals>
867+
<goal>reactor</goal>
868+
</goals>
869+
<phase>pre-site</phase>
870+
<configuration>
871+
<hideVersion>true</hideVersion>
872+
<hideGroupId>true</hideGroupId>
873+
<hideScopes>test</hideScopes>
874+
<hideTransitive>true</hideTransitive>
875+
<keepDot>true</keepDot>
876+
<target>${project.build.directory}/graph/reactor-graph.dot</target>
877+
</configuration>
878+
</execution>
879+
</executions>
880+
</plugin>
881+
<plugin>
882+
<groupId>dev.jbang</groupId>
883+
<artifactId>jbang-maven-plugin</artifactId>
884+
<version>0.0.8</version>
885+
<inherited>false</inherited>
886+
<executions>
887+
<execution>
888+
<id>graph</id>
889+
<goals>
890+
<goal>run</goal>
891+
</goals>
892+
<phase>pre-site</phase>
893+
<configuration>
894+
<script>${project.basedir}/src/graph/ReactorGraph.java</script>
895+
<jbangargs>
896+
<arg>--verbose</arg>
897+
</jbangargs>
898+
</configuration>
899+
</execution>
900+
</executions>
901+
</plugin>
858902
</plugins>
859903
</build>
860904

prepare-svg.sh

Lines changed: 0 additions & 30 deletions
This file was deleted.

src/graph/ReactorGraph.java

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
///usr/bin/env jbang "$0" "$@" ; exit $?
2+
//JAVA 14+
3+
//DEPS guru.nidi:graphviz-java:0.18.1
4+
/*
5+
* Licensed to the Apache Software Foundation (ASF) under one
6+
* or more contributor license agreements. See the NOTICE file
7+
* distributed with this work for additional information
8+
* regarding copyright ownership. The ASF licenses this file
9+
* to you under the Apache License, Version 2.0 (the
10+
* "License"); you may not use this file except in compliance
11+
* with the License. You may obtain a copy of the License at
12+
*
13+
* http://www.apache.org/licenses/LICENSE-2.0
14+
*
15+
* Unless required by applicable law or agreed to in writing,
16+
* software distributed under the License is distributed on an
17+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18+
* KIND, either express or implied. See the License for the
19+
* specific language governing permissions and limitations
20+
* under the License.
21+
*/
22+
23+
import guru.nidi.graphviz.attribute.*;
24+
import guru.nidi.graphviz.engine.Engine;
25+
import guru.nidi.graphviz.engine.Format;
26+
import guru.nidi.graphviz.engine.Graphviz;
27+
import guru.nidi.graphviz.model.*;
28+
import guru.nidi.graphviz.parse.Parser;
29+
30+
import java.io.File;
31+
import java.io.IOException;
32+
import java.nio.file.Files;
33+
import java.nio.file.Paths;
34+
import java.util.*;
35+
import java.util.regex.Pattern;
36+
37+
import static guru.nidi.graphviz.model.Factory.*;
38+
39+
public class ReactorGraph {
40+
private static final LinkedHashMap<String, Pattern> CLUSTER_PATTERNS = new LinkedHashMap<>();
41+
static {
42+
CLUSTER_PATTERNS.put("JLine", Pattern.compile("^org\\.jline:.*"));
43+
CLUSTER_PATTERNS.put("Maven API", Pattern.compile("^org\\.apache\\.maven:maven-api-(?!impl).*"));
44+
CLUSTER_PATTERNS.put("Maven Resolver", Pattern.compile("^org\\.apache\\.maven\\.resolver:.*"));
45+
CLUSTER_PATTERNS.put("Maven Implementation", Pattern.compile("^org\\.apache\\.maven:maven-(api-impl|di|core|cli|xml-impl|jline|logging):.*"));
46+
CLUSTER_PATTERNS.put("Maven Compatibility", Pattern.compile("^org\\.apache\\.maven:maven-(artifact|builder-support|compat|embedder|model|model-builder|plugin-api|repository-metadata|resolver-provider|settings|settings-builder|toolchain-builder|toolchain-model):.*"));
47+
CLUSTER_PATTERNS.put("Sisu", Pattern.compile("(^org\\.eclipse\\.sisu:.*)|(.*:guice:.*)|(.*:javax.inject:.*)|(.*:javax.annotation-api:.*)"));
48+
CLUSTER_PATTERNS.put("Plexus", Pattern.compile("^org\\.codehaus\\.plexus:.*"));
49+
CLUSTER_PATTERNS.put("XML Parsing", Pattern.compile("(.*:woodstox-core:.*)|(.*:stax2-api:.*)"));
50+
CLUSTER_PATTERNS.put("Wagon", Pattern.compile("^org\\.apache\\.maven\\.wagon:.*"));
51+
CLUSTER_PATTERNS.put("SLF4j", Pattern.compile("^org\\.slf4j:.*"));
52+
CLUSTER_PATTERNS.put("Commons", Pattern.compile("^commons-cli:.*"));
53+
}
54+
private static final Pattern HIDDEN_NODES = Pattern.compile(".*:(maven-docgen|roaster-api|roaster-jdt|velocity-engine-core|commons-lang3|asm|logback-classic|slf4j-simple):.*");
55+
56+
public static void main(String[] args) {
57+
try {
58+
// Parse DOT file
59+
MutableGraph originalGraph = new Parser().read(new File("target/graph/reactor-graph.dot"));
60+
61+
// Create final graph
62+
MutableGraph clusteredGraph = mutGraph("G").setDirected(true);
63+
clusteredGraph.graphAttrs().add(GraphAttr.COMPOUND);
64+
clusteredGraph.graphAttrs().add(Label.of("Reactor Graph"));
65+
66+
// Create clusters
67+
Map<String, MutableGraph> clusters = new HashMap<>();
68+
for (String clusterName : CLUSTER_PATTERNS.keySet()) {
69+
String key = "cluster_" + clusterName.replaceAll("\\s+", "");
70+
MutableGraph cluster = mutGraph(key).setDirected(true);
71+
cluster.graphAttrs().add(Label.of(clusterName));
72+
clusters.put(clusterName, cluster);
73+
clusteredGraph.add(cluster);
74+
}
75+
76+
// Map to store new nodes by node name
77+
Map<String, MutableNode> nodeMap = new HashMap<>();
78+
Map<String, String> nodeToCluster = new HashMap<>();
79+
Map<String, String> newNames = new HashMap<>();
80+
81+
// First pass: Create nodes and organize them into clusters
82+
for (MutableNode originalNode : originalGraph.nodes()) {
83+
String oldNodeName = originalNode.name().toString();
84+
if (HIDDEN_NODES.matcher(oldNodeName).matches()) {
85+
continue;
86+
}
87+
String nodeName = oldNodeName;
88+
if (originalNode.get("label") instanceof Label l) {
89+
nodeName = l.value();
90+
}
91+
MutableNode newNode = mutNode(nodeName);
92+
nodeMap.put(nodeName, newNode);
93+
newNames.put(oldNodeName, nodeName);
94+
95+
boolean added = false;
96+
for (Map.Entry<String, Pattern> entry : CLUSTER_PATTERNS.entrySet()) {
97+
if (entry.getValue().matcher(oldNodeName).matches()) {
98+
clusters.get(entry.getKey()).add(newNode);
99+
nodeToCluster.put(nodeName, entry.getKey());
100+
added = true;
101+
break;
102+
}
103+
}
104+
105+
if (!added) {
106+
clusteredGraph.add(newNode);
107+
}
108+
}
109+
110+
// Second pass: Add links to the clustered graph
111+
Map<String, MutableNode> substitutes = new HashMap<>();
112+
Set<Pair> existingLinks = new HashSet<>();
113+
for (MutableNode node : originalGraph.nodes()) {
114+
for (Link link : node.links()) {
115+
String sourceName = newNames.get(link.from().name().toString());
116+
String targetName = newNames.get(link.to().name().toString());
117+
String sourceCluster = nodeToCluster.get(sourceName);
118+
String targetCluster = nodeToCluster.get(targetName);
119+
MutableNode sourceNode = nodeMap.get(sourceName);
120+
MutableNode targetNode = nodeMap.get(targetName);
121+
if (sourceNode != null && targetNode != null ) {
122+
if (!Objects.equals(sourceCluster, targetCluster)) {
123+
// Inter-cluster link
124+
if (sourceCluster != null) {
125+
sourceName = "cluster_" + sourceCluster.replaceAll("\\s+", "");
126+
}
127+
if (targetCluster != null) {
128+
targetName = "cluster_" + targetCluster.replaceAll("\\s+", "");
129+
}
130+
sourceNode = substitutes.computeIfAbsent(sourceName, n -> createNode(n, clusteredGraph));
131+
targetNode = substitutes.computeIfAbsent(targetName, n -> createNode(n, clusteredGraph));
132+
}
133+
if (existingLinks.add(new Pair(sourceName, targetName))) {
134+
sourceNode.addLink(targetNode);
135+
}
136+
}
137+
}
138+
}
139+
140+
// Write intermediary graph to DOT file
141+
String dotContent = Graphviz.fromGraph(clusteredGraph).render(Format.DOT).toString();
142+
Files.write(Paths.get("target/graph/intermediary_graph.dot"), dotContent.getBytes());
143+
System.out.println("Intermediary graph written to intermediary_graph.dot");
144+
145+
// Render graph to SVF
146+
Graphviz.fromGraph(clusteredGraph)
147+
.engine(Engine.FDP)
148+
.render(Format.SVG).toFile(new File("target/graph/intermediary_graph.svg"));
149+
System.out.println("Final graph rendered to intermediary_graph.svg");
150+
151+
// Generate and render the high-level graph
152+
MutableGraph highLevelGraph = generateHighLevelGraph(clusteredGraph, clusters, nodeToCluster, nodeMap);
153+
154+
// Write high-level graph to DOT file
155+
String highLevelDotContent = Graphviz.fromGraph(highLevelGraph).render(Format.DOT).toString();
156+
Files.write(Paths.get("target/graph/high_level_graph.dot"), highLevelDotContent.getBytes());
157+
System.out.println("High-level graph written to high_level_graph.dot");
158+
159+
// Render high-level graph to SVG
160+
Graphviz.fromGraph(highLevelGraph)
161+
.engine(Engine.DOT)
162+
.render(Format.SVG).toFile(new File("target/site/images/maven-deps.svg"));
163+
System.out.println("High-level graph rendered to high_level_graph.svg");
164+
165+
} catch (IOException e) {
166+
e.printStackTrace();
167+
}
168+
}
169+
170+
private static MutableGraph generateHighLevelGraph(MutableGraph clusteredGraph, Map<String, MutableGraph> clusters,
171+
Map<String, String> nodeToCluster, Map<String, MutableNode> nodeMap) {
172+
MutableGraph highLevelGraph = mutGraph("HighLevelGraph").setDirected(true);
173+
highLevelGraph.graphAttrs().add(GraphAttr.COMPOUND);
174+
highLevelGraph.graphAttrs().add(Label.of("High-Level Reactor Graph"));
175+
176+
Map<String, MutableNode> highLevelNodes = new HashMap<>();
177+
178+
// Create nodes for each cluster
179+
for (Map.Entry<String, MutableGraph> entry : clusters.entrySet()) {
180+
String key = entry.getKey();
181+
String clusterName = key.replaceAll("\\s+", "");
182+
MutableGraph cluster = entry.getValue();
183+
184+
String headerColor = clusterName.startsWith("Maven") ? "black" : "#808080"; // #808080 is a middle gray
185+
StringBuilder labelBuilder = new StringBuilder();
186+
labelBuilder.append("<table border='0' cellborder='0' cellspacing='0'>");
187+
labelBuilder.append("<tr><td bgcolor='")
188+
.append(headerColor)
189+
.append("'><font color='white'>")
190+
.append(key)
191+
.append("</font></td></tr>");
192+
cluster.nodes().stream().map(MutableNode::name).map(Label::toString).sorted()
193+
.forEach(nodeName -> labelBuilder.append("<tr><td>").append(nodeName).append("</td></tr>"));
194+
labelBuilder.append("</table>");
195+
196+
MutableNode clusterNode = mutNode(clusterName).add(Label.html(labelBuilder.toString()))
197+
.add("shape", "rectangle");
198+
highLevelNodes.put(clusterName, clusterNode);
199+
highLevelGraph.add(clusterNode);
200+
}
201+
202+
// Add individual nodes for unclustered nodes
203+
for (MutableNode node : clusteredGraph.nodes()) {
204+
String nodeName = node.name().toString();
205+
if (!nodeToCluster.containsKey(nodeName) && !nodeName.startsWith("cluster_")) {
206+
throw new IllegalStateException("All nodes should be in a cluster: " + node.name());
207+
}
208+
}
209+
210+
// Add edges
211+
Set<Pair> existingLinks = new HashSet<>();
212+
for (MutableNode node : clusteredGraph.nodes()) {
213+
String sourceName = node.name().toString().replace("cluster_", "");
214+
String sourceCluster = nodeToCluster.getOrDefault(sourceName, sourceName);
215+
216+
for (Link link : node.links()) {
217+
String targetName = link.to().name().toString().replace("cluster_", "");
218+
String targetCluster = nodeToCluster.getOrDefault(targetName, targetName);
219+
220+
Pair linkPair = new Pair(sourceCluster, targetCluster);
221+
if (existingLinks.add(linkPair)) {
222+
MutableNode sourceNode = highLevelNodes.get(sourceCluster);
223+
MutableNode targetNode = highLevelNodes.get(targetCluster);
224+
if (sourceNode != null && targetNode != null && sourceNode != targetNode) {
225+
sourceNode.addLink(targetNode);
226+
}
227+
}
228+
}
229+
}
230+
231+
return highLevelGraph;
232+
}
233+
234+
private static MutableNode createNode(String n, MutableGraph clusteredGraph) {
235+
MutableNode t = mutNode(n);
236+
clusteredGraph.add(t);
237+
return t;
238+
}
239+
240+
record Pair(String from, String to) {};
241+
}

0 commit comments

Comments
 (0)