001package jmri.jmrit.ctc; 002 003import java.util.ArrayList; 004import java.util.HashMap; 005import java.util.HashSet; 006import jmri.Sensor; 007import jmri.jmrit.ctc.ctcserialdata.TrafficLockingData; 008import org.slf4j.Logger; 009import org.slf4j.LoggerFactory; 010 011/** 012 * 013 * @author Gregory J. Bedlek Copyright (C) 2018, 2019 014 */ 015 016public class TrafficLocking { 017 private final static Logger log = LoggerFactory.getLogger(TrafficLocking.class); 018 019 private static class TrafficLockingRecord { 020 private final SwitchIndicatorsRoute _mSwitchIndicatorsRoute; 021 private final NBHSensor _mOccupancyExternalSensor1; 022 private final NBHSensor _mOccupancyExternalSensor2; 023 private final NBHSensor _mOccupancyExternalSensor3; 024 private final NBHSensor _mOccupancyExternalSensor4; 025 private final NBHSensor _mOccupancyExternalSensor5; 026 private final NBHSensor _mOccupancyExternalSensor6; 027 private final NBHSensor _mOccupancyExternalSensor7; 028 private final NBHSensor _mOccupancyExternalSensor8; 029 private final NBHSensor _mOccupancyExternalSensor9; 030 private final NBHSensor _mOptionalSensor1; 031 private final NBHSensor _mOptionalSensor2; 032 private final boolean _mRuleEnabled; 033 034 public TrafficLockingRecord(String userIdentifier, 035 String parameter, 036 NBHSensor switchIndicator1, 037 NBHSensor switchIndicator2, 038 NBHSensor switchIndicator3, 039 NBHSensor switchIndicator4, 040 NBHSensor switchIndicator5, 041 NBHSensor occupancyExternalSensor1, 042 NBHSensor occupancyExternalSensor2, 043 NBHSensor occupancyExternalSensor3, 044 NBHSensor occupancyExternalSensor4, 045 NBHSensor occupancyExternalSensor5, 046 NBHSensor occupancyExternalSensor6, 047 NBHSensor occupancyExternalSensor7, 048 NBHSensor occupancyExternalSensor8, 049 NBHSensor occupancyExternalSensor9, 050 NBHSensor optionalSensor1, 051 NBHSensor optionalSensor2, 052 String ruleEnabled) { 053 _mSwitchIndicatorsRoute = new SwitchIndicatorsRoute(switchIndicator1, switchIndicator2, switchIndicator3, switchIndicator4, switchIndicator5, null); 054 _mOccupancyExternalSensor1 = occupancyExternalSensor1; 055 _mOccupancyExternalSensor2 = occupancyExternalSensor2; 056 _mOccupancyExternalSensor3 = occupancyExternalSensor3; 057 _mOccupancyExternalSensor4 = occupancyExternalSensor4; 058 _mOccupancyExternalSensor5 = occupancyExternalSensor5; 059 _mOccupancyExternalSensor6 = occupancyExternalSensor6; 060 _mOccupancyExternalSensor7 = occupancyExternalSensor7; 061 _mOccupancyExternalSensor8 = occupancyExternalSensor8; 062 _mOccupancyExternalSensor9 = occupancyExternalSensor9; 063 _mOptionalSensor1 = optionalSensor1; 064 _mOptionalSensor2 = optionalSensor2; 065 _mRuleEnabled = !ruleEnabled.equals(Bundle.getMessage("TLE_RuleDisabled")); // NOI18N Any problem, default is ENABLED! 066 } 067 068 public boolean isEnabled() { return _mRuleEnabled; } 069 public boolean isValid(boolean fleetingEnabled) { 070 if (!_mRuleEnabled) return false; // If disabled, treat as invalid so we skip this rule and try the next rule. 071 072 if (!_mSwitchIndicatorsRoute.isRouteSelected() 073 || !isOptionalSensorActive(_mOptionalSensor1) 074 || !isOptionalSensorActive(_mOptionalSensor2)) return false; 075 return true; 076 } 077 078// Put all non null and valid OCCUPANCY NBHSensor's in a HashSet and return it (the "ROUTE"!) for use by LockedRoutesManager. 079 public HashSet<Sensor> getOccupancySensors() { 080 HashSet<Sensor> returnValue = new HashSet<>(); 081 if (_mOccupancyExternalSensor1 != null && _mOccupancyExternalSensor1.valid()) returnValue.add(_mOccupancyExternalSensor1.getBean()); 082 if (_mOccupancyExternalSensor2 != null && _mOccupancyExternalSensor2.valid()) returnValue.add(_mOccupancyExternalSensor2.getBean()); 083 if (_mOccupancyExternalSensor3 != null && _mOccupancyExternalSensor3.valid()) returnValue.add(_mOccupancyExternalSensor3.getBean()); 084 if (_mOccupancyExternalSensor4 != null && _mOccupancyExternalSensor4.valid()) returnValue.add(_mOccupancyExternalSensor4.getBean()); 085 if (_mOccupancyExternalSensor5 != null && _mOccupancyExternalSensor5.valid()) returnValue.add(_mOccupancyExternalSensor5.getBean()); 086 if (_mOccupancyExternalSensor6 != null && _mOccupancyExternalSensor6.valid()) returnValue.add(_mOccupancyExternalSensor6.getBean()); 087 if (_mOccupancyExternalSensor7 != null && _mOccupancyExternalSensor7.valid()) returnValue.add(_mOccupancyExternalSensor7.getBean()); 088 if (_mOccupancyExternalSensor8 != null && _mOccupancyExternalSensor8.valid()) returnValue.add(_mOccupancyExternalSensor8.getBean()); 089 if (_mOccupancyExternalSensor9 != null && _mOccupancyExternalSensor9.valid()) returnValue.add(_mOccupancyExternalSensor9.getBean()); 090 returnValue.remove(null); // Safety: Remove null entry if it exists (There will ONLY be one in a set!) 091 return returnValue; 092 } 093 094// Quick and Dirty Routine: If it doesn't exist, it's lit. If it exists, ACTIVE = lit. Can't use CTCMain.getSensorKnownState() because of this. 095 private boolean isOptionalSensorActive(NBHSensor sensor) { 096 if (sensor.valid()) return sensor.getKnownState() == Sensor.ACTIVE; 097 return true; // Doesn't exist. 098 } 099 100 } 101 102 private final ArrayList<TrafficLockingRecord> _mLeftTrafficLockingRulesArrayList = new ArrayList<>(); 103 private final ArrayList<TrafficLockingRecord> _mRightTrafficLockingRulesArrayList = new ArrayList<>(); 104 private final String _mUserIdentifier; 105 private final ArrayList<TrafficLockingData> _mLeftTrafficLockingRulesList; 106 private final ArrayList<TrafficLockingData> _mRightTrafficLockingRulesList; 107 private final LockedRoutesManager _mLockedRoutesManager; 108 109 public TrafficLocking(String userIdentifier, ArrayList<TrafficLockingData> _mTRL_LeftTrafficLockingRules, ArrayList<TrafficLockingData> _mTRL_RightTrafficLockingRules, LockedRoutesManager lockedRoutesManager) 110 { 111 _mUserIdentifier = userIdentifier; // Squirrel it 112 _mLeftTrafficLockingRulesList = _mTRL_LeftTrafficLockingRules; // away for later 113 _mRightTrafficLockingRulesList = _mTRL_RightTrafficLockingRules; // "fileReadComplete" 114 _mLockedRoutesManager = lockedRoutesManager; 115 } 116 117 public void removeAllListeners() {} // None done. 118 119// Since the user may specify "forward referenced" O/S sections (i.e. an entry references an O.S. section that hasn't been read in and created yet), 120// we delay processing of everything until after the file has been completely read in. Here we do the real work: 121 public void fileReadComplete(HashMap<Integer, CodeButtonHandler> cbHashMap, HashMap<Integer, SwitchDirectionIndicators> swdiHashMap) { 122 addAllTrafficLockingEntries(_mUserIdentifier, _mLeftTrafficLockingRulesList, "leftTrafficLockingRulesList", cbHashMap, swdiHashMap, _mLeftTrafficLockingRulesArrayList); // NOI18N 123 addAllTrafficLockingEntries(_mUserIdentifier, _mRightTrafficLockingRulesList, "rightTrafficLockingRulesList", cbHashMap, swdiHashMap, _mRightTrafficLockingRulesArrayList); // NOI18N 124 } 125 126 private void addAllTrafficLockingEntries( String userIdentifier, 127 ArrayList<TrafficLockingData> trafficLockingRulesList, 128 String parameter, 129 HashMap<Integer, CodeButtonHandler> cbHashMap, 130 HashMap<Integer, SwitchDirectionIndicators> swdiHashMap, 131 ArrayList<TrafficLockingRecord> trafficLockingRecordsArrayList) { // <- Output 132 trafficLockingRulesList.forEach(row -> { 133 // Convert TrafficLockingData into a set of fixed size ArrayLists 134 ArrayList<NBHSensor> occupancySensors = row.getOccupancySensors(); 135 ArrayList<NBHSensor> optionalSensors = row.getOptionalSensors(); 136 ArrayList<Integer> ids = row.getUniqueIDs(); 137 ArrayList<String> alignments = row.getAlignments(); 138 139 int osSection1UniqueID = ids.get(0); 140 int osSection2UniqueID = ids.get(1); 141 int osSection3UniqueID = ids.get(2); 142 int osSection4UniqueID = ids.get(3); 143 int osSection5UniqueID = ids.get(4); 144 145 TrafficLockingRecord trafficLockingRecord 146 = new TrafficLockingRecord( userIdentifier, 147 parameter, 148 getSwitchDirectionIndicatorSensor(osSection1UniqueID, alignments.get(0), swdiHashMap), 149 getSwitchDirectionIndicatorSensor(osSection2UniqueID, alignments.get(1), swdiHashMap), 150 getSwitchDirectionIndicatorSensor(osSection3UniqueID, alignments.get(2), swdiHashMap), 151 getSwitchDirectionIndicatorSensor(osSection4UniqueID, alignments.get(3), swdiHashMap), 152 getSwitchDirectionIndicatorSensor(osSection5UniqueID, alignments.get(4), swdiHashMap), 153 occupancySensors.get(0), 154 occupancySensors.get(1), 155 occupancySensors.get(2), 156 occupancySensors.get(3), 157 occupancySensors.get(4), 158 occupancySensors.get(5), 159 occupancySensors.get(6), 160 occupancySensors.get(7), 161 occupancySensors.get(8), 162 optionalSensors.get(0), 163 optionalSensors.get(1), 164 row._mRuleEnabled); 165 if (!trafficLockingRecord.getOccupancySensors().isEmpty()) { 166 trafficLockingRecordsArrayList.add(trafficLockingRecord); 167 } 168 }); 169 } 170 171 public TrafficLockingInfo valid(int presentSignalDirectionLever, boolean fleetingEnabled) { 172 if (presentSignalDirectionLever == CTCConstants.LEFTTRAFFIC) return validForTraffic(_mLeftTrafficLockingRulesArrayList, false, fleetingEnabled); 173 return validForTraffic(_mRightTrafficLockingRulesArrayList, true, fleetingEnabled); 174 } 175 176 private TrafficLockingInfo validForTraffic(ArrayList<TrafficLockingRecord> trafficLockingRecordArrayList, boolean rightTraffic, boolean fleetingEnabled) { 177 TrafficLockingInfo returnValue = new TrafficLockingInfo(true); // ASSUME valid return status 178 if (trafficLockingRecordArrayList.isEmpty()) return returnValue; // No rules, OK all of the time. 179// If ALL are disabled, then treat as if nothing in there, always allow, otherwise NONE would be valid! 180 boolean anyEnabled = false; 181 for (int index = 0; index < trafficLockingRecordArrayList.size(); index++) { 182 if (trafficLockingRecordArrayList.get(index).isEnabled()) { anyEnabled = true; break; } 183 } 184 if (!anyEnabled) return returnValue; // None enabled, always allow. 185 186 for (int index = 0; index < trafficLockingRecordArrayList.size(); index++) { 187 TrafficLockingRecord trafficLockingRecord = trafficLockingRecordArrayList.get(index); 188 if (trafficLockingRecord.isValid(fleetingEnabled)) { 189// Ah, we found a rule that matches the route. See if that route 190// is in conflict with any other routes presently in effect: 191 String ruleNumber = Integer.toString(index+1); 192 returnValue._mLockedRoute = _mLockedRoutesManager.checkRouteAndAllocateIfAvailable(trafficLockingRecord.getOccupancySensors(), _mUserIdentifier, "Rule #" + ruleNumber, rightTraffic); 193 if (returnValue._mLockedRoute != null) { // OK: 194 if (jmri.InstanceManager.getDefault(CTCMain.class)._mCTCDebug_TrafficLockingRuleTriggeredDisplayLoggingEnabled) log.info("Rule {} valid", ruleNumber); 195 return returnValue; 196 } 197 } 198 } 199 returnValue._mReturnStatus = false; 200 return returnValue; 201 } 202 203 private NBHSensor getSwitchDirectionIndicatorSensor(int uniqueID, String switchAlignment, HashMap<Integer, SwitchDirectionIndicators> swdiHashMap) { 204 if (uniqueID < 0) return null; 205 boolean isNormalAlignment = !switchAlignment.equals(Bundle.getMessage("TLE_Reverse")); // NOI18N 206 SwitchDirectionIndicators switchDirectionIndicators = swdiHashMap.get(uniqueID); 207 if (switchDirectionIndicators == null) return null; // Safety, technically shouldn't happen.... 208 return switchDirectionIndicators.getProperIndicatorSensor(isNormalAlignment); 209 } 210}