001package jmri.jmrit.ctc;
002
003import java.util.ArrayList;
004import java.util.HashMap;
005import java.util.List;
006import jmri.Block;
007import jmri.ConfigureManager;
008import jmri.InstanceManager;
009import jmri.InstanceManagerAutoDefault;
010import jmri.Manager;
011import jmri.NamedBean;
012import jmri.NamedBeanHandle;
013import jmri.NamedBeanUsageReport;
014import jmri.jmrit.ctc.ctcserialdata.*;
015import jmri.jmrit.ctc.editor.code.*;
016
017/**
018 * Start the CtcManager and register with the instance and configuration managers.
019 * <ul>
020 *   <li>Create/provide the ProgramProperties instance</li>
021 *   <li>Create/provide the CTCSerialData instance</li>
022 *   <li>Provide the OtherData instance</li>
023 *   <li>Provide hash maps of beans used by CTC</li>
024 *   <li>Veto deletes for beans used by CTC</li>
025 * </ul>
026 *
027 * @author Dave Sand Copyright (C) 2020
028 */
029public class CtcManager implements InstanceManagerAutoDefault, java.beans.VetoableChangeListener {
030
031    ProgramProperties programProperties = null;
032    CTCSerialData ctcSerialData = null;
033    HashMap<String, NBHSensor> nbhSensors = new HashMap<>();
034    HashMap<String, NBHSignal> nbhSignals = new HashMap<>();
035    HashMap<String, NBHTurnout> nbhTurnouts = new HashMap<>();
036    HashMap<String, NamedBeanHandle<Block>> blocks = new HashMap<>();
037
038    // Search results
039    NBHSensor  foundSensor;
040    NBHSignal  foundSignal;
041    NBHTurnout foundTurnout;
042    Block      foundBlock;
043
044    List<NamedBeanUsageReport> usageReport;
045
046    public CtcManager() {
047        InstanceManager.setDefault(CtcManager.class, this);
048        InstanceManager.getOptionalDefault(ConfigureManager.class).ifPresent(cm -> {
049            cm.registerConfig(this, getXMLOrder());
050        });
051        InstanceManager.getDefault(jmri.SensorManager.class).addVetoableChangeListener(this);
052        InstanceManager.getDefault(jmri.SignalHeadManager.class).addVetoableChangeListener(this);
053        InstanceManager.getDefault(jmri.SignalMastManager.class).addVetoableChangeListener(this);
054        InstanceManager.getDefault(jmri.TurnoutManager.class).addVetoableChangeListener(this);
055        InstanceManager.getDefault(jmri.BlockManager.class).addVetoableChangeListener(this);
056        log.debug("CtcManager started");  // NOI18N
057    }
058
059    public ProgramProperties getProgramProperties() {
060        if (programProperties == null) {
061            programProperties = new ProgramProperties();
062        }
063        return programProperties;
064    }
065
066    public ProgramProperties newProgramProperties() {
067        programProperties = new ProgramProperties();
068        return programProperties;
069    }
070
071    public CTCSerialData getCTCSerialData() {
072        if (ctcSerialData == null) {
073            ctcSerialData = new CTCSerialData();
074        }
075        return ctcSerialData;
076    }
077
078    public CTCSerialData newCTCSerialData() {
079        ctcSerialData = new CTCSerialData();
080
081        nbhSensors.clear();
082        nbhSignals.clear();
083        nbhTurnouts.clear();
084        blocks.clear();
085
086        return ctcSerialData;
087    }
088
089    public OtherData getOtherData() {
090        if (ctcSerialData == null) {
091            ctcSerialData = getCTCSerialData();
092        }
093        return ctcSerialData.getOtherData();
094    }
095
096    public NBHSensor getNBHSensor(String name) {
097        // check for new names
098        return nbhSensors.get(name);
099    }
100
101    public void putNBHSensor(String name, NBHSensor nbh) {
102        NBHSensor oldSensor = nbhSensors.put(name, nbh);
103        log.debug("sensor put = {} -- {}", name, nbh);  // NOI18N
104        if (oldSensor != null) {
105            log.debug("---- duplicate sensor: {} -- {}", name, nbh);  // NOI18N
106        }
107    }
108
109    public NBHSignal getNBHSignal(String name) {
110        // check for new names
111        return nbhSignals.get(name);
112    }
113
114    public void putNBHSignal(String name, NBHSignal nbh) {
115        NBHSignal oldSignal = nbhSignals.put(name, nbh);
116        log.debug("signal put = {} -- {}", name, nbh);  // NOI18N
117        if (oldSignal != null) {
118            log.debug("---- duplicate signal: {} -- {}", name, nbh);  // NOI18N
119        }
120    }
121
122    public NBHTurnout getNBHTurnout(String name) {
123        // check for new names
124        return nbhTurnouts.get(name);
125    }
126
127    public void putNBHTurnout(String name, NBHTurnout nbh) {
128        NBHTurnout oldTurnout = nbhTurnouts.put(name, nbh);
129        log.debug("turnout put = {} -- {}", name, nbh);  // NOI18N
130        if (oldTurnout != null) {
131            log.debug("---- duplicate turnout: {} -- {}", name, nbh);  // NOI18N
132        }
133    }
134
135    public NamedBeanHandle<Block> getBlock(String name) {
136        // check for new names
137        return blocks.get(name);
138    }
139
140    public void putBlock(String name, NamedBeanHandle<Block> block) {
141        NamedBeanHandle<Block> oldBlock = blocks.put(name, block);
142        log.debug("block put = {} -- {}", name, block);  // NOI18N
143        if (oldBlock != null) {
144            log.debug("---- duplicate block: {} -- {}", name, block);  // NOI18N
145        }
146    }
147
148    public int getXMLOrder() {
149        return Manager.CTCDATA;
150    }
151
152    @Override
153    public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException {
154        jmri.NamedBean nb = (jmri.NamedBean) evt.getOldValue();
155        if ("CanDelete".equals(evt.getPropertyName())) { // NOI18N
156            if (findNBHforBean(nb)) {
157                java.beans.PropertyChangeEvent e = new java.beans.PropertyChangeEvent(this, "DoNotDelete", null, null);  // NOI18N
158                throw new java.beans.PropertyVetoException(getVetoDetails(nb), e);
159            }
160        }
161    }
162
163    String getVetoDetails(NamedBean nb) {
164        StringBuilder sb = new StringBuilder();
165        sb.append(Bundle.getMessage("CtcManagerDeleteVetoed", nb.getBeanType()));  // NOI18N
166        for (NamedBeanUsageReport report : getUsageReport(nb)) {
167            sb.append(Bundle.getMessage("VetoDetailLine", report.usageData));    // NOI18N
168        }
169        return sb.toString();
170    }
171
172    boolean findNBHforBean(NamedBean nb) {
173        if (nb == null) return false;
174        boolean found = false;
175        foundSensor = null;
176        foundSignal = null;
177        foundTurnout = null;
178        foundBlock = null;
179
180        if (nb instanceof jmri.Sensor) {
181            for (NBHSensor sensor : nbhSensors.values()) {
182                if (nb.equals(sensor.getBean())) {
183                    foundSensor = sensor;
184                    found = true;
185                    break;
186                }
187            }
188        }
189
190        if (nb instanceof jmri.SignalHead || nb instanceof jmri.SignalMast) {
191            for (NBHSignal signal : nbhSignals.values()) {
192                if (nb.equals(signal.getBean())) {
193                    foundSignal = signal;
194                    found = true;
195                    break;
196                }
197            }
198        }
199
200        if (nb instanceof jmri.Turnout) {
201            for (NBHTurnout turnout : nbhTurnouts.values()) {
202                if (nb.equals(turnout.getBean())) {
203                    foundTurnout = turnout;
204                    found = true;
205                    break;
206                }
207            }
208        }
209
210        if (nb instanceof Block) {
211            for (NamedBeanHandle<Block> block : blocks.values()) {
212                if (nb.equals(block.getBean())) {
213                    foundBlock = block.getBean();
214                    found = true;
215                    break;
216                }
217            }
218        }
219        return found;
220    }
221
222    public List<NamedBeanUsageReport> getUsageReport(NamedBean bean) {
223        usageReport = new ArrayList<>();
224        if (findNBHforBean(bean)) {
225            // Other data
226            if (getOtherData()._mFleetingToggleInternalSensor.equals(foundSensor) ||
227                    getOtherData()._mCTCDebugSystemReloadInternalSensor.equals(foundSensor) ||
228                    getOtherData()._mCTCDebug_TrafficLockingRuleTriggeredDisplayInternalSensor.equals(foundSensor)) {
229                usageReport.add(new NamedBeanUsageReport("CtcWhereUsedOther", Bundle.getMessage("WhereUsedOther")));  // NOI18N
230            }
231
232            // O.S. Sections
233            getCTCSerialData().getCodeButtonHandlerDataArrayList().forEach(cbhd -> {
234                getCodeButtonHandleDataUsage(cbhd);
235            });
236        }
237        return usageReport;
238    }
239
240    void getCodeButtonHandleDataUsage(CodeButtonHandlerData cbhd) {
241        String osName = cbhd.myShortStringNoComma();
242
243        // CB Sensors
244        if (cbhd._mCodeButtonInternalSensor.equals(foundSensor) ||
245                cbhd._mOSSectionOccupiedExternalSensor.equals(foundSensor) ||
246                cbhd._mOSSectionOccupiedExternalSensor2.equals(foundSensor)) {
247            usageReport.add(new NamedBeanUsageReport("CtcWhereUsedCBHD", Bundle.getMessage("WhereUsedSensor", osName, "CB")));  // NOI18N
248        }
249
250        // SIDI Sensors
251        if (cbhd._mSIDI_LeftInternalSensor.equals(foundSensor) ||
252                cbhd._mSIDI_NormalInternalSensor.equals(foundSensor) ||
253                cbhd._mSIDI_RightInternalSensor.equals(foundSensor)) {
254            usageReport.add(new NamedBeanUsageReport("CtcWhereUsedCBHD", Bundle.getMessage("WhereUsedSensor", osName, "SIDI")));  // NOI18N
255        }
256
257        // SIDI Signals
258        cbhd._mSIDI_LeftRightTrafficSignals.forEach(signal -> {
259            if (signal.equals(foundSignal)) {
260                usageReport.add(new NamedBeanUsageReport("CtcWhereUsedCBHD", Bundle.getMessage("WhereUsedSignal", osName, "SIDI")));  // NOI18N
261            }
262        });
263        cbhd._mSIDI_RightLeftTrafficSignals.forEach(signal -> {
264            if (signal.equals(foundSignal)) {
265                usageReport.add(new NamedBeanUsageReport("CtcWhereUsedCBHD", Bundle.getMessage("WhereUsedSignal", osName, "SIDI")));  // NOI18N
266            }
267        });
268
269        // SIDL Sensors
270        if (cbhd._mSIDL_LeftInternalSensor.equals(foundSensor) ||
271                cbhd._mSIDL_NormalInternalSensor.equals(foundSensor) ||
272                cbhd._mSIDL_RightInternalSensor.equals(foundSensor)) {
273            usageReport.add(new NamedBeanUsageReport("CtcWhereUsedCBHD", Bundle.getMessage("WhereUsedSensor", osName, "SIDL")));  // NOI18N
274        }
275
276        // SWDI Sensors
277        if (cbhd._mSWDI_NormalInternalSensor.equals(foundSensor) ||
278                cbhd._mSWDI_ReversedInternalSensor.equals(foundSensor)) {
279            usageReport.add(new NamedBeanUsageReport("CtcWhereUsedCBHD", Bundle.getMessage("WhereUsedSensor", osName, "SWDI")));  // NOI18N
280        }
281
282        // SWDI Turnout
283        if (cbhd._mSWDI_ExternalTurnout.equals(foundTurnout)) {
284            usageReport.add(new NamedBeanUsageReport("CtcWhereUsedCBHD", Bundle.getMessage("WhereUsedTurnout", osName, "SWDI")));  // NOI18N
285        }
286
287        // SWDL Sensor
288        if (cbhd._mSWDL_InternalSensor.equals(foundSensor)) {
289            usageReport.add(new NamedBeanUsageReport("CtcWhereUsedCBHD", Bundle.getMessage("WhereUsedSensor", osName, "SWDL")));  // NOI18N
290        }
291
292        callOnDataUsage(cbhd, osName);
293        traffficLockingDataUsage(cbhd, osName);
294
295        // TUL Sensors
296        if (cbhd._mTUL_DispatcherInternalSensorLockToggle.equals(foundSensor) ||
297                cbhd._mTUL_DispatcherInternalSensorUnlockedIndicator.equals(foundSensor)) {
298            usageReport.add(new NamedBeanUsageReport("CtcWhereUsedCBHD", Bundle.getMessage("WhereUsedSensor", osName, "TUL")));  // NOI18N
299        }
300
301        // TUL Turnouts
302        if (cbhd._mTUL_ExternalTurnout.equals(foundTurnout) ||
303                cbhd._mTUL_AdditionalExternalTurnout1.equals(foundTurnout) ||
304                cbhd._mTUL_AdditionalExternalTurnout2.equals(foundTurnout) ||
305                cbhd._mTUL_AdditionalExternalTurnout3.equals(foundTurnout)) {
306            usageReport.add(new NamedBeanUsageReport("CtcWhereUsedCBHD", Bundle.getMessage("WhereUsedTurnout", osName, "TUL")));  // NOI18N
307        }
308
309        // IL Signals
310        cbhd._mIL_Signals.forEach(signal -> {
311            if (signal.equals(foundSignal)) {
312                usageReport.add(new NamedBeanUsageReport("CtcWhereUsedCBHD", Bundle.getMessage("WhereUsedSignal", osName, "IL")));  // NOI18N
313            }
314        });
315    }
316
317    void callOnDataUsage(CodeButtonHandlerData cbhd, String osName) {
318        // CO Sensor
319        if (cbhd._mCO_CallOnToggleInternalSensor.equals(foundSensor)) {
320            usageReport.add(new NamedBeanUsageReport("CtcWhereUsedCBHD", Bundle.getMessage("WhereUsedSensor", osName, "CO")));  // NOI18N
321        }
322        cbhd._mCO_GroupingsList.forEach(row -> {
323            // Sensor
324            if (row._mCalledOnExternalSensor.equals(foundSensor)) {
325                usageReport.add(new NamedBeanUsageReport("CtcWhereUsedCBHD", Bundle.getMessage("WhereUsedSensor", osName, "CO")));  // NOI18N
326            }
327
328            // Signal
329            if (row._mExternalSignal.equals(foundSignal)) {
330                usageReport.add(new NamedBeanUsageReport("CtcWhereUsedCBHD", Bundle.getMessage("WhereUsedSignal", osName, "CO")));  // NOI18N
331            }
332
333            // Block
334            if (row._mExternalBlock != null && row._mExternalBlock.getBean().equals(foundBlock)) {
335                usageReport.add(new NamedBeanUsageReport("CtcWhereUsedCBHD", Bundle.getMessage("WhereUsedBlock", osName, "CO")));  // NOI18N
336            }
337
338            // Switch indicator sensors
339            row._mSwitchIndicators.forEach(sw -> {
340                if (sw.equals(foundSensor)) {
341                    usageReport.add(new NamedBeanUsageReport("CtcWhereUsedCBHD", Bundle.getMessage("WhereUsedSensor", osName, "CO")));  // NOI18N
342                }
343            });
344        });
345    }
346
347    void traffficLockingDataUsage(CodeButtonHandlerData cbhd, String osName) {
348        cbhd._mTRL_LeftTrafficLockingRules.forEach(rule -> {
349            traffficLockingRuleDataUsage(rule, osName);
350        });
351
352        cbhd._mTRL_RightTrafficLockingRules.forEach(rule -> {
353            traffficLockingRuleDataUsage(rule, osName);
354        });
355    }
356
357    void traffficLockingRuleDataUsage(TrafficLockingData rule, String osName) {
358        // Signal -- _mDestinationSignalOrComment
359        if (foundSignal != null) {
360            if (rule._mDestinationSignalOrComment.equals(foundSignal.getHandleName())) {
361                usageReport.add(new NamedBeanUsageReport("CtcWhereUsedCBHD", Bundle.getMessage("WhereUsedSignal", osName, "TRL")));  // NOI18N
362            }
363        }
364
365        // Occupancy sensors
366        for (NBHSensor sensor : rule._mOccupancyExternalSensors) {
367            if (sensor.equals(foundSensor)) {
368                usageReport.add(new NamedBeanUsageReport("CtcWhereUsedCBHD", Bundle.getMessage("WhereUsedSensor", osName, "TRL")));  // NOI18N
369                break;
370            }
371        }
372
373        // Optional sensors
374        for (NBHSensor sensor : rule._mOptionalExternalSensors) {
375            if (sensor.equals(foundSensor)) {
376                usageReport.add(new NamedBeanUsageReport("CtcWhereUsedCBHD", Bundle.getMessage("WhereUsedSensor", osName, "TRL")));  // NOI18N
377                break;
378            }
379        }
380    }
381
382    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CtcManager.class);
383}