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