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