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