diff --git a/pom.xml b/pom.xml index 6252af458e2..6c6197ff44c 100644 --- a/pom.xml +++ b/pom.xml @@ -796,6 +796,7 @@ under the License. **/*.svg .asf.yaml .mvn/** + .jbang/** @@ -855,6 +856,50 @@ under the License. + + org.fusesource.mvnplugins + maven-graph-plugin + 1.45 + false + + + graph + + reactor + + pre-site + + true + true + test + true + true + ${project.build.directory}/graph/reactor-graph.dot + + + + + + dev.jbang + jbang-maven-plugin + 0.0.8 + false + + + graph + + run + + pre-site + + + + --verbose + + + + + diff --git a/prepare-svg.sh b/prepare-svg.sh deleted file mode 100755 index 9b2d1424bc9..00000000000 --- a/prepare-svg.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash - -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -#libreoffice --headless --convert-to svg src/site/xdoc/maven-deps.odg -# CLI export keeps full A3 page -# I prefer doing it by hand, limiting export to "selection" = avoids extra space - -# svgo https://github.com/svg/svgo -svgo --config src/site/svgo.config.mjs maven-deps.svg -o maven-deps-optimized.svg - -cat maven-deps-optimized.svg \ - | sed 's/a xlink:href/a target="_parent" xlink:href/' \ - | sed 's_file://_.._' \ - > src/site/resources/images/maven-deps.svg diff --git a/src/graph/ReactorGraph.java b/src/graph/ReactorGraph.java new file mode 100755 index 00000000000..241d85edebf --- /dev/null +++ b/src/graph/ReactorGraph.java @@ -0,0 +1,266 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 14+ +//DEPS guru.nidi:graphviz-java:0.18.1 +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import guru.nidi.graphviz.attribute.*; +import guru.nidi.graphviz.engine.Engine; +import guru.nidi.graphviz.engine.Format; +import guru.nidi.graphviz.engine.Graphviz; +import guru.nidi.graphviz.model.*; +import guru.nidi.graphviz.parse.Parser; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.*; +import java.util.regex.Pattern; + +import static guru.nidi.graphviz.model.Factory.*; + +public class ReactorGraph { + private static final LinkedHashMap CLUSTER_PATTERNS = new LinkedHashMap<>(); + static { + CLUSTER_PATTERNS.put("JLine", Pattern.compile("^org\\.jline:.*")); + CLUSTER_PATTERNS.put("Maven API", Pattern.compile("^org\\.apache\\.maven:maven-api-(?!impl).*")); + CLUSTER_PATTERNS.put("Maven Resolver", Pattern.compile("^org\\.apache\\.maven\\.resolver:.*")); + CLUSTER_PATTERNS.put("Maven Implementation", Pattern.compile("^org\\.apache\\.maven:maven-(api-impl|di|core|cli|xml-impl|jline|logging):.*")); + 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):.*")); + CLUSTER_PATTERNS.put("Sisu", Pattern.compile("(^org\\.eclipse\\.sisu:.*)|(.*:guice:.*)|(.*:javax.inject:.*)|(.*:javax.annotation-api:.*)")); + CLUSTER_PATTERNS.put("Plexus", Pattern.compile("^org\\.codehaus\\.plexus:.*")); + CLUSTER_PATTERNS.put("XML Parsing", Pattern.compile("(.*:woodstox-core:.*)|(.*:stax2-api:.*)")); + CLUSTER_PATTERNS.put("Wagon", Pattern.compile("^org\\.apache\\.maven\\.wagon:.*")); + CLUSTER_PATTERNS.put("SLF4j", Pattern.compile("^org\\.slf4j:.*")); + CLUSTER_PATTERNS.put("Commons", Pattern.compile("^commons-cli:.*")); + } + private static final Pattern HIDDEN_NODES = Pattern.compile(".*:(maven-docgen|roaster-api|roaster-jdt|velocity-engine-core|commons-lang3|asm|logback-classic|slf4j-simple):.*"); + + public static void main(String[] args) { + try { + // Parse DOT file + MutableGraph originalGraph = new Parser().read(new File("target/graph/reactor-graph.dot")); + + // Create final graph + MutableGraph clusteredGraph = mutGraph("G").setDirected(true); + clusteredGraph.graphAttrs().add(GraphAttr.COMPOUND); + clusteredGraph.graphAttrs().add(Label.of("Reactor Graph")); + + // Create clusters + Map clusters = new HashMap<>(); + for (String clusterName : CLUSTER_PATTERNS.keySet()) { + String key = "cluster_" + clusterName.replaceAll("\\s+", ""); + MutableGraph cluster = mutGraph(key).setDirected(true); + cluster.graphAttrs().add(Label.of(clusterName)); + clusters.put(clusterName, cluster); + clusteredGraph.add(cluster); + } + + // Map to store new nodes by node name + Map nodeMap = new HashMap<>(); + Map nodeToCluster = new HashMap<>(); + Map newNames = new HashMap<>(); + + // First pass: Create nodes and organize them into clusters + for (MutableNode originalNode : originalGraph.nodes()) { + String oldNodeName = originalNode.name().toString(); + if (HIDDEN_NODES.matcher(oldNodeName).matches()) { + continue; + } + String nodeName = oldNodeName; + if (originalNode.get("label") instanceof Label l) { + nodeName = l.value(); + } + MutableNode newNode = mutNode(nodeName); + nodeMap.put(nodeName, newNode); + newNames.put(oldNodeName, nodeName); + + boolean added = false; + for (Map.Entry entry : CLUSTER_PATTERNS.entrySet()) { + if (entry.getValue().matcher(oldNodeName).matches()) { + clusters.get(entry.getKey()).add(newNode); + nodeToCluster.put(nodeName, entry.getKey()); + added = true; + break; + } + } + + if (!added) { + clusteredGraph.add(newNode); + } + } + + // Second pass: Add links to the clustered graph + Map substitutes = new HashMap<>(); + Set existingLinks = new HashSet<>(); + for (MutableNode node : originalGraph.nodes()) { + for (Link link : node.links()) { + String sourceName = newNames.get(link.from().name().toString()); + String targetName = newNames.get(link.to().name().toString()); + String sourceCluster = nodeToCluster.get(sourceName); + String targetCluster = nodeToCluster.get(targetName); + MutableNode sourceNode = nodeMap.get(sourceName); + MutableNode targetNode = nodeMap.get(targetName); + if (sourceNode != null && targetNode != null ) { + if (!Objects.equals(sourceCluster, targetCluster)) { + // Inter-cluster link + if (sourceCluster != null) { + sourceName = "cluster_" + sourceCluster.replaceAll("\\s+", ""); + } + if (targetCluster != null) { + targetName = "cluster_" + targetCluster.replaceAll("\\s+", ""); + } + sourceNode = substitutes.computeIfAbsent(sourceName, n -> createNode(n, clusteredGraph)); + targetNode = substitutes.computeIfAbsent(targetName, n -> createNode(n, clusteredGraph)); + } + if (existingLinks.add(new Pair(sourceName, targetName))) { + sourceNode.addLink(targetNode); + } + } + } + } + + // Write intermediary graph to DOT file + String dotContent = Graphviz.fromGraph(clusteredGraph).render(Format.DOT).toString(); + Files.write(Paths.get("target/graph/intermediary_graph.dot"), dotContent.getBytes()); + System.out.println("Intermediary graph written to intermediary_graph.dot"); + + // Render graph to SVF + Graphviz.fromGraph(clusteredGraph) + .engine(Engine.FDP) + .render(Format.SVG).toFile(new File("target/graph/intermediary_graph.svg")); + System.out.println("Final graph rendered to intermediary_graph.svg"); + + // Generate and render the high-level graph + MutableGraph highLevelGraph = generateHighLevelGraph(clusteredGraph, clusters, nodeToCluster, nodeMap); + + // Write high-level graph to DOT file + String highLevelDotContent = Graphviz.fromGraph(highLevelGraph).render(Format.DOT).toString(); + Files.write(Paths.get("target/graph/high_level_graph.dot"), highLevelDotContent.getBytes()); + System.out.println("High-level graph written to high_level_graph.dot"); + + // Render high-level graph to SVG + Graphviz.fromGraph(highLevelGraph) + .engine(Engine.DOT) + .render(Format.SVG).toFile(new File("target/site/images/maven-deps.svg")); + System.out.println("High-level graph rendered to high_level_graph.svg"); + + } catch (IOException e) { + e.printStackTrace(); + } + } + + private static MutableGraph generateHighLevelGraph(MutableGraph clusteredGraph, Map clusters, + Map nodeToCluster, Map nodeMap) { + MutableGraph highLevelGraph = mutGraph("HighLevelGraph").setDirected(true); + highLevelGraph.graphAttrs().add(GraphAttr.COMPOUND); + highLevelGraph.graphAttrs().add(Label.of("High-Level Reactor Graph")); + + Map highLevelNodes = new HashMap<>(); + + // Create nodes for each cluster + for (Map.Entry entry : clusters.entrySet()) { + String key = entry.getKey(); + String clusterName = key.replaceAll("\\s+", ""); + MutableGraph cluster = entry.getValue(); + + String headerColor = clusterName.startsWith("Maven") ? "black" : "#808080"; // #808080 is a middle gray + StringBuilder labelBuilder = new StringBuilder(); + labelBuilder.append(""); + labelBuilder.append(""); + cluster.nodes().stream().map(MutableNode::name).map(Label::toString).sorted() + .forEach(nodeName -> { + labelBuilder.append(""); + String prefix = null; + switch (clusterName) { + case "MavenAPI": prefix = "../api/"; break; + case "MavenImplementation": + case "MavenCompatibility": prefix = "../"; break; + case "MavenResolver": prefix = "https://maven.apache.org/resolver/"; break; + } + if (prefix != null) { + labelBuilder.append("") + .append(nodeName) + .append(""); + } else { + labelBuilder.append(""); + } + labelBuilder.append(""); + }); + labelBuilder.append("
") + .append(key) + .append("
").append(nodeName).append("
"); + + MutableNode clusterNode = mutNode(clusterName).add(Label.html(labelBuilder.toString())) + .add("shape", "rectangle"); + highLevelNodes.put(clusterName, clusterNode); + highLevelGraph.add(clusterNode); + } + + // Add individual nodes for unclustered nodes + for (MutableNode node : clusteredGraph.nodes()) { + String nodeName = node.name().toString(); + if (!nodeToCluster.containsKey(nodeName) && !nodeName.startsWith("cluster_")) { + throw new IllegalStateException("All nodes should be in a cluster: " + node.name()); + } + } + + // Add edges + Set existingLinks = new HashSet<>(); + for (MutableNode node : clusteredGraph.nodes()) { + String sourceName = node.name().toString().replace("cluster_", ""); + String sourceCluster = nodeToCluster.getOrDefault(sourceName, sourceName); + + for (Link link : node.links()) { + String targetName = link.to().name().toString().replace("cluster_", ""); + String targetCluster = nodeToCluster.getOrDefault(targetName, targetName); + + Pair linkPair = new Pair(sourceCluster, targetCluster); + if (existingLinks.add(linkPair)) { + MutableNode sourceNode = highLevelNodes.get(sourceCluster); + MutableNode targetNode = highLevelNodes.get(targetCluster); + if (sourceNode != null && targetNode != null && sourceNode != targetNode) { + sourceNode.addLink(targetNode); + } + } + } + } + + return highLevelGraph; + } + + private static MutableNode createNode(String n, MutableGraph clusteredGraph) { + MutableNode t = mutNode(n); + clusteredGraph.add(t); + return t; + } + + record Pair(String from, String to) {}; +} \ No newline at end of file diff --git a/src/site/resources/images/maven-deps.svg b/src/site/resources/images/maven-deps.svg deleted file mode 100644 index ae738b0799e..00000000000 --- a/src/site/resources/images/maven-deps.svg +++ /dev/null @@ -1,812 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Maven 4 API - - - - - - - - sisu - - - - - - - - plexus - - - - - - - - inject - - - - - - - - guice - - - - - - - - - - - - - - - - - - plexus - - - - - - - - core - - - - - - - - - - - - - distribution - - - - - - - - - - - - - - - - - - sec-dispatcher - - - - - - - - - - - - - commons-cli - - - - - - - - - - - - - utils - - - - - - - - plugin-api - - - - - - - - artifact - - - - - - - - embedder - - - - - - - - - - - - - - - - - - settings-builder - - - - - - - - - - - - - model-builder - - - - - - - - model - - - - - - - - - - - - - - - - - - cipher - - - - - - - - - - - - - interpolation - - - - - - - - - - - - - wagon-provider-api - - - - - - - - - - - - - classworlds - - - - - - - - - - - - - - - - - - repository-metadata - - - - - - - - - - - - - settings - - - - - - - - - - - - - resolver - - - - - - - - api - - - - - - - - spi - - - - - - - - impl - - - - - - - - util - - - - - - - - - - - - - - - - - - - - - - - - - - - - resolver-provider - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - builder-support - - - - - - - - - - - - - - - - - - jansi - - - - - - - - - - - - - slf4j-api - - - - - - - - - - - - - shared-utils - - - - - - - - - - - - - slf4j-provider - - - - - - - - - - - - - - - - - - bom - - - - - - - - - - - - - - - - - - toolchain-builder - - - - - - - - toolchain-model - - - - - - - - - - - - - - - - - - - - - - - - - - - - meta - - - - - - - - di - - - - - - - - xml - - - - - - - - model - - - - - - - - plugin - - - - - - - - toolchain - - - - - - - - core - - - - - - - - spi - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - xml-impl - - - - - - - - xml - - - - - - - - - - - - - - - - - - settings - - - - - - - - jline3 - - - - - - - - - - - - - - - - - - - - - - - compat - - - - - - - - slf4j-wrapper - - - - - - - - jline - - - - - - - - woodstox - - - - - - - - - - - - - api-impl - - - - - - - - - - - - - - - - - - - - - - - di - - - - - - - - - - - - - - - - - - metadata - - - - - - - - - - - - - - - - - - - diff --git a/src/site/svgo.config.mjs b/src/site/svgo.config.mjs deleted file mode 100644 index 84b3da2edd8..00000000000 --- a/src/site/svgo.config.mjs +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export default { - js2svg: { - indent: 1, - pretty: true, - }, - plugins: [ - 'preset-default', - { - name: "removeAttrs", - params: { - attrs: [ - "g:class", - "path:class", - ] - } - }, - ], - }; diff --git a/src/site/xdoc/maven-deps.odg b/src/site/xdoc/maven-deps.odg deleted file mode 100644 index bd599d221b6..00000000000 Binary files a/src/site/xdoc/maven-deps.odg and /dev/null differ