package com.yunhai.flowcenter.engine; import java.util.ArrayDeque; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * 分支归属判定(2026-09-24 §4.6 升级为路径前缀编码):节点属于哪个并行分支—— * 顶层分支键=分支头节点 id(无点号,与存量单层完全一致),内层分支键=「外层键.分支头id」 * (如 tb.tc,外层是内层的前缀),主线与汇聚链之后属 null。 * 运行时用于推导分支上下文({@link FlowEngine}:建行/到达标记/scope 级放行), * 发布期用于拒绝跨分支连线({@link DefValidator})——两处必须同口径,故提取为静态工具。 * *

嵌套口径(内层 JOIN 先于外层放行的关键):上溯遇 JOIN 视为透明块——跳到该 JOIN * 匹配 SPLIT 的上游继续走(内层块整体属外层某分支,块的出口链沿用外层上下文)。 */ public final class BranchScope { /** 上溯递归深度上限:发布期已拦环,此处仅对未校验脏图防御性兜底 */ private static final int MAX_WALK_DEPTH = 64; private BranchScope() { } /** * 节点的完整分支键(路径前缀编码)。沿入边反向 BFS 到最近的上游 PARALLEL_SPLIT, * 返回「该 split 自身所在分支键.本链分支头id」;顶层 split 的分支头即键本身(存量兼容)。 * 遇 START 或(透明跳越后的)耗尽即停——主线节点返回 null。 */ public static String keyOf(FlowDefModel model, String nodeId) { return keyOf(model, nodeId, new HashMap(), new HashMap(), 0); } /** * JOIN 的匹配分发节点:沿入边上溯(内层 JOIN 块透明跳越)首个 PARALLEL_SPLIT 即匹配者。 * 嵌套结构下内层 JOIN 必然先命中内层 split(内层先放行),外层 JOIN 透过内层块命中外层 split。 * 找不到(如主线节点直连 JOIN 的历史脏图)返回 null,由调用方决定兜底口径。 */ public static FlowDefModel.Node matchedSplitOf(FlowDefModel model, String joinNodeId) { return matchedSplitOf(model, joinNodeId, new HashMap(), new HashMap(), 0); } /** 两节点是否同属一个分支上下文(含"都属主线"=都属于 null) */ public static boolean sameScope(FlowDefModel model, String a, String b) { String ka = keyOf(model, a); String kb = keyOf(model, b); return ka == null ? kb == null : ka.equals(kb); } private static String keyOf(FlowDefModel model, String nodeId, Map keyMemo, Map splitMemo, int depth) { if (model == null || nodeId == null || depth > MAX_WALK_DEPTH) { return null; } if (keyMemo.containsKey(nodeId)) { return keyMemo.get(nodeId); } keyMemo.put(nodeId, null); // 占位防环:递归回访时按主线(null)处理并终止 String result = null; Set visited = new HashSet<>(); visited.add(nodeId); Deque queue = new ArrayDeque<>(); queue.add(nodeId); while (!queue.isEmpty() && result == null) { String cur = queue.poll(); for (FlowDefModel.Edge e : model.inEdges(cur)) { FlowDefModel.Node from = model.node(e.from); if (from == null) { continue; } if (FlowDefModel.TYPE_PARALLEL_SPLIT.equals(from.type)) { // 本链分支头确定:再取该 split 自身所在的外层上下文,拼成路径前缀 String outer = keyOf(model, from.id, keyMemo, splitMemo, depth + 1); result = outer == null ? e.to : outer + "." + e.to; break; } if (FlowDefModel.TYPE_START.equals(from.type)) { continue; } if (FlowDefModel.TYPE_PARALLEL_JOIN.equals(from.type)) { // 内层汇聚块透明:从其匹配 split 的上游继续上溯(块出口链沿用外层上下文) FlowDefModel.Node innerSplit = matchedSplitOf(model, from.id, keyMemo, splitMemo, depth + 1); if (innerSplit != null && visited.add(innerSplit.id)) { queue.add(innerSplit.id); } continue; } if (visited.add(e.from)) { queue.add(e.from); } } } keyMemo.put(nodeId, result); return result; } private static FlowDefModel.Node matchedSplitOf(FlowDefModel model, String joinNodeId, Map keyMemo, Map splitMemo, int depth) { if (model == null || joinNodeId == null || depth > MAX_WALK_DEPTH) { return null; } if (splitMemo.containsKey(joinNodeId)) { return splitMemo.get(joinNodeId); } splitMemo.put(joinNodeId, null); // 占位防环 FlowDefModel.Node result = null; Set visited = new HashSet<>(); visited.add(joinNodeId); Deque queue = new ArrayDeque<>(); queue.add(joinNodeId); while (!queue.isEmpty() && result == null) { String cur = queue.poll(); for (FlowDefModel.Edge e : model.inEdges(cur)) { FlowDefModel.Node from = model.node(e.from); if (from == null) { continue; } if (FlowDefModel.TYPE_PARALLEL_SPLIT.equals(from.type)) { result = from; break; } if (FlowDefModel.TYPE_START.equals(from.type)) { continue; } if (FlowDefModel.TYPE_PARALLEL_JOIN.equals(from.type)) { FlowDefModel.Node innerSplit = matchedSplitOf(model, from.id, keyMemo, splitMemo, depth + 1); if (innerSplit != null && visited.add(innerSplit.id)) { queue.add(innerSplit.id); } continue; } if (visited.add(e.from)) { queue.add(e.from); } } } splitMemo.put(joinNodeId, result); return result; } }