diff --git a/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/CorridorReservationManager.java b/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/CorridorReservationManager.java
index e7a8be3..b55ca1f 100644
--- a/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/CorridorReservationManager.java
+++ b/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/CorridorReservationManager.java
@@ -56,6 +56,98 @@ public final class CorridorReservationManager {
return ReservationDecision.granted();
}
+ /**
+ * 原子替换一个活动任务尚未通过的走廊方向预约。
+ *
+ *
方法先把当前任务自己的旧预约视为可替换资源,对新路线做完整预检;任一新边与其他任务方向冲突时
+ * 直接拒绝,旧预约保持原样。只有全部新边通过后才在同一次调用中提交移除和新增,因此重规划不会形成
+ * “旧路线已释放、新路线尚未取得”的安全空窗。
+ *
+ * @param taskId 正在重规划的任务
+ * @param newRoute 从车辆当前位置开始的新剩余路线
+ * @param effectivePriority 当前饥饿保护后的有效优先级
+ * @param arrivalMillis 任务到达队列的仿真时间
+ * @return 替换是否成功以及失败时优先持有冲突方向的任务
+ */
+ public ReservationDecision replaceRoute(String taskId, List newRoute,
+ int effectivePriority, long arrivalMillis) {
+ if (taskId == null || taskId.trim().isEmpty()
+ || newRoute == null || newRoute.isEmpty()) {
+ throw new IllegalArgumentException("task id and new route are required");
+ }
+
+ List requests = new ArrayList();
+ for (int i = 1; i < newRoute.size(); i++) {
+ EdgeRequest request = request(newRoute.get(i - 1), newRoute.get(i));
+ requests.add(request);
+ EdgeReservation existing = edges.get(request.edgeId);
+ if (existing == null || existing.direction == request.direction) continue;
+
+ // 当前任务旧预约可以被翻转;其他任务仍持有旧方向时,新路线不能提交。
+ Set otherHolders = new LinkedHashSet(existing.holders);
+ otherHolders.remove(taskId);
+ if (!otherHolders.isEmpty()) {
+ return ReservationDecision.denied(bestOwner(otherHolders),
+ "CORRIDOR_DIRECTION_OWNED");
+ }
+ }
+
+ // 预检全部通过后才开始提交。先移除当前任务,绝不触碰同向共享该边的其他任务。
+ Set oldEdges = taskEdges.get(taskId);
+ if (oldEdges != null) {
+ for (String edgeId : new ArrayList(oldEdges)) {
+ EdgeReservation reservation = edges.get(edgeId);
+ if (reservation == null) continue;
+ reservation.holders.remove(taskId);
+ if (reservation.holders.isEmpty()) edges.remove(edgeId);
+ }
+ }
+
+ Rank rank = new Rank(taskId, effectivePriority, arrivalMillis);
+ ranks.put(taskId, rank);
+ Set replacementEdges = new LinkedHashSet();
+ for (EdgeRequest request : requests) {
+ EdgeReservation reservation = edges.get(request.edgeId);
+ if (reservation == null) {
+ reservation = new EdgeReservation(request.direction);
+ edges.put(request.edgeId, reservation);
+ }
+ reservation.holders.add(taskId);
+ replacementEdges.add(request.edgeId);
+ }
+ if (replacementEdges.isEmpty()) {
+ taskEdges.remove(taskId);
+ } else {
+ taskEdges.put(taskId, replacementEdges);
+ }
+ return ReservationDecision.granted();
+ }
+
+ /**
+ * 冻结当前全部走廊方向和持有任务,供路线质量评价和审计报告使用。
+ *
+ * @return 按物理边规范键排序的不可变预约列表
+ */
+ public List snapshot() {
+ List edgeIds = new ArrayList(edges.keySet());
+ Collections.sort(edgeIds);
+ List result =
+ new ArrayList();
+ for (String edgeId : edgeIds) {
+ EdgeReservation reservation = edges.get(edgeId);
+ int separator = edgeId.indexOf('|');
+ String left = edgeId.substring(0, separator);
+ String right = edgeId.substring(separator + 1);
+ List holders = new ArrayList(reservation.holders);
+ Collections.sort(holders);
+ result.add(new CorridorReservationSnapshot(
+ reservation.direction > 0 ? left : right,
+ reservation.direction > 0 ? right : left,
+ holders));
+ }
+ return Collections.unmodifiableList(result);
+ }
+
/**
* 在车辆通过一条边后立即释放该段走廊,允许后续同向或反向任务重新竞争资源。
* @param taskId 执行该处理所需的 task id 输入。
@@ -207,6 +299,29 @@ public final class CorridorReservationManager {
public String getReason() { return reason; }
}
+ /** 一条物理走廊当前的有向预约及全部合法同向持有者。 */
+ public static final class CorridorReservationSnapshot {
+ private final String fromSite;
+ private final String toSite;
+ private final List taskIds;
+
+ private CorridorReservationSnapshot(String fromSite, String toSite,
+ List taskIds) {
+ this.fromSite = fromSite;
+ this.toSite = toSite;
+ this.taskIds = Collections.unmodifiableList(new ArrayList(taskIds));
+ }
+
+ /** 返回预约方向的起点。 */
+ public String getFromSite() { return fromSite; }
+
+ /** 返回预约方向的终点。 */
+ public String getToSite() { return toSite; }
+
+ /** 返回共享该方向预约的任务编号,按编号稳定排序。 */
+ public List getTaskIds() { return taskIds; }
+ }
+
private static final class EdgeReservation {
private final int direction;
private final Set holders = new LinkedHashSet();
diff --git a/javaTest/src/test/java/com/ft546/wcs/shadow/history/simulation/CorridorReservationManagerTest.java b/javaTest/src/test/java/com/ft546/wcs/shadow/history/simulation/CorridorReservationManagerTest.java
index 0f11da9..6f3075f 100644
--- a/javaTest/src/test/java/com/ft546/wcs/shadow/history/simulation/CorridorReservationManagerTest.java
+++ b/javaTest/src/test/java/com/ft546/wcs/shadow/history/simulation/CorridorReservationManagerTest.java
@@ -3,6 +3,8 @@ package com.ft546.wcs.shadow.history.simulation;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -50,4 +52,53 @@ class CorridorReservationManagerTest {
.isGranted());
assertEquals("FIRST", manager.ownerFor("A", "B"));
}
+
+ @Test
+ void rejectedReplacementKeepsEveryOldReservation() {
+ CorridorReservationManager manager = new CorridorReservationManager();
+ assertTrue(manager.reserve("TASK-1", Arrays.asList("A", "B", "C"), 1, 10L)
+ .isGranted());
+ assertTrue(manager.reserve("TASK-2", Arrays.asList("C", "D"), 2, 20L)
+ .isGranted());
+
+ CorridorReservationManager.ReservationDecision denied = manager.replaceRoute(
+ "TASK-1", Arrays.asList("A", "D", "C"), 1, 10L);
+
+ assertFalse(denied.isGranted());
+ assertEquals(new LinkedHashSet(Arrays.asList("A->B", "B->C")),
+ edgesFor(manager, "TASK-1"));
+ assertEquals("TASK-1", manager.ownerFor("A", "B"));
+ assertEquals("TASK-1", manager.ownerFor("B", "C"));
+ }
+
+ @Test
+ void successfulReplacementCommitsNewRouteAndPreservesSharedOldHolder() {
+ CorridorReservationManager manager = new CorridorReservationManager();
+ assertTrue(manager.reserve("TASK-1", Arrays.asList("A", "B", "C"), 1, 10L)
+ .isGranted());
+ assertTrue(manager.reserve("TASK-2", Arrays.asList("A", "B", "C"), 2, 20L)
+ .isGranted());
+
+ CorridorReservationManager.ReservationDecision replaced = manager.replaceRoute(
+ "TASK-1", Arrays.asList("A", "D", "C"), 1, 10L);
+
+ assertTrue(replaced.isGranted());
+ assertEquals(new LinkedHashSet(Arrays.asList("A->D", "D->C")),
+ edgesFor(manager, "TASK-1"));
+ assertEquals(new LinkedHashSet(Arrays.asList("A->B", "B->C")),
+ edgesFor(manager, "TASK-2"));
+ assertEquals("TASK-2", manager.ownerFor("A", "B"));
+ assertEquals("TASK-1", manager.ownerFor("A", "D"));
+ }
+
+ private Set edgesFor(CorridorReservationManager manager, String taskId) {
+ Set edges = new LinkedHashSet();
+ for (CorridorReservationManager.CorridorReservationSnapshot reservation
+ : manager.snapshot()) {
+ if (reservation.getTaskIds().contains(taskId)) {
+ edges.add(reservation.getFromSite() + "->" + reservation.getToSite());
+ }
+ }
+ return edges;
+ }
}