| | |
| | | import com.zy.core.network.DeviceConnectPool; |
| | | import com.zy.core.network.ZyStationConnectDriver; |
| | | import com.zy.core.network.entity.ZyStationStatusEntity; |
| | | import com.zy.core.service.StationTaskLoopService; |
| | | import com.zy.core.thread.impl.v5.StationV5SegmentExecutor; |
| | | import com.zy.core.trace.StationTaskTraceRegistry; |
| | | import com.zy.core.utils.DeviceLogRedisKeyBuilder; |
| | | import lombok.Data; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | |
| | | import java.text.MessageFormat; |
| | | import java.util.ArrayList; |
| | | import java.util.ArrayDeque; |
| | | import java.util.Collections; |
| | | import java.util.Date; |
| | | import java.util.Deque; |
| | | import java.util.HashMap; |
| | | import java.util.HashSet; |
| | | import java.util.LinkedHashSet; |
| | |
| | | |
| | | private static final int RUN_BLOCK_REROUTE_STATE_EXPIRE_SECONDS = 60 * 60 * 24; |
| | | private static final int SHORT_PATH_REPEAT_AVOID_THRESHOLD = 2; |
| | | private static final int SMALL_LOOP_REPEAT_AVOID_THRESHOLD = 2; |
| | | private static final int LOOP_REPEAT_TRIGGER_COUNT = 3; |
| | | private static final int LOCAL_LOOP_NEIGHBOR_HOP = 3; |
| | | |
| | | private List<StationProtocol> statusList = new ArrayList<>(); |
| | | private DeviceConfig deviceConfig; |
| | |
| | | } |
| | | |
| | | RunBlockRerouteState rerouteState = loadRunBlockRerouteState(taskNo, stationId); |
| | | TaskLoopRerouteState taskLoopRerouteState = loadTaskLoopRerouteState(taskNo); |
| | | LoopIdentity currentLoopIdentity = resolveStationLoopIdentity(stationId); |
| | | StationTaskLoopService taskLoopService = loadStationTaskLoopService(); |
| | | StationTaskLoopService.LoopEvaluation loopEvaluation = taskLoopService == null |
| | | ? new StationTaskLoopService.LoopEvaluation(taskNo, stationId, StationTaskLoopService.LoopIdentitySnapshot.empty(), 0, 0, false) |
| | | : taskLoopService.evaluateLoop(taskNo, stationId, true); |
| | | log.info("输送线堵塞重规划环线识别,taskNo={}, stationId={}, scopeType={}, localStationCount={}, sourceLoopStationCount={}", |
| | | taskNo, |
| | | stationId, |
| | | loopEvaluation.getLoopIdentity().getScopeType(), |
| | | loopEvaluation.getLoopIdentity().getLocalStationCount(), |
| | | loopEvaluation.getLoopIdentity().getSourceLoopStationCount()); |
| | | rerouteState.setTaskNo(taskNo); |
| | | rerouteState.setBlockStationId(stationId); |
| | | rerouteState.setLastTargetStationId(targetStationId); |
| | | rerouteState.setPlanCount((rerouteState.getPlanCount() == null ? 0 : rerouteState.getPlanCount()) + 1); |
| | | rerouteState.setLastPlanTime(System.currentTimeMillis()); |
| | | taskLoopRerouteState.setTaskNo(taskNo); |
| | | |
| | | List<List<NavigateNode>> candidatePathList = calcCandidatePathNavigateNodes(taskNo, stationId, targetStationId); |
| | | if (candidatePathList.isEmpty()) { |
| | |
| | | |
| | | StationCommand rerouteCommand = selectAvailableRerouteCommand( |
| | | rerouteState, |
| | | taskLoopRerouteState, |
| | | currentLoopIdentity, |
| | | loopEvaluation, |
| | | candidatePathList, |
| | | taskNo, |
| | | stationId, |
| | |
| | | rerouteState.resetIssuedRoutes(); |
| | | rerouteCommand = selectAvailableRerouteCommand( |
| | | rerouteState, |
| | | taskLoopRerouteState, |
| | | currentLoopIdentity, |
| | | loopEvaluation, |
| | | candidatePathList, |
| | | taskNo, |
| | | stationId, |
| | |
| | | |
| | | if (rerouteCommand != null) { |
| | | saveRunBlockRerouteState(rerouteState); |
| | | touchTaskLoopRerouteState(taskLoopRerouteState, currentLoopIdentity); |
| | | saveTaskLoopRerouteState(taskLoopRerouteState); |
| | | if (taskLoopService != null) { |
| | | taskLoopService.recordLoopIssue(loopEvaluation, "RUN_BLOCK_REROUTE"); |
| | | } |
| | | log.info("输送线堵塞重规划选中候选路线,taskNo={}, planCount={}, stationId={}, targetStationId={}, route={}", |
| | | taskNo, rerouteState.getPlanCount(), stationId, targetStationId, JSON.toJSONString(rerouteCommand.getNavigatePath())); |
| | | return rerouteCommand; |
| | |
| | | } |
| | | |
| | | private StationCommand selectAvailableRerouteCommand(RunBlockRerouteState rerouteState, |
| | | TaskLoopRerouteState taskLoopRerouteState, |
| | | LoopIdentity currentLoopIdentity, |
| | | StationTaskLoopService.LoopEvaluation loopEvaluation, |
| | | List<List<NavigateNode>> candidatePathList, |
| | | Integer taskNo, |
| | | Integer stationId, |
| | |
| | | } |
| | | |
| | | Set<String> issuedRouteSignatureSet = rerouteState.getIssuedRouteSignatureSet(); |
| | | int currentLoopIssuedCount = resolveCurrentLoopIssuedCount(taskLoopRerouteState, currentLoopIdentity); |
| | | List<RerouteCandidateCommand> candidateCommandList = new ArrayList<>(); |
| | | for (List<NavigateNode> candidatePath : candidatePathList) { |
| | | StationCommand rerouteCommand = buildMoveCommand(taskNo, stationId, targetStationId, palletSize, candidatePath); |
| | |
| | | candidateCommand.setRouteSignature(routeSignature); |
| | | candidateCommand.setPathLength(rerouteCommand.getNavigatePath().size()); |
| | | candidateCommand.setIssuedCount(rerouteState.getRouteIssueCountMap().getOrDefault(routeSignature, 0)); |
| | | candidateCommand.setLoopFingerprint(currentLoopIdentity.getLoopFingerprint()); |
| | | candidateCommand.setLoopIssuedCount(currentLoopIssuedCount); |
| | | candidateCommand.setCurrentLoopHitCount(countCurrentLoopStationHit(rerouteCommand.getNavigatePath(), currentLoopIdentity.getStationIdSet())); |
| | | candidateCommand.setLoopFingerprint(loopEvaluation.getLoopIdentity().getLoopFingerprint()); |
| | | candidateCommand.setLoopIssuedCount(loopEvaluation.getExpectedLoopIssueCount()); |
| | | candidateCommand.setLoopTriggered(loopEvaluation.isLargeLoopTriggered()); |
| | | candidateCommand.setCurrentLoopHitCount(countCurrentLoopStationHit( |
| | | rerouteCommand.getNavigatePath(), |
| | | loopEvaluation.getLoopIdentity().getStationIdSet() |
| | | )); |
| | | candidateCommandList.add(candidateCommand); |
| | | } |
| | | if (candidateCommandList.isEmpty()) { |
| | |
| | | shortestPathOverused = true; |
| | | } |
| | | if (!Cools.isEmpty(candidateCommand.getLoopFingerprint()) |
| | | && candidateCommand.getLoopIssuedCount() != null |
| | | && candidateCommand.getLoopIssuedCount() >= SMALL_LOOP_REPEAT_AVOID_THRESHOLD) { |
| | | && Boolean.TRUE.equals(candidateCommand.getLoopTriggered())) { |
| | | currentLoopOverused = true; |
| | | } |
| | | } |
| | |
| | | return taskLoopRerouteState.getLoopIssueCountMap().getOrDefault(currentLoopIdentity.getLoopFingerprint(), 0); |
| | | } |
| | | |
| | | private int resolveExpectedLoopIssuedCount(TaskLoopRerouteState taskLoopRerouteState, |
| | | LoopIdentity currentLoopIdentity) { |
| | | if (currentLoopIdentity == null || Cools.isEmpty(currentLoopIdentity.getLoopFingerprint())) { |
| | | return 0; |
| | | } |
| | | return resolveCurrentLoopIssuedCount(taskLoopRerouteState, currentLoopIdentity) + 1; |
| | | } |
| | | |
| | | private boolean isLoopRepeatTriggered(Integer loopIssuedCount) { |
| | | return loopIssuedCount != null && loopIssuedCount >= LOOP_REPEAT_TRIGGER_COUNT; |
| | | } |
| | | |
| | | private void syncTaskTraceLoopAlert(Integer taskNo, |
| | | Integer blockedStationId, |
| | | LoopIdentity currentLoopIdentity, |
| | | int loopIssuedCount) { |
| | | StationTaskTraceRegistry traceRegistry; |
| | | try { |
| | | traceRegistry = SpringUtils.getBean(StationTaskTraceRegistry.class); |
| | | } catch (Exception ignore) { |
| | | return; |
| | | } |
| | | if (traceRegistry == null || taskNo == null || taskNo <= 0) { |
| | | return; |
| | | } |
| | | |
| | | boolean active = currentLoopIdentity != null |
| | | && !Cools.isEmpty(currentLoopIdentity.getLoopFingerprint()) |
| | | && isLoopRepeatTriggered(loopIssuedCount); |
| | | Map<String, Object> details = new HashMap<>(); |
| | | details.put("blockedStationId", blockedStationId); |
| | | details.put("loopScopeType", currentLoopIdentity == null ? "" : currentLoopIdentity.getScopeType()); |
| | | details.put("loopStationCount", currentLoopIdentity == null ? 0 : currentLoopIdentity.getLocalStationCount()); |
| | | details.put("sourceLoopStationCount", currentLoopIdentity == null ? 0 : currentLoopIdentity.getSourceLoopStationCount()); |
| | | details.put("loopRepeatCount", loopIssuedCount); |
| | | String loopAlertType = resolveLoopAlertType(currentLoopIdentity); |
| | | String loopAlertText = buildLoopAlertText(loopAlertType, currentLoopIdentity, loopIssuedCount); |
| | | traceRegistry.updateLoopHint(taskNo, active, loopAlertType, loopAlertText, loopIssuedCount, details); |
| | | } |
| | | |
| | | private String resolveLoopAlertType(LoopIdentity currentLoopIdentity) { |
| | | if (currentLoopIdentity == null || Cools.isEmpty(currentLoopIdentity.getLoopFingerprint())) { |
| | | return ""; |
| | | } |
| | | return "wholeLoop".equals(currentLoopIdentity.getScopeType()) ? "LARGE_LOOP" : "SMALL_LOOP"; |
| | | } |
| | | |
| | | private String buildLoopAlertText(String loopAlertType, |
| | | LoopIdentity currentLoopIdentity, |
| | | int loopIssuedCount) { |
| | | if (Cools.isEmpty(loopAlertType) || currentLoopIdentity == null || !isLoopRepeatTriggered(loopIssuedCount)) { |
| | | return ""; |
| | | } |
| | | String typeLabel = "LARGE_LOOP".equals(loopAlertType) ? "大环线" : "小环线"; |
| | | return typeLabel + "绕圈预警,累计重规划" + loopIssuedCount + "次,当前识别范围" |
| | | + currentLoopIdentity.getLocalStationCount() + "站"; |
| | | } |
| | | |
| | | private int countCurrentLoopStationHit(List<Integer> path, Set<Integer> currentLoopStationIdSet) { |
| | | if (path == null || path.isEmpty() || currentLoopStationIdSet == null || currentLoopStationIdSet.isEmpty()) { |
| | | return 0; |
| | |
| | | return builder.toString(); |
| | | } |
| | | |
| | | private StationTaskLoopService loadStationTaskLoopService() { |
| | | try { |
| | | return SpringUtils.getBean(StationTaskLoopService.class); |
| | | } catch (Exception ignore) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private String buildRunBlockRerouteStateKey(Integer taskNo, Integer blockStationId) { |
| | | return RedisKeyType.STATION_RUN_BLOCK_REROUTE_STATE_.key + taskNo + "_" + blockStationId; |
| | | } |
| | |
| | | if (loopStationIdList.isEmpty() || !loopStationIdList.contains(stationId)) { |
| | | continue; |
| | | } |
| | | return new LoopIdentity(buildLoopFingerprint(loopStationIdList), new HashSet<>(loopStationIdList)); |
| | | Set<Integer> loopStationIdSet = new HashSet<>(loopStationIdList); |
| | | Map<Integer, Set<Integer>> stationGraph = loadUndirectedStationGraph(); |
| | | List<Integer> localCycleStationIdList = resolveLocalCycleStationIdList(stationId, loopStationIdSet, stationGraph); |
| | | if (localCycleStationIdList.size() >= 3) { |
| | | return buildLoopIdentity(localCycleStationIdList, loopStationIdList.size(), "localCycle"); |
| | | } |
| | | |
| | | List<Integer> localNeighborhoodStationIdList = resolveLocalNeighborhoodStationIdList(stationId, loopStationIdSet, stationGraph); |
| | | if (localNeighborhoodStationIdList.size() >= 3 && localNeighborhoodStationIdList.size() < loopStationIdList.size()) { |
| | | return buildLoopIdentity(localNeighborhoodStationIdList, loopStationIdList.size(), "localNeighborhood"); |
| | | } |
| | | return buildLoopIdentity(loopStationIdList, loopStationIdList.size(), "wholeLoop"); |
| | | } |
| | | } catch (Exception ignore) { |
| | | } |
| | | return LoopIdentity.empty(); |
| | | } |
| | | |
| | | private LoopIdentity buildLoopIdentity(List<Integer> stationIdList, |
| | | int sourceLoopStationCount, |
| | | String scopeType) { |
| | | List<Integer> normalizedStationIdList = normalizeLoopStationIdList(stationIdList); |
| | | if (normalizedStationIdList.isEmpty()) { |
| | | return LoopIdentity.empty(); |
| | | } |
| | | return new LoopIdentity( |
| | | buildLoopFingerprint(normalizedStationIdList), |
| | | new HashSet<>(normalizedStationIdList), |
| | | sourceLoopStationCount, |
| | | normalizedStationIdList.size(), |
| | | scopeType |
| | | ); |
| | | } |
| | | |
| | | private List<Integer> resolveLocalCycleStationIdList(Integer stationId, |
| | | Set<Integer> loopStationIdSet, |
| | | Map<Integer, Set<Integer>> stationGraph) { |
| | | if (stationId == null |
| | | || stationId <= 0 |
| | | || loopStationIdSet == null |
| | | || loopStationIdSet.isEmpty() |
| | | || stationGraph == null |
| | | || stationGraph.isEmpty()) { |
| | | return new ArrayList<>(); |
| | | } |
| | | Set<Integer> localNeighborhoodStationIdSet = collectLoopNeighborhoodStationIdSet( |
| | | stationId, |
| | | loopStationIdSet, |
| | | stationGraph, |
| | | LOCAL_LOOP_NEIGHBOR_HOP |
| | | ); |
| | | if (localNeighborhoodStationIdSet.size() < 3) { |
| | | return new ArrayList<>(); |
| | | } |
| | | |
| | | Set<Integer> directNeighborStationIdSet = filterLoopNeighborStationIdSet( |
| | | stationGraph.getOrDefault(stationId, Collections.emptySet()), |
| | | localNeighborhoodStationIdSet, |
| | | stationId |
| | | ); |
| | | if (directNeighborStationIdSet.size() < 2) { |
| | | return new ArrayList<>(); |
| | | } |
| | | |
| | | List<Integer> bestCycleStationIdList = new ArrayList<>(); |
| | | List<Integer> neighborStationIdList = new ArrayList<>(directNeighborStationIdSet); |
| | | for (int i = 0; i < neighborStationIdList.size(); i++) { |
| | | Integer leftNeighborStationId = neighborStationIdList.get(i); |
| | | if (leftNeighborStationId == null) { |
| | | continue; |
| | | } |
| | | for (int j = i + 1; j < neighborStationIdList.size(); j++) { |
| | | Integer rightNeighborStationId = neighborStationIdList.get(j); |
| | | if (rightNeighborStationId == null) { |
| | | continue; |
| | | } |
| | | List<Integer> pathBetweenNeighbors = findShortestScopePath( |
| | | leftNeighborStationId, |
| | | rightNeighborStationId, |
| | | stationId, |
| | | localNeighborhoodStationIdSet, |
| | | stationGraph |
| | | ); |
| | | if (pathBetweenNeighbors.isEmpty()) { |
| | | continue; |
| | | } |
| | | List<Integer> cycleStationIdList = new ArrayList<>(); |
| | | cycleStationIdList.add(stationId); |
| | | cycleStationIdList.addAll(pathBetweenNeighbors); |
| | | cycleStationIdList = normalizeLoopStationIdList(cycleStationIdList); |
| | | if (cycleStationIdList.size() < 3) { |
| | | continue; |
| | | } |
| | | if (bestCycleStationIdList.isEmpty() || cycleStationIdList.size() < bestCycleStationIdList.size()) { |
| | | bestCycleStationIdList = cycleStationIdList; |
| | | } |
| | | } |
| | | } |
| | | return bestCycleStationIdList; |
| | | } |
| | | |
| | | private List<Integer> resolveLocalNeighborhoodStationIdList(Integer stationId, |
| | | Set<Integer> loopStationIdSet, |
| | | Map<Integer, Set<Integer>> stationGraph) { |
| | | return normalizeLoopStationIdList(new ArrayList<>(collectLoopNeighborhoodStationIdSet( |
| | | stationId, |
| | | loopStationIdSet, |
| | | stationGraph, |
| | | LOCAL_LOOP_NEIGHBOR_HOP |
| | | ))); |
| | | } |
| | | |
| | | private Set<Integer> collectLoopNeighborhoodStationIdSet(Integer stationId, |
| | | Set<Integer> loopStationIdSet, |
| | | Map<Integer, Set<Integer>> stationGraph, |
| | | int maxHop) { |
| | | Set<Integer> neighborhoodStationIdSet = new LinkedHashSet<>(); |
| | | if (stationId == null |
| | | || stationId <= 0 |
| | | || loopStationIdSet == null |
| | | || loopStationIdSet.isEmpty() |
| | | || !loopStationIdSet.contains(stationId) |
| | | || stationGraph == null |
| | | || stationGraph.isEmpty() |
| | | || maxHop < 0) { |
| | | return neighborhoodStationIdSet; |
| | | } |
| | | |
| | | Deque<StationHopNode> queue = new ArrayDeque<>(); |
| | | queue.offer(new StationHopNode(stationId, 0)); |
| | | neighborhoodStationIdSet.add(stationId); |
| | | while (!queue.isEmpty()) { |
| | | StationHopNode current = queue.poll(); |
| | | if (current == null || current.getHop() >= maxHop) { |
| | | continue; |
| | | } |
| | | Set<Integer> neighborStationIdSet = filterLoopNeighborStationIdSet( |
| | | stationGraph.getOrDefault(current.getStationId(), Collections.emptySet()), |
| | | loopStationIdSet, |
| | | null |
| | | ); |
| | | for (Integer neighborStationId : neighborStationIdSet) { |
| | | if (neighborStationId == null || !neighborhoodStationIdSet.add(neighborStationId)) { |
| | | continue; |
| | | } |
| | | queue.offer(new StationHopNode(neighborStationId, current.getHop() + 1)); |
| | | } |
| | | } |
| | | return neighborhoodStationIdSet; |
| | | } |
| | | |
| | | private Set<Integer> filterLoopNeighborStationIdSet(Set<Integer> candidateNeighborStationIdSet, |
| | | Set<Integer> allowedStationIdSet, |
| | | Integer excludedStationId) { |
| | | Set<Integer> result = new LinkedHashSet<>(); |
| | | if (candidateNeighborStationIdSet == null || candidateNeighborStationIdSet.isEmpty() |
| | | || allowedStationIdSet == null || allowedStationIdSet.isEmpty()) { |
| | | return result; |
| | | } |
| | | for (Integer stationId : candidateNeighborStationIdSet) { |
| | | if (stationId == null |
| | | || !allowedStationIdSet.contains(stationId) |
| | | || (excludedStationId != null && excludedStationId.equals(stationId))) { |
| | | continue; |
| | | } |
| | | result.add(stationId); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private List<Integer> findShortestScopePath(Integer startStationId, |
| | | Integer endStationId, |
| | | Integer excludedStationId, |
| | | Set<Integer> allowedStationIdSet, |
| | | Map<Integer, Set<Integer>> stationGraph) { |
| | | if (startStationId == null |
| | | || endStationId == null |
| | | || Objects.equals(startStationId, excludedStationId) |
| | | || Objects.equals(endStationId, excludedStationId) |
| | | || allowedStationIdSet == null |
| | | || !allowedStationIdSet.contains(startStationId) |
| | | || !allowedStationIdSet.contains(endStationId) |
| | | || stationGraph == null |
| | | || stationGraph.isEmpty()) { |
| | | return new ArrayList<>(); |
| | | } |
| | | |
| | | Deque<Integer> queue = new ArrayDeque<>(); |
| | | Map<Integer, Integer> parentMap = new HashMap<>(); |
| | | Set<Integer> visitedStationIdSet = new HashSet<>(); |
| | | queue.offer(startStationId); |
| | | visitedStationIdSet.add(startStationId); |
| | | while (!queue.isEmpty()) { |
| | | Integer currentStationId = queue.poll(); |
| | | if (currentStationId == null) { |
| | | continue; |
| | | } |
| | | if (Objects.equals(currentStationId, endStationId)) { |
| | | break; |
| | | } |
| | | Set<Integer> neighborStationIdSet = filterLoopNeighborStationIdSet( |
| | | stationGraph.getOrDefault(currentStationId, Collections.emptySet()), |
| | | allowedStationIdSet, |
| | | excludedStationId |
| | | ); |
| | | for (Integer neighborStationId : neighborStationIdSet) { |
| | | if (neighborStationId == null || !visitedStationIdSet.add(neighborStationId)) { |
| | | continue; |
| | | } |
| | | parentMap.put(neighborStationId, currentStationId); |
| | | queue.offer(neighborStationId); |
| | | } |
| | | } |
| | | if (!visitedStationIdSet.contains(endStationId)) { |
| | | return new ArrayList<>(); |
| | | } |
| | | |
| | | List<Integer> pathStationIdList = new ArrayList<>(); |
| | | Integer cursorStationId = endStationId; |
| | | while (cursorStationId != null) { |
| | | pathStationIdList.add(cursorStationId); |
| | | if (Objects.equals(cursorStationId, startStationId)) { |
| | | break; |
| | | } |
| | | cursorStationId = parentMap.get(cursorStationId); |
| | | } |
| | | if (pathStationIdList.isEmpty() |
| | | || !Objects.equals(pathStationIdList.get(pathStationIdList.size() - 1), startStationId)) { |
| | | return new ArrayList<>(); |
| | | } |
| | | Collections.reverse(pathStationIdList); |
| | | return pathStationIdList; |
| | | } |
| | | |
| | | private Map<Integer, Set<Integer>> loadUndirectedStationGraph() { |
| | | NavigateUtils navigateUtils = SpringUtils.getBean(NavigateUtils.class); |
| | | if (navigateUtils == null) { |
| | | return new HashMap<>(); |
| | | } |
| | | Map<Integer, Set<Integer>> stationGraph = navigateUtils.loadUndirectedStationGraphSnapshot(); |
| | | return stationGraph == null ? new HashMap<>() : stationGraph; |
| | | } |
| | | |
| | | private List<Integer> normalizeLoopStationIdList(List<Integer> stationIdList) { |
| | |
| | | private Integer issuedCount; |
| | | private String loopFingerprint; |
| | | private Integer loopIssuedCount; |
| | | private Boolean loopTriggered; |
| | | private Integer currentLoopHitCount; |
| | | } |
| | | |
| | |
| | | private static class LoopIdentity { |
| | | private String loopFingerprint; |
| | | private Set<Integer> stationIdSet = new HashSet<>(); |
| | | private int sourceLoopStationCount; |
| | | private int localStationCount; |
| | | private String scopeType; |
| | | |
| | | private LoopIdentity(String loopFingerprint, Set<Integer> stationIdSet) { |
| | | private LoopIdentity(String loopFingerprint, |
| | | Set<Integer> stationIdSet, |
| | | int sourceLoopStationCount, |
| | | int localStationCount, |
| | | String scopeType) { |
| | | this.loopFingerprint = loopFingerprint; |
| | | this.stationIdSet = stationIdSet == null ? new HashSet<>() : stationIdSet; |
| | | this.sourceLoopStationCount = sourceLoopStationCount; |
| | | this.localStationCount = localStationCount; |
| | | this.scopeType = scopeType == null ? "" : scopeType; |
| | | } |
| | | |
| | | private static LoopIdentity empty() { |
| | | return new LoopIdentity("", new HashSet<>()); |
| | | return new LoopIdentity("", new HashSet<>(), 0, 0, "none"); |
| | | } |
| | | } |
| | | |
| | | @Data |
| | | private static class StationHopNode { |
| | | private final Integer stationId; |
| | | private final int hop; |
| | | } |
| | | } |