001package jmri.jmrit.ctc; 002 003import java.util.ArrayList; 004import java.util.HashSet; 005import jmri.Sensor; 006import org.slf4j.Logger; 007import org.slf4j.LoggerFactory; 008 009/** 010 * This object manages all of the active routes. 011 * As of 6/24/2020: 012 * <p> 013 * Code was added to support fleeting over "existing" routes. Either the 014 * dispatcher codes a O.S. section a second time in the same direction again for 015 * a following train, or the fleeting "toggle" is on. 016 * <p> 017 * Background: 018 * We KNOW from routine "anyInCommon", that it is IMPOSSIBLE to have overlapping 019 * routes in "_mArrayListOfLockedRoutes". That is enforced (and a requirement). 020 * <p> 021 * We ALSO know that when routine "checkRouteAndAllocateIfAvailable" is called, 022 * its parameter "sensors" has ALL of the new sensors for that route. We will 023 * take advantage of this later on. 024 * <p> 025 * New code design: 026 * In the routine "anyInCommon", we check to see if there is any overlap. If 027 * there is, a 2nd check is done to see if the overlapping route is in the same 028 * direction. If so, it is allowed. 029 * <p> 030 * IF (and only if) this is the case, we KNOW that the (old) existing overlapping 031 * route in "_mArrayListOfLockedRoutes" that it was checking against is either 032 * (regarding sensors) the same, or a (possible) subset. The subset case is due 033 * to occupancy sensor(s) turning off by the prior train as it advanced, and 034 * being removed from the set. Technically, it can NEVER be the same, since the 035 * ABS system would prevent the signal from going non-red if the prior train 036 * hasn't advanced at least one block past the O.S. section. But the code 037 * makes no assumptions regarding this. 038 * 039 * @author Gregory J. Bedlek Copyright (C) 2018, 2019, 2020 040 */ 041public class LockedRoutesManager { 042 private final static Logger log = LoggerFactory.getLogger(LockedRoutesManager.class); 043 private final ArrayList<LockedRoute> _mArrayListOfLockedRoutes = new ArrayList<>(); 044 045 public void clearAllLockedRoutes() { 046 _mArrayListOfLockedRoutes.clear(); 047 } 048 049 /** 050 * Call this routine with a set of resources that need to be checked against 051 * the presently allocated resources. 052 * <p> 053 * ONLY A CHECK IS DONE. 054 * No resources are modified! 055 * <p> 056 * Typically used for a O.S. section sensors check. 057 * Get the primary and possibly the secondary O.S. sensor(s) associated with it, and pass it 058 * in "sensors". NO CHECK is made of traffic direction. 059 * @param sensors set of sensors. 060 * @param osSectionDescription section description. 061 * @param ruleDescription rule description. 062 * @return true if there is no overlap of resources, else returns false. 063 */ 064 public boolean checkRoute(HashSet<Sensor> sensors, String osSectionDescription, String ruleDescription) { 065 return privateCheckRoute(sensors, osSectionDescription, ruleDescription, false, false) != null; /* Passed rightTraffic: Don't care because checkTraffic = false! */ 066 } 067 068 /** 069 * Call this routine with a set of resources that need to be checked against 070 * the presently allocated resources. IF the traffic direction is the same 071 * as presently allocated, then it is ALWAYS allowed, as some form of fleeting 072 * operation has been requested by the dispatcher. 073 * <p> 074 * If there is no overlap, then add the 075 * set to the presently allocated resources and return the LockedRoute object. 076 * If there is overlap, modify nothing and return null. 077 * 078 * See explanation at the start of this source code about support put in here 079 * for Fleeting. 080 * 081 * @param sensors set of sensors. 082 * @param osSectionDescription section description. 083 * @param ruleDescription rule description. 084 * @param rightTraffic true if right traffic, else false if left traffic 085 * @return locked route if success, null if failed. 086 */ 087 public LockedRoute checkRouteAndAllocateIfAvailable(HashSet<Sensor> sensors, String osSectionDescription, String ruleDescription, boolean rightTraffic) { 088 LockedRoute newLockedRoute = privateCheckRoute(sensors, osSectionDescription, ruleDescription, true, rightTraffic); 089 if (newLockedRoute == null) return null; 090// Ran the gambit, no collision. However, we may need to merge if there are common elements, since this may be a fleeting request of some kind: 091 if (existingLockedRouteThatHasCommonSensors == null) { // No conflict, not fleeting: 092 newLockedRoute.allocateRoute(); 093 _mArrayListOfLockedRoutes.add(newLockedRoute); 094 return newLockedRoute; 095 } else { // Merge here: 096/* Background: 097 "newLockedRoute" is NOT in "_mArrayListOfLockedRoutes" at this time, and "newLockedRoute" has NOT had "allocateRoute" called on it. 098 "existingLockedRouteThatHadFleeting" has some sensor(s) that have registered notification of sensor events already. 099 Shortcuts: 100 It would be easiest to merge "newLockedRoute" into "existingLockedRouteThatHadFleeting", since that is the least work. 101 That is what "mergeRoutes" ASSUMES! 102*/ 103 existingLockedRouteThatHasCommonSensors.mergeRoutes(newLockedRoute); 104 return existingLockedRouteThatHasCommonSensors; 105 } 106 } 107 108/* We leave a breadcrumb trail for "checkRouteAndAllocateIfAvailable", if we see a LockedRoute.AnyInCommonReturn.FLEETING, 109 we note which entry created it, and since there can be no overlap, that is fine. 110*/ 111 private LockedRoute existingLockedRouteThatHasCommonSensors; 112 private LockedRoute privateCheckRoute(HashSet<Sensor> sensors, String osSectionDescription, String ruleDescription, boolean checkTraffic, boolean rightTraffic) { 113 existingLockedRouteThatHasCommonSensors = null; // Flag none found. 114 LockedRoute newLockedRoute = new LockedRoute(this, sensors, osSectionDescription, ruleDescription, rightTraffic); 115 for (LockedRoute existingLockedRoute : _mArrayListOfLockedRoutes) { // Check against ALL others: 116// As of this moment, newLockedRoute has NOT allocated any resource(s). 117// Later, it will be locked down IF it doesn't conflict here with any other existing route: 118 LockedRoute.AnyInCommonReturn anyInCommonReturn = newLockedRoute.anyInCommon(existingLockedRoute, checkTraffic, rightTraffic); 119 if (anyInCommonReturn == LockedRoute.AnyInCommonReturn.YES) { // Collision, invalid! 120 return null; 121 } else if (anyInCommonReturn == LockedRoute.AnyInCommonReturn.FLEETING) { // Note which one: 122 existingLockedRouteThatHasCommonSensors = existingLockedRoute; 123 break; // Can only be one! 124 } 125 } 126 return newLockedRoute; 127 } 128 129 /** 130 * This routine frees all resources allocated by the passed lockedRoute (listeners primarily) 131 * and then removes it from our "_mArrayListOfLockedRoutes". Now the route does NOT exist anywhere. 132 * 133 * @param lockedRoute The route to cancel. 134 * <p> 135 * NOTE: 136 * The child (LockedRoute object) or running time done calls us when it determines it's empty, 137 * so we can delete it from our master list. 138 * It's already de-allocated all of its resources, but for safety call "removeAllListeners" anyways. 139 */ 140 public void cancelLockedRoute(LockedRoute lockedRoute) { 141 if (lockedRoute != null) lockedRoute.removeAllListeners(); // Safety 142 _mArrayListOfLockedRoutes.remove(lockedRoute); // Even null, no throw! 143 } 144 145 /** 146 * Primarily called when the CTC system is restarted from within JMRI, 147 * nothing else external to this module should call this. 148 */ 149 public void removeAllListeners() { 150 _mArrayListOfLockedRoutes.forEach((existingLockedRoute) -> { 151 existingLockedRoute.removeAllListeners(); 152 }); 153 } 154 155 /** 156 * Simple routine to dump all locked routes information. Called from 157 * CTCMain when the debug sensor goes active. 158 */ 159 void dumpAllRoutes() { 160 if ( !log.isInfoEnabled() ) { 161 return; 162 } 163 log.info("Locked Routes:"); 164 for (LockedRoute lockedRoute : _mArrayListOfLockedRoutes) { 165 log.info("Route: {}", lockedRoute.dumpRoute()); 166 } 167 } 168}