fix: use root tree for compound node detection instead of flat nodes list
The agent now sends shallow copies (without children) in the flat nodes list. Build nodeById map by walking graph.getRoot() tree which preserves children, falling back to flat list via putIfAbsent for compatibility. Also adds EIP_FILTER, EIP_IDEMPOTENT_CONSUMER, EIP_RECIPIENT_LIST as new compound container types per updated DIAGRAMS.md. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -109,7 +109,8 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
NodeType.DO_TRY, NodeType.DO_CATCH, NodeType.DO_FINALLY,
|
||||
NodeType.EIP_LOOP, NodeType.EIP_MULTICAST,
|
||||
NodeType.EIP_AGGREGATE, NodeType.ON_EXCEPTION, NodeType.ERROR_HANDLER,
|
||||
NodeType.ON_COMPLETION, NodeType.EIP_CIRCUIT_BREAKER
|
||||
NodeType.ON_COMPLETION, NodeType.EIP_CIRCUIT_BREAKER,
|
||||
NodeType.EIP_FILTER, NodeType.EIP_IDEMPOTENT_CONSUMER, NodeType.EIP_RECIPIENT_LIST
|
||||
);
|
||||
|
||||
/** Top-level handler types laid out in their own separate ELK graph. */
|
||||
@@ -185,15 +186,16 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
private LayoutResult computeLayout(RouteGraph graph, Direction rootDirection) {
|
||||
LayoutContext ctx = new LayoutContext(ElkGraphFactory.eINSTANCE);
|
||||
|
||||
// 1. Partition graph nodes into main flow vs handler sections
|
||||
// 1. Build node index from root tree (preserves children) with flat-list fallback
|
||||
Map<String, RouteNode> nodeById = buildNodeIndex(graph);
|
||||
LayoutContext ctx = new LayoutContext(ElkGraphFactory.eINSTANCE, nodeById);
|
||||
|
||||
// 2. Partition graph nodes into main flow vs handler sections
|
||||
List<RouteNode> mainNodes = new ArrayList<>();
|
||||
List<RouteNode> handlerNodes = new ArrayList<>();
|
||||
partitionNodes(graph, nodeById, mainNodes, handlerNodes);
|
||||
|
||||
// 2. Build ELK graphs — main flow + separate handler roots
|
||||
// 3. Build ELK graphs — main flow + separate handler roots
|
||||
ElkNode rootNode = createElkRoot("root", rootDirection, 1.0, ctx);
|
||||
for (RouteNode rn : mainNodes) {
|
||||
if (!ctx.elkNodeMap.containsKey(rn.getId())) {
|
||||
@@ -208,34 +210,53 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
handlerRoots.add(hr);
|
||||
}
|
||||
|
||||
// 3. Create ELK edges (filtering DO_TRY internals and cross-root edges)
|
||||
// 4. Create ELK edges (filtering DO_TRY internals and cross-root edges)
|
||||
createElkEdges(graph, ctx);
|
||||
|
||||
// 4. Run layout engine
|
||||
// 5. Run layout engine
|
||||
RecursiveGraphLayoutEngine engine = new RecursiveGraphLayoutEngine();
|
||||
engine.layout(rootNode, new BasicProgressMonitor());
|
||||
for (ElkNode hr : handlerRoots) {
|
||||
engine.layout(hr, new BasicProgressMonitor());
|
||||
}
|
||||
|
||||
// 5. Post-process: fix DO_TRY section ordering and widths
|
||||
// 6. Post-process: fix DO_TRY section ordering and widths
|
||||
postProcessDoTrySections(ctx);
|
||||
|
||||
// 6. Extract positioned result
|
||||
// 7. Extract positioned result
|
||||
return extractLayout(graph, rootNode, handlerRoots, ctx);
|
||||
}
|
||||
|
||||
/** Build a lookup from the flat nodes list (deduplicates nested + top-level entries). */
|
||||
/**
|
||||
* Build a node lookup by walking the root tree (which preserves children for
|
||||
* compound nodes). Falls back to the flat nodes list for any nodes not in the
|
||||
* tree (backward compatibility when root is not set).
|
||||
*/
|
||||
private Map<String, RouteNode> buildNodeIndex(RouteGraph graph) {
|
||||
Map<String, RouteNode> nodeById = new HashMap<>();
|
||||
if (graph.getRoot() != null) {
|
||||
collectNodesRecursive(graph.getRoot(), nodeById);
|
||||
}
|
||||
if (graph.getNodes() != null) {
|
||||
for (RouteNode rn : graph.getNodes()) {
|
||||
nodeById.put(rn.getId(), rn);
|
||||
nodeById.putIfAbsent(rn.getId(), rn);
|
||||
}
|
||||
}
|
||||
return nodeById;
|
||||
}
|
||||
|
||||
/** Recursively collect RouteNodes from the tree into a map (preserves children). */
|
||||
private void collectNodesRecursive(RouteNode node, Map<String, RouteNode> map) {
|
||||
if (node.getId() != null) {
|
||||
map.put(node.getId(), node);
|
||||
}
|
||||
if (node.getChildren() != null) {
|
||||
for (RouteNode child : node.getChildren()) {
|
||||
collectNodesRecursive(child, map);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Separate main flow nodes from handler sections by walking FLOW edges
|
||||
* from the graph root. Handler sections (onException, onCompletion, etc.)
|
||||
@@ -266,16 +287,14 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
}
|
||||
|
||||
Set<String> seen = new HashSet<>();
|
||||
if (graph.getNodes() != null) {
|
||||
for (RouteNode rn : graph.getNodes()) {
|
||||
if (seen.contains(rn.getId())) continue;
|
||||
seen.add(rn.getId());
|
||||
if (rn.getType() != null && HANDLER_SECTION_TYPES.contains(rn.getType())
|
||||
&& rn.getChildren() != null && !rn.getChildren().isEmpty()) {
|
||||
handlerNodes.add(rn);
|
||||
} else if (mainNodeIds.isEmpty() || mainNodeIds.contains(rn.getId())) {
|
||||
mainNodes.add(rn);
|
||||
}
|
||||
for (RouteNode rn : nodeById.values()) {
|
||||
if (seen.contains(rn.getId())) continue;
|
||||
seen.add(rn.getId());
|
||||
if (rn.getType() != null && HANDLER_SECTION_TYPES.contains(rn.getType())
|
||||
&& rn.getChildren() != null && !rn.getChildren().isEmpty()) {
|
||||
handlerNodes.add(rn);
|
||||
} else if (mainNodeIds.isEmpty() || mainNodeIds.contains(rn.getId())) {
|
||||
mainNodes.add(rn);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -378,15 +397,13 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
/** Extract positioned nodes, edges, and bounding box from the ELK layout result. */
|
||||
private LayoutResult extractLayout(RouteGraph graph, ElkNode rootNode,
|
||||
List<ElkNode> handlerRoots, LayoutContext ctx) {
|
||||
// Extract positioned nodes
|
||||
// Extract positioned nodes (uses tree-aware nodeById for compound children)
|
||||
List<PositionedNode> positionedNodes = new ArrayList<>();
|
||||
if (graph.getNodes() != null) {
|
||||
for (RouteNode rn : graph.getNodes()) {
|
||||
if (ctx.childNodeIds.contains(rn.getId())) continue;
|
||||
ElkNode elkNode = ctx.elkNodeMap.get(rn.getId());
|
||||
if (elkNode == null) continue;
|
||||
positionedNodes.add(extractPositionedNode(rn, elkNode, getElkRoot(elkNode), ctx));
|
||||
}
|
||||
for (RouteNode rn : ctx.nodeById.values()) {
|
||||
if (ctx.childNodeIds.contains(rn.getId())) continue;
|
||||
ElkNode elkNode = ctx.elkNodeMap.get(rn.getId());
|
||||
if (elkNode == null) continue;
|
||||
positionedNodes.add(extractPositionedNode(rn, elkNode, getElkRoot(elkNode), ctx));
|
||||
}
|
||||
|
||||
// Extract edges from main root + handler roots
|
||||
@@ -977,6 +994,7 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
/** Mutable state accumulated during ELK graph construction and extraction. */
|
||||
private static class LayoutContext {
|
||||
final ElkGraphFactory factory;
|
||||
final Map<String, RouteNode> nodeById;
|
||||
final Map<String, ElkNode> elkNodeMap = new HashMap<>();
|
||||
final Map<String, Color> nodeColors = new HashMap<>();
|
||||
final Set<String> compoundNodeIds = new HashSet<>();
|
||||
@@ -985,8 +1003,9 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
final Map<String, List<String>> doTrySectionOrder = new LinkedHashMap<>();
|
||||
final Map<String, CompoundInfo> compoundInfos = new HashMap<>();
|
||||
|
||||
LayoutContext(ElkGraphFactory factory) {
|
||||
LayoutContext(ElkGraphFactory factory, Map<String, RouteNode> nodeById) {
|
||||
this.factory = factory;
|
||||
this.nodeById = nodeById;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ const COMPOUND_TYPES = new Set([
|
||||
'ON_EXCEPTION', 'ERROR_HANDLER',
|
||||
'ON_COMPLETION',
|
||||
'EIP_CIRCUIT_BREAKER', '_CB_MAIN', '_CB_FALLBACK',
|
||||
'EIP_FILTER', 'EIP_IDEMPOTENT_CONSUMER', 'EIP_RECIPIENT_LIST',
|
||||
]);
|
||||
|
||||
const ERROR_COMPOUND_TYPES = new Set([
|
||||
|
||||
Reference in New Issue
Block a user