package jnpf.flowable.util; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; /** * 流程状态回写必须先于业务事件的源码级回归检查。 */ public class FlowStatusEventOrderChecks { private static final Path REPOSITORY_ROOT = Paths.get("").toAbsolutePath().normalize(); public static void main(String[] args) throws IOException { String operatorUtil = read( "jnpf-flowable/jnpf-flowable-biz/src/main/java/jnpf/flowable/util/OperatorUtil.java"); String taskController = read( "jnpf-flowable/jnpf-flowable-controller/src/main/java/jnpf/flowable/controller/TaskController.java"); String operatorController = read( "jnpf-flowable/jnpf-flowable-controller/src/main/java/jnpf/flowable/controller/OperatorController.java"); String autoAuditJob = read( "jnpf-flowable/jnpf-flowable-biz/src/main/java/jnpf/flowable/job/AutoAuditJob.java"); String queuedEvent = extractMethod(operatorUtil, "public void handleTaskStatusThenEvent()"); requireOrder(queuedEvent, "handleTaskStatus();", "handleEvent();", "queued events must run after form flow-state synchronization"); String directEvent = extractMethod(operatorUtil, "public void handleTaskStatusThenEvent(FlowModel flowModel, Integer eventStatus)"); requireOrder(directEvent, "handleTaskStatus();", "flowUtil.event(flowModel, eventStatus);", "direct events must run after form flow-state synchronization"); requireCount(operatorUtil, "handleEvent();", 1, "OperatorUtil callers must use the status-then-event entry point"); forbid(taskController, "operatorUtil.handleEvent();", "TaskController must not dispatch queued events directly"); forbid(taskController, "flowUtil.event(", "TaskController must not dispatch direct events without state synchronization"); forbid(operatorController, "operatorUtil.handleEvent();", "OperatorController must not dispatch queued events directly"); forbid(operatorController, "flowUtil.event(", "OperatorController must not dispatch direct events without state synchronization"); forbid(autoAuditJob, "operatorUtil.handleEvent();", "AutoAuditJob must not dispatch queued events directly"); } private static String read(String relativePath) throws IOException { return new String(Files.readAllBytes(REPOSITORY_ROOT.resolve(relativePath)), StandardCharsets.UTF_8); } private static String extractMethod(String source, String marker) { int start = source.indexOf(marker); if (start < 0) { return ""; } int bodyStart = source.indexOf('{', start); int depth = 0; for (int index = bodyStart; index < source.length(); index++) { char current = source.charAt(index); if (current == '{') { depth++; } else if (current == '}') { depth--; if (depth == 0) { return source.substring(start, index + 1); } } } return ""; } private static void requireOrder(String source, String first, String second, String message) { int firstIndex = source.indexOf(first); int secondIndex = source.indexOf(second); if (firstIndex < 0 || secondIndex < 0 || firstIndex >= secondIndex) { throw new AssertionError(message); } } private static void requireCount(String source, String value, int expected, String message) { int count = 0; int index = 0; while ((index = source.indexOf(value, index)) >= 0) { count++; index += value.length(); } if (count != expected) { throw new AssertionError(message + ": expected=" + expected + ", actual=" + count); } } private static void forbid(String source, String forbidden, String message) { if (source.contains(forbidden)) { throw new AssertionError(message); } } }