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.DO_TRY, NodeType.DO_CATCH, NodeType.DO_FINALLY,
|
||||||
NodeType.EIP_LOOP, NodeType.EIP_MULTICAST,
|
NodeType.EIP_LOOP, NodeType.EIP_MULTICAST,
|
||||||
NodeType.EIP_AGGREGATE, NodeType.ON_EXCEPTION, NodeType.ERROR_HANDLER,
|
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. */
|
/** 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) {
|
private LayoutResult computeLayout(RouteGraph graph, Direction rootDirection) {
|
||||||
LayoutContext ctx = new LayoutContext(ElkGraphFactory.eINSTANCE);
|
// 1. Build node index from root tree (preserves children) with flat-list fallback
|
||||||
|
|
||||||
// 1. Partition graph nodes into main flow vs handler sections
|
|
||||||
Map<String, RouteNode> nodeById = buildNodeIndex(graph);
|
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> mainNodes = new ArrayList<>();
|
||||||
List<RouteNode> handlerNodes = new ArrayList<>();
|
List<RouteNode> handlerNodes = new ArrayList<>();
|
||||||
partitionNodes(graph, nodeById, mainNodes, handlerNodes);
|
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);
|
ElkNode rootNode = createElkRoot("root", rootDirection, 1.0, ctx);
|
||||||
for (RouteNode rn : mainNodes) {
|
for (RouteNode rn : mainNodes) {
|
||||||
if (!ctx.elkNodeMap.containsKey(rn.getId())) {
|
if (!ctx.elkNodeMap.containsKey(rn.getId())) {
|
||||||
@@ -208,34 +210,53 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
|||||||
handlerRoots.add(hr);
|
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);
|
createElkEdges(graph, ctx);
|
||||||
|
|
||||||
// 4. Run layout engine
|
// 5. Run layout engine
|
||||||
RecursiveGraphLayoutEngine engine = new RecursiveGraphLayoutEngine();
|
RecursiveGraphLayoutEngine engine = new RecursiveGraphLayoutEngine();
|
||||||
engine.layout(rootNode, new BasicProgressMonitor());
|
engine.layout(rootNode, new BasicProgressMonitor());
|
||||||
for (ElkNode hr : handlerRoots) {
|
for (ElkNode hr : handlerRoots) {
|
||||||
engine.layout(hr, new BasicProgressMonitor());
|
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);
|
postProcessDoTrySections(ctx);
|
||||||
|
|
||||||
// 6. Extract positioned result
|
// 7. Extract positioned result
|
||||||
return extractLayout(graph, rootNode, handlerRoots, ctx);
|
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) {
|
private Map<String, RouteNode> buildNodeIndex(RouteGraph graph) {
|
||||||
Map<String, RouteNode> nodeById = new HashMap<>();
|
Map<String, RouteNode> nodeById = new HashMap<>();
|
||||||
|
if (graph.getRoot() != null) {
|
||||||
|
collectNodesRecursive(graph.getRoot(), nodeById);
|
||||||
|
}
|
||||||
if (graph.getNodes() != null) {
|
if (graph.getNodes() != null) {
|
||||||
for (RouteNode rn : graph.getNodes()) {
|
for (RouteNode rn : graph.getNodes()) {
|
||||||
nodeById.put(rn.getId(), rn);
|
nodeById.putIfAbsent(rn.getId(), rn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nodeById;
|
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
|
* Separate main flow nodes from handler sections by walking FLOW edges
|
||||||
* from the graph root. Handler sections (onException, onCompletion, etc.)
|
* from the graph root. Handler sections (onException, onCompletion, etc.)
|
||||||
@@ -266,8 +287,7 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Set<String> seen = new HashSet<>();
|
Set<String> seen = new HashSet<>();
|
||||||
if (graph.getNodes() != null) {
|
for (RouteNode rn : nodeById.values()) {
|
||||||
for (RouteNode rn : graph.getNodes()) {
|
|
||||||
if (seen.contains(rn.getId())) continue;
|
if (seen.contains(rn.getId())) continue;
|
||||||
seen.add(rn.getId());
|
seen.add(rn.getId());
|
||||||
if (rn.getType() != null && HANDLER_SECTION_TYPES.contains(rn.getType())
|
if (rn.getType() != null && HANDLER_SECTION_TYPES.contains(rn.getType())
|
||||||
@@ -278,7 +298,6 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/** Create a configured ELK root node for the layered algorithm. */
|
/** Create a configured ELK root node for the layered algorithm. */
|
||||||
private ElkNode createElkRoot(String identifier, Direction direction,
|
private ElkNode createElkRoot(String identifier, Direction direction,
|
||||||
@@ -378,16 +397,14 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
|||||||
/** Extract positioned nodes, edges, and bounding box from the ELK layout result. */
|
/** Extract positioned nodes, edges, and bounding box from the ELK layout result. */
|
||||||
private LayoutResult extractLayout(RouteGraph graph, ElkNode rootNode,
|
private LayoutResult extractLayout(RouteGraph graph, ElkNode rootNode,
|
||||||
List<ElkNode> handlerRoots, LayoutContext ctx) {
|
List<ElkNode> handlerRoots, LayoutContext ctx) {
|
||||||
// Extract positioned nodes
|
// Extract positioned nodes (uses tree-aware nodeById for compound children)
|
||||||
List<PositionedNode> positionedNodes = new ArrayList<>();
|
List<PositionedNode> positionedNodes = new ArrayList<>();
|
||||||
if (graph.getNodes() != null) {
|
for (RouteNode rn : ctx.nodeById.values()) {
|
||||||
for (RouteNode rn : graph.getNodes()) {
|
|
||||||
if (ctx.childNodeIds.contains(rn.getId())) continue;
|
if (ctx.childNodeIds.contains(rn.getId())) continue;
|
||||||
ElkNode elkNode = ctx.elkNodeMap.get(rn.getId());
|
ElkNode elkNode = ctx.elkNodeMap.get(rn.getId());
|
||||||
if (elkNode == null) continue;
|
if (elkNode == null) continue;
|
||||||
positionedNodes.add(extractPositionedNode(rn, elkNode, getElkRoot(elkNode), ctx));
|
positionedNodes.add(extractPositionedNode(rn, elkNode, getElkRoot(elkNode), ctx));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Extract edges from main root + handler roots
|
// Extract edges from main root + handler roots
|
||||||
List<PositionedEdge> positionedEdges = new ArrayList<>();
|
List<PositionedEdge> positionedEdges = new ArrayList<>();
|
||||||
@@ -977,6 +994,7 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
|||||||
/** Mutable state accumulated during ELK graph construction and extraction. */
|
/** Mutable state accumulated during ELK graph construction and extraction. */
|
||||||
private static class LayoutContext {
|
private static class LayoutContext {
|
||||||
final ElkGraphFactory factory;
|
final ElkGraphFactory factory;
|
||||||
|
final Map<String, RouteNode> nodeById;
|
||||||
final Map<String, ElkNode> elkNodeMap = new HashMap<>();
|
final Map<String, ElkNode> elkNodeMap = new HashMap<>();
|
||||||
final Map<String, Color> nodeColors = new HashMap<>();
|
final Map<String, Color> nodeColors = new HashMap<>();
|
||||||
final Set<String> compoundNodeIds = new HashSet<>();
|
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, List<String>> doTrySectionOrder = new LinkedHashMap<>();
|
||||||
final Map<String, CompoundInfo> compoundInfos = new HashMap<>();
|
final Map<String, CompoundInfo> compoundInfos = new HashMap<>();
|
||||||
|
|
||||||
LayoutContext(ElkGraphFactory factory) {
|
LayoutContext(ElkGraphFactory factory, Map<String, RouteNode> nodeById) {
|
||||||
this.factory = factory;
|
this.factory = factory;
|
||||||
|
this.nodeById = nodeById;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ const COMPOUND_TYPES = new Set([
|
|||||||
'ON_EXCEPTION', 'ERROR_HANDLER',
|
'ON_EXCEPTION', 'ERROR_HANDLER',
|
||||||
'ON_COMPLETION',
|
'ON_COMPLETION',
|
||||||
'EIP_CIRCUIT_BREAKER', '_CB_MAIN', '_CB_FALLBACK',
|
'EIP_CIRCUIT_BREAKER', '_CB_MAIN', '_CB_FALLBACK',
|
||||||
|
'EIP_FILTER', 'EIP_IDEMPOTENT_CONSUMER', 'EIP_RECIPIENT_LIST',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const ERROR_COMPOUND_TYPES = new Set([
|
const ERROR_COMPOUND_TYPES = new Set([
|
||||||
|
|||||||
Reference in New Issue
Block a user