001package jmri.implementation;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyChangeListener;
005import java.beans.PropertyVetoException;
006import java.beans.VetoableChangeListener;
007import java.util.*;
008
009import javax.annotation.CheckForNull;
010import javax.annotation.Nonnull;
011
012import jmri.*;
013import jmri.jmrit.display.EditorManager;
014import jmri.jmrit.display.layoutEditor.ConnectivityUtil;
015import jmri.jmrit.display.layoutEditor.LayoutBlock;
016import jmri.jmrit.display.layoutEditor.LayoutBlockConnectivityTools;
017import jmri.jmrit.display.layoutEditor.LayoutBlockManager;
018import jmri.jmrit.display.layoutEditor.LayoutEditor;
019import jmri.jmrit.display.layoutEditor.LayoutSlip;
020import jmri.jmrit.display.layoutEditor.LayoutTrackExpectedState;
021import jmri.jmrit.display.layoutEditor.LayoutTurnout;
022import jmri.jmrit.display.layoutEditor.LayoutTraverser;
023import jmri.jmrit.display.layoutEditor.LayoutTurntable;
024import jmri.jmrit.display.layoutEditor.LevelXing;
025import jmri.util.ThreadingUtil;
026
027/**
028 * Default implementation of {@link jmri.SignalMastLogic}.
029 *
030 * @author Kevin Dickerson Copyright (C) 2011
031 */
032public class DefaultSignalMastLogic extends AbstractNamedBean implements SignalMastLogic, VetoableChangeListener {
033
034    SignalMast source;
035    SignalMast destination;
036    String stopAspect;
037
038    Hashtable<SignalMast, DestinationMast> destList = new Hashtable<>();
039    LayoutEditor editor;
040
041    LayoutBlock facingBlock = null;
042    LayoutBlock remoteProtectingBlock = null;
043
044    boolean disposing = false;
045
046    /**
047     * Initialise a Signal Mast Logic for a given source Signal Mast.
048     *
049     * @param source  The Signal Mast we are configuring an SML for
050     */
051    public DefaultSignalMastLogic(@Nonnull SignalMast source) {
052        super(source.toString()); // default system name
053        this.source = source;
054        try {
055            this.stopAspect = source.getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.DANGER);
056            this.source.addPropertyChangeListener(propertySourceMastListener);
057            if (source.getAspect() == null) {
058                source.setAspect(stopAspect);
059            }
060        } catch (Exception ex) {
061            log.error("Error while creating Signal Logic", ex);
062        }
063    }
064
065    // Most of the following methods will inherit Javadoc from SignalMastLogic.java
066    /**
067     * {@inheritDoc }
068     */
069    @Override
070    public void setFacingBlock(LayoutBlock facing) {
071        facingBlock = facing;
072    }
073
074    /**
075     * {@inheritDoc }
076     */
077    @Override
078    public LayoutBlock getFacingBlock() {
079        return facingBlock;
080    }
081
082    /**
083     * {@inheritDoc }
084     */
085    @Override
086    public LayoutBlock getProtectingBlock(@Nonnull SignalMast dest) {
087        if (!destList.containsKey(dest)) {
088            return null;
089        }
090        return destList.get(dest).getProtectingBlock();
091    }
092
093    /**
094     * {@inheritDoc }
095     */
096    @Override
097    public SignalMast getSourceMast() {
098        return source;
099    }
100
101    /**
102     * {@inheritDoc }
103     */
104    @Override
105    public void replaceSourceMast(SignalMast oldMast, SignalMast newMast) {
106        if (oldMast != source) {
107            // Old mast does not match new mast so will exit replace
108            return;
109        }
110        source.removePropertyChangeListener(propertySourceMastListener);
111        source = newMast;
112        stopAspect = source.getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.DANGER);
113        source.addPropertyChangeListener(propertySourceMastListener);
114        if (source.getAspect() == null) {
115            source.setAspect(stopAspect);
116        }
117        getDestinationList().forEach(sm -> {
118            DestinationMast destMast = destList.get(sm);
119            if (destMast.getAssociatedSection() != null) {
120                String oldUserName = destMast.getAssociatedSection().getUserName();
121                String newUserName = source.getDisplayName() + ":" + sm.getDisplayName();
122                if (oldUserName != null) {
123                    InstanceManager.getDefault(NamedBeanHandleManager.class)
124                        .renameBean(oldUserName, newUserName, ((NamedBean) destMast.getAssociatedSection()));
125                } else {
126                    log.warn("AssociatedSection oldUserName null for destination mast {}, skipped",
127                        destMast.getDisplayName());
128                }
129            }
130        });
131        firePropertyChange(PROPERTY_UPDATED_SOURCE, oldMast, newMast);
132    }
133
134    /**
135     * {@inheritDoc }
136     */
137    @Override
138    public void replaceDestinationMast(SignalMast oldMast, SignalMast newMast) {
139        if (!destList.containsKey(oldMast)) {
140            return;
141        }
142        DestinationMast destMast = destList.get(oldMast);
143        destMast.updateDestinationMast(newMast);
144        if (destination == oldMast) {
145            oldMast.removePropertyChangeListener(propertyDestinationMastListener);
146            newMast.addPropertyChangeListener(propertyDestinationMastListener);
147            destination = newMast;
148            setSignalAppearance();
149        }
150        destList.remove(oldMast);
151        if (destMast.getAssociatedSection() != null) {
152            String oldUserName = destMast.getAssociatedSection().getUserName();
153            String newUserName = source.getDisplayName() + ":" + newMast.getDisplayName();
154            if (oldUserName != null) {
155                InstanceManager.getDefault(NamedBeanHandleManager.class)
156                    .renameBean(oldUserName, newUserName, destMast.getAssociatedSection());
157            } else {
158                log.warn("AssociatedSection oldUserName null for destination mast {}, skipped",
159                    destMast.getDisplayName());
160            }
161        }
162        destList.put(newMast, destMast);
163        firePropertyChange(PROPERTY_UPDATED_DESTINATION, oldMast, newMast);
164    }
165
166    /**
167     * {@inheritDoc }
168     */
169    @Override
170    public void setDestinationMast(SignalMast dest) {
171        if (destList.containsKey(dest)) {
172            // if already present, not a change
173            log.debug("Destination mast '{}' was already defined in SML with this source mast", dest.getDisplayName());
174            return;
175        }
176        int oldSize = destList.size();
177        destList.put(dest, new DestinationMast(dest));
178        //InstanceManager.getDefault(SignalMastLogicManager.class).addDestinationMastToLogic(this, dest);
179        firePropertyChange(PROPERTY_LENGTH, oldSize, destList.size());
180        // make new dest mast appear in (update of) SignallingSourcePanel Table by having that table listen to PropertyChange Events from SML TODO
181    }
182
183    /**
184     * {@inheritDoc }
185     */
186    @Override
187    public boolean isDestinationValid(SignalMast dest) {
188        if (dest == null) {
189            return false;
190        }
191        return destList.containsKey(dest);
192    }
193
194    /**
195     * {@inheritDoc }
196     */
197    @Override
198    public List<SignalMast> getDestinationList() {
199        List<SignalMast> out = new ArrayList<>();
200        Enumeration<SignalMast> en = destList.keys();
201        while (en.hasMoreElements()) {
202            out.add(en.nextElement());
203        }
204        return out;
205    }
206
207    /**
208     * {@inheritDoc }
209     */
210    @Override
211    public String getComment(SignalMast dest) {
212        if (!destList.containsKey(dest)) {
213            return "";
214        }
215        return destList.get(dest).getComment();
216    }
217
218    /**
219     * {@inheritDoc }
220     */
221    @Override
222    public void setComment(String comment, SignalMast dest) {
223        if (!destList.containsKey(dest)) {
224            return;
225        }
226        destList.get(dest).setComment(comment);
227    }
228
229    /**
230     * {@inheritDoc }
231     */
232    @Override
233    public void setStore(int store, SignalMast destination) {
234        if (!destList.containsKey(destination)) {
235            return;
236        }
237        destList.get(destination).setStore(store);
238    }
239
240    /**
241     * {@inheritDoc }
242     */
243    @Override
244    public int getStoreState(SignalMast destination) {
245        if (!destList.containsKey(destination)) {
246            return STORENONE;
247        }
248        return destList.get(destination).getStoreState();
249    }
250
251    /**
252     * {@inheritDoc }
253     */
254    @Override
255    public void setEnabled(SignalMast dest) {
256        if (!destList.containsKey(dest)) {
257            return;
258        }
259        destList.get(dest).setEnabled();
260    }
261
262    /**
263     * {@inheritDoc }
264     */
265    @Override
266    public void setDisabled(SignalMast dest) {
267        if (!destList.containsKey(dest)) {
268            return;
269        }
270        destList.get(dest).setDisabled();
271    }
272
273    /**
274     * {@inheritDoc }
275     */
276    @Override
277    public boolean isEnabled(SignalMast dest) {
278        if (!destList.containsKey(dest)) {
279            return false;
280        }
281        return destList.get(dest).isEnabled();
282    }
283
284    /**
285     * {@inheritDoc }
286     */
287    @Override
288    public boolean isActive(SignalMast dest) {
289        if (!destList.containsKey(dest)) {
290            return false;
291        }
292        return destList.get(dest).isActive();
293    }
294
295    /**
296     * {@inheritDoc }
297     */
298    @Override
299    public SignalMast getActiveDestination() {
300        for (SignalMast sm : getDestinationList()) {
301            if (destList.get(sm).isActive()) {
302                return sm;
303            }
304        }
305        return null;
306    }
307
308    /**
309     * {@inheritDoc }
310     */
311    @Override
312    public boolean removeDestination(SignalMast dest) {
313        int oldSize = destList.size();
314        if (destList.containsKey(dest)) {
315            //InstanceManager.getDefault(SignalMastLogicManager.class).removeDestinationMastToLogic(this, dest);
316            destList.get(dest).dispose();
317            destList.remove(dest);
318            firePropertyChange(PROPERTY_LENGTH, oldSize, destList.size());
319        }
320        return destList.isEmpty();
321    }
322
323    /**
324     * {@inheritDoc }
325     */
326    @Override
327    public void disableLayoutEditorUse() {
328        destList.values().forEach(dest -> {
329            try {
330                dest.useLayoutEditor(false);
331            } catch (JmriException e) {
332                log.error("Could not disable LayoutEditor ",  e);
333            }
334        });
335    }
336
337    /**
338     * {@inheritDoc }
339     */
340    @Override
341    public void useLayoutEditor(boolean boo, SignalMast destination) throws JmriException {
342        if (!destList.containsKey(destination)) {
343            return;
344        }
345        if (boo) {
346            log.debug("Set use layout editor");
347            Set<LayoutEditor> layout = InstanceManager.getDefault(EditorManager.class).getAll(LayoutEditor.class);
348            /*We don't care which layout editor panel the signalmast is on, just so long as
349             the routing is done via layout blocks*/
350            // TODO: what is this?
351            log.debug("userLayoutEditor finds layout list size is {}", layout.size());
352            for (LayoutEditor findeditor : layout) {
353                log.debug("layouteditor {}", findeditor.getLayoutName());
354                if (facingBlock == null) {
355                    facingBlock = InstanceManager.getDefault(LayoutBlockManager.class)
356                        .getFacingBlockByMast(getSourceMast(), findeditor);
357                }
358            }
359        }
360        destList.get(destination).useLayoutEditor(boo);
361    }
362
363    /**
364     * Add direction sensors to SML
365     *
366     * @return number of errors
367     */
368    @Override
369    public int setupDirectionSensors() {
370        // iterrate over the signal masts
371        int errorCount = 0;
372        for (SignalMast sm : getDestinationList()) {
373            String displayName = sm.getDisplayName();
374            Section sec = getAssociatedSection(sm);
375            if (sec != null) {
376                Block thisFacingBlock;
377                Sensor fwd = sec.getForwardBlockingSensor();
378                Sensor rev = sec.getReverseBlockingSensor();
379                LayoutBlock lBlock = getFacingBlock();
380                if (lBlock == null) {
381                    try {
382                        useLayoutEditor(true, sm); // force a refind
383                    } catch (JmriException ex) {
384                        continue;
385                    }
386                }
387                if (lBlock != null) {
388                    thisFacingBlock = lBlock.getBlock();
389                    EntryPoint fwdEntryPoint = sec.getEntryPointFromBlock(thisFacingBlock, Section.FORWARD);
390                    EntryPoint revEntryPoint = sec.getEntryPointFromBlock(thisFacingBlock, Section.REVERSE);
391                    log.debug("Mast[{}] Sec[{}] Fwd[{}] Rev [{}]",
392                            displayName, sec, fwd, rev);
393                    if (fwd != null && fwdEntryPoint != null) {
394                        addSensor(fwd.getUserName(), Sensor.INACTIVE, sm);
395                        log.debug("Mast[{}] Sec[{}] Fwd[{}] fwdEP[{}]",
396                                displayName, sec, fwd,
397                                fwdEntryPoint.getBlock().getUserName());
398
399                    } else if (rev != null && revEntryPoint != null) {
400                        addSensor(rev.getUserName(), Sensor.INACTIVE, sm);
401                        log.debug("Mast[{}] Sec[{}] Rev [{}] revEP[{}]",
402                                displayName, sec, rev,
403                                revEntryPoint.getBlock().getUserName());
404
405                    } else {
406                        log.error("Mast[{}] Cannot Establish entry point to protected section", displayName);
407                        errorCount += 1;
408                    }
409                } else {
410                    log.error("Mast[{}] No Facing Block", displayName);
411                    errorCount += 1;
412                }
413            } else {
414                log.error("Mast[{}] No Associated Section", displayName);
415                errorCount += 1;
416            }
417        }
418        return errorCount;
419    }
420
421    /**
422     * {@inheritDoc }
423     */
424    @Override
425    public void removeDirectionSensors() {
426        //TODO find aaway of easilty identifying the ones we added.
427    }
428
429    /**
430     * {@inheritDoc }
431     */
432    @Override
433    public boolean useLayoutEditor(SignalMast destination) {
434        if (!destList.containsKey(destination)) {
435            return false;
436        }
437        return destList.get(destination).useLayoutEditor();
438    }
439
440    /**
441     * {@inheritDoc }
442     */
443    @Override
444    public void useLayoutEditorDetails(boolean turnouts, boolean blocks, SignalMast destination) throws JmriException {
445        if (!destList.containsKey(destination)) {
446            return;
447        }
448        destList.get(destination).useLayoutEditorDetails(turnouts, blocks);
449    }
450
451    /**
452     * {@inheritDoc }
453     */
454    @Override
455    public boolean useLayoutEditorBlocks(SignalMast destination) {
456        if (!destList.containsKey(destination)) {
457            return false;
458        }
459        return destList.get(destination).useLayoutEditorBlocks();
460    }
461
462    /**
463     * {@inheritDoc }
464     */
465    @Override
466    public boolean useLayoutEditorTurnouts(SignalMast destination) {
467        if (!destList.containsKey(destination)) {
468            return false;
469        }
470        return destList.get(destination).useLayoutEditorTurnouts();
471    }
472
473    /**
474     * {@inheritDoc }
475     */
476    @Override
477    public Section getAssociatedSection(SignalMast destination) {
478        if (!destList.containsKey(destination)) {
479            return null;
480        }
481        return destList.get(destination).getAssociatedSection();
482    }
483
484    /**
485     * {@inheritDoc }
486     */
487    @Override
488    public void setAssociatedSection(Section sec, SignalMast destination) {
489        if (!destList.containsKey(destination)) {
490            return;
491        }
492        destList.get(destination).setAssociatedSection(sec);
493    }
494
495    /**
496     * {@inheritDoc }
497     */
498    @Override
499    public boolean allowAutoMaticSignalMastGeneration(SignalMast destination) {
500        if (!destList.containsKey(destination)) {
501            return false;
502        }
503        return destList.get(destination).allowAutoSignalMastGen();
504    }
505
506    /**
507     * {@inheritDoc }
508     */
509    @Override
510    public void allowAutoMaticSignalMastGeneration(boolean allow, SignalMast destination) {
511        if (!destList.containsKey(destination)) {
512            return;
513        }
514        destList.get(destination).allowAutoSignalMastGen(allow);
515    }
516
517    /**
518     * {@inheritDoc }
519     */
520    @Override
521    public void allowTurnoutLock(boolean lock, SignalMast destination) {
522        if (!destList.containsKey(destination)) {
523            return;
524        }
525        destList.get(destination).allowTurnoutLock(lock);
526    }
527
528    /**
529     * {@inheritDoc }
530     */
531    @Override
532    public boolean isTurnoutLockAllowed(SignalMast destination) {
533        if (!destList.containsKey(destination)) {
534            return false;
535        }
536        return destList.get(destination).isTurnoutLockAllowed();
537    }
538
539    /**
540     * {@inheritDoc }
541     */
542    @Override
543    public void setTurnouts(Hashtable<NamedBeanHandle<Turnout>, Integer> turnouts, SignalMast destination) {
544        if (!destList.containsKey(destination)) {
545            return;
546        }
547        destList.get(destination).setTurnouts(turnouts);
548    }
549
550    /**
551     * {@inheritDoc }
552     */
553    @Override
554    public void setAutoTurnouts(Hashtable<Turnout, Integer> turnouts, SignalMast destination) {
555        if (!destList.containsKey(destination)) {
556            return;
557        }
558        destList.get(destination).setAutoTurnouts(turnouts);
559    }
560
561    /**
562     * {@inheritDoc }
563     */
564    @Override
565    public void setBlocks(Hashtable<Block, Integer> blocks, SignalMast destination) {
566        if (!destList.containsKey(destination)) {
567            return;
568        }
569        destList.get(destination).setBlocks(blocks);
570    }
571
572    /**
573     * {@inheritDoc }
574     */
575    @Override
576    public void setAutoBlocks(LinkedHashMap<Block, Integer> blocks, SignalMast destination) {
577        if (!destList.containsKey(destination)) {
578            return;
579        }
580        destList.get(destination).setAutoBlocks(blocks);
581    }
582
583    /**
584     * {@inheritDoc }
585     */
586    @Override
587    public void setMasts(Hashtable<SignalMast, String> masts, SignalMast destination) {
588        if (!destList.containsKey(destination)) {
589            return;
590        }
591        destList.get(destination).setMasts(masts);
592    }
593
594    /**
595     * {@inheritDoc }
596     */
597    @Override
598    public void setAutoMasts(Hashtable<SignalMast, String> masts, SignalMast destination) {
599        if (!destList.containsKey(destination)) {
600            return;
601        }
602        destList.get(destination).setAutoMasts(masts, true);
603    }
604
605    /**
606     * {@inheritDoc }
607     */
608    @Override
609    public void setSensors(Hashtable<NamedBeanHandle<Sensor>, Integer> sensors, SignalMast destination) {
610        if (!destList.containsKey(destination)) {
611            return;
612        }
613        destList.get(destination).setSensors(sensors);
614    }
615
616    /**
617     * {@inheritDoc }
618     */
619    @Override
620    public void addSensor(String sensorName, int state, SignalMast destination) {
621        if (!destList.containsKey(destination)) {
622            return;
623        }
624        Sensor sen = InstanceManager.sensorManagerInstance().getSensor(sensorName);
625        if (sen != null) {
626            NamedBeanHandle<Sensor> namedSensor = InstanceManager.getDefault(NamedBeanHandleManager.class)
627                .getNamedBeanHandle(sensorName, sen);
628            destList.get(destination).addSensor(namedSensor, state);
629        }
630    }
631
632    /**
633     * {@inheritDoc }
634     */
635    @Override
636    public void removeSensor(String sensorName, SignalMast destination) {
637        Sensor sen = InstanceManager.sensorManagerInstance().getSensor(sensorName);
638        removeSensor(sen, destination);
639    }
640
641    public void removeSensor(Sensor sen, SignalMast destination) {
642        if (!destList.containsKey(destination)) {
643            return;
644        }
645        if (sen != null) {
646            destList.get(destination).removeSensor(sen);
647        }
648    }
649
650    /**
651     * {@inheritDoc }
652     */
653    @Override
654    public List<Block> getBlocks(SignalMast destination) {
655        if (!destList.containsKey(destination)) {
656            return new ArrayList<>();
657        }
658        return destList.get(destination).getBlocks();
659    }
660
661    /**
662     * {@inheritDoc }
663     */
664    @Override
665    public List<Block> getAutoBlocks(SignalMast destination) {
666        if (!destList.containsKey(destination)) {
667            return new ArrayList<>();
668        }
669        return destList.get(destination).getAutoBlocks();
670    }
671
672    /**
673     * {@inheritDoc }
674     */
675    @Override
676    public List<Block> getAutoBlocksBetweenMasts(SignalMast destination) {
677        if (!destList.containsKey(destination)) {
678            return new ArrayList<>();
679        }
680        return destList.get(destination).getAutoBlocksBetweenMasts();
681    }
682
683    /**
684     * {@inheritDoc }
685     */
686    @Override
687    public List<Turnout> getTurnouts(SignalMast destination) {
688        if (!destList.containsKey(destination)) {
689            return new ArrayList<>();
690        }
691        return destList.get(destination).getTurnouts();
692    }
693
694    /**
695     * {@inheritDoc }
696     */
697    @Override
698    public List<NamedBeanHandle<Turnout>> getNamedTurnouts(SignalMast destination) {
699        if (!destList.containsKey(destination)) {
700            return new ArrayList<>();
701        }
702        return destList.get(destination).getNamedTurnouts();
703    }
704
705    public void removeTurnout(Turnout turn, SignalMast destination) {
706        if (!destList.containsKey(destination)) {
707            return;
708        }
709
710        if (turn != null) {
711            destList.get(destination).removeTurnout(turn);
712        }
713    }
714
715    /**
716     * {@inheritDoc }
717     */
718    @Override
719    public List<Turnout> getAutoTurnouts(SignalMast destination) {
720        if (!destList.containsKey(destination)) {
721            return new ArrayList<>();
722        }
723        return destList.get(destination).getAutoTurnouts();
724    }
725
726    /**
727     * {@inheritDoc }
728     */
729    @Override
730    public List<Sensor> getSensors(SignalMast destination) {
731        if (!destList.containsKey(destination)) {
732            return new ArrayList<>();
733        }
734        return destList.get(destination).getSensors();
735    }
736
737    /**
738     * {@inheritDoc }
739     */
740    @Override
741    public List<NamedBeanHandle<Sensor>> getNamedSensors(SignalMast destination) {
742        if (!destList.containsKey(destination)) {
743            return new ArrayList<>();
744        }
745        return destList.get(destination).getNamedSensors();
746    }
747
748    /**
749     * {@inheritDoc }
750     */
751    @Override
752    public List<SignalMast> getSignalMasts(SignalMast destination) {
753        if (!destList.containsKey(destination)) {
754            return new ArrayList<>();
755        }
756        return destList.get(destination).getSignalMasts();
757    }
758
759    /**
760     * {@inheritDoc }
761     */
762    @Override
763    public List<SignalMast> getAutoMasts(SignalMast destination) {
764        if (!destList.containsKey(destination)) {
765            return new ArrayList<>();
766        }
767        return destList.get(destination).getAutoSignalMasts();
768    }
769
770    /**
771     * {@inheritDoc }
772     */
773    @Override
774    public void initialise() {
775        Enumeration<SignalMast> en = destList.keys();
776        while (en.hasMoreElements()) {
777            destList.get(en.nextElement()).initialise();
778        }
779    }
780
781    /**
782     * {@inheritDoc }
783     */
784    @Override
785    public void initialise(SignalMast destination) {
786        if (disposing) {
787            return;
788        }
789
790        if (!destList.containsKey(destination)) {
791            return;
792        }
793        destList.get(destination).initialise();
794    }
795
796    /**
797     * {@inheritDoc }
798     */
799    @Override
800    public LinkedHashMap<Block, Integer> setupLayoutEditorTurnoutDetails(List<LayoutBlock> blks, SignalMast destination) {
801        if (disposing) {
802            return new LinkedHashMap<>();
803        }
804
805        if (!destList.containsKey(destination)) {
806            return new LinkedHashMap<>();
807        }
808        return destList.get(destination).setupLayoutEditorTurnoutDetails(blks);
809    }
810
811    /**
812     * {@inheritDoc }
813     */
814    @Override
815    public void setupLayoutEditorDetails() {
816        if (disposing) {
817            return;
818        }
819        Enumeration<SignalMast> en = destList.keys();
820        while (en.hasMoreElements()) {
821            try {
822                destList.get(en.nextElement()).setupLayoutEditorDetails();
823            } catch (JmriException e) {
824                //Considered normal if no route is valid on a Layout Editor panel
825            }
826        }
827    }
828
829    /**
830     * Check if routes to the destination Signal Mast are clear.
831     *
832     * @return true if the path to the next signal is clear
833     */
834    boolean checkStates() {
835        SignalMast oldActiveMast = destination;
836        SignalMast newActiveMast = null;
837        boolean pairChanged = false;
838
839        Enumeration<SignalMast> en = destList.keys();
840        log.debug("checkStates enumerates over {} mast(s)", destList.size());
841        while (en.hasMoreElements()) {
842            SignalMast key = en.nextElement();
843            DestinationMast dm = destList.get(key);
844            log.debug("  Destination mast {}", key.getDisplayName());
845            log.debug("    isEnabled: {}", dm.isEnabled());
846            log.debug("    isActive: {}", dm.active);
847
848            if (dm.active != dm.reportedActive) {
849                dm.reportedActive = dm.active;
850                pairChanged = true;
851            }
852            if (newActiveMast == null && dm.isEnabled() && dm.isActive()) {
853                newActiveMast = key;
854            }
855        }
856        if (newActiveMast == oldActiveMast) {
857            // no change, so leave the listener, the turnout locks and the state as they are
858            if (pairChanged) {
859                // but let listeners such as the signalling tables show the pair that changed
860                firePropertyChange(PROPERTY_STATE, null, newActiveMast);
861            }
862            return newActiveMast != null;
863        }
864
865        if (oldActiveMast != null) {
866            log.debug("Remove listener from destination");
867            oldActiveMast.removePropertyChangeListener(propertyDestinationMastListener);
868            if (destList.containsKey(oldActiveMast)) {
869                destList.get(oldActiveMast).clearTurnoutLock();
870            }
871        }
872        destination = newActiveMast;
873        if (newActiveMast == null) {
874            firePropertyChange(PROPERTY_STATE, oldActiveMast, null);
875            return false;
876        }
877        log.debug("      Add listener to destination");
878        newActiveMast.addPropertyChangeListener(propertyDestinationMastListener);
879        log.debug("      firePropertyChange: \"state\"");
880        firePropertyChange(PROPERTY_STATE, oldActiveMast, newActiveMast);
881        destList.get(newActiveMast).lockTurnouts();
882        return true;
883    }
884
885    /**
886     * {@inheritDoc }
887     */
888    @Override
889    public boolean areBlocksIncluded(List<Block> blks) {
890        Enumeration<SignalMast> en = destList.keys();
891        while (en.hasMoreElements()) {
892            SignalMast dm = en.nextElement();
893            boolean included;
894            for (int i = 0; i < blks.size(); i++) {
895                included = destList.get(dm).isBlockIncluded(blks.get(i));
896                if (included) {
897                    return true;
898                }
899                included = destList.get(dm).isAutoBlockIncluded(blks.get(i));
900                if (included) {
901                    return true;
902                }
903            }
904        }
905        return false;
906    }
907
908    /**
909     * {@inheritDoc }
910     */
911    @Override
912    public int getBlockState(Block block, SignalMast destination) {
913        if (!destList.containsKey(destination)) {
914            return -1;
915        }
916        return destList.get(destination).getBlockState(block);
917    }
918
919    /**
920     * {@inheritDoc }
921     */
922    @Override
923    public boolean isBlockIncluded(Block block, SignalMast destination) {
924        if (!destList.containsKey(destination)) {
925            return false;
926        }
927        return destList.get(destination).isBlockIncluded(block);
928    }
929
930    /**
931     * {@inheritDoc }
932     */
933    @Override
934    public boolean isTurnoutIncluded(Turnout turnout, SignalMast destination) {
935        if (!destList.containsKey(destination)) {
936            return false;
937        }
938        return destList.get(destination).isTurnoutIncluded(turnout);
939    }
940
941    /**
942     * {@inheritDoc }
943     */
944    @Override
945    public boolean isSensorIncluded(Sensor sensor, SignalMast destination) {
946        if (!destList.containsKey(destination)) {
947            return false;
948        }
949        return destList.get(destination).isSensorIncluded(sensor);
950    }
951
952    /**
953     * {@inheritDoc }
954     */
955    @Override
956    public boolean isSignalMastIncluded(SignalMast signal, SignalMast destination) {
957        if (!destList.containsKey(destination)) {
958            return false;
959        }
960        return destList.get(destination).isSignalMastIncluded(signal);
961    }
962
963    /**
964     * {@inheritDoc }
965     */
966    @Override
967    public int getAutoBlockState(Block block, SignalMast destination) {
968        if (!destList.containsKey(destination)) {
969            return -1;
970        }
971        return destList.get(destination).getAutoBlockState(block);
972    }
973
974    /**
975     * {@inheritDoc }
976     */
977    @Override
978    public int getSensorState(Sensor sensor, SignalMast destination) {
979        if (!destList.containsKey(destination)) {
980            return -1;
981        }
982        return destList.get(destination).getSensorState(sensor);
983    }
984
985    /**
986     * {@inheritDoc }
987     */
988    @Override
989    public int getTurnoutState(Turnout turnout, SignalMast destination) {
990        if (!destList.containsKey(destination)) {
991            return -1;
992        }
993        return destList.get(destination).getTurnoutState(turnout);
994    }
995
996    /**
997     * {@inheritDoc }
998     */
999    @Override
1000    public int getAutoTurnoutState(Turnout turnout, SignalMast destination) {
1001        if (!destList.containsKey(destination)) {
1002            return -1;
1003        }
1004        return destList.get(destination).getAutoTurnoutState(turnout);
1005    }
1006
1007    /**
1008     * {@inheritDoc }
1009     */
1010    @Override
1011    public String getSignalMastState(SignalMast mast, SignalMast destination) {
1012        if (!destList.containsKey(destination)) {
1013            return null;
1014        }
1015        return destList.get(destination).getSignalMastState(mast);
1016    }
1017
1018    /**
1019     * {@inheritDoc }
1020     */
1021    @Override
1022    public String getAutoSignalMastState(SignalMast mast, SignalMast destination) {
1023        if (!destList.containsKey(destination)) {
1024            return null;
1025        }
1026        return destList.get(destination).getAutoSignalMastState(mast);
1027    }
1028
1029    /**
1030     * {@inheritDoc }
1031     */
1032    @Override
1033    public float getMaximumSpeed(SignalMast destination) {
1034        if (!destList.containsKey(destination)) {
1035            return -1;
1036        }
1037        return destList.get(destination).getMinimumSpeed();
1038    }
1039
1040    /**
1041     * True from when a job to set the source mast aspect is scheduled until
1042     * it, and any repeat of it, has finished.
1043     */
1044    volatile boolean inWait = false;
1045
1046    /**
1047     * True while the scheduled job is working out and setting the aspect.
1048     * Guarded by this.
1049     */
1050    private boolean appearanceJobRunning = false;
1051
1052    /**
1053     * True if a change arrived while the job was running, so the job must run
1054     * again to take it into account. Guarded by this.
1055     */
1056    private boolean appearanceJobRepeat = false;
1057
1058    /**
1059     * Before going active or checking that we can go active, wait half the
1060     * signal logic delay for things to settle down to help prevent a race
1061     * condition.
1062     * <p>
1063     * A change that arrives while a job is waiting to start needs nothing
1064     * more, as the job will see it. A change that arrives while the job is
1065     * running may come after the job has read the state it changed, so the
1066     * job then runs again.
1067     */
1068    synchronized void setSignalAppearance() {
1069        log.debug("setMastAppearance (Aspect) called for {}", source.getDisplayName());
1070        if (inWait) {
1071            if (appearanceJobRunning) {
1072                log.debug("setMastAppearance (Aspect) called while the aspect is being set, will run again");
1073                appearanceJobRepeat = true;
1074            } else {
1075                log.debug("setMastAppearance (Aspect) called with inWait set, returning");
1076            }
1077            return;
1078        }
1079        inWait = true;
1080        scheduleAppearanceJob();
1081    }
1082
1083    /**
1084     * Schedule the job that sets the source mast aspect. Called with this
1085     * locked.
1086     */
1087    private void scheduleAppearanceJob() {
1088        // The next line forces a single initialization of InstanceManager.getDefault(SignalSpeedMap.class)
1089        // before launching parallel threads
1090        InstanceManager.getDefault(SignalSpeedMap.class);
1091
1092        // The next line forces a single initialization of InstanceManager.getDefault(SignalMastLogicManager.class)
1093        // before launching delay
1094        int tempDelay = InstanceManager.getDefault(SignalMastLogicManager.class).getSignalLogicDelay() / 2;
1095        log.debug("SignalMastLogicManager started (delay)");
1096        ThreadingUtil.runOnLayoutDelayed(this::runAppearanceJob, tempDelay);
1097    }
1098
1099    private void runAppearanceJob() {
1100        synchronized (this) {
1101            appearanceJobRunning = true;
1102            appearanceJobRepeat = false;
1103        }
1104        try {
1105            setMastAppearance();
1106        } finally {
1107            synchronized (this) {
1108                appearanceJobRunning = false;
1109                boolean repeat = appearanceJobRepeat && !disposing;
1110                appearanceJobRepeat = false;
1111                if (repeat) {
1112                    try {
1113                        scheduleAppearanceJob();
1114                    } catch (RuntimeException ex) {
1115                        inWait = false;
1116                        log.error("Could not set the aspect of {} again", source.getDisplayName(), ex);
1117                    }
1118                } else {
1119                    inWait = false;
1120                }
1121            }
1122        }
1123    }
1124
1125    /**
1126     * Evaluate the destination signal mast Aspect and set ours accordingly.
1127     */
1128    void setMastAppearance() {
1129        log.debug("Set source Signal Mast Aspect");
1130        if (disposing) {
1131            // a delayed check can still run after the logic has been disposed
1132            return;
1133        }
1134        if (getSourceMast().getHeld()) {
1135            log.debug("Signal is at a Held state so will set to the aspect defined for Held or Danger");
1136
1137            String heldAspect = getSourceMast().getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.HELD);
1138            if (heldAspect != null && !getSourceMast().isAspectDisabled(heldAspect)) {
1139                log.debug("  Setting to HELD value of {}", heldAspect);
1140            } else {
1141                heldAspect = getSourceMast().getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.DANGER);
1142                log.debug("  Setting to DANGER value of {}", heldAspect);
1143            }
1144            String aspectSet = heldAspect; // for lambda
1145            try {
1146                ThreadingUtil.runOnLayout(() -> {
1147                    getSourceMast().setAspect(aspectSet);
1148                });
1149            } catch (RuntimeException ex) {
1150                log.error("Could not set held aspect {} on {}", aspectSet, getSourceMast().getDisplayName(), ex);
1151            }
1152            return;
1153        }
1154        if (!checkStates()) {
1155            log.debug("Advanced routes not clear, set Stop aspect");
1156            setSourceAspect(stopAspect);
1157            return;
1158        }
1159        String[] advancedAspect;
1160        if (destination.getHeld()) {
1161            if (destination.getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.HELD) != null) {
1162                advancedAspect = getSourceMast().getAppearanceMap().getValidAspectsForAdvancedAspect(destination.getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.HELD));
1163            } else {
1164                advancedAspect = getSourceMast().getAppearanceMap().getValidAspectsForAdvancedAspect(destination.getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.DANGER));
1165            }
1166        } else {
1167            advancedAspect = getSourceMast().getAppearanceMap().getValidAspectsForAdvancedAspect(destination.getAspect());
1168        }
1169
1170        log.debug("distant aspect is {}", destination.getAspect());
1171        log.debug("advanced aspect is {}", advancedAspect != null ? advancedAspect : "<null>");
1172
1173        if (advancedAspect != null) {
1174            String aspect = stopAspect;
1175            if (destList.get(destination).permissiveBlock) {
1176                if (!getSourceMast().isPermissiveSmlDisabled()) {
1177                    //if a block is in a permissive state then we set the permissive appearance
1178                    aspect = getSourceMast().getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.PERMISSIVE);
1179                }
1180            } else {
1181                for (String advancedAspect1 : advancedAspect) {
1182                    if (!getSourceMast().isAspectDisabled(advancedAspect1)) {
1183                        aspect = advancedAspect1;
1184                        break;
1185                    }
1186                }
1187                List<Integer> divergAspects = new ArrayList<>();
1188                List<Integer> nonDivergAspects = new ArrayList<>();
1189                List<Integer> eitherAspects = new ArrayList<>();
1190                if (advancedAspect.length > 1) {
1191                    float maxSigSpeed = -1;
1192                    float maxPathSpeed = destList.get(destination).getMinimumSpeed();
1193                    boolean divergRoute = destList.get(destination).turnoutThrown;
1194
1195                    log.debug("Diverging route? {}", divergRoute);
1196                    boolean divergFlagsAvailable = false;
1197                    //We split the aspects into two lists, one with diverging flag set, the other without.
1198                    for (int i = 0; i < advancedAspect.length; i++) {
1199                        String div = null;
1200                        if (!getSourceMast().isAspectDisabled(advancedAspect[i])) {
1201                            div = (String) getSourceMast().getSignalSystem().getProperty(advancedAspect[i], "route");
1202                        }
1203                        if (div != null) {
1204                            if (div.equals("Diverging")) {
1205                                log.debug("Aspect {} added as Diverging Route", advancedAspect[i]);
1206                                divergAspects.add(i);
1207                                divergFlagsAvailable = true;
1208                                log.debug("Using Diverging Flag");
1209                            } else if (div.equals("Either")) {
1210                                log.debug("Aspect {} added as both Diverging and Normal Route", advancedAspect[i]);
1211                                nonDivergAspects.add(i);
1212                                divergAspects.add(i);
1213                                divergFlagsAvailable = true;
1214                                eitherAspects.add(i);
1215                                log.debug("Using Diverging Flag");
1216                            } else {
1217                                log.debug("Aspect {} added as Normal Route", advancedAspect[i]);
1218                                nonDivergAspects.add(i);
1219                                log.debug("Aspect {} added as Normal Route", advancedAspect[i]);
1220                            }
1221                        } else {
1222                            nonDivergAspects.add(i);
1223                            log.debug("Aspect {} added as Normal Route", advancedAspect[i]);
1224                        }
1225                    }
1226                    if ((eitherAspects.equals(divergAspects)) && (divergAspects.size() < nonDivergAspects.size())) {
1227                        //There are no unique diverging aspects
1228                        log.debug("'Either' aspects equals divergAspects and is less than non-diverging aspects");
1229                        divergFlagsAvailable = false;
1230                    }
1231                    log.debug("path max speed : {}", maxPathSpeed);
1232                    for (int i = 0; i < advancedAspect.length; i++) {
1233                        if (!getSourceMast().isAspectDisabled(advancedAspect[i])) {
1234                            String strSpeed = (String) getSourceMast().getSignalSystem().getProperty(advancedAspect[i], "speed");
1235                            log.debug("Aspect Speed = {} for aspect {}", strSpeed, advancedAspect[i]);
1236                            /*  if the diverg flags available is set and the diverg aspect
1237                             array contains the entry then we will check this aspect.
1238
1239                             If the diverg flag has not been set then we will check.
1240                             */
1241                            log.debug("advanced aspect {}",advancedAspect[i]);
1242                            if ((divergRoute && (divergFlagsAvailable) && (divergAspects.contains(i))) || ((divergRoute && !divergFlagsAvailable) || (!divergRoute)) && (nonDivergAspects.contains(i))) {
1243                                log.debug("In list");
1244                                if ((strSpeed != null) && (!strSpeed.isEmpty())) {
1245                                    float speed = 0.0f;
1246                                    try {
1247                                        speed = Float.parseFloat(strSpeed);
1248                                    } catch (NumberFormatException nx) {
1249                                        // not a number, perhaps a name?
1250                                        try {
1251                                            speed = InstanceManager.getDefault(SignalSpeedMap.class).getSpeed(strSpeed);
1252                                        } catch (IllegalArgumentException ex) {
1253                                            // not a name either
1254                                            log.warn("Using speed = 0.0 because could not understand \"{}\"", strSpeed);
1255                                        }
1256                                    }
1257                                    //Integer state = Integer.parseInt(strSpeed);
1258                                    /* This pics out either the highest speed signal if there
1259                                     * is no block speed specified or the highest speed signal
1260                                     * that is under the minimum block speed.
1261                                     */
1262                                    log.debug("{} signal state speed {} maxSigSpeed {} maxPathSpeed {}", destination.getDisplayName(), speed, maxSigSpeed, maxPathSpeed);
1263                                    if (maxPathSpeed == 0) {
1264                                        if (maxSigSpeed == -1) {
1265                                            log.debug("min speed on this route is equal to 0 so will set this as our max speed");
1266                                            maxSigSpeed = speed;
1267                                            aspect = advancedAspect[i];
1268                                            log.debug("Aspect to set is {}", aspect);
1269                                        } else if (speed > maxSigSpeed) {
1270                                            log.debug("new speed is faster than old will use this");
1271                                            maxSigSpeed = speed;
1272                                            aspect = advancedAspect[i];
1273                                            log.debug("Aspect to set is {}", aspect);
1274                                        }
1275                                    } else if ((speed > maxSigSpeed) && (maxSigSpeed < maxPathSpeed) && (speed <= maxPathSpeed)) {
1276                                        //Only set the speed to the lowest if the max speed is greater than the path speed
1277                                        //and the new speed is less than the last max speed
1278                                        log.debug("our minimum speed on this route is less than our state speed, we will set this as our max speed");
1279                                        maxSigSpeed = speed;
1280                                        aspect = advancedAspect[i];
1281                                        log.debug("Aspect to set is {}", aspect);
1282                                    } else if ((maxSigSpeed > maxPathSpeed) && (speed < maxSigSpeed)) {
1283                                        log.debug("our max signal speed is greater than our path speed on this route, our speed is less that the maxSigSpeed");
1284                                        maxSigSpeed = speed;
1285                                        aspect = advancedAspect[i];
1286                                        log.debug("Aspect to set is {}", aspect);
1287
1288                                    } else if (maxSigSpeed == -1) {
1289                                        log.debug("maxSigSpeed returned as -1");
1290                                        maxSigSpeed = speed;
1291                                        aspect = advancedAspect[i];
1292                                        log.debug("Aspect to set is {}", aspect);
1293                                    }
1294                                }
1295                            }
1296                        } else {
1297                            log.debug("Aspect has been disabled {}", advancedAspect[i]);
1298                        }
1299                    }
1300                }
1301            }
1302            if ((aspect != null) && (!aspect.isEmpty())) {
1303                log.debug("setMastAppearance setting aspect \"{}\"", aspect);
1304                String aspectSet = aspect; // for lambda
1305                try {
1306                    ThreadingUtil.runOnLayout(() -> {
1307                        setSourceAspect(aspectSet);
1308                    });
1309                } catch (Exception ex) {
1310                    log.error("Exception while setting Signal Logic", ex);
1311                }
1312                return;
1313            }
1314        }
1315        log.debug("Aspect returned is not valid, setting stop");
1316        ThreadingUtil.runOnLayout(() -> {
1317            setSourceAspect(stopAspect);
1318        });
1319    }
1320
1321    /**
1322     * Set the aspect of the source mast, unless it already shows that aspect.
1323     * Hardware masts send a command to the layout every time their aspect is
1324     * set, so an unchanged aspect is not sent again.
1325     *
1326     * A null aspect, for example a mast whose appearance map has no Danger
1327     * aspect, is not passed to the mast.
1328     *
1329     * @param aspect the aspect to show
1330     */
1331    private void setSourceAspect(@CheckForNull String aspect) {
1332        SignalMast mast = getSourceMast();
1333        if (aspect == null) {
1334            log.warn("No aspect to set on source mast {}", mast.getDisplayName());
1335            return;
1336        }
1337        if (aspect.equals(mast.getAspect())) {
1338            log.debug("Source mast {} already shows {}", mast.getDisplayName(), aspect);
1339            return;
1340        }
1341        mast.setAspect(aspect);
1342    }
1343
1344    /**
1345     * {@inheritDoc }
1346     */
1347    @Override
1348    public void setConflictingLogic(SignalMast sm, LevelXing lx) {
1349        if (sm == null) {
1350            return;
1351        }
1352        log.debug("setConflicting logic mast {}", sm.getDisplayName());
1353        if (sm == source) {
1354            log.debug("source is us so exit");
1355            return;
1356        }
1357        Enumeration<SignalMast> en = destList.keys();
1358        while (en.hasMoreElements()) {
1359            SignalMast dm = en.nextElement();
1360            if (destList.get(dm).isBlockIncluded(lx.getLayoutBlockAC())) {
1361                destList.get(dm).addAutoSignalMast(sm);
1362            } else if (destList.get(dm).isBlockIncluded(lx.getLayoutBlockBD())) {
1363                destList.get(dm).addAutoSignalMast(sm);
1364            } else if (destList.get(dm).isAutoBlockIncluded(lx.getLayoutBlockAC())) {
1365                destList.get(dm).addAutoSignalMast(sm);
1366            } else if (destList.get(dm).isAutoBlockIncluded(lx.getLayoutBlockBD())) {
1367                destList.get(dm).addAutoSignalMast(sm);
1368            } else {
1369                log.debug("Block not found");
1370            }
1371        }
1372    }
1373
1374    /**
1375     * {@inheritDoc }
1376     */
1377    @Override
1378    public void removeConflictingLogic(SignalMast sm, LevelXing lx) {
1379        if (sm == source) {
1380            return;
1381        }
1382        Enumeration<SignalMast> en = destList.keys();
1383        while (en.hasMoreElements()) {
1384            SignalMast dm = en.nextElement();
1385            if (destList.get(dm).isBlockIncluded(lx.getLayoutBlockAC())) {
1386                destList.get(dm).removeAutoSignalMast(sm);
1387            } else if (destList.get(dm).isBlockIncluded(lx.getLayoutBlockBD())) {
1388                destList.get(dm).removeAutoSignalMast(sm);
1389            }
1390        }
1391    }
1392
1393    /**
1394     * Class to store SML properties for a destination mast paired with this
1395     * source mast.
1396     */
1397    private class DestinationMast {
1398
1399        LayoutBlock destinationBlock = null;
1400        LayoutBlock protectingBlock = null; //this is the block that the source signal is protecting
1401
1402        List<NamedBeanSetting> userSetTurnouts = new ArrayList<>(0);
1403        Hashtable<Turnout, Integer> autoTurnouts = new Hashtable<>(0);
1404        //Hashtable<Turnout, Boolean> turnoutThroats = new Hashtable<Turnout, Boolean>(0);
1405        //Hashtable<Turnout, Boolean> autoTurnoutThroats = new Hashtable<Turnout, Boolean>(0);
1406
1407        List<NamedBeanSetting> userSetMasts = new ArrayList<>(0);
1408        Hashtable<SignalMast, String> autoMasts = new Hashtable<>(0);
1409        List<NamedBeanSetting> userSetSensors = new ArrayList<>(0);
1410        List<NamedBeanSetting> userSetBlocks = new ArrayList<>(0);
1411        boolean turnoutThrown = false;
1412        boolean permissiveBlock = false;
1413        boolean disposed = false;
1414
1415        List<LevelXing> blockInXings = new ArrayList<>();
1416
1417        //autoBlocks are for those automatically generated by the system.
1418        LinkedHashMap<Block, Integer> autoBlocks = new LinkedHashMap<>(0);
1419
1420        List<Block> xingAutoBlocks = new ArrayList<>(0);
1421        List<Block> dblCrossoverAutoBlocks = new ArrayList<>(0);
1422        SignalMast destinationSignalMast;
1423        volatile boolean active = false;
1424        boolean reportedActive = false; // active state last reported by checkStates
1425        boolean destMastInit = false;
1426
1427        float minimumBlockSpeed = 0.0f;
1428
1429        boolean useLayoutEditor = false;
1430        boolean useLayoutEditorTurnouts = false;
1431        boolean useLayoutEditorBlocks = false;
1432        boolean lockTurnouts = false;
1433
1434        NamedBeanHandle<Section> associatedSection = null;
1435
1436        DestinationMast(SignalMast destination) {
1437            this.destinationSignalMast = destination;
1438            if (destination.getAspect() == null) {
1439                try {
1440                    destination.setAspect(destination.getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.DANGER));
1441                } catch (Exception ex) {
1442                    log.error("Error while creating Signal Logic", ex);
1443                }
1444            }
1445        }
1446
1447        void updateDestinationMast(SignalMast newMast) {
1448            destinationSignalMast = newMast;
1449            if (destinationSignalMast.getAspect() == null) {
1450                try {
1451                    destinationSignalMast.setAspect(destinationSignalMast.getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.DANGER));
1452                } catch (Exception ex) {
1453                    log.error("Error while creating Signal Logic", ex);
1454                }
1455            }
1456        }
1457
1458        LayoutBlock getProtectingBlock() {
1459            return protectingBlock;
1460        }
1461
1462        String getDisplayName() {
1463            return destinationSignalMast.getDisplayName();
1464        }
1465
1466        String comment;
1467
1468        String getComment() {
1469            return comment;
1470        }
1471
1472        void setComment(String comment) {
1473            String old = this.comment;
1474            this.comment = comment;
1475            firePropertyChange(PROPERTY_COMMENT, old, comment);
1476        }
1477
1478        boolean isActive() {
1479            if (disposed) {
1480                log.error("checkState called even though this has been disposed of");
1481                return false;
1482            }
1483            return active;
1484        }
1485
1486        float getMinimumSpeed() {
1487            return minimumBlockSpeed;
1488        }
1489
1490        boolean enable = true;
1491
1492        void setEnabled() {
1493            enable = true;
1494            firePropertyChange(PROPERTY_ENABLED, false, this.destinationSignalMast);
1495        }
1496
1497        void setDisabled() {
1498            enable = false;
1499            firePropertyChange(PROPERTY_ENABLED, true, this.destinationSignalMast);
1500        }
1501
1502        boolean isEnabled() {
1503            return enable;
1504        }
1505
1506        int store = STOREALL;
1507
1508        void setStore(int store) {
1509            this.store = store;
1510        }
1511
1512        int getStoreState() {
1513            return store;
1514        }
1515
1516        void setAssociatedSection(Section section) {
1517            if (section != null && (!useLayoutEditor || !useLayoutEditorBlocks)) {
1518                log.warn("This Logic {} to {} is not using the Layout Editor or its Blocks, the associated Section will not be populated correctly", source.getDisplayName(), destinationSignalMast.getDisplayName());
1519            }
1520            if (section == null) {
1521                associatedSection = null;
1522                return;
1523            }
1524            associatedSection = InstanceManager.getDefault(NamedBeanHandleManager.class).getNamedBeanHandle(section.getDisplayName(), section);
1525            if (!autoBlocks.isEmpty()) { // associatedSection is guaranteed to exist
1526                createSectionDetails();
1527            }
1528        }
1529
1530        Section getAssociatedSection() {
1531            if (associatedSection != null) {
1532                return associatedSection.getBean();
1533            }
1534            return null;
1535        }
1536
1537        void createSectionDetails() {
1538            getAssociatedSection().removeAllBlocksFromSection();
1539            getAutoBlocksBetweenMasts().forEach(key -> {
1540                getAssociatedSection().addBlock(key);
1541            });
1542            String dir = Path.decodeDirection(getFacingBlock().getNeighbourDirection(getProtectingBlock()));
1543            EntryPoint ep = new EntryPoint(getProtectingBlock().getBlock(), getFacingBlock().getBlock(), dir);
1544            ep.setTypeForward();
1545            getAssociatedSection().addToForwardList(ep);
1546
1547            LayoutBlock proDestLBlock = InstanceManager.getDefault(LayoutBlockManager.class).getProtectedBlockByNamedBean(destinationSignalMast, destinationBlock.getMaxConnectedPanel());
1548            if (proDestLBlock != null) {
1549                log.debug("Add protecting Block {}", proDestLBlock.getDisplayName());
1550                dir = Path.decodeDirection(proDestLBlock.getNeighbourDirection(destinationBlock));
1551                ep = new EntryPoint(destinationBlock.getBlock(), proDestLBlock.getBlock(), dir);
1552                ep.setTypeReverse();
1553                getAssociatedSection().addToReverseList(ep);
1554            } else {
1555                log.debug(" ### Protecting Block not found ### ");
1556            }
1557        }
1558
1559        boolean isTurnoutLockAllowed() {
1560            return lockTurnouts;
1561        }
1562
1563        void allowTurnoutLock(boolean lock) {
1564            if (lockTurnouts == lock) {
1565                return;
1566            }
1567            if (!lock) {
1568                clearTurnoutLock();
1569            }
1570            lockTurnouts = lock;
1571        }
1572
1573        void setTurnouts(Hashtable<NamedBeanHandle<Turnout>, Integer> turnouts) {
1574            if (this.userSetTurnouts != null) {
1575                userSetTurnouts.forEach(nbh ->
1576                    nbh.getBean().removePropertyChangeListener(propertyTurnoutListener));
1577            }
1578            destMastInit = false;
1579            if (turnouts == null) {
1580                userSetTurnouts = new ArrayList<>(0);
1581            } else {
1582                userSetTurnouts = new ArrayList<>();
1583                Enumeration<NamedBeanHandle<Turnout>> e = turnouts.keys();
1584                while (e.hasMoreElements()) {
1585                    NamedBeanHandle<Turnout> nbh = e.nextElement();
1586                    NamedBeanSetting nbs = new NamedBeanSetting(nbh, turnouts.get(nbh));
1587                    userSetTurnouts.add(nbs);
1588                }
1589            }
1590            firePropertyChange(PROPERTY_TURNOUTS, null, this.destinationSignalMast);
1591        }
1592
1593        void setAutoTurnouts(Hashtable<Turnout, Integer> turnouts) {
1594            log.debug("{} called setAutoTurnouts with {}", destinationSignalMast.getDisplayName(),
1595                (turnouts != null ? "" + turnouts.size() + " turnouts in hash table" : "null hash table reference"));
1596            if (this.autoTurnouts != null) {
1597                Enumeration<Turnout> keys = this.autoTurnouts.keys();
1598                while (keys.hasMoreElements()) {
1599                    Turnout key = keys.nextElement();
1600                    key.removePropertyChangeListener(propertyTurnoutListener);
1601                }
1602                //minimumBlockSpeed = 0;
1603            }
1604            destMastInit = false;
1605            if (turnouts == null) {
1606                this.autoTurnouts = new Hashtable<>(0);
1607            } else {
1608                this.autoTurnouts = new Hashtable<>(turnouts);
1609            }
1610            firePropertyChange(PROPERTY_AUTO_TURNOUTS, null, this.destinationSignalMast);
1611        }
1612
1613        void setBlocks(Hashtable<Block, Integer> blocks) {
1614            log.debug("{} Set blocks called", destinationSignalMast.getDisplayName());
1615            if (this.userSetBlocks != null) {
1616                userSetBlocks.forEach( nbh ->
1617                    nbh.getBean().removePropertyChangeListener(propertyBlockListener));
1618            }
1619            destMastInit = false;
1620
1621            userSetBlocks = new ArrayList<>(0);
1622            if (blocks != null) {
1623                userSetBlocks = new ArrayList<>();
1624                Enumeration<Block> e = blocks.keys();
1625                while (e.hasMoreElements()) {
1626                    Block blk = e.nextElement();
1627                    NamedBeanHandle<?> nbh = InstanceManager.getDefault(NamedBeanHandleManager.class).getNamedBeanHandle(blk.getDisplayName(), blk);
1628                    NamedBeanSetting nbs = new NamedBeanSetting(nbh, blocks.get(blk));
1629                    userSetBlocks.add(nbs);
1630                }
1631            }
1632            firePropertyChange(PROPERTY_BLOCKS, null, this.destinationSignalMast);
1633        }
1634
1635        public void setAutoBlocks(LinkedHashMap<Block, Integer> blocks) {
1636            if (log.isDebugEnabled()) {
1637                log.debug("{} called setAutoBlocks with {}", destinationSignalMast.getDisplayName(),
1638                    (blocks != null ? "" + blocks.size() + " blocks in hash table" : "null hash table reference"));
1639            }
1640            if (this.autoBlocks != null) {
1641                autoBlocks.keySet().forEach( key ->
1642                    key.removePropertyChangeListener(propertyBlockListener));
1643            }
1644            destMastInit = false;
1645            if (blocks == null) {
1646                this.autoBlocks = new LinkedHashMap<>(0);
1647
1648            } else {
1649                this.autoBlocks = new LinkedHashMap<>(blocks);
1650                //We shall remove the facing block in the list.
1651                if (facingBlock != null && autoBlocks.containsKey(facingBlock.getBlock())) {
1652                    autoBlocks.remove(facingBlock.getBlock());
1653                }
1654                if (getAssociatedSection() != null) {
1655                    createSectionDetails();
1656                }
1657            }
1658            firePropertyChange(PROPERTY_AUTO_BLOCKS, null, this.destinationSignalMast);
1659        }
1660
1661        void setMasts(Hashtable<SignalMast, String> masts) {
1662            if (this.userSetMasts != null) {
1663                userSetMasts.forEach( nbh ->
1664                    nbh.getBean().removePropertyChangeListener(propertySignalMastListener));
1665            }
1666
1667            destMastInit = false;
1668
1669            if (masts == null) {
1670                userSetMasts = new ArrayList<>(0);
1671            } else {
1672                userSetMasts = new ArrayList<>();
1673                Enumeration<SignalMast> e = masts.keys();
1674                while (e.hasMoreElements()) {
1675                    SignalMast mast = e.nextElement();
1676                    NamedBeanHandle<?> nbh = InstanceManager.getDefault(NamedBeanHandleManager.class)
1677                        .getNamedBeanHandle(mast.getDisplayName(), mast);
1678                    NamedBeanSetting nbs = new NamedBeanSetting(nbh, masts.get(mast));
1679                    userSetMasts.add(nbs);
1680                }
1681            }
1682            firePropertyChange(PROPERTY_MASTS, null, this.destinationSignalMast);
1683        }
1684
1685        /**
1686         *
1687         * @param newAutoMasts Hashtable of signal masts and set to Aspects
1688         * @param overwrite    When true, replace an existing autoMasts list in
1689         *                     the SML
1690         */
1691        void setAutoMasts(Hashtable<SignalMast, String> newAutoMasts, boolean overwrite) {
1692            log.debug("{} setAutoMast Called", destinationSignalMast.getDisplayName());
1693            if (this.autoMasts != null) {
1694                Enumeration<SignalMast> keys = this.autoMasts.keys();
1695                while (keys.hasMoreElements()) {
1696                    SignalMast key = keys.nextElement();
1697                    key.removePropertyChangeListener(propertySignalMastListener);
1698                }
1699                //minimumBlockSpeed = 0;
1700            }
1701            destMastInit = false;
1702            if (overwrite) {
1703                if (newAutoMasts == null) {
1704                    this.autoMasts = new Hashtable<>(0);
1705                } else {
1706                    this.autoMasts = new Hashtable<>(newAutoMasts);
1707                }
1708            } else {
1709                if (newAutoMasts == null) {
1710                    this.autoMasts = new Hashtable<>(0);
1711                } else {
1712                    Enumeration<SignalMast> keys = newAutoMasts.keys();
1713                    while (keys.hasMoreElements()) {
1714                        SignalMast key = keys.nextElement();
1715                        this.autoMasts.put(key, newAutoMasts.get(key));
1716                    }
1717                }
1718            }
1719            //kick off the process to add back in signal masts at crossings.
1720            for (int i = 0; i < blockInXings.size(); i++) {
1721                blockInXings.get(i).addSignalMastLogic(source);
1722            }
1723
1724            firePropertyChange(PROPERTY_AUTO_MASTS, null, this.destinationSignalMast);
1725        }
1726
1727        void setSensors(Hashtable<NamedBeanHandle<Sensor>, Integer> sensors) {
1728            if (this.userSetSensors != null) {
1729                userSetSensors.forEach(nbh ->
1730                    nbh.getBean().removePropertyChangeListener(propertySensorListener));
1731            }
1732            destMastInit = false;
1733
1734            if (sensors == null) {
1735                userSetSensors = new ArrayList<>(0);
1736            } else {
1737                userSetSensors = new ArrayList<>();
1738                Enumeration<NamedBeanHandle<Sensor>> e = sensors.keys();
1739                while (e.hasMoreElements()) {
1740                    NamedBeanHandle<Sensor> nbh = e.nextElement();
1741                    NamedBeanSetting nbs = new NamedBeanSetting(nbh, sensors.get(nbh));
1742                    userSetSensors.add(nbs);
1743                }
1744            }
1745            firePropertyChange(PROPERTY_SENSORS, null, this.destinationSignalMast);
1746        }
1747
1748        void addSensor(NamedBeanHandle<Sensor> sen, int state) {
1749            for (NamedBeanSetting nbh : userSetSensors) {
1750                if (nbh.getBean().equals(sen.getBean())) {
1751                    return;
1752                }
1753            }
1754            sen.getBean().addPropertyChangeListener(propertySensorListener);
1755            NamedBeanSetting nbs = new NamedBeanSetting(sen, state);
1756            userSetSensors.add(nbs);
1757            firePropertyChange(PROPERTY_SENSORS, null, this.destinationSignalMast);
1758        }
1759
1760// not used now, preserved for later use
1761//         void removeSensor(NamedBeanHandle<Sensor> sen) {
1762//             for (NamedBeanSetting nbh : userSetSensors) {
1763//                 if (nbh.getBean().equals(sen.getBean())) {
1764//                     sen.getBean().removePropertyChangeListener(propertySensorListener);
1765//                     userSetSensors.remove(nbh);
1766//                     firePropertyChange("sensors", null, this.destination);
1767//                     return;
1768//                 }
1769//             }
1770//         }
1771
1772        void removeSensor(Sensor sen) {
1773            for (NamedBeanSetting nbh : userSetSensors) {
1774                if (nbh.getBean().equals(sen)) {
1775                    sen.removePropertyChangeListener(propertySensorListener);
1776                    userSetSensors.remove(nbh);
1777                    firePropertyChange(PROPERTY_SENSORS, null, this.destinationSignalMast);
1778                    return;
1779                }
1780            }
1781        }
1782
1783        List<Block> getBlocks() {
1784            List<Block> out = new ArrayList<>();
1785            userSetBlocks.forEach( nbh -> out.add((Block) nbh.getBean()));
1786            return out;
1787        }
1788
1789        List<Block> getAutoBlocks() {
1790            List<Block> out = new ArrayList<>();
1791            Set<Block> blockKeys = autoBlocks.keySet();
1792            blockKeys.forEach(key -> out.add(key));
1793            return out;
1794        }
1795
1796        List<Block> getAutoBlocksBetweenMasts() {
1797            if (destList.get(destinationSignalMast).xingAutoBlocks.isEmpty()
1798                    && destList.get(destinationSignalMast).dblCrossoverAutoBlocks.isEmpty()) {
1799                return getAutoBlocks();
1800            }
1801            List<Block> returnList = getAutoBlocks();
1802            for (Block blk : getAutoBlocks()) {
1803                if (xingAutoBlocks.contains(blk)) {
1804                    returnList.remove(blk);
1805                }
1806                if (dblCrossoverAutoBlocks.contains(blk)) {
1807                    returnList.remove(blk);
1808                }
1809            }
1810
1811            return returnList;
1812        }
1813
1814        List<Turnout> getTurnouts() {
1815            List<Turnout> out = new ArrayList<>();
1816            userSetTurnouts.forEach( nbh -> out.add((Turnout) nbh.getBean()));
1817            return out;
1818        }
1819
1820        void removeTurnout(Turnout turn) {
1821            Iterator<NamedBeanSetting> nbh = userSetTurnouts.iterator();
1822            while (nbh.hasNext()) {
1823                NamedBeanSetting i = nbh.next();
1824                if (i.getBean().equals(turn)) {
1825                    turn.removePropertyChangeListener(propertyTurnoutListener);
1826                    nbh.remove();
1827                    firePropertyChange(PROPERTY_TURNOUTS, null, this.destinationSignalMast);
1828                }
1829            }
1830        }
1831
1832        @SuppressWarnings("unchecked") // (NamedBeanHandle<Turnout>) nbh.getNamedBean() is unchecked cast
1833        List<NamedBeanHandle<Turnout>> getNamedTurnouts() {
1834            List<NamedBeanHandle<Turnout>> out = new ArrayList<>();
1835            userSetTurnouts.forEach(nbh ->
1836                out.add((NamedBeanHandle<Turnout>) nbh.getNamedBean()));
1837            return out;
1838        }
1839
1840        List<Turnout> getAutoTurnouts() {
1841            List<Turnout> out = new ArrayList<>();
1842            Enumeration<Turnout> en = autoTurnouts.keys();
1843            while (en.hasMoreElements()) {
1844                out.add(en.nextElement());
1845            }
1846            return out;
1847        }
1848
1849        List<SignalMast> getSignalMasts() {
1850            List<SignalMast> out = new ArrayList<>();
1851            userSetMasts.forEach( nbh -> out.add((SignalMast) nbh.getBean()));
1852            return out;
1853        }
1854
1855        List<SignalMast> getAutoSignalMasts() {
1856            List<SignalMast> out = new ArrayList<>();
1857            Enumeration<SignalMast> en = autoMasts.keys();
1858            while (en.hasMoreElements()) {
1859                out.add(en.nextElement());
1860            }
1861            return out;
1862        }
1863
1864        List<Sensor> getSensors() {
1865            List<Sensor> out = new ArrayList<>();
1866            userSetSensors.forEach( nbh -> out.add((Sensor) nbh.getBean()));
1867            return out;
1868        }
1869
1870        @SuppressWarnings("unchecked") // (NamedBeanHandle<Sensor>) nbh.getNamedBean() is unchecked cast
1871        List<NamedBeanHandle<Sensor>> getNamedSensors() {
1872            List<NamedBeanHandle<Sensor>> out = new ArrayList<>();
1873            userSetSensors.forEach( nbh -> out.add((NamedBeanHandle<Sensor>) nbh.getNamedBean()));
1874            return out;
1875        }
1876
1877        boolean isBlockIncluded(Block block) {
1878            return userSetBlocks.stream().anyMatch(nbh -> (nbh.getBean().equals(block)));
1879        }
1880
1881        boolean isAutoBlockIncluded(LayoutBlock block) {
1882            if (block != null) {
1883                return autoBlocks.containsKey(block.getBlock());
1884            }
1885            return false;
1886        }
1887
1888        boolean isAutoBlockIncluded(Block block) {
1889            return autoBlocks.containsKey(block);
1890        }
1891
1892        boolean isBlockIncluded(LayoutBlock block) {
1893            return userSetBlocks.stream().anyMatch(nbh -> (nbh.getBean().equals(block.getBlock())));
1894        }
1895
1896        boolean isTurnoutIncluded(Turnout turnout) {
1897            return userSetTurnouts.stream().anyMatch(nbh -> (nbh.getBean().equals(turnout)));
1898        }
1899
1900        boolean isSensorIncluded(Sensor sensor) {
1901            return userSetSensors.stream().anyMatch(nbh -> (nbh.getBean().equals(sensor)));
1902        }
1903
1904        boolean isSignalMastIncluded(SignalMast signal) {
1905            return userSetMasts.stream().anyMatch(nbh -> (nbh.getBean().equals(signal)));
1906        }
1907
1908        int getAutoBlockState(Block block) {
1909            if (autoBlocks == null) {
1910                return -1;
1911            }
1912            return autoBlocks.get(block);
1913        }
1914
1915        int getBlockState(Block block) {
1916            if (userSetBlocks == null) {
1917                return -1;
1918            }
1919            for (NamedBeanSetting nbh : userSetBlocks) {
1920                if (nbh.getBean().equals(block)) {
1921                    return nbh.getSetting();
1922                }
1923            }
1924            return -1;
1925        }
1926
1927        int getSensorState(Sensor sensor) {
1928            if (userSetSensors == null) {
1929                return -1;
1930            }
1931            for (NamedBeanSetting nbh : userSetSensors) {
1932                if (nbh.getBean().equals(sensor)) {
1933                    return nbh.getSetting();
1934                }
1935            }
1936            return -1;
1937        }
1938
1939        int getTurnoutState(Turnout turnout) {
1940            if (userSetTurnouts == null) {
1941                return -1;
1942            }
1943            for (NamedBeanSetting nbh : userSetTurnouts) {
1944                if (nbh.getBean().equals(turnout)) {
1945                    return nbh.getSetting();
1946                }
1947            }
1948            return -1;
1949        }
1950
1951        int getAutoTurnoutState(Turnout turnout) {
1952            if (autoTurnouts == null) {
1953                return -1;
1954            }
1955            if (autoTurnouts.containsKey(turnout)) {
1956                return autoTurnouts.get(turnout);
1957            }
1958            return -1;
1959        }
1960
1961        String getSignalMastState(SignalMast mast) {
1962            if (userSetMasts == null) {
1963                return null;
1964            }
1965            for (NamedBeanSetting nbh : userSetMasts) {
1966                if (nbh.getBean().equals(mast)) {
1967                    return nbh.getStringSetting();
1968                }
1969            }
1970            return null;
1971        }
1972
1973        String getAutoSignalMastState(SignalMast mast) {
1974            if (autoMasts == null) {
1975                return null;
1976            }
1977            return autoMasts.get(mast);
1978        }
1979
1980        /**
1981         * True from when a check of this pair is scheduled until it, and any
1982         * repeat of it, has finished.
1983         */
1984        volatile boolean inWait = false;
1985
1986        /**
1987         * The number of checks of this pair running now: a scheduled one, and
1988         * the first one, which {@link #initialise()} runs on its own thread.
1989         * Guarded by this.
1990         */
1991        private int checksRunning = 0;
1992
1993        /**
1994         * True if something on the route changed while a check was running,
1995         * so what it found may be out of date and the check must run again.
1996         * Guarded by this.
1997         */
1998        private boolean checkRepeat = false;
1999
2000        /*
2001         * Before going active or checking that we can go active, wait
2002         * for things to settle down to help prevent a race condition.
2003         * A change that arrives while a check is running may come after the
2004         * check has read the item that changed, so the check then runs again.
2005         */
2006        synchronized void checkState() {
2007            if (disposed) {
2008                log.error("checkState called even though this has been disposed of {}",
2009                    getSourceMast().getDisplayName());
2010                return;
2011            }
2012
2013            if (!enable) {
2014                return;
2015            }
2016            if (checksRunning > 0) {
2017                checkRepeat = true;
2018            }
2019            if (inWait) {
2020                return;
2021            }
2022
2023            log.debug("check Signal Dest State called");
2024            inWait = true;
2025            scheduleCheck();
2026        }
2027
2028        /**
2029         * Schedule the check of this pair. Called with this locked.
2030         */
2031        private void scheduleCheck() {
2032            // The next line forces a single initialization of InstanceManager.getDefault(SignalMastLogicManager.class)
2033            // before launching parallel threads
2034            int tempDelay = InstanceManager.getDefault(SignalMastLogicManager.class).getSignalLogicDelay();
2035
2036            ThreadingUtil.runOnLayoutDelayed(() -> runCheck(true), tempDelay);
2037        }
2038
2039        /**
2040         * Check this pair, as a scheduled check or straight away.
2041         *
2042         * @param scheduled true for the check scheduled by
2043         *                  {@link #checkState()}, which clears
2044         *                  {@link #inWait} when it has finished
2045         */
2046        private void runCheck(boolean scheduled) {
2047            synchronized (this) {
2048                if (checksRunning == 0) {
2049                    checkRepeat = false;
2050                }
2051                checksRunning++;
2052            }
2053            try {
2054                // a delayed check can still run after this pair has been disposed
2055                if (!disposed) {
2056                    checkStateDetails();
2057                }
2058            } finally {
2059                synchronized (this) {
2060                    checksRunning--;
2061                    boolean repeat = false;
2062                    if (checksRunning == 0) {
2063                        // a scheduled check that has not started yet will see the change anyway
2064                        repeat = checkRepeat && !disposed && enable && (scheduled || !inWait);
2065                        checkRepeat = false;
2066                    }
2067                    if (repeat) {
2068                        inWait = true;
2069                        try {
2070                            scheduleCheck();
2071                        } catch (RuntimeException ex) {
2072                            inWait = false;
2073                            log.error("Could not check {} to {} again", getSourceMast().getDisplayName(),
2074                                destinationSignalMast.getDisplayName(), ex);
2075                        }
2076                    } else if (scheduled) {
2077                        inWait = false;
2078                    }
2079                }
2080            }
2081        }
2082
2083        /**
2084         * A bean on the route has changed so that the route is no longer
2085         * set: go to danger at once. A check that is running may already have
2086         * read that bean, so it must not put back what it found; it runs
2087         * again instead. Called by the bean listeners, on whatever thread the
2088         * bean reports its change on.
2089         */
2090        private void routeNoLongerSet() {
2091            boolean wasActive;
2092            synchronized (this) {
2093                if (checksRunning > 0) {
2094                    checkRepeat = true;
2095                }
2096                wasActive = active;
2097                active = false;
2098            }
2099            if (wasActive) {
2100                setSignalAppearance();
2101            }
2102        }
2103
2104        /**
2105         * Check the details of this source-destination signal mast logic pair.
2106         * Steps through every sensor, turnout etc. before setting the SML
2107         * Aspect on the source mast via {
2108         *
2109         * @see #setSignalAppearance } and {
2110         * @see #setMastAppearance }
2111         */
2112        private void checkStateDetails() {
2113            turnoutThrown = false;
2114            permissiveBlock = false;
2115            if (disposed) {
2116                log.error("checkStateDetails called even though this has been disposed of {} {}",
2117                    getSourceMast().getDisplayName(), destinationSignalMast.getDisplayName());
2118                return;
2119            }
2120            if (!enable) {
2121                return;
2122            }
2123            log.debug("From {} to {} internal check state", getSourceMast().getDisplayName(),
2124                destinationSignalMast.getDisplayName());
2125            boolean wasActive = active;
2126            active = false;
2127            if ((useLayoutEditor) && (autoTurnouts.isEmpty()) && (autoBlocks.isEmpty())) {
2128                return;
2129            }
2130            boolean state = true;
2131            Enumeration<Turnout> keys = autoTurnouts.keys();
2132            while (keys.hasMoreElements()) {
2133                Turnout key = keys.nextElement();
2134                if (key.getKnownState() != autoTurnouts.get(key)) {
2135                    if (key.getState() != autoTurnouts.get(key)) {
2136                        if (isTurnoutIncluded(key)) {
2137                            if (key.getState() != getTurnoutState(key)) {
2138                                state = false;
2139                            } else if (key.getState() == Turnout.THROWN) {
2140                                turnoutThrown = true;
2141                            }
2142                        } else {
2143                            state = false;
2144                        }
2145                    }
2146                } else if (key.getState() == Turnout.THROWN) {
2147                    turnoutThrown = true;
2148                }
2149            }
2150
2151            for (NamedBeanSetting nbh : userSetTurnouts) {
2152                Turnout key = (Turnout) nbh.getBean();
2153                if (key.getKnownState() != nbh.getSetting()) {
2154                    state = false;
2155                } else if (key.getState() == Turnout.THROWN) {
2156                    turnoutThrown = true;
2157                }
2158            }
2159
2160            Enumeration<SignalMast> mastKeys = autoMasts.keys();
2161            while (mastKeys.hasMoreElements()) {
2162                SignalMast key = mastKeys.nextElement();
2163                String aspect = key.getAspect();
2164                log.debug("key {} {} {}", key.getDisplayName(), aspect, autoMasts.get(key));
2165                if ((aspect != null) && (!aspect.equals(autoMasts.get(key)))) {
2166                    if (isSignalMastIncluded(key)) {
2167                        //Basically if we have a blank aspect, we don't care about the state of the signalmast
2168                        if (!getSignalMastState(key).isEmpty()) {
2169                            if (!aspect.equals(getSignalMastState(key))) {
2170                                state = false;
2171                            }
2172                        }
2173                    } else {
2174                        state = false;
2175                    }
2176                }
2177            }
2178            for (NamedBeanSetting nbh : userSetMasts) {
2179                SignalMast key = (SignalMast) nbh.getBean();
2180                String aspect = key.getAspect();
2181                if ((aspect == null) || (!aspect.equals(nbh.getStringSetting()))) {
2182                    state = false;
2183                }
2184            }
2185
2186            for (NamedBeanSetting nbh : userSetSensors) {
2187                Sensor key = (Sensor) nbh.getBean();
2188                if (key.getKnownState() != nbh.getSetting()) {
2189                    state = false;
2190                }
2191            }
2192
2193            for (Map.Entry<Block, Integer> entry : this.autoBlocks.entrySet()) {
2194                log.debug(" entry {} {} {}", entry.getKey().getDisplayName(),
2195                    entry.getKey().getState(), entry.getValue());
2196                if (entry.getKey().getState() != autoBlocks.get(entry.getKey())) {
2197                    if (isBlockIncluded(entry.getKey())) {
2198                        if (getBlockState(entry.getKey()) != 0x03) {
2199                            if (entry.getKey().getState() != getBlockState(entry.getKey())) {
2200                                if (entry.getKey().getState() == Block.OCCUPIED && entry.getKey().getPermissiveWorking()) {
2201                                    permissiveBlock = true;
2202                                } else {
2203                                    state = false;
2204                                }
2205                            }
2206                        }
2207                    } else {
2208                        if (entry.getKey().getState() == Block.OCCUPIED && entry.getKey().getPermissiveWorking()) {
2209                            permissiveBlock = true;
2210                        } else if (entry.getKey().getState() == Block.UNDETECTED) {
2211                            log.debug("Block {} is UNDETECTED so treat as unoccupied", entry.getKey().getDisplayName());
2212                        } else {
2213                            state = false;
2214                        }
2215                    }
2216                }
2217            }
2218
2219            for (NamedBeanSetting nbh : userSetBlocks) {
2220                Block key = (Block) nbh.getBean();
2221                if (nbh.getSetting() != 0x03) {
2222                    if (key.getState() != nbh.getSetting()) {
2223                        if (key.getState() == Block.OCCUPIED && key.getPermissiveWorking()) {
2224                            permissiveBlock = true;
2225                        } else {
2226                            state = false;
2227                        }
2228                    }
2229                }
2230            }
2231            if (permissiveBlock
2232                /*If a block has been found to be permissive, but the source signalmast
2233                 does not support a call-on/permissive aspect then the route can not be set*/
2234                && getSourceMast().getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.PERMISSIVE) == null) {
2235                    state = false;
2236            }
2237
2238            /*This check is purely for use with the dispatcher. A block set to "useExtraColor" has been
2239             allocated; if it is held by a train other than the one approaching this mast, the signal
2240             is set to danger.*/
2241            if (state && getAssociatedSection() != null
2242                    && InstanceManager.isInitialized(jmri.jmrit.dispatcher.DispatcherFrame.class)
2243                    && InstanceManager.getNullableDefault(LayoutBlockManager.class) != null) {
2244
2245                LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class);
2246                List<Block> routeBlocks = new ArrayList<>(autoBlocks.keySet());
2247                for (NamedBeanSetting nbh : userSetBlocks) {
2248                    routeBlocks.add((Block) nbh.getBean());
2249                }
2250                if (isRouteHeldByAnotherTrain(routeBlocks, lbm)) {
2251                    state = false;
2252                }
2253            }
2254
2255            synchronized (this) {
2256                if (checkRepeat) {
2257                    // something on the route changed while this check ran, so
2258                    // what it found may be out of date: stay at danger until
2259                    // the check has run again
2260                    state = false;
2261                }
2262                if (!state) {
2263                    turnoutThrown = false;
2264                    permissiveBlock = false;
2265                }
2266                active = state;
2267            }
2268            // The source mast only needs setting again if this pair has changed
2269            // or it is the pair the source mast is currently showing.
2270            if (state != wasActive || destination == destinationSignalMast) {
2271                ThreadingUtil.runOnLayout(() -> {
2272                    setSignalAppearance();
2273                });
2274            }
2275        }
2276
2277        /**
2278         * Set up this source-destination signal mast logic pair. Steps through
2279         * every list defined on the source mast.
2280         */
2281        void initialise() {
2282            if ((destMastInit) || (disposed)) {
2283                return;
2284            }
2285
2286            active = false;
2287            turnoutThrown = false;
2288            permissiveBlock = false;
2289            if ((useLayoutEditor) && (autoTurnouts.isEmpty()) && (autoBlocks.isEmpty()) && (autoMasts.isEmpty())) {
2290                return;
2291            }
2292
2293            calculateSpeed();
2294
2295            // Each listener is removed before it is added, so that initialising
2296            // again (e.g. after one list has been changed) does not add it twice.
2297            Enumeration<Turnout> keys = autoTurnouts.keys();
2298            while (keys.hasMoreElements()) {
2299                Turnout key = keys.nextElement();
2300                key.removePropertyChangeListener(propertyTurnoutListener);
2301                key.addPropertyChangeListener(propertyTurnoutListener);
2302            }
2303
2304            for (NamedBeanSetting nbh : userSetTurnouts) {
2305                Turnout key = (Turnout) nbh.getBean();
2306                key.removePropertyChangeListener(propertyTurnoutListener);
2307                key.addPropertyChangeListener(propertyTurnoutListener, nbh.getBeanName(),
2308                    "Signal Mast Logic:" + source.getDisplayName() + " to " + destinationSignalMast.getDisplayName());
2309            }
2310
2311            Enumeration<SignalMast> mastKeys = autoMasts.keys();
2312            while (mastKeys.hasMoreElements()) {
2313                SignalMast key = mastKeys.nextElement();
2314                log.debug("{} auto mast add list {}", destinationSignalMast.getDisplayName(), key.getDisplayName());
2315                key.removePropertyChangeListener(propertySignalMastListener);
2316                key.addPropertyChangeListener(propertySignalMastListener);
2317            }
2318
2319            for (NamedBeanSetting nbh : userSetMasts) {
2320                SignalMast key = (SignalMast) nbh.getBean();
2321                key.removePropertyChangeListener(propertySignalMastListener);
2322                key.addPropertyChangeListener(propertySignalMastListener);
2323            }
2324            for (NamedBeanSetting nbh : userSetSensors) {
2325                Sensor sensor = (Sensor) nbh.getBean();
2326                sensor.removePropertyChangeListener(propertySensorListener);
2327                sensor.addPropertyChangeListener(propertySensorListener, nbh.getBeanName(),
2328                    "Signal Mast Logic:" + source.getDisplayName() + " to " + destinationSignalMast.getDisplayName());
2329            }
2330
2331            for (Block key : autoBlocks.keySet()) {
2332                log.debug("{} auto block add list {}", destinationSignalMast.getDisplayName(), key.getDisplayName());
2333                key.removePropertyChangeListener(propertyBlockListener);
2334                key.addPropertyChangeListener(propertyBlockListener);
2335            }
2336
2337            for (NamedBeanSetting nbh : userSetBlocks) {
2338                Block key = (Block) nbh.getBean();
2339                key.removePropertyChangeListener(propertyBlockListener);
2340                key.addPropertyChangeListener(propertyBlockListener);
2341            }
2342            listenForSectionHolders();
2343            destMastInit = true;
2344
2345            // Use the same checks as when one of these beans changes.
2346            runCheck(false);
2347        }
2348
2349        void useLayoutEditor(boolean boo) throws JmriException {
2350            log.debug("{} called useLayoutEditor({}), is {}",
2351                destinationSignalMast.getDisplayName(), boo, useLayoutEditor);
2352            if (useLayoutEditor == boo) {
2353                return;
2354            }
2355            useLayoutEditor = boo;
2356            if ((boo) && (InstanceManager.getDefault(LayoutBlockManager.class).routingStablised())) {
2357                // JmriException considered normal if there is no valid path using the layout editor.
2358                setupLayoutEditorDetails();
2359            } else {
2360                destinationBlock = null;
2361                // the facing block belongs to the source mast, so keep it while
2362                // another destination still uses the Layout Editor
2363                if (destList.values().stream().noneMatch(DestinationMast::useLayoutEditor)) {
2364                    facingBlock = null;
2365                }
2366                protectingBlock = null;
2367                setAutoBlocks(null);
2368                setAutoTurnouts(null);
2369            }
2370        }
2371
2372        void useLayoutEditorDetails(boolean turnouts, boolean blocks) throws JmriException {
2373            log.debug("{} use layout editor details called {}",
2374                destinationSignalMast.getDisplayName(), useLayoutEditor);
2375            useLayoutEditorTurnouts = turnouts;
2376            useLayoutEditorBlocks = blocks;
2377            if ((useLayoutEditor) && (InstanceManager.getDefault(LayoutBlockManager.class).routingStablised())) {
2378                // JmriException considered normal if there is no valid path using the Layout Editor.
2379                setupLayoutEditorDetails();
2380            }
2381        }
2382
2383        void setupLayoutEditorDetails() throws JmriException {
2384            log.debug("setupLayoutEditorDetails: useLayoutEditor={} disposed={}", useLayoutEditor, disposed);
2385            if ((!useLayoutEditor) || (disposed)) {
2386                return;
2387            }
2388            LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class);
2389            if ( destinationBlock != null) {
2390                log.debug("{} Set use layout editor", destinationSignalMast.getDisplayName());
2391            }
2392            Set<LayoutEditor> layout = InstanceManager.getDefault(EditorManager.class).getAll(LayoutEditor.class);
2393            List<LayoutBlock> protectingBlocks = new ArrayList<>();
2394            // We don't care which Layout Editor panel the signal mast is on, just so long as
2395            // the routing is done via layout blocks.
2396            remoteProtectingBlock = null;
2397            for (LayoutEditor panel : layout) {
2398                log.debug("{} Layout name {}", destinationSignalMast.getDisplayName(), panel.getLayoutName());
2399                if (facingBlock == null) {
2400                    facingBlock = lbm.getFacingBlockByNamedBean(getSourceMast(), panel);
2401                }
2402                if (protectingBlock == null && protectingBlocks.isEmpty()) {
2403                    //This is wrong
2404                    protectingBlocks = lbm.getProtectingBlocksByNamedBean(getSourceMast(), panel);
2405                }
2406                if (destinationBlock == null) {
2407                    destinationBlock = lbm.getFacingBlockByNamedBean(destinationSignalMast, panel);
2408                }
2409                if (remoteProtectingBlock == null) {
2410                    remoteProtectingBlock = lbm.getProtectedBlockByNamedBean(destinationSignalMast, panel);
2411                }
2412            }
2413            // At this point, if we are not using the Layout Editor turnout or block
2414            // details then there is no point in trying to gather them.
2415            if ((!useLayoutEditorTurnouts) && (!useLayoutEditorBlocks)) {
2416                return;
2417            }
2418            if (facingBlock == null) {
2419                log.error("No facing block found for source mast {}", getSourceMast().getDisplayName());
2420                throw new JmriException("No facing block found for source mast " + getSourceMast().getDisplayName());
2421            }
2422            if (destinationBlock == null) {
2423                log.error("No facing block found for destination mast {}", destinationSignalMast.getDisplayName());
2424                throw new JmriException("No facing block found for destination mast " + destinationSignalMast.getDisplayName());
2425            }
2426            List<LayoutBlock> lblks = new ArrayList<>();
2427            if (protectingBlock == null) {
2428                log.debug("protecting block is null");
2429                String pBlkNames = "";
2430                StringBuffer lBlksNamesBuf = new StringBuffer();
2431                for (LayoutBlock pBlk : protectingBlocks) {
2432                    log.debug("checking layoutBlock {}", pBlk.getDisplayName());
2433                    pBlkNames = pBlkNames + pBlk.getDisplayName() + " (" + lbm.getLayoutBlockConnectivityTools().checkValidDest(facingBlock, pBlk, destinationBlock, remoteProtectingBlock, LayoutBlockConnectivityTools.Routing.MASTTOMAST) + "), ";
2434                    if (lbm.getLayoutBlockConnectivityTools().checkValidDest(facingBlock, pBlk, destinationBlock, remoteProtectingBlock, LayoutBlockConnectivityTools.Routing.MASTTOMAST)) {
2435                        try {
2436                            lblks = lbm.getLayoutBlockConnectivityTools().getLayoutBlocks(facingBlock, destinationBlock, pBlk, true, LayoutBlockConnectivityTools.Routing.MASTTOMAST);
2437                            protectingBlock = pBlk;
2438                            log.debug("building path names...");
2439                            for (LayoutBlock lBlk : lblks) {
2440                                lBlksNamesBuf.append(" ");
2441                                lBlksNamesBuf.append(lBlk.getDisplayName());
2442                            }
2443                            break;
2444                        } catch (JmriException ee) {
2445                            log.debug("path not found this time");
2446                        }
2447                    }
2448                }
2449                String lBlksNames = new String(lBlksNamesBuf);
2450
2451                if (protectingBlock == null) {
2452                    throw new JmriException("Path not valid, protecting block is null. Protecting block: " + pBlkNames
2453                        + " not connected to " + facingBlock.getDisplayName() + ". Layout block names: " + lBlksNames);
2454                }
2455            }
2456            if (!lbm.getLayoutBlockConnectivityTools().checkValidDest(facingBlock,protectingBlock,
2457                destinationBlock, remoteProtectingBlock, LayoutBlockConnectivityTools.Routing.MASTTOMAST)) {
2458                throw new JmriException("Path not valid, destination check failed.");
2459            }
2460            if (log.isDebugEnabled()) {
2461                log.debug("{} face {}", destinationSignalMast.getDisplayName(), facingBlock);
2462                log.debug("{} prot {}", destinationSignalMast.getDisplayName(), protectingBlock);
2463                log.debug("{} dest {}", destinationSignalMast.getDisplayName(), destinationBlock);
2464            }
2465
2466            if (destinationBlock != null && protectingBlock != null && facingBlock != null) {
2467                setAutoMasts(null, true);
2468                if (log.isDebugEnabled()) {
2469                    log.debug("{} face {}", destinationSignalMast.getDisplayName(), facingBlock.getDisplayName());
2470                    log.debug("{} prot {}", destinationSignalMast.getDisplayName(), protectingBlock.getDisplayName());
2471                    log.debug("{} dest {}", destinationSignalMast.getDisplayName(), destinationBlock.getDisplayName());
2472                }
2473
2474                try {
2475                    lblks = lbm.getLayoutBlockConnectivityTools().getLayoutBlocks(
2476                        facingBlock, destinationBlock, protectingBlock,
2477                            true, LayoutBlockConnectivityTools.Routing.MASTTOMAST);
2478                } catch (JmriException ee) {
2479                    log.error("No blocks found by the layout editor for pair {}-{}",
2480                        source.getDisplayName(), destinationSignalMast.getDisplayName());
2481                }
2482                LinkedHashMap<Block, Integer> block = setupLayoutEditorTurnoutDetails(lblks);
2483
2484                for (int i = 0; i < blockInXings.size(); i++) {
2485                    blockInXings.get(i).removeSignalMastLogic(source);
2486                }
2487                blockInXings = new ArrayList<>(0);
2488                xingAutoBlocks = new ArrayList<>(0);
2489                for (LayoutEditor lay : layout) {
2490                    for (LevelXing levelXing : lay.getLevelXings()) {
2491                        //Looking for a crossing that both layout blocks defined and they are individual.
2492                        if ((levelXing.getLayoutBlockAC() != null)
2493                                && (levelXing.getLayoutBlockBD() != null)
2494                                && (levelXing.getLayoutBlockAC() != levelXing.getLayoutBlockBD())) {
2495                            if (lblks.contains(levelXing.getLayoutBlockAC()) &&
2496                                    levelXing.getLayoutBlockAC() != facingBlock) {  // Don't include the facing xing blocks
2497                                block.put(levelXing.getLayoutBlockBD().getBlock(), Block.UNOCCUPIED);
2498                                xingAutoBlocks.add(levelXing.getLayoutBlockBD().getBlock());
2499                                blockInXings.add(levelXing);
2500                            } else if (lblks.contains(levelXing.getLayoutBlockBD()) &&
2501                                    levelXing.getLayoutBlockBD() != facingBlock) {  // Don't include the facing xing blocks
2502                                block.put(levelXing.getLayoutBlockAC().getBlock(), Block.UNOCCUPIED);
2503                                xingAutoBlocks.add(levelXing.getLayoutBlockAC().getBlock());
2504                                blockInXings.add(levelXing);
2505                            }
2506                        }
2507                    }
2508                }
2509                if (useLayoutEditorBlocks) {
2510                    setAutoBlocks(block);
2511                } else {
2512                    setAutoBlocks(null);
2513                }
2514                if (!useLayoutEditorTurnouts) {
2515                    setAutoTurnouts(null);
2516                }
2517
2518                setupAutoSignalMast(null, false);
2519            }
2520            initialise();
2521        }
2522
2523        /**
2524         * From a list of Layout Blocks, search for included Turnouts and their
2525         * Set To state.
2526         *
2527         * @param lblks List of Layout Blocks
2528         * @return a list of block - turnout state pairs
2529         */
2530        LinkedHashMap<Block, Integer> setupLayoutEditorTurnoutDetails(List<LayoutBlock> lblks) {
2531            ConnectivityUtil connection;
2532            List<LayoutTrackExpectedState<LayoutTurnout>> turnoutList;
2533            Hashtable<Turnout, Integer> turnoutSettings = new Hashtable<>();
2534            LinkedHashMap<Block, Integer> block = new LinkedHashMap<>();
2535            for (int i = 0; i < lblks.size(); i++) {
2536                log.debug("layoutblock {}",lblks.get(i).getDisplayName());
2537                block.put(lblks.get(i).getBlock(), Block.UNOCCUPIED);
2538                if ((i > 0)) {
2539                    int nxtBlk = i + 1;
2540                    int preBlk = i - 1;
2541                    if (i == lblks.size() - 1) {
2542                        nxtBlk = i;
2543                    }
2544                    //We use the best connectivity for the current block.
2545                    connection = new ConnectivityUtil(lblks.get(i).getMaxConnectedPanel());
2546                    if (i == lblks.size() - 1 && remoteProtectingBlock != null) {
2547                        turnoutList = connection.getTurnoutList(lblks.get(i)
2548                            .getBlock(), lblks.get(preBlk).getBlock(), remoteProtectingBlock.getBlock());
2549                    }else{
2550                        turnoutList = connection.getTurnoutList(lblks.get(i)
2551                            .getBlock(), lblks.get(preBlk).getBlock(), lblks.get(nxtBlk).getBlock());
2552                    }
2553                    for (int x = 0; x < turnoutList.size(); x++) {
2554                        LayoutTurnout lt = turnoutList.get(x).getObject();
2555                        if (lt instanceof LayoutSlip) {
2556                            LayoutSlip ls = (LayoutSlip) lt;
2557                            int slipState = turnoutList.get(x).getExpectedState();
2558                            int taState = ls.getTurnoutState(slipState);
2559                            Turnout tTemp = ls.getTurnout();
2560                            if (tTemp == null ) {
2561                                log.error("Unexpected null Turnout in {}, skipped", ls);
2562                                continue; // skip this one in loop, what else can you do?
2563                            }
2564                            turnoutSettings.put(ls.getTurnout(), taState);
2565                            int tbState = ls.getTurnoutBState(slipState);
2566                            turnoutSettings.put(ls.getTurnoutB(), tbState);
2567                        } else if ( lt != null ) {
2568                            String t = lt.getTurnoutName();
2569                            // temporary = why is this looking up the Turnout instead of using getTurnout()?
2570                            Turnout turnout = InstanceManager.turnoutManagerInstance().getTurnout(t);
2571                            if (log.isDebugEnabled()) {
2572                                if (    (lt.getTurnoutType() == LayoutTurnout.TurnoutType.RH_TURNOUT ||
2573                                         lt.getTurnoutType() == LayoutTurnout.TurnoutType.LH_TURNOUT ||
2574                                         lt.getTurnoutType() == LayoutTurnout.TurnoutType.WYE_TURNOUT)
2575                                        && (!lt.getBlockName().isEmpty())) {
2576                                    log.debug("turnout in list is straight left/right wye");
2577                                    log.debug("turnout block Name {}", lt.getBlockName());
2578                                    log.debug("current {} - pre {}", lblks.get(i).getBlock().getDisplayName(), lblks.get(preBlk).getBlock().getDisplayName());
2579                                    log.debug("A {}", lt.getConnectA());
2580                                    log.debug("B {}", lt.getConnectB());
2581                                    log.debug("C {}", lt.getConnectC());
2582                                    log.debug("D {}", lt.getConnectD());
2583                                }
2584                            }
2585                            if (turnout != null ) {
2586                                turnoutSettings.put(turnout, turnoutList.get(x).getExpectedState());
2587                            }
2588                            Turnout tempT;
2589                            if ((tempT = lt.getSecondTurnout()) != null) {
2590                                turnoutSettings.put(tempT, turnoutList.get(x).getExpectedState());
2591                            }
2592                            /* TODO: We could do with a more intelligent way to deal with double crossovers, other than
2593                                just looking at the state of the other conflicting blocks, such as looking at Signalmasts
2594                                that protect the other blocks and the settings of any other turnouts along the way.
2595                             */
2596                            if (lt.getTurnoutType() == LayoutTurnout.TurnoutType.DOUBLE_XOVER) {
2597                                LayoutBlock tempLB;
2598                                if (turnoutList.get(x).getExpectedState() == Turnout.THROWN) {
2599                                    if (lt.getLayoutBlock() == lblks.get(i) || lt.getLayoutBlockC() == lblks.get(i)) {
2600                                        // A or C, add B and D to remove list unless A=B or C=D
2601                                        if ((tempLB = lt.getLayoutBlockB()) != null) {
2602                                            if (!tempLB.equals(lt.getLayoutBlock())) {
2603                                                dblCrossoverAutoBlocks.add(tempLB.getBlock());
2604                                            }
2605                                            block.put(tempLB.getBlock(), Block.UNOCCUPIED);
2606                                        }
2607                                        if ((tempLB = lt.getLayoutBlockD()) != null) {
2608                                            if (!tempLB.equals(lt.getLayoutBlockC())) {
2609                                                dblCrossoverAutoBlocks.add(tempLB.getBlock());
2610                                            }
2611                                            block.put(tempLB.getBlock(), Block.UNOCCUPIED);
2612                                        }
2613                                    } else if (lt.getLayoutBlockB() == lblks.get(i) || lt.getLayoutBlockD() == lblks.get(i)) {
2614                                        // B or D, add A and C to remove list unless A=B or C=D
2615                                        if ((tempLB = lt.getLayoutBlock()) != null) {
2616                                            if (!tempLB.equals(lt.getLayoutBlockB())) {
2617                                                dblCrossoverAutoBlocks.add(tempLB.getBlock());
2618                                            }
2619                                            block.put(tempLB.getBlock(), Block.UNOCCUPIED);
2620                                        }
2621                                        if ((tempLB = lt.getLayoutBlockC()) != null) {
2622                                            if (!tempLB.equals(lt.getLayoutBlockD())) {
2623                                                dblCrossoverAutoBlocks.add(tempLB.getBlock());
2624                                            }
2625                                            block.put(tempLB.getBlock(), Block.UNOCCUPIED);
2626                                        }
2627                                    }
2628                                }
2629                            }
2630                        }
2631                    }
2632                }
2633            }
2634            // ----- Begin Turntable Alignment Check -----
2635            // For paths onto a turntable, we must add a condition for the turntable's alignment. The
2636            // turnout that controls the turntable position is associated with the specific ray track.
2637            // Each ray track can have a control turnout assigned. Setting this turnout to a specific
2638            // state (usually THROWN) is the command to align the turntable to that ray.
2639            // The following logic adds a CONDITION to the Signal Mast Logic, requiring that the
2640            // correct ray's turnout is in its required state before the signal will clear.
2641            // This does NOT command the turnout to move; it only checks its state for the interlocking.
2642            Set<LayoutEditor> layout = InstanceManager.getDefault(EditorManager.class).getAll(LayoutEditor.class);
2643            for (LayoutEditor lay : layout) {
2644                for (LayoutTurntable turntable : lay.getLayoutTurntables()) {
2645                    // Check for a path from an Approach Mast to the Buffer Mast (Path 3).
2646                    // The destination block is the turntable block, and the destination mast is the buffer mast.
2647                    if (turntable.getLayoutBlock() == destinationBlock && turntable.getBufferMast() == destinationSignalMast) {
2648                        // The source mast's facing block is the ray block for this path.
2649                        for (LayoutTurntable.RayTrack ray : turntable.getRayTrackList()) {
2650                            if (ray.getConnect() != null && ray.getConnect().getLayoutBlock() == DefaultSignalMastLogic.this.facingBlock) {
2651                                // This is the correct ray. Get its control turnout and required state.
2652                                Turnout rayTurnout = ray.getTurnout();
2653                                int requiredState = ray.getTurnoutState();
2654                                if (rayTurnout != null) {
2655                                    turnoutSettings.put(rayTurnout, requiredState);
2656                                }
2657                                break; // Found the ray, no need to check others.
2658                            }
2659                        }
2660                    }
2661                    // Check for a path from the Exit Mast to a remote mast (Path 1).
2662                    // The source mast is the turntable's exit mast.
2663                    if (turntable.getExitSignalMast() == getSourceMast()) {
2664                        // The protecting block is the ray block for this path.
2665                        for (LayoutTurntable.RayTrack ray : turntable.getRayTrackList()) {
2666                            if (ray.getConnect() != null && ray.getConnect().getLayoutBlock() == protectingBlock) {
2667                                // This is the correct ray. Get its control turnout and required state.
2668                                Turnout rayTurnout = ray.getTurnout();
2669                                int requiredState = ray.getTurnoutState();
2670                                if (rayTurnout != null) {
2671                                    turnoutSettings.put(rayTurnout, requiredState);
2672                                }
2673                                break; // Found the ray, no need to check others.
2674                            }
2675                        }
2676                    }
2677                }
2678            }
2679            // ----- End Turntable Alignment Check -----
2680            // ----- Begin Traverser Alignment Check -----
2681            for (LayoutEditor lay : layout) {
2682                for (LayoutTraverser traverser : lay.getLayoutTraversers()) {
2683                    // Check for a path from an Approach Mast to the Buffer Mast (Path 3).
2684                    // The destination block is the traverser block, and the destination mast is the buffer mast.
2685                    if (traverser.getLayoutBlock() == destinationBlock && traverser.getBufferMast() == destinationSignalMast) {
2686                        // The source mast's facing block is the slot block for this path.
2687                        for (LayoutTraverser.SlotTrack slot : traverser.getSlotList()) {
2688                            if (slot.getConnect() != null && slot.getConnect().getLayoutBlock() == DefaultSignalMastLogic.this.facingBlock) {
2689                                // This is the correct slot. Get its control turnout and required state.
2690                                Turnout slotTurnout = slot.getTurnout();
2691                                int requiredState = slot.getTurnoutState();
2692                                if (slotTurnout != null) {
2693                                    turnoutSettings.put(slotTurnout, requiredState);
2694                                }
2695                                break; // Found the slot, no need to check others.
2696                            }
2697                        }
2698                    }
2699                    // Check for a path from the Exit Mast to a remote mast (Path 1).
2700                    // The source mast is the traverser's exit mast.
2701                    if (traverser.getExitSignalMast() == getSourceMast()) {
2702                        // The protecting block is the slot block for this path.
2703                        for (LayoutTraverser.SlotTrack slot : traverser.getSlotList()) {
2704                            if (slot.getConnect() != null && slot.getConnect().getLayoutBlock() == protectingBlock) {
2705                                // This is the correct slot. Get its control turnout and required state.
2706                                Turnout slotTurnout = slot.getTurnout();
2707                                int requiredState = slot.getTurnoutState();
2708                                if (slotTurnout != null) {
2709                                    turnoutSettings.put(slotTurnout, requiredState);
2710                                }
2711                                break; // Found the slot, no need to check others.
2712                            }
2713                        }
2714                    }
2715                }
2716            }
2717            // ----- End Traverser Alignment Check -----
2718            if (useLayoutEditorTurnouts) {
2719                setAutoTurnouts(turnoutSettings);
2720            }
2721            return block;
2722        }
2723
2724        /**
2725         * Generate auto signalmast for a given SML. Looks through all the other
2726         * logics to see if there are any blocks that are in common and thus
2727         * will add the other signal mast protecting that block.
2728         *
2729         * @param sml       The Signal Mast Logic for which to set up
2730         *                  autoSignalMasts
2731         * @param overwrite When true, replace an existing autoMasts list in the
2732         *                  SML
2733         */
2734        void setupAutoSignalMast(SignalMastLogic sml, boolean overwrite) {
2735            if (!allowAutoSignalMastGeneration) {
2736                return;
2737            }
2738            List<SignalMastLogic> smlList = InstanceManager.getDefault(SignalMastLogicManager.class)
2739                .getLogicsByDestination(destinationSignalMast);
2740            List<Block> allBlock = new ArrayList<>();
2741
2742            userSetBlocks.forEach(nbh -> allBlock.add((Block) nbh.getBean()));
2743
2744            Set<Block> blockKeys = autoBlocks.keySet();
2745            blockKeys.stream().filter(key -> (!allBlock.contains(key))).forEachOrdered(key ->
2746                allBlock.add(key));
2747            Hashtable<SignalMast, String> masts;
2748            if (sml != null) {
2749                masts = autoMasts;
2750                if (sml.areBlocksIncluded(allBlock)) {
2751                    SignalMast mast = sml.getSourceMast();
2752                    String danger = mast.getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.DANGER);
2753                    masts.put(mast, danger);
2754                } else {
2755                    //No change so will leave.
2756                    return;
2757                }
2758            } else {
2759                masts = new Hashtable<>();
2760                for (int i = 0; i < smlList.size(); i++) {
2761                    if (smlList.get(i).areBlocksIncluded(allBlock)) {
2762                        SignalMast mast = smlList.get(i).getSourceMast();
2763                        String danger = mast.getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.DANGER);
2764                        masts.put(mast, danger);
2765                    }
2766                }
2767            }
2768            setAutoMasts(masts, overwrite);
2769        }
2770
2771        /**
2772         * Add a certain Signal Mast to the list of AutoSignalMasts for this
2773         * SML.
2774         *
2775         * @param mast The Signal Mast to be added
2776         */
2777        void addAutoSignalMast(SignalMast mast) {
2778            log.debug("{} add mast to auto list {}", destinationSignalMast.getDisplayName(), mast);
2779            String danger = mast.getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.DANGER);
2780            if (danger == null) {
2781                log.error("Can not add SignalMast {} to logic for {} to {} "
2782                    + "as it does not have a Danger appearance configured",
2783                        mast.getDisplayName(), source.getDisplayName(), destinationSignalMast.getDisplayName());
2784                return;
2785            }
2786            this.autoMasts.put(mast, danger);
2787            if (destMastInit) {
2788                mast.addPropertyChangeListener(propertySignalMastListener);
2789            }
2790            firePropertyChange(PROPERTY_AUTO_MASTS, null, this.destinationSignalMast);
2791        }
2792
2793        /**
2794         * Remove a certain Signal Mast from the list of AutoSignalMasts for
2795         * this SML.
2796         *
2797         * @param mast The Signal Mast to be removed
2798         */
2799        void removeAutoSignalMast(SignalMast mast) {
2800            this.autoMasts.remove(mast);
2801            if (destMastInit) {
2802                mast.removePropertyChangeListener(propertySignalMastListener);
2803            }
2804            firePropertyChange(PROPERTY_AUTO_MASTS, this.destinationSignalMast, null);
2805        }
2806
2807        boolean useLayoutEditor() {
2808            return useLayoutEditor;
2809        }
2810
2811        boolean useLayoutEditorBlocks() {
2812            return useLayoutEditorBlocks;
2813        }
2814
2815        boolean useLayoutEditorTurnouts() {
2816            return useLayoutEditorTurnouts;
2817        }
2818
2819        boolean allowAutoSignalMastGeneration = false;
2820
2821        boolean allowAutoSignalMastGen() {
2822            return allowAutoSignalMastGeneration;
2823        }
2824
2825        void allowAutoSignalMastGen(boolean gen) {
2826            if (allowAutoSignalMastGeneration == gen) {
2827                return;
2828            }
2829            allowAutoSignalMastGeneration = gen;
2830        }
2831
2832        /**
2833         * Remove references from this Destination Mast and clear lists, so that
2834         * it can eventually be garbage-collected.
2835         * <p>
2836         * Note: This does not stop any delayed operations that might be queued.
2837         */
2838        void dispose() {
2839            disposed = true;
2840            stopListeningForSectionHolders();
2841            clearTurnoutLock();
2842            destinationSignalMast.removePropertyChangeListener(propertyDestinationMastListener);
2843            setBlocks(null);
2844            setAutoBlocks(null);
2845            setTurnouts(null);
2846            setAutoTurnouts(null);
2847            setSensors(null);
2848            setMasts(null);
2849            setAutoMasts(null, true);
2850        }
2851
2852        void lockTurnouts() {
2853            // We do not allow the turnouts to be locked if we are disposing the logic,
2854            // if the logic is not active, or if we do not allow the turnouts to be locked.
2855            if ((disposed) || (!lockTurnouts) || (!active)) {
2856                return;
2857            }
2858
2859            userSetTurnouts.stream().map(nbh -> (Turnout) nbh.getBean()).forEachOrdered(key -> {
2860                key.setLocked(Turnout.CABLOCKOUT + Turnout.PUSHBUTTONLOCKOUT, true);
2861            });
2862            Enumeration<Turnout> keys = autoTurnouts.keys();
2863            while (keys.hasMoreElements()) {
2864                Turnout key = keys.nextElement();
2865                key.setLocked(Turnout.CABLOCKOUT + Turnout.PUSHBUTTONLOCKOUT, true);
2866            }
2867        }
2868
2869        void clearTurnoutLock() {
2870            // We do not allow the turnout lock to be cleared if we are not active,
2871            // and the lock flag has not been set.
2872            if ((!lockTurnouts) && (!active)) {
2873                return;
2874            }
2875
2876            Enumeration<Turnout> keys = autoTurnouts.keys();
2877            while (keys.hasMoreElements()) {
2878                Turnout key = keys.nextElement();
2879                key.setLocked(Turnout.CABLOCKOUT + Turnout.PUSHBUTTONLOCKOUT, false);
2880            }
2881
2882            userSetTurnouts.stream().map(nbh -> (Turnout) nbh.getBean()).forEachOrdered(key ->
2883                key.setLocked(Turnout.CABLOCKOUT + Turnout.PUSHBUTTONLOCKOUT, false));
2884        }
2885
2886        protected void calculateSpeed() {
2887            log.debug("{} calculate the speed setting for this logic ie what the signalmast will display", destinationSignalMast.getDisplayName());
2888            minimumBlockSpeed = 0.0f;
2889            Enumeration<Turnout> keys = autoTurnouts.keys();
2890            while (keys.hasMoreElements()) {
2891                Turnout key = keys.nextElement();
2892                log.debug("{} turnout {}", destinationSignalMast.getDisplayName(), key.getDisplayName());
2893                if (!isTurnoutIncluded(key)) {
2894                    if (autoTurnouts.get(key) == Turnout.CLOSED) {
2895                        if (((key.getStraightLimit() < minimumBlockSpeed) || (minimumBlockSpeed == 0)) && (key.getStraightLimit() != -1)) {
2896                            minimumBlockSpeed = key.getStraightLimit();
2897                            log.debug("{} turnout {} set speed to {}", destinationSignalMast.getDisplayName(), key.getDisplayName(), minimumBlockSpeed);
2898                        }
2899                    } else {
2900                        if (((key.getDivergingLimit() < minimumBlockSpeed) || (minimumBlockSpeed == 0)) && (key.getDivergingLimit() != -1)) {
2901                            minimumBlockSpeed = key.getDivergingLimit();
2902                            log.debug("{} turnout {} set speed to {}", destinationSignalMast.getDisplayName(), key.getDisplayName(), minimumBlockSpeed);
2903                        }
2904                    }
2905                }
2906            }
2907
2908            userSetTurnouts.forEach(nbh -> {
2909                Turnout key = (Turnout) nbh.getBean();
2910                if (nbh.getSetting() == Turnout.CLOSED) {
2911                    if (((key.getStraightLimit() < minimumBlockSpeed) || (minimumBlockSpeed == 0)) && (key.getStraightLimit() != -1)) {
2912                        minimumBlockSpeed = key.getStraightLimit();
2913                        log.debug("{} turnout {} set speed to {}", destinationSignalMast.getDisplayName(), key.getDisplayName(), minimumBlockSpeed);
2914                    }
2915                } else if (nbh.getSetting() == Turnout.THROWN) {
2916                    if (((key.getDivergingLimit() < minimumBlockSpeed) || (minimumBlockSpeed == 0)) && (key.getDivergingLimit() != -1)) {
2917                        minimumBlockSpeed = key.getDivergingLimit();
2918                        log.debug("{} turnout {} set speed to {}", destinationSignalMast.getDisplayName(), key.getDisplayName(), minimumBlockSpeed);
2919                    }
2920                }
2921            });
2922
2923            Set<Block> autoBlockKeys = autoBlocks.keySet();
2924            for (Block key : autoBlockKeys) {
2925                log.debug("{} auto block add list {}", destinationSignalMast.getDisplayName(), key.getDisplayName());
2926                if (!isBlockIncluded(key)) {
2927                    if (((key.getSpeedLimit() < minimumBlockSpeed) || (minimumBlockSpeed == 0)) && (key.getSpeedLimit() != -1)) {
2928                        minimumBlockSpeed = key.getSpeedLimit();
2929                        log.debug("{} block {} set speed to {}", destinationSignalMast.getDisplayName(), key.getDisplayName(), minimumBlockSpeed);
2930                    }
2931                }
2932            }
2933            for (NamedBeanSetting nbh : userSetBlocks) {
2934                Block key = (Block) nbh.getBean();
2935                if (((key.getSpeedLimit() < minimumBlockSpeed) || (minimumBlockSpeed == 0)) && (key.getSpeedLimit() != -1)) {
2936                    log.debug("{} block {} set speed to {}", destinationSignalMast.getDisplayName(), key.getDisplayName(), minimumBlockSpeed);
2937                    minimumBlockSpeed = key.getSpeedLimit();
2938                }
2939            }
2940            /*if(minimumBlockSpeed==-0.1f)
2941             minimumBlockSpeed==0.0f;*/
2942        }
2943
2944        /**
2945         * Check whether a block on this route is reserved for a train other
2946         * than the one approaching the source mast.
2947         * <p>
2948         * Only blocks marked as allocated (shown in the extra color) are
2949         * checked. A Section records who holds it, see
2950         * {@link Section#getAllocatedTo()}; the approaching train is the
2951         * holder of the block the source mast faces. A marked block whose
2952         * holder is not the approaching train, or that has no holder, puts the
2953         * mast to danger. So does a marked block when no train holds the
2954         * facing block.
2955         *
2956         * @param routeBlocks the blocks on this route
2957         * @param lbm         the layout block manager
2958         * @return true if the mast must show danger
2959         */
2960        private boolean isRouteHeldByAnotherTrain(List<Block> routeBlocks, LayoutBlockManager lbm) {
2961            List<Block> marked = new ArrayList<>();
2962            for (Block b : routeBlocks) {
2963                LayoutBlock lb = lbm.getLayoutBlock(b);
2964                if (lb != null && lb.getUseExtraColor()) {
2965                    marked.add(b);
2966                }
2967            }
2968            if (marked.isEmpty()) {
2969                return false;
2970            }
2971            LayoutBlock approach = getApproachBlock(lbm);
2972            Object approaching = (approach == null) ? null : heldBy(approach.getBlock());
2973            for (Block b : marked) {
2974                Object holder = heldBy(b);
2975                if (holder == null || holder != approaching) {
2976                    log.debug("{} block {} is held by {}, approaching train is {}",
2977                            destinationSignalMast.getDisplayName(), b.getDisplayName(), holder, approaching);
2978                    return true;
2979                }
2980            }
2981            return false;
2982        }
2983
2984        /*
2985         * The block the source mast faces, where a train approaching the mast is.
2986         * Found from the Layout Editor panels when this logic does not use the
2987         * Layout Editor, without changing the stored facing block.
2988         */
2989        private LayoutBlock getApproachBlock(LayoutBlockManager lbm) {
2990            if (getFacingBlock() != null) {
2991                return getFacingBlock();
2992            }
2993            for (LayoutEditor editor : InstanceManager.getDefault(EditorManager.class).getAll(LayoutEditor.class)) {
2994                LayoutBlock lb = lbm.getFacingBlockByNamedBean(getSourceMast(), editor);
2995                if (lb != null) {
2996                    return lb;
2997                }
2998            }
2999            return null;
3000        }
3001
3002        /*
3003         * The Sections this logic listens to for a change of holder, and the
3004         * blocks it cares about: those on the route and the block the source
3005         * mast faces.
3006         */
3007        private final Set<Section> holderSections = new HashSet<>();
3008        private final Set<Block> holderBlocks = new HashSet<>();
3009
3010        /*
3011         * Listen for changes of holder on Sections, so the dispatcher check
3012         * above is made again when a train is given or releases a Section
3013         * containing one of the blocks this logic cares about. Every Section
3014         * is listened to, and the blocks are checked when the holder changes,
3015         * because a Section's blocks can be set after it is created without
3016         * any event being sent.
3017         */
3018        private void listenForSectionHolders() {
3019            stopListeningForSectionHolders();
3020            // Only use a Section manager that already exists: with no Sections
3021            // defined there is nothing to listen to, and asking for one would
3022            // create it.
3023            if (!InstanceManager.containsDefault(SectionManager.class)) {
3024                return;
3025            }
3026            SectionManager sm = InstanceManager.getDefault(SectionManager.class);
3027            holderBlocks.addAll(autoBlocks.keySet());
3028            for (NamedBeanSetting nbh : userSetBlocks) {
3029                holderBlocks.add((Block) nbh.getBean());
3030            }
3031            if (holderBlocks.isEmpty()) {
3032                return;
3033            }
3034            if (InstanceManager.containsDefault(LayoutBlockManager.class)) {
3035                LayoutBlock approach = getApproachBlock(InstanceManager.getDefault(LayoutBlockManager.class));
3036                if (approach != null && approach.getBlock() != null) {
3037                    holderBlocks.add(approach.getBlock());
3038                }
3039            }
3040            sm.addPropertyChangeListener(Manager.PROPERTY_BEANS, sectionManagerListener);
3041            for (Section sec : sm.getNamedBeanSet()) {
3042                listenTo(sec);
3043            }
3044        }
3045
3046        private void listenTo(Section sec) {
3047            if (!holderSections.contains(sec)) {
3048                sec.addPropertyChangeListener(Section.PROPERTY_ALLOCATED_TO, sectionHolderListener);
3049                holderSections.add(sec);
3050            }
3051        }
3052
3053        private void stopListeningForSectionHolders() {
3054            for (Section sec : holderSections) {
3055                sec.removePropertyChangeListener(Section.PROPERTY_ALLOCATED_TO, sectionHolderListener);
3056            }
3057            holderSections.clear();
3058            holderBlocks.clear();
3059            if (InstanceManager.containsDefault(SectionManager.class)) {
3060                InstanceManager.getDefault(SectionManager.class)
3061                        .removePropertyChangeListener(Manager.PROPERTY_BEANS, sectionManagerListener);
3062            }
3063        }
3064
3065        private final PropertyChangeListener sectionHolderListener = e -> {
3066            Section sec = (Section) e.getSource();
3067            for (Block b : holderBlocks) {
3068                if (sec.containsBlock(b)) {
3069                    log.debug("{} section {} now held by {}", destinationSignalMast.getDisplayName(),
3070                            sec.getDisplayName(), e.getNewValue());
3071                    checkState();
3072                    return;
3073                }
3074            }
3075        };
3076
3077        /*
3078         * A Section added is listened to; one removed is dropped. An event
3079         * without a Section (sent after a silenced bulk load) starts again.
3080         */
3081        private final PropertyChangeListener sectionManagerListener = e -> {
3082            if (!destMastInit || disposed) {
3083                return;
3084            }
3085            if (e.getNewValue() instanceof Section) {
3086                listenTo((Section) e.getNewValue());
3087            } else if (e.getOldValue() instanceof Section) {
3088                Section sec = (Section) e.getOldValue();
3089                if (holderSections.remove(sec)) {
3090                    sec.removePropertyChangeListener(Section.PROPERTY_ALLOCATED_TO, sectionHolderListener);
3091                }
3092            } else {
3093                listenForSectionHolders();
3094            }
3095        };
3096
3097        /*
3098         * The holder of an allocated Section containing the block, or null.
3099         */
3100        private Object heldBy(Block b) {
3101            if (b == null || !InstanceManager.containsDefault(SectionManager.class)) {
3102                return null;
3103            }
3104            for (Section sec : InstanceManager.getDefault(SectionManager.class).getNamedBeanSet()) {
3105                Object holder = sec.getAllocatedTo();
3106                if (holder != null && sec.containsBlock(b)) {
3107                    return holder;
3108                }
3109            }
3110            return null;
3111        }
3112
3113        protected PropertyChangeListener propertySensorListener = new PropertyChangeListener() {
3114            @Override
3115            public void propertyChange(PropertyChangeEvent e) {
3116                Sensor sen = (Sensor) e.getSource();
3117                log.debug("{} to {} destination sensor {} trigger {}",source.getDisplayName(), destinationSignalMast.getDisplayName(), sen.getDisplayName(), e.getPropertyName());
3118                if ( Sensor.PROPERTY_KNOWN_STATE.equals(e.getPropertyName())) {
3119                    int now = ((Integer) e.getNewValue());
3120                    log.debug("current value {} value we want {}", now, getSensorState(sen));
3121                    if (isSensorIncluded(sen) && getSensorState(sen) != now) {
3122                        log.debug("Sensor {} caused the signalmast to be set to danger", sen.getDisplayName());
3123                        //getSourceMast().setAspect(stopAspect);
3124                        routeNoLongerSet();
3125                    } else if (getSensorState(sen) == now) {
3126                        log.debug("{} sensor {} triggers a calculation of change", destinationSignalMast.getDisplayName(), sen.getDisplayName());
3127                        checkState();
3128                    }
3129                }
3130            }
3131        };
3132
3133        protected PropertyChangeListener propertyTurnoutListener = new PropertyChangeListener() {
3134            @Override
3135            public void propertyChange(PropertyChangeEvent e) {
3136                Turnout turn = (Turnout) e.getSource();
3137                //   log.debug(destination.getDisplayName() + " destination sensor "+ sen.getDisplayName() + "trigger");
3138                if ( Turnout.PROPERTY_KNOWN_STATE.equals(e.getPropertyName())) {
3139                    //Need to check this against the manual list vs auto list
3140                    //The manual list should over-ride the auto list
3141                    int now = ((Integer) e.getNewValue());
3142                    if (isTurnoutIncluded(turn)) {
3143                        if (getTurnoutState(turn) != now) {
3144                            log.debug("Turnout {} caused the signalmast to be set", turn.getDisplayName());
3145                            log.debug("From {} to {} Turnout {} caused the signalmast to be set to danger", getSourceMast().getDisplayName(), destinationSignalMast.getDisplayName(), turn.getDisplayName());
3146                            routeNoLongerSet();
3147                        } else {
3148                            log.debug("{} turnout {} triggers a calculation of change", destinationSignalMast.getDisplayName(), turn.getDisplayName());
3149                            checkState();
3150                        }
3151                    } else if (autoTurnouts.containsKey(turn)) {
3152                        if (getAutoTurnoutState(turn) != now) {
3153                            log.debug("Turnout {} auto caused the signalmast to be set", turn.getDisplayName());
3154                            log.debug("From {} to {} Auto Turnout {} auto caused the signalmast to be set to danger", getSourceMast().getDisplayName(), destinationSignalMast.getDisplayName(), turn.getDisplayName());
3155                            routeNoLongerSet();
3156                        } else {
3157                            log.debug("From {} to {} turnout {} triggers a calculation of change", getSourceMast().getDisplayName(), destinationSignalMast.getDisplayName(), turn.getDisplayName());
3158                            checkState();
3159                        }
3160                    }
3161
3162                } else if ( Turnout.PROPERTY_TURNOUT_STRAIGHT_SPEED.equals(e.getPropertyName())
3163                        || Turnout.PROPERTY_TURNOUT_DIVERGING_SPEED.equals(e.getPropertyName())) {
3164                    calculateSpeed();
3165                }
3166            }
3167        };
3168
3169        /**
3170         * Check if a block on this route has become occupied while the route
3171         * needs it to be unoccupied.
3172         *
3173         * @param block the block to check
3174         * @return true if the block is occupied, not in permissive working,
3175         *         and this logic needs it to be unoccupied
3176         */
3177        boolean isOccupiedAgainstRoute(Block block) {
3178            if (block.getState() != Block.OCCUPIED || block.getPermissiveWorking()) {
3179                return false;
3180            }
3181            int required;
3182            if (isBlockIncluded(block)) {
3183                required = getBlockState(block);
3184            } else if (autoBlocks.containsKey(block)) {
3185                required = getAutoBlockState(block);
3186            } else {
3187                return false;
3188            }
3189            return required != Block.OCCUPIED && required != 0x03;
3190        }
3191
3192        protected PropertyChangeListener propertyBlockListener = new PropertyChangeListener() {
3193            @Override
3194            public void propertyChange(PropertyChangeEvent e) {
3195                Block block = (Block) e.getSource();
3196                log.debug("{} destination block {} trigger {} {}", destinationSignalMast.getDisplayName(), block.getDisplayName(), e.getPropertyName(), e.getNewValue());
3197                if ( Block.PROPERTY_STATE.equals(e.getPropertyName()) && isOccupiedAgainstRoute(block)) {
3198                    // Going to danger is not delayed, only a change towards clear is.
3199                    log.debug("Block {} occupied, caused the signalmast to be set to danger", block.getDisplayName());
3200                    routeNoLongerSet();
3201                } else if ( Block.PROPERTY_STATE.equals(e.getPropertyName()) || Block.PROPERTY_ALLOCATED.equals(e.getPropertyName())) {
3202                    // TODO: what is this?
3203                    log.debug("Included in user entered block {}", Boolean.toString(isBlockIncluded(block)));
3204                    log.debug("Included in AutoGenerated Block {}", Boolean.toString(autoBlocks.containsKey(block)));
3205                    if (isBlockIncluded(block)) {
3206                        log.debug("{} in manual block", destinationSignalMast.getDisplayName());
3207                        log.debug("  state: {}  {}", getBlockState(block), block.getState());
3208                        checkState();
3209                    } else if (autoBlocks.containsKey(block)) {
3210                        log.debug("{} in auto block", destinationSignalMast.getDisplayName());
3211                        log.debug("  states: {}  {}", getAutoBlockState(block), block.getState());
3212                        checkState();
3213                    } else {
3214                        log.debug("{} Not found", destinationSignalMast.getDisplayName());
3215                    }
3216                } else if ( e.getPropertyName().equals("BlockSpeedChange")) {
3217                    calculateSpeed();
3218                }
3219            }
3220        };
3221
3222        protected PropertyChangeListener propertySignalMastListener = new PropertyChangeListener() {
3223            @Override
3224            public void propertyChange(PropertyChangeEvent e) {
3225
3226                SignalMast mast = (SignalMast) e.getSource();
3227                log.debug("{} signalmast change {} {}", destinationSignalMast.getDisplayName(), mast.getDisplayName(), e.getPropertyName());
3228                //   log.debug(destination.getDisplayName() + " destination sensor "+ sen.getDisplayName() + "trigger");
3229                if ( SignalMast.PROPERTY_ASPECT.equals(e.getPropertyName())) {
3230
3231                    String now = ((String) e.getNewValue());
3232                    log.debug("{} match property {}", destinationSignalMast.getDisplayName(), now);
3233                    if (isSignalMastIncluded(mast)) {
3234                        if (!now.equals(getSignalMastState(mast))) {
3235                            log.debug("{} in mast list SignalMast {} caused the signalmast to be set", destinationSignalMast.getDisplayName(), mast.getDisplayName());
3236                            log.debug("SignalMast {} caused the signalmast to be set", mast.getDisplayName());
3237                            routeNoLongerSet();
3238                        } else {
3239                            log.debug("{} in mast list signalmast change", destinationSignalMast.getDisplayName());
3240                            checkState();
3241                        }
3242                    } else if (autoMasts.containsKey(mast)) {
3243                        if (!now.equals(getAutoSignalMastState(mast))) {
3244                            log.debug("SignalMast {} caused the signalmast to be set", mast.getDisplayName());
3245                            log.debug("{} in auto mast list SignalMast {} caused the signalmast to be set", destinationSignalMast.getDisplayName(), mast.getDisplayName());
3246                            routeNoLongerSet();
3247                        } else {
3248                            log.debug("{} in auto mast list signalmast change", destinationSignalMast.getDisplayName());
3249                            checkState();
3250                        }
3251                    }
3252                }
3253            }
3254        };
3255
3256        private class NamedBeanSetting {
3257
3258            NamedBeanHandle<?> namedBean;
3259            int setting = 0;
3260            String strSetting = null;
3261
3262            NamedBeanSetting(NamedBeanHandle<?> namedBean, int setting) {
3263                this.namedBean = namedBean;
3264                this.setting = setting;
3265            }
3266
3267            NamedBeanSetting(NamedBeanHandle<?> namedBean, String setting) {
3268                this.namedBean = namedBean;
3269                strSetting = setting;
3270            }
3271
3272            NamedBean getBean() {
3273                return namedBean.getBean();
3274            }
3275
3276            NamedBeanHandle<?> getNamedBean() {
3277                return namedBean;
3278            }
3279
3280            int getSetting() {
3281                return setting;
3282            }
3283
3284            String getStringSetting() {
3285                return strSetting;
3286            }
3287
3288            String getBeanName() {
3289                return namedBean.getName();
3290            }
3291        }
3292    }
3293
3294    /**
3295     * The listener on the destination Signal Mast.
3296     */
3297    private PropertyChangeListener propertyDestinationMastListener = new PropertyChangeListener() {
3298        @Override
3299        public void propertyChange(PropertyChangeEvent e) {
3300            SignalMast mast = (SignalMast) e.getSource();
3301            if (mast == destination) {
3302                log.debug("destination mast change {}", mast.getDisplayName());
3303                setSignalAppearance();
3304            }
3305        }
3306    };
3307
3308    /**
3309     * The listener on the source Signal Mast.
3310     */
3311    private PropertyChangeListener propertySourceMastListener = new PropertyChangeListener() {
3312        @Override
3313        public void propertyChange(PropertyChangeEvent e) {
3314            SignalMast mast = (SignalMast) e.getSource();
3315            if ((mast == source) && ( SignalMast.PROPERTY_HELD.equals(e.getPropertyName()))) {
3316                log.debug("source mast change {} {}", mast.getDisplayName(), e.getPropertyName());
3317                setSignalAppearance();
3318            }
3319        }
3320    };
3321
3322    //@todo need to think how we deal with auto generated lists based upon the layout editor.
3323    @Override
3324    public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
3325        NamedBean nb = (NamedBean) evt.getOldValue();
3326        if (Manager.PROPERTY_CAN_DELETE.equals(evt.getPropertyName())) {
3327            boolean found = false;
3328            StringBuilder message = new StringBuilder();
3329            if (nb instanceof SignalMast) {
3330                if (nb.equals(source)) {
3331                    message.append("Has SignalMast Logic attached which will be <b>Deleted</b> to <ul>");
3332                    for (SignalMast sm : getDestinationList()) {
3333                        message.append("<li>");
3334                        message.append(sm.getDisplayName());
3335                        message.append("</li>");
3336                    }
3337                    message.append("</ul>");
3338                    throw new PropertyVetoException(message.toString(), evt);
3339
3340                } else if (isDestinationValid((SignalMast) nb)) {
3341                    throw new PropertyVetoException("Is the end point mast for logic attached to signal mast " + source.getDisplayName() + " which will be <b>Deleted</b> ", evt);
3342                }
3343                for (SignalMast sm : getDestinationList()) {
3344                    if (isSignalMastIncluded((SignalMast) nb, sm)) {
3345                        message.append("<li>");
3346                        message.append("Used in conflicting logic of ").append(source.getDisplayName())
3347                            .append(" & ").append(sm.getDisplayName()).append("</li>");
3348                    }
3349                }
3350            }
3351            if (nb instanceof Turnout) {
3352                for (SignalMast sm : getDestinationList()) {
3353                    if (isTurnoutIncluded((Turnout) nb, sm)) {
3354                        message.append("<li>Is in logic between Signal Masts ").append(source.getDisplayName())
3355                            .append(" ").append(sm.getDisplayName()).append("</li>");
3356                        found = true;
3357                    }
3358                }
3359            }
3360            if (nb instanceof Sensor) {
3361                for (SignalMast sm : getDestinationList()) {
3362                    if (isSensorIncluded((Sensor) nb, sm)) {
3363                        message.append("<li>");
3364                        message.append("Is in logic between Signal Masts ").append(source.getDisplayName())
3365                            .append(" ").append(sm.getDisplayName()).append("</li>");
3366                        found = true;
3367                    }
3368                }
3369            }
3370            if (found) {
3371                throw new PropertyVetoException(message.toString(), evt);
3372            }
3373        } else if (Manager.PROPERTY_DO_DELETE.equals(evt.getPropertyName())) {
3374            if (nb instanceof SignalMast) {
3375                if (nb.equals(source)) {
3376                    dispose();
3377                }
3378                if (isDestinationValid((SignalMast) nb)) {
3379                    removeDestination((SignalMast) nb);
3380                }
3381                for (SignalMast sm : getDestinationList()) {
3382                    if (isSignalMastIncluded((SignalMast) nb, sm)) {
3383                        log.warn("Unhandled condition: signal mast included during DoDelete");
3384                        // @todo need to deal with this situation
3385                    }
3386                }
3387            }
3388            if (nb instanceof Turnout) {
3389                Turnout t = (Turnout) nb;
3390                getDestinationList().stream().filter(sm -> (isTurnoutIncluded(t, sm))).forEachOrdered(sm -> {
3391                    removeTurnout(t, sm);
3392                });
3393            }
3394            if (nb instanceof Sensor) {
3395                Sensor s = (Sensor) nb;
3396                getDestinationList().stream().filter(sm -> (isSensorIncluded(s, sm))).forEachOrdered(sm -> {
3397                    removeSensor(s, sm);
3398                });
3399            }
3400        }
3401    }
3402
3403    /**
3404     * Note: This does not stop any delayed operations that might be queued.
3405     */
3406    @Override
3407    public void dispose() {
3408        disposing = true;
3409        getSourceMast().removePropertyChangeListener(propertySourceMastListener);
3410        Enumeration<SignalMast> en = destList.keys();
3411        while (en.hasMoreElements()) {
3412            SignalMast dm = en.nextElement();
3413            destList.get(dm).dispose();
3414        }
3415        super.dispose(); // release any prop change listeners
3416    }
3417
3418    /**
3419     * {@inheritDoc }
3420     */
3421    @Override
3422    public String getBeanType() {
3423        return Bundle.getMessage("BeanNameSignalMastLogic");
3424    }
3425
3426    /**
3427     * No valid integer state, always return a constant.
3428     *
3429     * @return Always zero
3430     */
3431    @Override
3432    public int getState() {
3433        return 0;
3434    }
3435
3436    @Override
3437    public void setState(int i) {
3438    }
3439
3440    /**
3441     * {@inheritDoc }
3442     */
3443    @Override
3444    public List<NamedBeanUsageReport> getUsageReport(NamedBean bean) {
3445        List<NamedBeanUsageReport> report = new ArrayList<>();
3446        if (bean != null) {
3447            if (bean.equals(getSourceMast())) {
3448                report.add(new NamedBeanUsageReport("SMLSourceMast"));  // NOI18N
3449            }
3450            getDestinationList().forEach((dest) -> {
3451                if (bean.equals(dest)) {
3452                    report.add(new NamedBeanUsageReport("SMLDestinationMast"));  // NOI18N
3453                }
3454                getAutoBlocks(dest).forEach((block) -> {
3455                    if (bean.equals(block)) {
3456                        report.add(new NamedBeanUsageReport("SMLBlockAuto", dest));  // NOI18N
3457                    }
3458                });
3459                getBlocks(dest).forEach((block) -> {
3460                    if (bean.equals(block)) {
3461                        report.add(new NamedBeanUsageReport("SMLBlockUser", dest));  // NOI18N
3462                    }
3463                });
3464                getAutoTurnouts(dest).forEach((turnout) -> {
3465                    if (bean.equals(turnout)) {
3466                        report.add(new NamedBeanUsageReport("SMLTurnoutAuto", dest));  // NOI18N
3467                    }
3468                });
3469                getTurnouts(dest).forEach((turnout) -> {
3470                    if (bean.equals(turnout)) {
3471                        report.add(new NamedBeanUsageReport("SMLTurnoutUser", dest));  // NOI18N
3472                    }
3473                });
3474                getSensors(dest).forEach((sensor) -> {
3475                    if (bean.equals(sensor)) {
3476                        report.add(new NamedBeanUsageReport("SMLSensor", dest));  // NOI18N
3477                    }
3478                });
3479                getAutoMasts(dest).forEach((mast) -> {
3480                    if (bean.equals(mast)) {
3481                        report.add(new NamedBeanUsageReport("SMLMastAuto", dest));  // NOI18N
3482                    }
3483                });
3484                getSignalMasts(dest).forEach((mast) -> {
3485                    if (bean.equals(mast)) {
3486                        report.add(new NamedBeanUsageReport("SMLMastUser", dest));  // NOI18N
3487                    }
3488                });
3489            });
3490        }
3491        return report;
3492    }
3493
3494    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultSignalMastLogic.class);
3495
3496}