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