001package jmri.jmrit.blockboss;
002
003import java.beans.PropertyChangeEvent;
004import java.util.*;
005import javax.annotation.Nonnull;
006
007import jmri.InstanceManager;
008import jmri.NamedBean;
009import jmri.NamedBeanHandle;
010import jmri.NamedBeanUsageReport;
011import jmri.Sensor;
012import jmri.SignalHead;
013import jmri.Turnout;
014import jmri.jmrit.automat.Siglet;
015
016/**
017 * Drives the "simple signal" logic for one signal.
018 * <p>
019 * Signals "protect" by telling the engineer about the conditions ahead. The
020 * engineer controls the speed of the train based on what the signals show, and
021 * the signals in turn react to whether the track ahead is occupied, what
022 * signals further down the line show, etc.
023 * <p>
024 * There are four situations that this logic can handle:
025 * <ol>
026 * <li>SINGLEBLOCK - A simple block, without a turnout.
027 * <p>
028 * In this case, there is only a single set of sensors and a single next signal
029 * to protect.
030 * <li>TRAILINGMAIN - This signal is protecting a trailing point turnout, which
031 * can only be passed when the turnout is closed. It can also be used for the
032 * upper head of a two head signal on the facing end of the turnout.
033 * <p>
034 * In this case, the signal is forced red if the specified turnout is THROWN.
035 * When the turnout is CLOSED, there is a single set of sensors and next
036 * signal(s) to protect.
037 * <li>TRAILINGDIVERGING - This signal is protecting a trailing point turnout,
038 * which can only be passed when the turnout is thrown. It can also be used for
039 * the lower head of a two head signal on the facing end of the turnout.
040 * <p>
041 * In this case, the signal is forced red if the specified turnout is CLOSED.
042 * When the turnout is THROWN, there is a single set of sensors and next
043 * signal(s) to protect.
044 * <li>FACING - This single head signal protects a facing point turnout, which
045 * may therefore have two next signals and two sets of next sensors for the
046 * closed and thrown states of the turnout.
047 * <p>
048 * If the turnout is THROWN, one set of sensors and next signal(s) is protected.
049 * If the turnout is CLOSED, another set of sensors and next signal(s) is
050 * protected.
051 * </ol>
052 * <p>
053 * Note that these four possibilities logically require that certain information
054 * be configured consistently; e.g. not specifying a turnout in TRAILINGMAIN
055 * doesn't make any sense. That's not enforced explicitly, but violating it can
056 * result in confusing behavior.
057 *
058 * <p>
059 * The protected sensors should cover the track to the next signal. If any of
060 * the protected sensors show ACTIVE, the signal will be dropped to red.
061 * Normally, the protected sensors cover the occupancy of the track to the next
062 * signal. In this case, the signal will show red to prevent trains from
063 * entering an occupied stretch of track (often called a "block"). But the
064 * actual source of the sensors can be anything useful, for example a
065 * microswitch on a local turnout, etc.
066 * <p>
067 * There are several variants to how a next signal is protected. In the simplest
068 * form, the controlled signal provides a warning to the engineer of what the
069 * signal being protected will show when it becomes visible:
070 * <ul>
071 * <li>If the next signal is red, the engineer needs to be told to slow down;
072 * this signal will be set to yellow.
073 * <li>If the next signal is green, the engineer can proceed at track speed;
074 * this signal will be set to green.
075 * </ul>
076 * If the next signal is yellow, there are two possible variants that can be
077 * configured:
078 * <ul>
079 * <li>For the common "three-aspect" signaling system, an engineer doesn't need
080 * any warning before a yellow signal. In this case, this signal is set to green
081 * when the protected signal is yellow.
082 * <li>For lines where track speed is very fast or braking distances are very
083 * long, it can be useful to give engineers warning that the next signal is
084 * yellow (and the one after that is red) so that slowing the train can start
085 * early. Usually flashing yellow preceeds the yellow signal, and the system is
086 * called "four-aspect" signaling.
087 * </ul>
088 *
089 * <p>
090 * In some cases, you want a signal to show <i>exactly</I> what the next signal
091 * shows, instead of one speed faster. E.g. if the (protected) next signal is
092 * red, this one should be red, instead of yellow. In this case, this signal is
093 * called a "distant signal", as it provides a "distant" view of the protected
094 * signal heads's appearance. Note that when in this mode, this signal still protects
095 * the interveneing track, etc.
096 * <p>
097 * The "hold" unbound parameter can be used to set this logic to show red,
098 * regardless of input. That's intended for use with CTC logic, etc.
099 * <p>
100 * "Approach lit" signaling sets the signal head to dark (off) unless the
101 * specified sensor(s) are ACTIVE. Normally, those sensors are in front of
102 * (before) the signal head. The signal heads then only light when a train is
103 * approaching. This is used to preserve bulbs and batteries (and sometimes to
104 * reduce engineer workload) on prototype railroads, but is uncommon on model
105 * railroads; once the layout owner has gone to the trouble and expense of
106 * installing signals, he usually wants them lit up.
107 * <p>
108 * Two signal heads can be protected. For example, if the next signal has two
109 * heads to control travel onto a main track or siding, then both heads should
110 * be provided here. The <i>faster</i> signal aspect will control the appearance
111 * of this head. For example, if the next signal is showing a green head and a
112 * red head, this signal will be green, because the train will be able to
113 * proceed at track speed when it reaches that next signal (along the track with
114 * the green signal).
115 *
116 * @author Bob Jacobsen Copyright (C) 2003, 2005
117 * @author Dick Bronson 2006 Revisions to add facing point sensors, approach lighting
118 * and check box to limit speed.
119 */
120public class BlockBossLogic extends Siglet implements java.beans.VetoableChangeListener {
121
122    public static final int SINGLEBLOCK = 1;
123    public static final int TRAILINGMAIN = 2;
124    public static final int TRAILINGDIVERGING = 3;
125    public static final int FACING = 4;
126    private static final String BEAN_X_NOT_FOUND = "BeanXNotFound";
127    private static final String BEAN_NAME_SIGNAL_HEAD = "BeanNameSignalHead";
128    private static final String BEAN_NAME_SENSOR = "BeanNameSensor";
129
130    private int mode = 0;
131
132    /**
133     * Create an object to drive a specific signal head.
134     *
135     * @param name System or user name of the driven signal head, which must exist
136     */
137    public BlockBossLogic(@Nonnull String name) {
138        super(name + Bundle.getMessage("_BlockBossLogic"));
139        java.util.Objects.requireNonNull(name, "BlockBossLogic name cannot be null");
140        this.name = name;
141        log.trace("Create BBL {}", name);
142
143        jmri.InstanceManager.getDefault(jmri.SignalHeadManager.class).addVetoableChangeListener(this);
144        jmri.InstanceManager.turnoutManagerInstance().addVetoableChangeListener(this);
145        jmri.InstanceManager.sensorManagerInstance().addVetoableChangeListener(this);
146        SignalHead driveHead = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(name);
147        if (driveHead == null) {
148            log.warn(Bundle.getMessage(BEAN_X_NOT_FOUND, Bundle.getMessage(BEAN_NAME_SIGNAL_HEAD), name));
149            throw new IllegalArgumentException("SignalHead \"" + name + "\" does not exist");
150        }
151        driveSignal = nbhm.getNamedBeanHandle(name, driveHead);
152        java.util.Objects.requireNonNull(driveSignal, "driveSignal should not have been null");
153    }
154
155    /**
156     * The "driven signal" is controlled by this element.
157     *
158     * @return system name of the driven signal head
159     */
160    public @Nonnull String getDrivenSignal() {
161        java.util.Objects.requireNonNull(driveSignal, "driveSignal should not have been null");
162        String retVal = driveSignal.getName();
163        java.util.Objects.requireNonNull(retVal, "driveSignal system name should not have been null");
164        return retVal;
165    }
166
167    public @Nonnull NamedBeanHandle<SignalHead> getDrivenSignalNamedBean() {
168        java.util.Objects.requireNonNull(driveSignal, "driveSignal should have been null");
169        return driveSignal;
170    }
171
172    private final jmri.NamedBeanHandleManager nbhm = jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class);
173
174    public void setSensor1(String name) {
175        if (name == null || name.equals("")) {
176            watchSensor1 = null;
177            return;
178        }
179        try {
180            watchSensor1 = nbhm.getNamedBeanHandle(name, InstanceManager.sensorManagerInstance().provideSensor(name));
181        } catch (IllegalArgumentException ex) {
182            log.warn(Bundle.getMessage(BEAN_X_NOT_FOUND, Bundle.getMessage(BEAN_NAME_SENSOR) + "1", name));
183        }
184    }
185
186    public void setSensor2(String name) {
187        if (name == null || name.equals("")) {
188            watchSensor2 = null;
189            return;
190        }
191        try {
192            watchSensor2 = nbhm.getNamedBeanHandle(name, InstanceManager.sensorManagerInstance().provideSensor(name));
193        } catch (IllegalArgumentException ex) {
194            log.warn(Bundle.getMessage(BEAN_X_NOT_FOUND, Bundle.getMessage(BEAN_NAME_SENSOR) + "2", name));
195        }
196    }
197
198    public void setSensor3(String name) {
199        if (name == null || name.equals("")) {
200            watchSensor3 = null;
201            return;
202        }
203        try {
204            watchSensor3 = nbhm.getNamedBeanHandle(name, InstanceManager.sensorManagerInstance().provideSensor(name));
205        } catch (IllegalArgumentException ex) {
206            log.warn(Bundle.getMessage(BEAN_X_NOT_FOUND, Bundle.getMessage(BEAN_NAME_SENSOR) + "3", name));
207        }
208    }
209
210    public void setSensor4(String name) {
211        if (name == null || name.equals("")) {
212            watchSensor4 = null;
213            return;
214        }
215        try {
216            watchSensor4 = nbhm.getNamedBeanHandle(name, InstanceManager.sensorManagerInstance().provideSensor(name));
217        } catch (IllegalArgumentException ex) {
218            log.warn(Bundle.getMessage(BEAN_X_NOT_FOUND, Bundle.getMessage(BEAN_NAME_SENSOR) + "4", name));
219        }
220    }
221
222    public void setSensor5(String name) {
223        if (name == null || name.equals("")) {
224            watchSensor5 = null;
225            return;
226        }
227        try {
228            watchSensor5 = nbhm.getNamedBeanHandle(name, InstanceManager.sensorManagerInstance().provideSensor(name));
229        } catch (IllegalArgumentException ex) {
230            log.warn(Bundle.getMessage(BEAN_X_NOT_FOUND, Bundle.getMessage(BEAN_NAME_SENSOR) + "5", name));
231        }
232    }
233
234    /**
235     * Get the system name of the sensors 1-5 being monitored.
236     *
237     * @return system name; null if no sensor configured
238     */
239    public String getSensor1() {
240        if (watchSensor1 == null) {
241            return null;
242        }
243        return watchSensor1.getName();
244    }
245
246    public String getSensor2() {
247        if (watchSensor2 == null) {
248            return null;
249        }
250        return watchSensor2.getName();
251    }
252
253    public String getSensor3() {
254        if (watchSensor3 == null) {
255            return null;
256        }
257        return watchSensor3.getName();
258    }
259
260    public String getSensor4() {
261        if (watchSensor4 == null) {
262            return null;
263        }
264        return watchSensor4.getName();
265    }
266
267    public String getSensor5() {
268        if (watchSensor5 == null) {
269            return null;
270        }
271        return watchSensor5.getName();
272    }
273
274    public void setTurnout(String name) {
275        if (name == null || name.equals("")) {
276            watchTurnout = null;
277            return;
278        }
279        try {
280            watchTurnout = nbhm.getNamedBeanHandle(name, InstanceManager.turnoutManagerInstance().provideTurnout(name));
281        } catch (IllegalArgumentException ex) {
282            log.warn(Bundle.getMessage(BEAN_X_NOT_FOUND, Bundle.getMessage("BeanNameTurnout"), name));
283        }
284    }
285
286    /**
287     * Get the system name of the turnout being monitored.
288     *
289     * @return system name; null if no turnout configured
290     */
291    public String getTurnout() {
292        if (watchTurnout == null) {
293            return null;
294        }
295        return watchTurnout.getName();
296    }
297
298    public void setMode(int mode) {
299        this.mode = mode;
300    }
301
302    public int getMode() {
303        return mode;
304    }
305
306    private String comment;
307
308    public void setComment(String comment) {
309        this.comment = comment;
310    }
311
312    public String getComment() {
313        return this.comment;
314    }
315
316    public void setWatchedSignal1(String name, boolean useFlash) {
317        if (name == null || name.equals("")) {
318            watchedSignal1 = null;
319            return;
320        }
321        SignalHead head = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(name);
322        if (head != null) {
323            watchedSignal1 = nbhm.getNamedBeanHandle(name, head);
324        } else {
325            log.warn(Bundle.getMessage(BEAN_X_NOT_FOUND, Bundle.getMessage(BEAN_NAME_SIGNAL_HEAD), name));
326            watchedSignal1 = null;
327        }
328        protectWithFlashing = useFlash;
329    }
330
331    /**
332     * Get the system name of the signal head being monitored for first route.
333     *
334     * @return system name; null if no primary signal head is configured
335     */
336    public String getWatchedSignal1() {
337        if (watchedSignal1 == null) {
338            return null;
339        }
340        return watchedSignal1.getName();
341    }
342
343    public void setWatchedSignal1Alt(String name) {
344        if (name == null || name.equals("")) {
345            watchedSignal1Alt = null;
346            return;
347        }
348        SignalHead head = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(name);
349        if (head != null) {
350            watchedSignal1Alt = nbhm.getNamedBeanHandle(name, head);
351        } else {
352            log.warn(Bundle.getMessage(BEAN_X_NOT_FOUND, Bundle.getMessage(BEAN_NAME_SIGNAL_HEAD), name));
353            watchedSignal1Alt = null;
354        }
355    }
356
357    /**
358     * Get the system name of the alternate signal head being monitored for first
359     * route.
360     *
361     * @return system name; null if no signal head is configured
362     */
363    public String getWatchedSignal1Alt() {
364        if (watchedSignal1Alt == null) {
365            return null;
366        }
367        return watchedSignal1Alt.getName();
368    }
369
370    public void setWatchedSignal2(String name) {
371        if (name == null || name.equals("")) {
372            watchedSignal2 = null;
373            return;
374        }
375        SignalHead head = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(name);
376        if (head != null) {
377            watchedSignal2 = nbhm.getNamedBeanHandle(name, head);
378        } else {
379            log.warn(Bundle.getMessage(BEAN_X_NOT_FOUND, Bundle.getMessage(BEAN_NAME_SIGNAL_HEAD), name));
380            watchedSignal2 = null;
381        }
382    }
383
384    /**
385     * Get the system name of the signal head being monitored for the 2nd route.
386     *
387     * @return system name; null if no signal head is configured
388     */
389    public String getWatchedSignal2() {
390        if (watchedSignal2 == null) {
391            return null;
392        }
393        return watchedSignal2.getName();
394    }
395
396    public void setWatchedSignal2Alt(String name) {
397        if (name == null || name.equals("")) {
398            watchedSignal2Alt = null;
399            return;
400        }
401        SignalHead head = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(name);
402        if (head != null) {
403            watchedSignal2Alt = nbhm.getNamedBeanHandle(name, head);
404        } else {
405            log.warn(Bundle.getMessage(BEAN_X_NOT_FOUND, Bundle.getMessage(BEAN_NAME_SIGNAL_HEAD), name));
406            watchedSignal2Alt = null;
407        }
408    }
409
410    /**
411     * Get the system name of the secondary signal head being monitored for the
412     * 2nd route.
413     *
414     * @return system name; null if no secondary signal head is configured
415     */
416    public String getWatchedSignal2Alt() {
417        if (watchedSignal2Alt == null) {
418            return null;
419        }
420        return watchedSignal2Alt.getName();
421    }
422
423    public void setWatchedSensor1(String name) {
424        if (name == null || name.equals("")) {
425            watchedSensor1 = null;
426            return;
427        }
428        try {
429            watchedSensor1 = nbhm.getNamedBeanHandle(name, InstanceManager.sensorManagerInstance().provideSensor(name));
430        } catch (IllegalArgumentException ex) {
431            log.warn(Bundle.getMessage(BEAN_X_NOT_FOUND, Bundle.getMessage(BEAN_NAME_SENSOR) + "1", name));
432            watchedSensor1 = null;
433        }
434    }
435
436    /**
437     * Get the original name of the sensor1 being monitored.
438     *
439     * @return original name; null if no sensor is configured
440     */
441    public String getWatchedSensor1() {
442        if (watchedSensor1 == null) {
443            return null;
444        }
445        return watchedSensor1.getName();
446    }
447
448    public void setWatchedSensor1Alt(String name) {
449        if (name == null || name.equals("")) {
450            watchedSensor1Alt = null;
451            return;
452        }
453        try {
454            watchedSensor1Alt = nbhm.getNamedBeanHandle(name, InstanceManager.sensorManagerInstance().provideSensor(name));
455        } catch (IllegalArgumentException ex) {
456            log.warn(Bundle.getMessage(BEAN_X_NOT_FOUND, Bundle.getMessage(BEAN_NAME_SENSOR) + "1Alt", name));
457            watchedSensor1Alt = null;
458        }
459    }
460
461    /**
462     * Get the system name of the sensor1Alt being monitored.
463     *
464     * @return system name; null if no sensor is configured
465     */
466    public String getWatchedSensor1Alt() {
467        if (watchedSensor1Alt == null) {
468            return null;
469        }
470        return watchedSensor1Alt.getName();
471    }
472
473    public void setWatchedSensor2(String name) {
474        if (name == null || name.equals("")) {
475            watchedSensor2 = null;
476            return;
477        }
478        try {
479            watchedSensor2 = nbhm.getNamedBeanHandle(name, InstanceManager.sensorManagerInstance().provideSensor(name));
480        } catch (IllegalArgumentException ex) {
481            log.warn(Bundle.getMessage(BEAN_X_NOT_FOUND, Bundle.getMessage(BEAN_NAME_SENSOR) + "2", name));
482            watchedSensor2 = null;
483        }
484    }
485
486    /**
487     * Get the system name of the sensor2 being monitored.
488     *
489     * @return system name; null if no sensor is configured
490     */
491    public String getWatchedSensor2() {
492        if (watchedSensor2 == null) {
493            return null;
494        }
495        return watchedSensor2.getName();
496    }
497
498    public void setWatchedSensor2Alt(String name) {
499        if (name == null || name.equals("")) {
500            watchedSensor2Alt = null;
501            return;
502        }
503        try {
504            watchedSensor2Alt = nbhm.getNamedBeanHandle(name, InstanceManager.sensorManagerInstance().provideSensor(name));
505        } catch (IllegalArgumentException ex) {
506            log.warn(Bundle.getMessage(BEAN_X_NOT_FOUND, Bundle.getMessage(BEAN_NAME_SENSOR) + "2Alt", name));
507            watchedSensor2Alt = null;
508        }
509    }
510
511    /**
512     * Get the system name of the sensor2Alt being monitored.
513     *
514     * @return system name; null if no sensor is configured
515     */
516    public String getWatchedSensor2Alt() {
517        if (watchedSensor2Alt == null) {
518            return null;
519        }
520        return watchedSensor2Alt.getName();
521    }
522
523    public void setLimitSpeed1(boolean d) {
524        limitSpeed1 = d;
525    }
526
527    public boolean getLimitSpeed1() {
528        return limitSpeed1;
529    }
530
531    public void setRestrictingSpeed1(boolean d) {
532        restrictingSpeed1 = d;
533    }
534
535    public boolean getRestrictingSpeed1() {
536        return restrictingSpeed1;
537    }
538
539    public void setLimitSpeed2(boolean d) {
540        limitSpeed2 = d;
541    }
542
543    public boolean getLimitSpeed2() {
544        return limitSpeed2;
545    }
546
547    public void setRestrictingSpeed2(boolean d) {
548        restrictingSpeed2 = d;
549    }
550
551    public boolean getRestrictingSpeed2() {
552        return restrictingSpeed2;
553    }
554
555    public boolean getUseFlash() {
556        return protectWithFlashing;
557    }
558
559    public void setDistantSignal(boolean d) {
560        distantSignal = d;
561    }
562
563    public boolean getDistantSignal() {
564        return distantSignal;
565    }
566
567    private boolean mHold = false;
568
569    /**
570     * Provide the current value of the "hold" parameter.
571     * <p>
572     * If true, the output is forced to a RED "stop" appearance.
573     * This allows CTC and other higher-level functions to control
574     * permission to enter this section of track.
575     *
576     * @return true if this Logic currently is Held
577     */
578    private boolean getHold() {
579        return mHold;
580    }
581
582    /**
583     * Set the current value of the "hold" parameter.
584     * If true, the output is forced to a RED "stop" appearance.
585     * This allows CTC and other higher-level functions to
586     * control permission to enter this section of track.
587     *
588     * @param m true to set Logic to Held
589     */
590    public void setHold(boolean m) {
591        mHold = m;
592        setOutput();  // to invoke the new state
593    }
594
595    private final String name;
596
597    @Nonnull NamedBeanHandle<SignalHead> driveSignal;
598
599    private NamedBeanHandle<Sensor> watchSensor1 = null;
600    private NamedBeanHandle<Sensor> watchSensor2 = null;
601    private NamedBeanHandle<Sensor> watchSensor3 = null;
602    private NamedBeanHandle<Sensor> watchSensor4 = null;
603    private NamedBeanHandle<Sensor> watchSensor5 = null;
604    private NamedBeanHandle<Turnout> watchTurnout = null;
605    private NamedBeanHandle<SignalHead> watchedSignal1 = null;
606    private NamedBeanHandle<SignalHead> watchedSignal1Alt = null;
607    private NamedBeanHandle<SignalHead> watchedSignal2 = null;
608    private NamedBeanHandle<SignalHead> watchedSignal2Alt = null;
609    private NamedBeanHandle<Sensor> watchedSensor1 = null;
610    private NamedBeanHandle<Sensor> watchedSensor1Alt = null;
611    private NamedBeanHandle<Sensor> watchedSensor2 = null;
612    private NamedBeanHandle<Sensor> watchedSensor2Alt = null;
613    private NamedBeanHandle<Sensor> approachSensor1 = null;
614
615    private boolean limitSpeed1 = false;
616    private boolean restrictingSpeed1 = false;
617    private boolean limitSpeed2 = false;
618    private boolean restrictingSpeed2 = false;
619    private boolean protectWithFlashing = false;
620    private boolean distantSignal = false;
621
622    public void setApproachSensor1(String name) {
623        if (name == null || name.equals("")) {
624            approachSensor1 = null;
625            return;
626        }
627        approachSensor1 = nbhm.getNamedBeanHandle(name, InstanceManager.sensorManagerInstance().provideSensor(name));
628        if (approachSensor1.getBean() == null) {
629            log.warn(Bundle.getMessage(BEAN_X_NOT_FOUND, Bundle.getMessage("Approach_Sensor1_"), name));
630        }
631    }
632
633    /**
634     * Get the system name of the sensor being monitored.
635     *
636     * @return system name; null if no sensor configured
637     */
638    public String getApproachSensor1() {
639        if (approachSensor1 == null) {
640            return null;
641        }
642        return approachSensor1.getName();
643    }
644
645    /**
646     * Define the siglet's input and output.
647     */
648    @Override
649    public void defineIO() {
650        List<NamedBean> namedBeanList = new ArrayList<>();
651
652        addBeanToListIfItExists(namedBeanList,watchTurnout);
653        addBeanToListIfItExists(namedBeanList,watchSensor1);
654        addBeanToListIfItExists(namedBeanList,watchSensor2);
655        addBeanToListIfItExists(namedBeanList,watchSensor3);
656        addBeanToListIfItExists(namedBeanList,watchSensor4);
657        addBeanToListIfItExists(namedBeanList,watchSensor5);
658        addBeanToListIfItExists(namedBeanList,watchedSignal1);
659        addBeanToListIfItExists(namedBeanList,watchedSignal1Alt);
660        addBeanToListIfItExists(namedBeanList,watchedSignal2);
661        addBeanToListIfItExists(namedBeanList,watchedSignal2Alt);
662        addBeanToListIfItExists(namedBeanList,watchedSensor1);
663        addBeanToListIfItExists(namedBeanList,watchedSensor1Alt);
664        addBeanToListIfItExists(namedBeanList,watchedSensor2);
665        addBeanToListIfItExists(namedBeanList,watchedSensor2Alt);
666        addBeanToListIfItExists(namedBeanList,approachSensor1);
667
668        // copy temp to definitive inputs
669        inputs = namedBeanList.toArray(new NamedBean[1]);
670
671        outputs = new NamedBean[]{driveSignal.getBean()};
672
673        // also need to act if the _signal's_ "held"
674        // parameter changes, but we don't want to
675        // act if the signals appearance changes (to
676        // avoid a loop, or avoid somebody changing appearance
677        // manually and having it instantly recomputed & changed back
678        driveSignal.getBean().addPropertyChangeListener(e -> {
679            if (e.getPropertyName().equals(Bundle.getMessage("Held"))) {
680                setOutput();
681            }
682        }, driveSignal.getName(), "BlockBossLogic:" + name);
683    }
684
685    private void addBeanToListIfItExists(List<NamedBean> namedBeanList, NamedBeanHandle<?> namedBeanHandle) {
686        if (namedBeanHandle != null) {
687            namedBeanList.add(namedBeanHandle.getBean());
688        }
689    }
690
691    /**
692     * Recompute new output state and apply it.
693     */
694    @Override
695    public void setOutput() {
696
697        log.trace("setOutput for {}", name);
698
699        // make sure init is complete
700        if ((outputs == null) || (outputs[0] == null)) {
701            return;
702        }
703
704        // if "hold" is true, must show red
705        if (getHold()) {
706            ((SignalHead) outputs[0]).setAppearance(SignalHead.RED);
707            log.debug("setOutput red due to held for {}", name);
708            return;
709        }
710
711        // otherwise, process algorithm
712        switch (mode) {
713            case SINGLEBLOCK:
714                doSingleBlock();
715                break;
716            case TRAILINGMAIN:
717                doTrailingMain();
718                break;
719            case TRAILINGDIVERGING:
720                doTrailingDiverging();
721                break;
722            case FACING:
723                doFacing();
724                break;
725            default:
726                log.error("{}{}_Signal_{}", Bundle.getMessage("UnexpectedMode"), mode, getDrivenSignal());
727        }
728    }
729
730    private int fastestColor1() {
731        int result = SignalHead.RED;
732        // special case:  GREEN if no next signal
733        if (watchedSignal1 == null && watchedSignal1Alt == null) {
734            result = SignalHead.GREEN;
735        }
736
737        int val = result;
738        if (watchedSignal1 != null) {
739            val = watchedSignal1.getBean().getAppearance();
740        }
741        if (watchedSignal1 != null && watchedSignal1.getBean().getHeld()) {
742            val = SignalHead.RED;  // if Held, act as if Red
743        }
744        int valAlt = result;
745        if (watchedSignal1Alt != null) {
746            valAlt = watchedSignal1Alt.getBean().getAppearance();
747        }
748        if (watchedSignal1Alt != null && watchedSignal1Alt.getBean().getHeld()) {
749            valAlt = SignalHead.RED; // if Held, act as if Red
750        }
751        return fasterOf(val, valAlt);
752    }
753
754    private int fastestColor2() {
755        int result = SignalHead.RED;
756        // special case:  GREEN if no next signal
757        if (watchedSignal2 == null && watchedSignal2Alt == null) {
758            result = SignalHead.GREEN;
759        }
760
761        int val = result;
762        if (watchedSignal2 != null) {
763            val = watchedSignal2.getBean().getAppearance();
764        }
765        if (watchedSignal2 != null && watchedSignal2.getBean().getHeld()) {
766            val = SignalHead.RED;
767        }
768
769        int valAlt = result;
770        if (watchedSignal2Alt != null) {
771            valAlt = watchedSignal2Alt.getBean().getAppearance();
772        }
773        if (watchedSignal2Alt != null && watchedSignal2Alt.getBean().getHeld()) {
774            valAlt = SignalHead.RED;
775        }
776
777        return fasterOf(val, valAlt);
778    }
779
780    /**
781     * Given two {@link SignalHead} color constants, return the one
782     * corresponding to the slower speed.
783     *
784     * @param a color constant 1 to compare with
785     * @param b color constant 2
786     * @return the lowest of the two values entered
787     */
788    private static int slowerOf(int a, int b) {
789        // DARK is smallest, FLASHING GREEN is largest
790        return Math.min(a, b);
791    }
792
793    /**
794     * Given two {@link SignalHead} color constants, return the one
795     * corresponding to the faster speed.
796     *
797     * @param a color constant 1 to compare with
798     * @param b color constant 2
799     * @return the highest of the two values entered
800     */
801    private static int fasterOf(int a, int b) {
802        // DARK is smallest, FLASHING GREEN is largest
803        return Math.max(a, b);
804    }
805
806    private void doSingleBlock() {
807        int appearance = SignalHead.GREEN;
808        int oldAppearance = ((SignalHead) outputs[0]).getAppearance();
809        // check for yellow, flashing yellow overriding green
810        if (protectWithFlashing && fastestColor1() == SignalHead.YELLOW) {
811            appearance = SignalHead.FLASHYELLOW;
812        }
813        if (fastestColor1() == SignalHead.RED || fastestColor1() == SignalHead.FLASHRED) {
814            appearance = SignalHead.YELLOW;
815        }
816
817        // if distant signal, show exactly what the home signal does
818        if (distantSignal) {
819            appearance = fastestColor1();
820        }
821
822        // if limited speed and green, reduce to yellow
823        if (limitSpeed1) {
824            appearance = slowerOf(appearance, SignalHead.YELLOW);
825        }
826
827        // if restricting, limit to flashing red
828        if (restrictingSpeed1) {
829            appearance = slowerOf(appearance, SignalHead.FLASHRED);
830        }
831
832        // check for red overriding yellow or green
833        if (watchSensor1 != null && watchSensor1.getBean().getKnownState() != Sensor.INACTIVE) {
834            appearance = SignalHead.RED;
835        }
836        if (watchSensor2 != null && watchSensor2.getBean().getKnownState() != Sensor.INACTIVE) {
837            appearance = SignalHead.RED;
838        }
839        if (watchSensor3 != null && watchSensor3.getBean().getKnownState() != Sensor.INACTIVE) {
840            appearance = SignalHead.RED;
841        }
842        if (watchSensor4 != null && watchSensor4.getBean().getKnownState() != Sensor.INACTIVE) {
843            appearance = SignalHead.RED;
844        }
845        if (watchSensor5 != null && watchSensor5.getBean().getKnownState() != Sensor.INACTIVE) {
846            appearance = SignalHead.RED;
847        }
848
849        // check if signal if held, forcing a red appearance by this calculation
850        if (((SignalHead) outputs[0]).getHeld()) {
851            appearance = SignalHead.RED;
852        }
853
854        // handle approach lighting
855        doApproach();
856
857        // show result if changed
858        if (appearance != oldAppearance) {
859            ((SignalHead) outputs[0]).setAppearance(appearance);
860            log.debug("Change appearance of {} to: {}", name, appearance);
861        }
862    }
863
864    private void doTrailingMain() {
865        int appearance = SignalHead.GREEN;
866        int oldAppearance = ((SignalHead) outputs[0]).getAppearance();
867        // check for yellow, flashing yellow overriding green
868        if (protectWithFlashing && fastestColor1() == SignalHead.YELLOW) {
869            appearance = SignalHead.FLASHYELLOW;
870        }
871        if (fastestColor1() == SignalHead.RED || fastestColor1() == SignalHead.FLASHRED) {
872            appearance = SignalHead.YELLOW;
873        }
874
875        // if distant signal, show exactly what the home signal does
876        if (distantSignal) {
877            appearance = fastestColor1();
878        }
879
880        // if limited speed and green, reduce to yellow
881        if (limitSpeed1) {
882            appearance = slowerOf(appearance, SignalHead.YELLOW);
883        }
884        // if restricting, limit to flashing red
885        if (restrictingSpeed1) {
886            appearance = slowerOf(appearance, SignalHead.FLASHRED);
887        }
888
889        // check for red overriding yellow or green
890        if (watchSensor1 != null && watchSensor1.getBean().getKnownState() != Sensor.INACTIVE) {
891            appearance = SignalHead.RED;
892        }
893        if (watchSensor2 != null && watchSensor2.getBean().getKnownState() != Sensor.INACTIVE) {
894            appearance = SignalHead.RED;
895        }
896        if (watchSensor3 != null && watchSensor3.getBean().getKnownState() != Sensor.INACTIVE) {
897            appearance = SignalHead.RED;
898        }
899        if (watchSensor4 != null && watchSensor4.getBean().getKnownState() != Sensor.INACTIVE) {
900            appearance = SignalHead.RED;
901        }
902        if (watchSensor5 != null && watchSensor5.getBean().getKnownState() != Sensor.INACTIVE) {
903            appearance = SignalHead.RED;
904        }
905
906        if (watchTurnout != null && watchTurnout.getBean().getKnownState() != Turnout.CLOSED) {
907            appearance = SignalHead.RED;
908        }
909        if (watchTurnout != null && watchTurnout.getBean().getCommandedState() != Turnout.CLOSED) {
910            appearance = SignalHead.RED;
911        }
912
913        // check if signal if held, forcing a red appearance by this calculation
914        if (((SignalHead) outputs[0]).getHeld()) {
915            appearance = SignalHead.RED;
916        }
917
918        // handle approach lighting
919        doApproach();
920
921        // show result if changed
922        if (appearance != oldAppearance) {
923            ((SignalHead) outputs[0]).setAppearance(appearance);
924            log.debug("Change appearance of {} to:{}", name, appearance);
925        }
926    }
927
928    private void doTrailingDiverging() {
929        int appearance = SignalHead.GREEN;
930        int oldAppearance = ((SignalHead) outputs[0]).getAppearance();
931        // check for yellow, flashing yellow overriding green
932        if (protectWithFlashing && fastestColor1() == SignalHead.YELLOW) {
933            appearance = SignalHead.FLASHYELLOW;
934        }
935        if (fastestColor1() == SignalHead.RED || fastestColor1() == SignalHead.FLASHRED) {
936            appearance = SignalHead.YELLOW;
937        }
938
939        // if distant signal, show exactly what the home signal does
940        if (distantSignal) {
941            appearance = fastestColor1();
942        }
943
944        // if limited speed and green, reduce to yellow
945        if (limitSpeed2) {
946            appearance = slowerOf(appearance, SignalHead.YELLOW);
947        }
948        // if restricting, limit to flashing red
949        if (restrictingSpeed2) {
950            appearance = slowerOf(appearance, SignalHead.FLASHRED);
951        }
952
953        // check for red overriding yellow or green
954        if (watchSensor1 != null && watchSensor1.getBean().getKnownState() != Sensor.INACTIVE) {
955            appearance = SignalHead.RED;
956        }
957        if (watchSensor2 != null && watchSensor2.getBean().getKnownState() != Sensor.INACTIVE) {
958            appearance = SignalHead.RED;
959        }
960        if (watchSensor3 != null && watchSensor3.getBean().getKnownState() != Sensor.INACTIVE) {
961            appearance = SignalHead.RED;
962        }
963        if (watchSensor4 != null && watchSensor4.getBean().getKnownState() != Sensor.INACTIVE) {
964            appearance = SignalHead.RED;
965        }
966        if (watchSensor5 != null && watchSensor5.getBean().getKnownState() != Sensor.INACTIVE) {
967            appearance = SignalHead.RED;
968        }
969
970        if (watchTurnout != null && watchTurnout.getBean().getKnownState() != Turnout.THROWN) {
971            appearance = SignalHead.RED;
972        }
973        if (watchTurnout != null && watchTurnout.getBean().getCommandedState() != Turnout.THROWN) {
974            appearance = SignalHead.RED;
975        }
976
977        // check if signal if held, forcing a red appearance by this calculation
978        if (((SignalHead) outputs[0]).getHeld()) {
979            appearance = SignalHead.RED;
980        }
981
982        // handle approach lighting
983        doApproach();
984
985        // show result if changed
986        if (appearance != oldAppearance) {
987            ((SignalHead) outputs[0]).setAppearance(appearance);
988            if (log.isDebugEnabled()) {
989                log.debug("Change appearance of {} to: {}", name, appearance);
990            }
991        }
992    }
993
994    private void doFacing() {
995        int appearance = SignalHead.GREEN;
996        int oldAppearance = ((SignalHead) outputs[0]).getAppearance();
997
998        // find downstream appearance, being pessimistic if we're not sure of the state
999        int s = SignalHead.GREEN;
1000        if (watchTurnout != null && watchTurnout.getBean().getKnownState() != Turnout.THROWN) {
1001            s = slowerOf(s, fastestColor1());
1002        }
1003        if (watchTurnout != null && watchTurnout.getBean().getKnownState() != Turnout.CLOSED) {
1004            s = slowerOf(s, fastestColor2());
1005        }
1006
1007        // check for yellow, flashing yellow overriding green
1008        if (protectWithFlashing && s == SignalHead.YELLOW) {
1009            appearance = SignalHead.FLASHYELLOW;
1010        }
1011        if (s == SignalHead.RED || s == SignalHead.FLASHRED) {
1012            appearance = SignalHead.YELLOW;
1013        }
1014        // if distant signal, show exactly what the home signal does
1015        if (distantSignal) {
1016            appearance = s;
1017        }
1018
1019        // if limited speed and green or flashing yellow, reduce to yellow
1020        if (watchTurnout != null && limitSpeed1 && watchTurnout.getBean().getKnownState() != Turnout.THROWN) {
1021            appearance = slowerOf(appearance, SignalHead.YELLOW);
1022        }
1023        if (watchTurnout != null && limitSpeed2 && watchTurnout.getBean().getKnownState() != Turnout.CLOSED) {
1024            appearance = slowerOf(appearance, SignalHead.YELLOW);
1025        }
1026        // if restricting, limit to flashing red
1027        if (watchTurnout != null && restrictingSpeed1 && watchTurnout.getBean().getKnownState() != Turnout.THROWN) {
1028            appearance = slowerOf(appearance, SignalHead.FLASHRED);
1029        }
1030        if (watchTurnout != null && restrictingSpeed2 && watchTurnout.getBean().getKnownState() != Turnout.CLOSED) {
1031            appearance = slowerOf(appearance, SignalHead.FLASHRED);
1032        }
1033
1034        // check for red overriding yellow or green
1035        if (watchSensor1 != null && watchSensor1.getBean().getKnownState() != Sensor.INACTIVE) {
1036            appearance = SignalHead.RED;
1037        }
1038        if (watchSensor2 != null && watchSensor2.getBean().getKnownState() != Sensor.INACTIVE) {
1039            appearance = SignalHead.RED;
1040        }
1041        if (watchSensor3 != null && watchSensor3.getBean().getKnownState() != Sensor.INACTIVE) {
1042            appearance = SignalHead.RED;
1043        }
1044        if (watchSensor4 != null && watchSensor4.getBean().getKnownState() != Sensor.INACTIVE) {
1045            appearance = SignalHead.RED;
1046        }
1047        if (watchSensor5 != null && watchSensor5.getBean().getKnownState() != Sensor.INACTIVE) {
1048            appearance = SignalHead.RED;
1049        }
1050
1051        if ((watchTurnout != null && watchTurnout.getBean().getKnownState() == Turnout.CLOSED)
1052                && (watchedSensor1 != null && watchedSensor1.getBean().getKnownState() != Sensor.INACTIVE)) {
1053            appearance = SignalHead.RED;
1054        }
1055        if ((watchTurnout != null && watchTurnout.getBean().getKnownState() == Turnout.CLOSED) &&
1056                (watchedSensor1Alt != null && watchedSensor1Alt.getBean().getKnownState() != Sensor.INACTIVE)) {
1057            appearance = SignalHead.RED;
1058        }
1059        if ((watchTurnout != null && watchTurnout.getBean().getKnownState() == Turnout.THROWN) &&
1060                (watchedSensor2 != null && watchedSensor2.getBean().getKnownState() != Sensor.INACTIVE)) {
1061            appearance = SignalHead.RED;
1062        }
1063        if ((watchTurnout != null && watchTurnout.getBean().getKnownState() == Turnout.THROWN) &&
1064                (watchedSensor2Alt != null && watchedSensor2Alt.getBean().getKnownState() != Sensor.INACTIVE)) {
1065            appearance = SignalHead.RED;
1066        }
1067
1068        // check if turnout in motion, if so force red
1069        if (watchTurnout != null && (watchTurnout.getBean().getKnownState() != watchTurnout.getBean().getCommandedState())) {
1070            appearance = SignalHead.RED;
1071        }
1072        if (watchTurnout != null && (watchTurnout.getBean().getKnownState() != Turnout.THROWN) && (watchTurnout.getBean().getKnownState() != Turnout.CLOSED)) // checking for other states
1073        {
1074            appearance = SignalHead.RED;
1075        }
1076
1077        // check if signal if held, forcing a red appearance by this calculation
1078        if (((SignalHead) outputs[0]).getHeld()) {
1079            appearance = SignalHead.RED;
1080        }
1081
1082        // handle approach lighting
1083        doApproach();
1084
1085        // show result if changed
1086        if (appearance != oldAppearance) {
1087            ((SignalHead) outputs[0]).setAppearance(appearance);
1088        }
1089    }
1090
1091    /**
1092     * Handle the approach lighting logic for all modes.
1093     */
1094    private void doApproach() {
1095        if (approachSensor1 != null && approachSensor1.getBean().getKnownState() == Sensor.INACTIVE) {
1096            // should not be lit
1097            if (driveSignal.getBean().getLit()) {
1098                driveSignal.getBean().setLit(false);
1099            }
1100        } else {
1101            // should be lit
1102            if (!driveSignal.getBean().getLit()) {
1103                driveSignal.getBean().setLit(true);
1104            }
1105        }
1106    }
1107
1108    /**
1109     * @return an enumeration of the collection of BlockBossLogic objects.
1110     * @deprecated Since 4.21.1 use {@link BlockBossLogicProvider#provideAll()} instead.
1111     */
1112    @Deprecated
1113    public static Enumeration<BlockBossLogic> entries() {
1114        return Collections.enumeration(InstanceManager.getDefault(BlockBossLogicProvider.class).provideAll());
1115    }
1116
1117    /**
1118     * Ensure that this BlockBossLogic object is available for later retrieval.
1119     * @deprecated Since 4.21.1 use {@link BlockBossLogicProvider#register(BlockBossLogic)} instead.
1120     */
1121    @Deprecated
1122    public void retain() {
1123        InstanceManager.getDefault(BlockBossLogicProvider.class).register(this);
1124    }
1125
1126    /**
1127     * Get the BlockBossLogic item governing a specific signal head by its name,
1128     * having removed it from use.
1129     *
1130     * @param signal name of the signal head object
1131     * @return never null
1132     */
1133    @Nonnull
1134    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE",
1135                        justification="enforced dynamically, too hard to prove statically")
1136    public static BlockBossLogic getStoppedObject(String signal) {
1137        // As a static requirement, the signal head must exist, but
1138        // we can't express that statically.  We test it dynamically.
1139        SignalHead sh = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(signal);
1140        java.util.Objects.requireNonNull(sh, "signal head must exist");
1141        return getStoppedObject(sh);
1142    }
1143
1144    /**
1145     * Get the BlockBossLogic item governing a specific signal head, having
1146     * removed it from use.
1147     *
1148     * @param sh signal head object
1149     * @return never null
1150     */
1151    @Nonnull
1152    public static BlockBossLogic getStoppedObject(@Nonnull SignalHead sh) {
1153        BlockBossLogic b = InstanceManager.getDefault(BlockBossLogicProvider.class).provide(sh);
1154        b.stop();
1155        return b;
1156    }
1157
1158    /**
1159     * Get the BlockBossLogic item governing a specific signal head located from its name.
1160     * <p>
1161     * Unlike {@link BlockBossLogic#getStoppedObject(String signal)} this does
1162     * not remove the object from being used.
1163     *
1164     * @param signal SignalHead system or user name
1165     * @return never null - creates new object if none exists
1166     * @deprecated Since 4.21.1 use {@link BlockBossLogicProvider#provide(String)} instead.
1167     */
1168    @Nonnull
1169    @Deprecated
1170    public static BlockBossLogic getExisting(@Nonnull String signal) {
1171        return InstanceManager.getDefault(BlockBossLogicProvider.class).provide(signal);
1172    }
1173
1174    /**
1175     * Get the BlockBossLogic item governing a specific signal head object.
1176     * <p>
1177     * Unlike {@link BlockBossLogic#getStoppedObject(String signal)} this does
1178     * not remove the object from being used.
1179     *
1180     * @param sh Existing SignalHead object
1181     * @return never null - creates new object if none exists
1182     * @deprecated Since 4.21.1 use {@link BlockBossLogicProvider#provide(SignalHead)} instead.
1183     */
1184    @Nonnull
1185    @Deprecated
1186    public static BlockBossLogic getExisting(@Nonnull SignalHead sh) {
1187        return InstanceManager.getDefault(BlockBossLogicProvider.class).provide(sh);
1188    }
1189
1190    @Override
1191    public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException {
1192        NamedBean nb = (NamedBean) evt.getOldValue();
1193        if ("CanDelete".equals(evt.getPropertyName())) { // NOI18N
1194            processCanDelete(evt, nb);
1195        } else if ("DoDelete".equals(evt.getPropertyName())) { // NOI18N
1196            processDoDelete(nb);
1197        }
1198    }
1199
1200    private void processDoDelete(NamedBean nb) {
1201        if (nb instanceof SignalHead) {
1202            deleteSignalHead(nb);
1203        } else if (nb instanceof Turnout) {
1204            deleteTurnout(nb);
1205        } else if (nb instanceof Sensor) {
1206            deleteSensor(nb);
1207        }
1208    }
1209
1210    private void deleteSensor(NamedBean nb) {
1211        if (watchSensor1 != null && watchSensor1.getBean().equals(nb)) {
1212            stop();
1213            setSensor1(null);
1214            start();
1215        }
1216        if (watchSensor2 != null && watchSensor2.getBean().equals(nb)) {
1217            stop();
1218            setSensor2(null);
1219            start();
1220        }
1221        if (watchSensor3 != null && watchSensor3.getBean().equals(nb)) {
1222            stop();
1223            setSensor3(null);
1224            start();
1225        }
1226        if (watchSensor4 != null && watchSensor4.getBean().equals(nb)) {
1227            stop();
1228            setSensor4(null);
1229            start();
1230        }
1231        if (watchSensor5 != null && watchSensor5.getBean().equals(nb)) {
1232            stop();
1233            setSensor5(null);
1234            start();
1235        }
1236        if (watchedSensor1 != null && watchedSensor1.getBean().equals(nb)) {
1237            stop();
1238            setWatchedSensor1(null);
1239            start();
1240        }
1241        if (watchedSensor2 != null && watchedSensor2.getBean().equals(nb)) {
1242            stop();
1243            setWatchedSensor2(null);
1244            start();
1245        }
1246        if (watchedSensor1Alt != null && watchedSensor1Alt.getBean().equals(nb)) {
1247            stop();
1248            setWatchedSensor1Alt(null);
1249            start();
1250        }
1251        if (watchedSensor2Alt != null && watchedSensor2Alt.getBean().equals(nb)) {
1252            stop();
1253            setWatchedSensor2Alt(null);
1254            start();
1255        }
1256        if (approachSensor1 != null && approachSensor1.getBean().equals(nb)) {
1257            stop();
1258            setApproachSensor1(null);
1259            start();
1260        }
1261    }
1262
1263    private void deleteTurnout(NamedBean nb) {
1264        if (watchTurnout != null && watchTurnout.getBean().equals(nb)) {
1265            stop();
1266            setTurnout(null);
1267            start();
1268        }
1269    }
1270
1271    private void deleteSignalHead(NamedBean nb) {
1272        if (nb.equals(getDrivenSignalNamedBean().getBean())) {
1273            stop();
1274
1275            InstanceManager.getDefault(BlockBossLogicProvider.class).remove(this);
1276        }
1277        if (watchedSignal1 != null && watchedSignal1.getBean().equals(nb)) {
1278            stop();
1279            setWatchedSignal1(null, false);
1280            start();
1281        }
1282        if (watchedSignal1Alt != null && watchedSignal1Alt.getBean().equals(nb)) {
1283            stop();
1284            setWatchedSignal1Alt(null);
1285            start();
1286        }
1287        if (watchedSignal2 != null && watchedSignal2.getBean().equals(nb)) {
1288            stop();
1289            setWatchedSignal2(null);
1290            start();
1291        }
1292        if (watchedSignal2Alt != null && watchedSignal2Alt.getBean().equals(nb)) {
1293            stop();
1294            setWatchedSignal2Alt(null);
1295            start();
1296        }
1297    }
1298
1299    private void processCanDelete(PropertyChangeEvent evt, NamedBean nb) throws java.beans.PropertyVetoException {
1300        log.debug("name: {} got {} from {}", name, evt, evt.getSource());
1301
1302        StringBuilder message = new StringBuilder();
1303        message.append(Bundle.getMessage("InUseBlockBossHeader", getDrivenSignal()));
1304
1305        boolean found = false;
1306
1307        if (nb instanceof SignalHead) {
1308            found = canDeleteSignalHead(evt, nb, message, found);
1309        } else if (nb instanceof Turnout) {
1310            found = canDeleteTurnout(nb, message, found);
1311        } else if (nb instanceof Sensor) {
1312            found = canDeleteSensor(nb, message, found);
1313        }
1314        if (found) {
1315            message.append(Bundle.getMessage("InUseBlockBossFooter")); // NOI18N
1316            throw new java.beans.PropertyVetoException(message.toString(), evt);
1317        }
1318    }
1319
1320    private boolean canDeleteSensor(NamedBean nb, StringBuilder message, boolean found) {
1321        message.append("<ul>");
1322        if ((watchSensor1 != null && watchSensor1.getBean().equals(nb))
1323                || (watchSensor2 != null && watchSensor2.getBean().equals(nb))
1324                || (watchSensor3 != null && watchSensor3.getBean().equals(nb))
1325                || (watchSensor4 != null && watchSensor4.getBean().equals(nb))
1326                || (watchSensor5 != null && watchSensor5.getBean().equals(nb))) {
1327            addMessageToHtmlList(message, "<li>", "InUseWatchedSensor", "</li>");
1328            found = true;
1329        }
1330        if ((watchedSensor1 != null && watchedSensor1.getBean().equals(nb))
1331                || (watchedSensor2 != null && watchedSensor2.getBean().equals(nb))
1332                || (watchedSensor1Alt != null && watchedSensor1Alt.getBean().equals(nb))
1333                || (watchedSensor2Alt != null && watchedSensor2Alt.getBean().equals(nb))) {
1334            addMessageToHtmlList(message, "<li>", "InUseWatchedSensor", "</li>");
1335            found = true;
1336
1337        }
1338        if (approachSensor1 != null && approachSensor1.getBean().equals(nb)) {
1339            found = true;
1340            addMessageToHtmlList(message, "<li>", "InUseApproachSensor", "</li>");
1341        }
1342
1343        message.append("</ul>");
1344        return found;
1345    }
1346
1347    private boolean canDeleteTurnout(NamedBean nb, StringBuilder message, boolean found) {
1348        if (watchTurnout != null && watchTurnout.getBean().equals(nb)) {
1349            found = true;
1350            addMessageToHtmlList(message, "<ul>", "InUseWatchedTurnout", "</ul>");
1351        }
1352        return found;
1353    }
1354
1355    private boolean canDeleteSignalHead(PropertyChangeEvent evt, NamedBean nb, StringBuilder message, boolean found) throws java.beans.PropertyVetoException {
1356        if (nb.equals(getDrivenSignalNamedBean().getBean())) {
1357            message.append("<br><b>").append(Bundle.getMessage("InUseThisSslWillBeDeleted")).append("</b>");
1358            throw new java.beans.PropertyVetoException(message.toString(), evt);
1359        }
1360        if ((watchedSignal1 != null && watchedSignal1.getBean().equals(nb))
1361                || (watchedSignal1Alt != null && watchedSignal1Alt.getBean().equals(nb))
1362                || (watchedSignal2 != null && watchedSignal2.getBean().equals(nb))
1363                || (watchedSignal2Alt != null && watchedSignal2Alt.getBean().equals(nb))) {
1364            addMessageToHtmlList(message, "<ul>", "InUseWatchedSignal", "</ul>");
1365            found = true;
1366        }
1367        return found;
1368    }
1369
1370    private void addMessageToHtmlList(StringBuilder message, String s, String inUseWatchedSignal, String s2) {
1371        message.append(s);
1372        message.append(Bundle.getMessage(inUseWatchedSignal));
1373        message.append(s2);
1374    }
1375
1376    /**
1377     * Stop() all existing objects and clear the list.
1378     * <p>
1379     * Intended to be only used during testing.
1380     * @deprecated Since 4.21.1 use {@link BlockBossLogicProvider#dispose()} instead.
1381     */
1382    @Deprecated
1383    public static void stopAllAndClear() {
1384        InstanceManager.getDefault(BlockBossLogicProvider.class).dispose();
1385    }
1386
1387    public List<NamedBeanUsageReport> getUsageReport(NamedBean bean) {
1388        List<NamedBeanUsageReport> report = new ArrayList<>();
1389        SignalHead head = driveSignal.getBean();
1390        if (bean != null) {
1391            if (watchSensor1 != null && bean.equals(getDrivenSignalNamedBean().getBean())) {
1392                report.add(new NamedBeanUsageReport("SSLSignal", head));  // NOI18N
1393            }
1394            if (watchSensor1 != null && bean.equals(watchSensor1.getBean())) {
1395                report.add(new NamedBeanUsageReport("SSLSensor1", head));  // NOI18N
1396            }
1397            if (watchSensor2 != null && bean.equals(watchSensor2.getBean())) {
1398                report.add(new NamedBeanUsageReport("SSLSensor2", head));  // NOI18N
1399            }
1400            if (watchSensor3 != null && bean.equals(watchSensor3.getBean())) {
1401                report.add(new NamedBeanUsageReport("SSLSensor3", head));  // NOI18N
1402            }
1403            if (watchSensor4 != null && bean.equals(watchSensor4.getBean())) {
1404                report.add(new NamedBeanUsageReport("SSLSensor4", head));  // NOI18N
1405            }
1406            if (watchSensor5 != null && bean.equals(watchSensor5.getBean())) {
1407                report.add(new NamedBeanUsageReport("SSLSensor5", head));  // NOI18N
1408            }
1409            if (watchTurnout != null && bean.equals(watchTurnout.getBean())) {
1410                report.add(new NamedBeanUsageReport("SSLTurnout", head));  // NOI18N
1411            }
1412            if (watchedSignal1 != null && bean.equals(watchedSignal1.getBean())) {
1413                report.add(new NamedBeanUsageReport("SSLSignal1", head));  // NOI18N
1414            }
1415            if (watchedSignal1Alt != null && bean.equals(watchedSignal1Alt.getBean())) {
1416                report.add(new NamedBeanUsageReport("SSLSignal1Alt", head));  // NOI18N
1417            }
1418            if (watchedSignal2 != null && bean.equals(watchedSignal2.getBean())) {
1419                report.add(new NamedBeanUsageReport("SSLSignal2", head));  // NOI18N
1420            }
1421            if (watchedSignal2Alt != null && bean.equals(watchedSignal2Alt.getBean())) {
1422                report.add(new NamedBeanUsageReport("SSLSignal2Alt", head));  // NOI18N
1423            }
1424            if (watchedSensor1 != null && bean.equals(watchedSensor1.getBean())) {
1425                report.add(new NamedBeanUsageReport("SSLSensorWatched1", head));  // NOI18N
1426            }
1427            if (watchedSensor1Alt != null && bean.equals(watchedSensor1Alt.getBean())) {
1428                report.add(new NamedBeanUsageReport("SSLSensorWatched1Alt", head));  // NOI18N
1429            }
1430            if (watchedSensor2 != null && bean.equals(watchedSensor2.getBean())) {
1431                report.add(new NamedBeanUsageReport("SSLSensorWatched2", head));  // NOI18N
1432            }
1433            if (watchedSensor2Alt != null && bean.equals(watchedSensor2Alt.getBean())) {
1434                report.add(new NamedBeanUsageReport("SSLSensorWatched2Alt", head));  // NOI18N
1435            }
1436            if (approachSensor1 != null && bean.equals(approachSensor1.getBean())) {
1437                report.add(new NamedBeanUsageReport("SSLSensorApproach", head));  // NOI18N
1438            }
1439        }
1440        return report;
1441    }
1442
1443    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(BlockBossLogic.class);
1444
1445}