001package jmri.jmrit.ctc.topology; 002 003import java.util.*; 004import jmri.*; 005import jmri.jmrit.ctc.ctcserialdata.CTCSerialData; 006import jmri.jmrit.display.layoutEditor.*; 007 008/** 009 * 010 * IF AND ONLY IF LayoutDesign is available: 011 * 012 * This object creates a list of objects that describe the path 013 * from the passed O.S. section to ALL other adjacent O.S. sections in a 014 * specified direction. It finds all sensors and turnouts (and their alignments). 015 * 016 * Ultimately, this will provide information to fill in completely 017 * (for the direction indicated: left / right traffic) the table 018 * _mTRL_TrafficLockingRulesSSVList in FrmTRL_Rules.java 019 * 020 * Sorry to say that as of 7/16/2020, LayoutBlock routine 021 * "getNeighbourAtIndex" does NOT work in complex track situations 022 * (one that I know of: Double crossovers), which leads to a 023 * failure to "auto-generate" properly Signal Mast Logic for specific 024 * signal masts. I wanted to avoid this. 025 * 026 * By this time the user SHOULD HAVE been done with all ABS (or APB, etc) rules 027 * for signals. We rely on this to generate our information. 028 * 029 * Also, NEVER allocate the terminating O.S. section, otherwise the dispatcher 030 * cannot clear the terminating O.S. section IN THE SAME DIRECTION as the 031 * originating O.S. section! 032 * 033 * @author Gregory J. Bedlek Copyright (C) 2018, 2019, 2020 034 */ 035 036public class Topology { 037 private final CTCSerialData _mCTCSerialData; 038 private final String _mNormal; 039 private final String _mReverse; 040 private final SignalMastLogicManager _mSignalMastLogicManager = InstanceManager.getDefault(jmri.SignalMastLogicManager.class); 041 private final LayoutBlockManager _mLayoutBlockManager = InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager.class); 042 private ArrayList<Block> _mStartingBlocks = new ArrayList<>(); 043 044 /** 045 * DO NOT USE, only for test suite! Object will crash if anything referenced in it. 046 * This constructor will be replaced by something more useful to the test suite 047 * "someday" when the test suite it coded for it. 048 */ 049 public Topology() { 050 _mCTCSerialData = null; 051 _mNormal = null; 052 _mReverse = null; 053 } 054 055 056 /** 057 * @param CTCSerialData The one and only. 058 * @param OSSectionOccupiedExternalSensors List of sensors to start from. 059 * @param normal Bundle.getMessage("TLE_Normal") 060 * @param reverse Bundle.getMessage("TLE_Reverse") 061 */ 062 public Topology(CTCSerialData CTCSerialData, ArrayList<String> OSSectionOccupiedExternalSensors, String normal, String reverse) { 063 _mCTCSerialData = CTCSerialData; 064 _mNormal = normal; 065 _mReverse = reverse; 066 for (String OSSectionOccupiedExternalSensor : OSSectionOccupiedExternalSensors) { 067 Sensor startingOSSectionSensor = InstanceManager.getDefault(SensorManager.class).getSensor(OSSectionOccupiedExternalSensor); 068 if (null != startingOSSectionSensor) { // Available: 069 LayoutBlock startingLayoutBlock = _mLayoutBlockManager.getBlockWithSensorAssigned(startingOSSectionSensor); 070 if (null != startingLayoutBlock) { // Available: 071 _mStartingBlocks.add(startingLayoutBlock.getBlock()); 072 } 073 } 074 } 075 } 076 077 078 /** 079 * @return boolean - true if available, else false. 080 */ 081 public boolean isTopologyAvailable() { return !_mStartingBlocks.isEmpty(); } 082 083 /** 084 * 085 * @param leftTraffic Traffic direction ON THE CTC PANEL that we are generating the rules for. 086 * @return TopologyInfo. All of the possible paths from this O.S. section to all 087 * destination(s) in the direction indicated. 088 * 089 * WE HAVE TO GO in the opposite direction to get the mast for the O.S. section we are in! 090 * There can ONLY be one signal in that direction, so upon encountering it, PROCESS ONLY IT! 091 * (we could check if there is another, and issue an error, but I believe that the Panel Editor 092 * would prevent this!) 093 * 094 * JMRI: West is left, East is right. 095 */ 096 public ArrayList<TopologyInfo> getTrafficLockingRules(boolean leftTraffic) { 097 ArrayList<TopologyInfo> returnValue = new ArrayList<>(); 098 for (Block startingBlock: _mStartingBlocks) { 099 for (Path path : startingBlock.getPaths()) { 100 Block neighborBlock = path.getBlock(); 101 if (inSameDirectionGenerally(getDirectionArrayListFrom(leftTraffic ? Path.EAST : Path.WEST), path.getToBlockDirection())) { 102 SignalMast facingSignalMast = _mLayoutBlockManager.getFacingSignalMast(neighborBlock, startingBlock); 103 if (null != facingSignalMast) { // Safety 104 SignalMastLogic facingSignalMastLogic = _mSignalMastLogicManager.getSignalMastLogic(facingSignalMast); 105 if (null != facingSignalMastLogic) { // Safety 106 processDestinations(returnValue, facingSignalMastLogic); 107 } 108 } 109 } 110 } 111 } 112 return returnValue; 113 } 114 115 116 /** 117 * Once we've "backed up" to look forward from the starting O.S. section and gotten a facingSignalMastLogic, this 118 * routine fills in "topologyInfo" with everything it finds from that point on. Handles intermediate blocks also 119 * (ABS, APB, etc.). Goes until there is no more. 120 * 121 * @param topologyInfos Entry(s) added to this ArrayList as they are encountered. It is NOT cleared first, 122 * as there may be entries from prior calls to this routine in here. 123 * @param facingSignalMastLogic Facing signal mast logic from O.S. section code. 124 */ 125 private void processDestinations(ArrayList<TopologyInfo> topologyInfos, SignalMastLogic facingSignalMastLogic) { 126 for (SignalMast destinationSignalMast : facingSignalMastLogic.getDestinationList()) { 127 TopologyInfo topologyInfo = new TopologyInfo(_mCTCSerialData, destinationSignalMast.getUserName(), _mNormal, _mReverse); 128 createRules(topologyInfo, facingSignalMastLogic, destinationSignalMast); 129 for (SignalMast tempSignalMast = destinationSignalMast; isIntermediateSignalMast(tempSignalMast); ) { 130 SignalMastLogic tempSignalMastLogic = _mSignalMastLogicManager.getSignalMastLogic(tempSignalMast); 131 if (null != tempSignalMastLogic) { // Safety: 132 // No safety check needed here, "isIntermediateSignalMast" GUARANTEES that there is EXACTLY one entry in "getDestinationList" list: 133 SignalMast onlyTerminatingSignalMast = tempSignalMastLogic.getDestinationList().get(0); 134 createRules(topologyInfo, tempSignalMastLogic, onlyTerminatingSignalMast); 135 tempSignalMast = onlyTerminatingSignalMast; // Next iteration, start where we left off. 136 } else { 137 break; // Stop immediately, got an issue. 138 } 139 } 140 if (topologyInfo.nonEmpty()) { 141 topologyInfos.add(topologyInfo); 142 } 143 } 144 } 145 146 147 /** 148 * Simple routine to create all of the rules from the past information in "topologyInfo". 149 * @param topologyInfo What to fill in. 150 * @param signalMastLogic From this 151 * @param signalMast And this. 152 */ 153 private void createRules(TopologyInfo topologyInfo, SignalMastLogic signalMastLogic, SignalMast signalMast) { 154 topologyInfo.addBlocks(signalMastLogic.getBlocks(signalMast)); 155 topologyInfo.addBlocks(signalMastLogic.getAutoBlocks(signalMast)); 156 topologyInfo.addTurnouts(signalMastLogic, signalMast); 157 } 158 159 160 /** 161 * Is the past Signal Mast a intermediate signal? (ABS / APB or some such)? 162 * If there are no turnouts, and there is ONLY ONE destination signal, then true, else false. 163 * 164 * @param signalMast 165 * @return true if so, else false. False also returned if invalid parameter in some way. 166 */ 167// Plagerization of DefaultSignalMastLogicManager/discoverSignallingDest "intermediateSignal" property code. 168 private boolean isIntermediateSignalMast(SignalMast signalMast) { 169 if (null != signalMast) { // Safety 170 SignalMastLogic signalMastLogic = _mSignalMastLogicManager.getSignalMastLogic(signalMast); 171 if (null != signalMastLogic) { // Safety: 172 return signalMastLogic.getDestinationList().size() == 1 173 && signalMastLogic.getAutoTurnouts(signalMastLogic.getDestinationList().get(0)).isEmpty() 174 && signalMastLogic.getTurnouts(signalMastLogic.getDestinationList().get(0)).isEmpty(); 175 } 176 } 177 return false; // OOPPSS invalid, can't determine, assume NOT a intermediate signal. 178 } 179 180 181 /** 182 * 183 * @param direction Direction to generate list from. 184 * @return IF passed a valid direction, a 3 element set of "generally in the same direction" directions, else an EMPTY set (NOT null!) 185 */ 186 private ArrayList<Integer> getDirectionArrayListFrom(int direction) { 187 switch (direction) { 188 case Path.NORTH: 189 return new ArrayList<>(Arrays.asList(Path.NORTH_WEST, Path.NORTH, Path.NORTH_EAST)); 190 case Path.NORTH_EAST: 191 return new ArrayList<>(Arrays.asList(Path.NORTH, Path.NORTH_EAST, Path.EAST)); 192 case Path.EAST: 193 return new ArrayList<>(Arrays.asList(Path.NORTH_EAST, Path.EAST, Path.SOUTH_EAST)); 194 case Path.SOUTH_EAST: 195 return new ArrayList<>(Arrays.asList(Path.EAST, Path.SOUTH_EAST, Path.SOUTH)); 196 case Path.SOUTH: 197 return new ArrayList<>(Arrays.asList(Path.SOUTH_EAST, Path.SOUTH, Path.SOUTH_WEST)); 198 case Path.SOUTH_WEST: 199 return new ArrayList<>(Arrays.asList(Path.SOUTH, Path.SOUTH_WEST, Path.WEST)); 200 case Path.WEST: 201 return new ArrayList<>(Arrays.asList(Path.SOUTH_WEST, Path.WEST, Path.NORTH_WEST)); 202 case Path.NORTH_WEST: 203 return new ArrayList<>(Arrays.asList(Path.WEST, Path.NORTH_WEST, Path.NORTH)); 204 default: 205 return new ArrayList<>(); // Huh? 206 } 207 } 208 209 210 /** 211 * 212 * @param possibleDirections The set of possible directions to check. Caveat Emptor: IF this 213 * array has no entries, then this routine returns false. 214 * 215 * @param direction Direction to check. 216 * @return True if direction in "possibleDirections", else false. 217 */ 218 private boolean inSameDirectionGenerally(ArrayList<Integer> possibleDirections, int direction) { 219 if (possibleDirections.isEmpty()) return false; 220 return possibleDirections.contains(direction); 221 } 222}