DotGraphSupport.java
package network.ike.plugin.ws;
import network.ike.workspace.Subproject;
import network.ike.workspace.WorkspaceGraph;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Graphviz DOT rendering for the workspace dependency graph.
*
* <p>Pure, stateless helpers — no mojo state, no I/O. Shared by
* {@link GraphWorkspaceMojo} ({@code ws:graph}) and
* {@link OverviewWorkspaceMojo} ({@code ws:overview}), both of which
* emit the graph as DOT on the console ({@code -Dformat=dot}) and as a
* fenced {@code ```dot} block in their markdown reports.
*
* <p>Extracted from {@code GraphWorkspaceMojo} so diagram rendering
* lives in a support class rather than on a mojo, and so
* {@code ws:overview} no longer reaches into a sibling mojo statically
* (IKE-Network/ike-issues#408, closing the #406 leftover).
*
* <p>IKE-DIAGRAMS.md mandates GraphViz for dependency graphs and
* IKE-DOC.md discourages Mermaid; this class is the single place the
* workspace graph becomes DOT.
*/
final class DotGraphSupport {
private DotGraphSupport() {
}
/**
* Build Graphviz DOT source for a whole workspace graph.
*
* <p>Extracts the subproject names and dependency edges from the
* {@link WorkspaceGraph} and delegates to {@link #buildDotGraph}.
*
* @param graph the workspace graph
* @return complete DOT source (a {@code digraph} block)
*/
static String dotFromGraph(WorkspaceGraph graph) {
List<String> subprojectNames = graph.manifest().subprojects()
.values().stream()
.map(Subproject::name)
.toList();
Map<String, List<String[]>> edges = new LinkedHashMap<>();
for (Subproject sub : graph.manifest().subprojects().values()) {
List<String[]> compEdges = sub.dependsOn().stream()
.map(dep -> new String[]{
dep.subproject(), dep.relationship()})
.toList();
if (!compEdges.isEmpty()) {
edges.put(sub.name(), compEdges);
}
}
return buildDotGraph("workspace", subprojectNames, edges);
}
/**
* Build a fenced Graphviz DOT block for a markdown report.
*
* <p>The workspace reports embed the graph as a {@code ```dot}
* block (IKE-Network/ike-issues#406).
*
* @param graph the workspace graph
* @return a fenced {@code ```dot} block, newline-terminated
*/
static String buildDotReportBlock(WorkspaceGraph graph) {
return "```dot\n" + dotFromGraph(graph) + "```\n";
}
/**
* Build a Graphviz DOT graph from subproject names and edges.
*
* <p>This is a pure function with no workspace-model dependencies,
* suitable for direct unit testing.
*
* @param title graph name used in {@code digraph <title>}
* @param subprojectNames names of subprojects to include as nodes
* @param edges map of source subproject to list of
* {@code [target, relationship]} pairs
* @return complete DOT source
*/
static String buildDotGraph(String title,
List<String> subprojectNames,
Map<String, List<String[]>> edges) {
StringBuilder dot = new StringBuilder(1024);
dot.append("digraph ").append(title).append(" {\n");
dot.append(" rankdir=BT;\n");
dot.append(" node [shape=box, style=rounded, "
+ "fontname=\"Helvetica\"];\n");
dot.append("\n");
// Node declarations
for (String subName : subprojectNames) {
dot.append(" \"").append(subName).append("\";\n");
}
dot.append("\n");
// Edges
for (var entry : edges.entrySet()) {
String source = entry.getKey();
for (String[] edge : entry.getValue()) {
String target = edge[0];
String relationship = edge[1];
String style = "content".equals(relationship)
? " [style=dashed]" : "";
dot.append(" \"").append(source).append("\" -> \"")
.append(target).append("\"").append(style)
.append(";\n");
}
}
dot.append("}\n");
return dot.toString();
}
}