001package jmri.jmrit.blockboss;
002
003import java.beans.PropertyChangeEvent;
004import java.util.*;
005import javax.annotation.Nonnull;
006
007import jmri.*;
008import jmri.jmrit.automat.Siglet;
009
010/**
011 * Drives the "simple signal" logic for one signal.
012 * <p>
013 * Signals "protect" by telling the engineer about the conditions ahead. The
014 * engineer controls the speed of the train based on what the signals show, and
015 * the signals in turn react to whether the track ahead is occupied, what
016 * signals further down the line show, etc.
017 * <p>
018 * There are four situations that this logic can handle:
019 * <ol>
020 * <li>SINGLEBLOCK - A simple block, without a turnout.
021 * <p>
022 * In this case, there is only a single set of sensors and a single next signal
023 * to protect.
024 * <li>TRAILINGMAIN - This signal is protecting a trailing point turnout, which
025 * can only be passed when the turnout is closed. It can also be used for the
026 * upper head of a two head signal on the facing end of the turnout.
027 * <p>
028 * In this case, the signal is forced red if the specified turnout is THROWN.
029 * When the turnout is CLOSED, there is a single set of sensors and next
030 * signal(s) to protect.
031 * <li>TRAILINGDIVERGING - This signal is protecting a trailing point turnout,
032 * which can only be passed when the turnout is thrown. It can also be used for
033 * the lower head of a two head signal on the facing end of the turnout.
034 * <p>
035 * In this case, the signal is forced red if the specified turnout is CLOSED.
036 * When the turnout is THROWN, there is a single set of sensors and next
037 * signal(s) to protect.
038 * <li>FACING - This single head signal protects a facing point turnout, which
039 * may therefore have two next signals and two sets of next sensors for the
040 * closed and thrown states of the turnout.
041 * <p>
042 * If the turnout is THROWN, one set of sensors and next signal(s) is protected.
043 * If the turnout is CLOSED, another set of sensors and next signal(s) is
044 * protected.
045 * </ol>
046 * <p>
047 * Note that these four possibilities logically require that certain information
048 * be configured consistently; e.g. not specifying a turnout in TRAILINGMAIN
049 * doesn't make any sense. That's not enforced explicitly, but violating it can
050 * result in confusing behavior.
051 *
052 * <p>
053 * The protected sensors should cover the track to the next signal. If any of
054 * the protected sensors show ACTIVE, the signal will be dropped to red.
055 * Normally, the protected sensors cover the occupancy of the track to the next
056 * signal. In this case, the signal will show red to prevent trains from
057 * entering an occupied stretch of track (often called a "block"). But the
058 * actual source of the sensors can be anything useful, for example a
059 * microswitch on a local turnout, etc.
060 * <p>
061 * There are several variants to how a next signal is protected. In the simplest
062 * form, the controlled signal provides a warning to the engineer of what the
063 * signal being protected will show when it becomes visible:
064 * <ul>
065 * <li>If the next signal is red, the engineer needs to be told to slow down;
066 * this signal will be set to yellow.
067 * <li>If the next signal is green, the engineer can proceed at track speed;
068 * this signal will be set to green.
069 * </ul>
070 * If the next signal is yellow, there are two possible variants that can be
071 * configured:
072 * <ul>
073 * <li>For the common "three-aspect" signaling system, an engineer doesn't need
074 * any warning before a yellow signal. In this case, this signal is set to green
075 * when the protected signal is yellow.
076 * <li>For lines where track speed is very fast or braking distances are very
077 * long, it can be useful to give engineers warning that the next signal is
078 * yellow (and the one after that is red) so that slowing the train can start
079 * early. Usually flashing yellow preceeds the yellow signal, and the system is
080 * called "four-aspect" signaling.
081 * </ul>
082 *
083 * <p>
084 * In some cases, you want a signal to show <i>exactly</I> what the next signal
085 * shows, instead of one speed faster. E.g. if the (protected) next signal is
086 * red, this one should be red, instead of yellow. In this case, this signal is
087 * called a "distant signal", as it provides a "distant" view of the protected
088 * signal heads's appearance. Note that when in this mode, this signal still protects
089 * the interveneing track, etc.
090 * <p>
091 * The "hold" unbound parameter can be used to set this logic to show red,
092 * regardless of input. That's intended for use with CTC logic, etc.
093 * <p>
094 * "Approach lit" signaling sets the signal head to dark (off) unless the
095 * specified sensor(s) are ACTIVE. Normally, those sensors are in front of
096 * (before) the signal head. The signal heads then only light when a train is
097 * approaching. This is used to preserve bulbs and batteries (and sometimes to
098 * reduce engineer workload) on prototype railroads, but is uncommon on model
099 * railroads; once the layout owner has gone to the trouble and expense of
100 * installing signals, he usually wants them lit up.
101 * <p>
102 * Two signal heads can be protected. For example, if the next signal has two
103 * heads to control travel onto a main track or siding, then both heads should
104 * be provided here. The <i>faster</i> signal aspect will control the appearance
105 * of this head. For example, if the next signal is showing a green head and a
106 * red head, this signal will be green, because the train will be able to
107 * proceed at track speed when it reaches that next signal (along the track with
108 * the green signal).
109 *
110 * @author Bob Jacobsen Copyright (C) 2003, 2005
111 * @author Dick Bronson 2006 Revisions to add facing point sensors, approach lighting
112 * and check box to limit speed.
113 */
114public class BlockBossLogic extends Siglet implements java.beans.VetoableChangeListener {
115
116    public static final int SINGLEBLOCK = 1;
117    public static final int TRAILINGMAIN = 2;
118    public static final int TRAILINGDIVERGING = 3;
119    public static final int FACING = 4;
120    private static final String BEAN_X_NOT_FOUND = "BeanXNotFound";
121    private static final String BEAN_NAME_SIGNAL_HEAD = "BeanNameSignalHead";
122    private static final String BEAN_NAME_SENSOR = "BeanNameSensor";
123
124    private int mode = 0;
125
126    /**
127     * Create an object to drive a specific signal head.
128     *
129     * @param name System or user name of the driven signal head, which must exist
130     */
131    public BlockBossLogic(@Nonnull String name) {
132        super(name + Bundle.getMessage("_BlockBossLogic"));
133        java.util.Objects.requireNonNull(name, "BlockBossLogic name cannot be null");
134        this.name = name;
135        log.trace("Create BBL {}", name);
136
137        InstanceManager.getDefault(SignalHeadManager.class).addVetoableChangeListener(BlockBossLogic.this);
138        InstanceManager.turnoutManagerInstance().addVetoableChangeListener(BlockBossLogic.this);
139        InstanceManager.sensorManagerInstance().addVetoableChangeListener(BlockBossLogic.this);
140        SignalHead driveHead = InstanceManager.getDefault(SignalHeadManager.class).getSignalHead(name);
141        if (driveHead == null) {
142            logWarn(Bundle.getMessage(BEAN_X_NOT_FOUND, Bundle.getMessage(BEAN_NAME_SIGNAL_HEAD), name));
143            throw new IllegalArgumentException("SignalHead \"" + name + "\" does not exist");
144        }
145        driveSignal = nbhm.getNamedBeanHandle(name, driveHead);
146        java.util.Objects.requireNonNull(driveSignal, "driveSignal should not have been null");
147    }
148
149    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value="SLF4J_FORMAT_SHOULD_BE_CONST",
150        justification="I18N Warning strings.")
151    private void logWarn(String stringToLogAsWarning) {
152        log.warn(stringToLogAsWarning);
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 NamedBeanHandleManager nbhm = InstanceManager.getDefault(NamedBeanHandleManager.class);
173
174    public void setSensor1(String name) {
175        if (name == null || name.isEmpty()) {
176            watchSensor1 = null;
177            return;
178        }
179        try {
180            watchSensor1 = nbhm.getNamedBeanHandle(name, InstanceManager.sensorManagerInstance().provideSensor(name));
181        } catch (IllegalArgumentException ex) {
182            logWarn(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.isEmpty()) {
188            watchSensor2 = null;
189            return;
190        }
191        try {
192            watchSensor2 = nbhm.getNamedBeanHandle(name, InstanceManager.sensorManagerInstance().provideSensor(name));
193        } catch (IllegalArgumentException ex) {
194            logWarn(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.isEmpty()) {
200            watchSensor3 = null;
201            return;
202        }
203        try {
204            watchSensor3 = nbhm.getNamedBeanHandle(name, InstanceManager.sensorManagerInstance().provideSensor(name));
205        } catch (IllegalArgumentException ex) {
206            logWarn(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.isEmpty()) {
212            watchSensor4 = null;
213            return;
214        }
215        try {
216            watchSensor4 = nbhm.getNamedBeanHandle(name, InstanceManager.sensorManagerInstance().provideSensor(name));
217        } catch (IllegalArgumentException ex) {
218            logWarn(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.isEmpty()) {
224            watchSensor5 = null;
225            return;
226        }
227        try {
228            watchSensor5 = nbhm.getNamedBeanHandle(name, InstanceManager.sensorManagerInstance().provideSensor(name));
229        } catch (IllegalArgumentException ex) {
230            logWarn(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.isEmpty()) {
276            watchTurnout = null;
277            return;
278        }
279        try {
280            watchTurnout = nbhm.getNamedBeanHandle(name, InstanceManager.turnoutManagerInstance().provideTurnout(name));
281        } catch (IllegalArgumentException ex) {
282            logWarn(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.isEmpty()) {
318            watchedSignal1 = null;
319            return;
320        }
321        SignalHead head = InstanceManager.getDefault(SignalHeadManager.class).getSignalHead(name);
322        if (head != null) {
323            watchedSignal1 = nbhm.getNamedBeanHandle(name, head);
324        } else {
325            logWarn(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.isEmpty()) {
345            watchedSignal1Alt = null;
346            return;
347        }
348        SignalHead head = InstanceManager.getDefault(SignalHeadManager.class).getSignalHead(name);
349        if (head != null) {
350            watchedSignal1Alt = nbhm.getNamedBeanHandle(name, head);
351        } else {
352            logWarn(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.isEmpty()) {
372            watchedSignal2 = null;
373            return;
374        }
375        SignalHead head = InstanceManager.getDefault(SignalHeadManager.class).getSignalHead(name);
376        if (head != null) {
377            watchedSignal2 = nbhm.getNamedBeanHandle(name, head);
378        } else {
379            logWarn(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.isEmpty()) {
398            watchedSignal2Alt = null;
399            return;
400        }
401        SignalHead head = InstanceManager.getDefault(SignalHeadManager.class).getSignalHead(name);
402        if (head != null) {
403            watchedSignal2Alt = nbhm.getNamedBeanHandle(name, head);
404        } else {
405            logWarn(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.isEmpty()) {
425            watchedSensor1 = null;
426            return;
427        }
428        try {
429            watchedSensor1 = nbhm.getNamedBeanHandle(name, InstanceManager.sensorManagerInstance().provideSensor(name));
430        } catch (IllegalArgumentException ex) {
431            logWarn(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.isEmpty()) {
450            watchedSensor1Alt = null;
451            return;
452        }
453        try {
454            watchedSensor1Alt = nbhm.getNamedBeanHandle(name, InstanceManager.sensorManagerInstance().provideSensor(name));
455        } catch (IllegalArgumentException ex) {
456            logWarn(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.isEmpty()) {
475            watchedSensor2 = null;
476            return;
477        }
478        try {
479            watchedSensor2 = nbhm.getNamedBeanHandle(name, InstanceManager.sensorManagerInstance().provideSensor(name));
480        } catch (IllegalArgumentException ex) {
481            logWarn(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.isEmpty()) {
500            watchedSensor2Alt = null;
501            return;
502        }
503        try {
504            watchedSensor2Alt = nbhm.getNamedBeanHandle(name, InstanceManager.sensorManagerInstance().provideSensor(name));
505        } catch (IllegalArgumentException ex) {
506            logWarn(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.isEmpty()) {
624            approachSensor1 = null;
625            return;
626        }
627        approachSensor1 = nbhm.getNamedBeanHandle(name, InstanceManager.sensorManagerInstance().provideSensor(name));
628        if (approachSensor1.getBean() == null) {
629            logWarn(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     * Get the BlockBossLogic item governing a specific signal head by its name,
1110     * having removed it from use.
1111     *
1112     * @param signal name of the signal head object
1113     * @return never null
1114     */
1115    @Nonnull
1116    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE",
1117                        justification="enforced dynamically, too hard to prove statically")
1118    public static BlockBossLogic getStoppedObject(String signal) {
1119        // As a static requirement, the signal head must exist, but
1120        // we can't express that statically.  We test it dynamically.
1121        SignalHead sh = InstanceManager.getDefault(SignalHeadManager.class).getSignalHead(signal);
1122        java.util.Objects.requireNonNull(sh, "signal head must exist");
1123        return getStoppedObject(sh);
1124    }
1125
1126    /**
1127     * Get the BlockBossLogic item governing a specific signal head, having
1128     * removed it from use.
1129     *
1130     * @param sh signal head object
1131     * @return never null
1132     */
1133    @Nonnull
1134    public static BlockBossLogic getStoppedObject(@Nonnull SignalHead sh) {
1135        BlockBossLogic b = InstanceManager.getDefault(BlockBossLogicProvider.class).provide(sh);
1136        b.stop();
1137        return b;
1138    }
1139
1140    @Override
1141    public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException {
1142        NamedBean nb = (NamedBean) evt.getOldValue();
1143        if ("CanDelete".equals(evt.getPropertyName())) { // NOI18N
1144            processCanDelete(evt, nb);
1145        } else if ("DoDelete".equals(evt.getPropertyName())) { // NOI18N
1146            processDoDelete(nb);
1147        }
1148    }
1149
1150    private void processDoDelete(NamedBean nb) {
1151        if (nb instanceof SignalHead) {
1152            deleteSignalHead(nb);
1153        } else if (nb instanceof Turnout) {
1154            deleteTurnout(nb);
1155        } else if (nb instanceof Sensor) {
1156            deleteSensor(nb);
1157        }
1158    }
1159
1160    private void deleteSensor(NamedBean nb) {
1161        if (watchSensor1 != null && watchSensor1.getBean().equals(nb)) {
1162            stop();
1163            setSensor1(null);
1164            start();
1165        }
1166        if (watchSensor2 != null && watchSensor2.getBean().equals(nb)) {
1167            stop();
1168            setSensor2(null);
1169            start();
1170        }
1171        if (watchSensor3 != null && watchSensor3.getBean().equals(nb)) {
1172            stop();
1173            setSensor3(null);
1174            start();
1175        }
1176        if (watchSensor4 != null && watchSensor4.getBean().equals(nb)) {
1177            stop();
1178            setSensor4(null);
1179            start();
1180        }
1181        if (watchSensor5 != null && watchSensor5.getBean().equals(nb)) {
1182            stop();
1183            setSensor5(null);
1184            start();
1185        }
1186        if (watchedSensor1 != null && watchedSensor1.getBean().equals(nb)) {
1187            stop();
1188            setWatchedSensor1(null);
1189            start();
1190        }
1191        if (watchedSensor2 != null && watchedSensor2.getBean().equals(nb)) {
1192            stop();
1193            setWatchedSensor2(null);
1194            start();
1195        }
1196        if (watchedSensor1Alt != null && watchedSensor1Alt.getBean().equals(nb)) {
1197            stop();
1198            setWatchedSensor1Alt(null);
1199            start();
1200        }
1201        if (watchedSensor2Alt != null && watchedSensor2Alt.getBean().equals(nb)) {
1202            stop();
1203            setWatchedSensor2Alt(null);
1204            start();
1205        }
1206        if (approachSensor1 != null && approachSensor1.getBean().equals(nb)) {
1207            stop();
1208            setApproachSensor1(null);
1209            start();
1210        }
1211    }
1212
1213    private void deleteTurnout(NamedBean nb) {
1214        if (watchTurnout != null && watchTurnout.getBean().equals(nb)) {
1215            stop();
1216            setTurnout(null);
1217            start();
1218        }
1219    }
1220
1221    private void deleteSignalHead(NamedBean nb) {
1222        if (nb.equals(getDrivenSignalNamedBean().getBean())) {
1223            stop();
1224
1225            InstanceManager.getDefault(BlockBossLogicProvider.class).remove(this);
1226        }
1227        if (watchedSignal1 != null && watchedSignal1.getBean().equals(nb)) {
1228            stop();
1229            setWatchedSignal1(null, false);
1230            start();
1231        }
1232        if (watchedSignal1Alt != null && watchedSignal1Alt.getBean().equals(nb)) {
1233            stop();
1234            setWatchedSignal1Alt(null);
1235            start();
1236        }
1237        if (watchedSignal2 != null && watchedSignal2.getBean().equals(nb)) {
1238            stop();
1239            setWatchedSignal2(null);
1240            start();
1241        }
1242        if (watchedSignal2Alt != null && watchedSignal2Alt.getBean().equals(nb)) {
1243            stop();
1244            setWatchedSignal2Alt(null);
1245            start();
1246        }
1247    }
1248
1249    private void processCanDelete(PropertyChangeEvent evt, NamedBean nb) throws java.beans.PropertyVetoException {
1250        log.debug("name: {} got {} from {}", name, evt, evt.getSource());
1251
1252        StringBuilder message = new StringBuilder();
1253        message.append(Bundle.getMessage("InUseBlockBossHeader", getDrivenSignal()));
1254
1255        boolean found = false;
1256
1257        if (nb instanceof SignalHead) {
1258            found = canDeleteSignalHead(evt, nb, message, found);
1259        } else if (nb instanceof Turnout) {
1260            found = canDeleteTurnout(nb, message, found);
1261        } else if (nb instanceof Sensor) {
1262            found = canDeleteSensor(nb, message, found);
1263        }
1264        if (found) {
1265            message.append(Bundle.getMessage("InUseBlockBossFooter")); // NOI18N
1266            throw new java.beans.PropertyVetoException(message.toString(), evt);
1267        }
1268    }
1269
1270    private boolean canDeleteSensor(NamedBean nb, StringBuilder message, boolean found) {
1271        message.append("<ul>");
1272        if ((watchSensor1 != null && watchSensor1.getBean().equals(nb))
1273                || (watchSensor2 != null && watchSensor2.getBean().equals(nb))
1274                || (watchSensor3 != null && watchSensor3.getBean().equals(nb))
1275                || (watchSensor4 != null && watchSensor4.getBean().equals(nb))
1276                || (watchSensor5 != null && watchSensor5.getBean().equals(nb))) {
1277            addMessageToHtmlList(message, "<li>", "InUseWatchedSensor", "</li>");
1278            found = true;
1279        }
1280        if ((watchedSensor1 != null && watchedSensor1.getBean().equals(nb))
1281                || (watchedSensor2 != null && watchedSensor2.getBean().equals(nb))
1282                || (watchedSensor1Alt != null && watchedSensor1Alt.getBean().equals(nb))
1283                || (watchedSensor2Alt != null && watchedSensor2Alt.getBean().equals(nb))) {
1284            addMessageToHtmlList(message, "<li>", "InUseWatchedSensor", "</li>");
1285            found = true;
1286
1287        }
1288        if (approachSensor1 != null && approachSensor1.getBean().equals(nb)) {
1289            found = true;
1290            addMessageToHtmlList(message, "<li>", "InUseApproachSensor", "</li>");
1291        }
1292
1293        message.append("</ul>");
1294        return found;
1295    }
1296
1297    private boolean canDeleteTurnout(NamedBean nb, StringBuilder message, boolean found) {
1298        if (watchTurnout != null && watchTurnout.getBean().equals(nb)) {
1299            found = true;
1300            addMessageToHtmlList(message, "<ul>", "InUseWatchedTurnout", "</ul>");
1301        }
1302        return found;
1303    }
1304
1305    private boolean canDeleteSignalHead(PropertyChangeEvent evt, NamedBean nb, StringBuilder message, boolean found) throws java.beans.PropertyVetoException {
1306        if (nb.equals(getDrivenSignalNamedBean().getBean())) {
1307            message.append("<br><b>").append(Bundle.getMessage("InUseThisSslWillBeDeleted")).append("</b>");
1308            throw new java.beans.PropertyVetoException(message.toString(), evt);
1309        }
1310        if ((watchedSignal1 != null && watchedSignal1.getBean().equals(nb))
1311                || (watchedSignal1Alt != null && watchedSignal1Alt.getBean().equals(nb))
1312                || (watchedSignal2 != null && watchedSignal2.getBean().equals(nb))
1313                || (watchedSignal2Alt != null && watchedSignal2Alt.getBean().equals(nb))) {
1314            addMessageToHtmlList(message, "<ul>", "InUseWatchedSignal", "</ul>");
1315            found = true;
1316        }
1317        return found;
1318    }
1319
1320    private void addMessageToHtmlList(StringBuilder message, String s, String inUseWatchedSignal, String s2) {
1321        message.append(s);
1322        message.append(Bundle.getMessage(inUseWatchedSignal));
1323        message.append(s2);
1324    }
1325
1326    public List<NamedBeanUsageReport> getUsageReport(NamedBean bean) {
1327        List<NamedBeanUsageReport> report = new ArrayList<>();
1328        SignalHead head = driveSignal.getBean();
1329        if (bean != null) {
1330            if (watchSensor1 != null && bean.equals(getDrivenSignalNamedBean().getBean())) {
1331                report.add(new NamedBeanUsageReport("SSLSignal", head));  // NOI18N
1332            }
1333            if (watchSensor1 != null && bean.equals(watchSensor1.getBean())) {
1334                report.add(new NamedBeanUsageReport("SSLSensor1", head));  // NOI18N
1335            }
1336            if (watchSensor2 != null && bean.equals(watchSensor2.getBean())) {
1337                report.add(new NamedBeanUsageReport("SSLSensor2", head));  // NOI18N
1338            }
1339            if (watchSensor3 != null && bean.equals(watchSensor3.getBean())) {
1340                report.add(new NamedBeanUsageReport("SSLSensor3", head));  // NOI18N
1341            }
1342            if (watchSensor4 != null && bean.equals(watchSensor4.getBean())) {
1343                report.add(new NamedBeanUsageReport("SSLSensor4", head));  // NOI18N
1344            }
1345            if (watchSensor5 != null && bean.equals(watchSensor5.getBean())) {
1346                report.add(new NamedBeanUsageReport("SSLSensor5", head));  // NOI18N
1347            }
1348            if (watchTurnout != null && bean.equals(watchTurnout.getBean())) {
1349                report.add(new NamedBeanUsageReport("SSLTurnout", head));  // NOI18N
1350            }
1351            if (watchedSignal1 != null && bean.equals(watchedSignal1.getBean())) {
1352                report.add(new NamedBeanUsageReport("SSLSignal1", head));  // NOI18N
1353            }
1354            if (watchedSignal1Alt != null && bean.equals(watchedSignal1Alt.getBean())) {
1355                report.add(new NamedBeanUsageReport("SSLSignal1Alt", head));  // NOI18N
1356            }
1357            if (watchedSignal2 != null && bean.equals(watchedSignal2.getBean())) {
1358                report.add(new NamedBeanUsageReport("SSLSignal2", head));  // NOI18N
1359            }
1360            if (watchedSignal2Alt != null && bean.equals(watchedSignal2Alt.getBean())) {
1361                report.add(new NamedBeanUsageReport("SSLSignal2Alt", head));  // NOI18N
1362            }
1363            if (watchedSensor1 != null && bean.equals(watchedSensor1.getBean())) {
1364                report.add(new NamedBeanUsageReport("SSLSensorWatched1", head));  // NOI18N
1365            }
1366            if (watchedSensor1Alt != null && bean.equals(watchedSensor1Alt.getBean())) {
1367                report.add(new NamedBeanUsageReport("SSLSensorWatched1Alt", head));  // NOI18N
1368            }
1369            if (watchedSensor2 != null && bean.equals(watchedSensor2.getBean())) {
1370                report.add(new NamedBeanUsageReport("SSLSensorWatched2", head));  // NOI18N
1371            }
1372            if (watchedSensor2Alt != null && bean.equals(watchedSensor2Alt.getBean())) {
1373                report.add(new NamedBeanUsageReport("SSLSensorWatched2Alt", head));  // NOI18N
1374            }
1375            if (approachSensor1 != null && bean.equals(approachSensor1.getBean())) {
1376                report.add(new NamedBeanUsageReport("SSLSensorApproach", head));  // NOI18N
1377            }
1378        }
1379        return report;
1380    }
1381
1382    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(BlockBossLogic.class);
1383
1384}