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}