001package jmri.jmrit.entryexit;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004import java.awt.BorderLayout;
005import java.awt.Color;
006import java.awt.Container;
007import java.awt.event.ActionEvent;
008import java.awt.event.ActionListener;
009import java.beans.PropertyChangeEvent;
010import java.beans.PropertyChangeListener;
011import java.util.ArrayList;
012import java.util.Hashtable;
013import java.util.LinkedHashMap;
014import java.util.List;
015import java.util.Map;
016import java.util.UUID;
017
018import javax.swing.JButton;
019import javax.swing.JFrame;
020import javax.swing.JLabel;
021import javax.swing.JPanel;
022
023import jmri.*;
024import jmri.jmrit.dispatcher.ActiveTrain;
025import jmri.jmrit.dispatcher.DispatcherFrame;
026import jmri.jmrit.display.layoutEditor.ConnectivityUtil;
027import jmri.jmrit.display.layoutEditor.LayoutBlock;
028import jmri.jmrit.display.layoutEditor.LayoutBlockConnectivityTools;
029import jmri.jmrit.display.layoutEditor.LayoutBlockManager;
030import jmri.jmrit.display.layoutEditor.LayoutSlip;
031import jmri.jmrit.display.layoutEditor.LayoutTrackExpectedState;
032import jmri.jmrit.display.layoutEditor.LayoutTurnout;
033import jmri.util.swing.JmriJOptionPane;
034import jmri.util.ThreadingUtil;
035
036public class DestinationPoints extends jmri.implementation.AbstractNamedBean {
037
038    @Override
039    public String getBeanType() {
040        return Bundle.getMessage("BeanNameDestination");  // NOI18N
041    }
042
043    transient PointDetails point = null;
044    Boolean uniDirection = true;
045    int entryExitType = EntryExitPairs.SETUPTURNOUTSONLY;//SETUPSIGNALMASTLOGIC;
046    boolean enabled = true;
047    boolean activeEntryExit = false;
048    boolean activeEntryExitReversed = false;
049    List<LayoutBlock> routeDetails = new ArrayList<>();
050    LayoutBlock destination;
051    boolean disposed = false;
052
053    transient EntryExitPairs manager = InstanceManager.getDefault(jmri.jmrit.entryexit.EntryExitPairs.class);
054
055    transient SignalMastLogic sml;
056
057    final static int NXMESSAGEBOXCLEARTIMEOUT = 30;
058
059    /**
060     * public for testing purposes.
061     * @return true if enabled, else false.
062     */
063    public boolean isEnabled() {
064        return enabled;
065    }
066
067    public void setEnabled(boolean boo) {
068        enabled = boo;
069
070        // Modify source signal mast held state
071        Sensor sourceSensor = src.getPoint().getSensor();
072        if (sourceSensor == null) {
073            return;
074        }
075        SignalMast sourceMast = src.getPoint().getSignalMast();
076        if (sourceMast == null) {
077            return;
078        }
079        if (enabled) {
080            if (!manager.isAbsSignalMode()) {
081                sourceMast.setHeld(true);
082            }
083        } else {
084            // All destinations for the source must be disabled before the mast hold can be released
085            for (PointDetails pd : src.getDestinationPoints()) {
086                if (src.getDestForPoint(pd).isEnabled()) {
087                    return;
088                }
089            }
090            sourceMast.setHeld(false);
091        }
092    }
093
094    transient Source src = null;
095
096    protected DestinationPoints(PointDetails point, String id, Source src) {
097        super(id != null ? id : "IN:" + UUID.randomUUID().toString());
098        this.src = src;
099        this.point = point;
100        setUserName(src.getPoint().getDisplayName() + " to " + this.point.getDisplayName());
101
102        propertyBlockListener = new PropertyChangeListener() {
103            @Override
104            public void propertyChange(PropertyChangeEvent e) {
105                blockStateUpdated(e);
106            }
107        };
108    }
109
110    String getUniqueId() {
111        return getSystemName();
112    }
113
114    public PointDetails getDestPoint() {
115        return point;
116    }
117
118    /**
119     * @since 4.17.4
120     * Making the source object available for scripting in Jython.
121     * @return source.
122     */
123    public Source getSource() {
124        return src ;
125    }
126
127    boolean getUniDirection() {
128        return uniDirection;
129    }
130
131    void setUniDirection(boolean uni) {
132        uniDirection = uni;
133    }
134
135    NamedBean getSignal() {
136        return point.getSignal();
137    }
138
139    void setRouteTo(boolean set) {
140        if (set && getEntryExitType() == EntryExitPairs.FULLINTERLOCK) {
141            point.setRouteTo(true);
142            point.setNXButtonState(EntryExitPairs.NXBUTTONACTIVE);
143        } else {
144            point.setRouteTo(false);
145            point.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE);
146        }
147    }
148
149    void setRouteFrom(boolean set) {
150        if (set && getEntryExitType() == EntryExitPairs.FULLINTERLOCK) {
151            src.pd.setRouteFrom(true);
152            src.pd.setNXButtonState(EntryExitPairs.NXBUTTONACTIVE);
153        } else {
154            src.pd.setRouteFrom(false);
155            src.pd.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE);
156        }
157    }
158
159    boolean isRouteToPointSet() {
160        return point.isRouteToPointSet();
161    }
162
163    LayoutBlock getFacing() {
164        return point.getFacing();
165    }
166
167    List<LayoutBlock> getProtecting() {
168        return point.getProtecting();
169    }
170
171    int getEntryExitType() {
172        return entryExitType;
173    }
174
175    void setEntryExitType(int type) {
176        entryExitType = type;
177        if ((type != EntryExitPairs.SETUPTURNOUTSONLY) && (getSignal() != null) && point.getSignal() != null) {
178            uniDirection = true;
179        }
180    }
181
182    @SuppressFBWarnings(value = "SE_TRANSIENT_FIELD_NOT_RESTORED",
183            justification = "No auto serialization")
184    transient protected PropertyChangeListener propertyBlockListener;
185
186    protected void blockStateUpdated(PropertyChangeEvent e) {
187        Block blk = (Block) e.getSource();
188        if (e.getPropertyName().equals("state")) {  // NOI18N
189            if (log.isDebugEnabled()) {
190                log.debug("{}  We have a change of state on the block {}", getUserName(), blk.getDisplayName());  // NOI18N
191            }
192            int now = ((Integer) e.getNewValue());
193
194            LayoutBlock lBlock = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlock(blk);
195            if (lBlock == null){
196                log.error("Unable to get layout block from block {}",blk);
197                return;
198            }
199
200            if (now == Block.OCCUPIED) {
201                //If the block was previously active or inactive then we will
202                //reset the useExtraColor, but not if it was previously unknown or inconsistent.
203                lBlock.setUseExtraColor(false);
204                blk.removePropertyChangeListener(propertyBlockListener); //was this
205                removeBlockFromRoute(lBlock);
206            } else {
207                if (src.getStart() == lBlock) {
208                    // Remove listener when the start block becomes unoccupied.
209                    // When the start block is occupied when the route is created, the normal
210                    // removal does not occur.
211                    lBlock.getBlock().removePropertyChangeListener(propertyBlockListener);
212                    log.debug("Remove listener from start block {} for {}", lBlock.getDisplayName(), this.getDisplayName());
213                } else {
214                    log.debug("state was {} and did not go through reset",now);  // NOI18N
215                }
216            }
217        }
218    }
219
220    Object lastSeenActiveBlockObject;
221
222    synchronized void removeBlockFromRoute(LayoutBlock lBlock) {
223
224        if (routeDetails != null) {
225            if (routeDetails.indexOf(lBlock) == -1) {
226                if (src.getStart() == lBlock) {
227                    log.debug("Start block went active");  // NOI18N
228                    lastSeenActiveBlockObject = src.getStart().getBlock().getValue();
229                    lBlock.getBlock().removePropertyChangeListener(propertyBlockListener);
230                    return;
231                } else {
232                    log.error("Block {} went active but it is not part of our NX path", lBlock.getDisplayName());  // NOI18N
233                }
234            }
235            if (routeDetails.indexOf(lBlock) != 0) {
236                log.debug("A block has been skipped will set the value of the active block to that of the original one");  // NOI18N
237                lBlock.getBlock().setValue(lastSeenActiveBlockObject);
238                if (routeDetails.indexOf(lBlock) != -1) {
239                    while (routeDetails.indexOf(lBlock) != 0) {
240                        LayoutBlock tbr = routeDetails.get(0);
241                        log.debug("Block skipped {} and removed from list", tbr.getDisplayName());  // NOI18N
242                        tbr.getBlock().removePropertyChangeListener(propertyBlockListener);
243                        tbr.setUseExtraColor(false);
244                        routeDetails.remove(0);
245                    }
246                }
247            }
248            if (routeDetails.contains(lBlock)) {
249                routeDetails.remove(lBlock);
250                setRouteFrom(false);
251                src.pd.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE);
252                if (sml != null && getEntryExitType() == EntryExitPairs.FULLINTERLOCK) {
253                    if (!manager.isAbsSignalMode()) {
254                        sml.getSourceMast().setHeld(true);
255                    }
256                    SignalMast mast = (SignalMast) getSignal();
257                    if (sml.getStoreState(mast) == SignalMastLogic.STORENONE) {
258                        sml.removeDestination(mast);
259                    }
260                }
261            } else {
262                log.error("Block {} that went Occupied was not in the routeDetails list", lBlock.getDisplayName());  // NOI18N
263            }
264            if (log.isDebugEnabled()) {
265                log.debug("Route details contents {}", routeDetails);  // NOI18N
266                for (int i = 0; i < routeDetails.size(); i++) {
267                    log.debug("    name: {}", routeDetails.get(i).getDisplayName());
268                }
269            }
270            if ((routeDetails.size() == 1) && (routeDetails.contains(destination))) {
271                routeDetails.get(0).getBlock().removePropertyChangeListener(propertyBlockListener);  // was set against block sensor
272                routeDetails.remove(destination);
273            }
274        }
275        lastSeenActiveBlockObject = lBlock.getBlock().getValue();
276
277        if ((routeDetails == null) || (routeDetails.size() == 0)) {
278            //At this point the route has cleared down/the last remaining block are now active.
279            routeDetails = null;
280            setRouteTo(false);
281            setRouteFrom(false);
282            setActiveEntryExit(false);
283            lastSeenActiveBlockObject = null;
284        }
285    }
286
287    //For a clear down we need to add a message, if it is a cancel, manual clear down or I didn't mean it.
288    // For creating routes, this is run in a thread.
289    void setRoute(boolean state) {
290        log.debug("[setRoute] Start, dp = {}", getUserName());
291
292        if (disposed) {
293            log.error("Set route called even though interlock has been disposed of");  // NOI18N
294            return;
295        }
296
297        if (routeDetails == null) {
298            log.error("No route to set or clear down");  // NOI18N
299            setActiveEntryExit(false);
300            setRouteTo(false);
301            setRouteFrom(false);
302            if ((getSignal() instanceof SignalMast) && (getEntryExitType() != EntryExitPairs.FULLINTERLOCK)) {
303                SignalMast mast = (SignalMast) getSignal();
304                mast.setHeld(false);
305            }
306            synchronized (this) {
307                destination = null;
308            }
309            return;
310        }
311        if (!state) {
312            switch (manager.getClearDownOption()) {
313                case EntryExitPairs.PROMPTUSER:
314                    cancelClearOptionBox();
315                    break;
316                case EntryExitPairs.AUTOCANCEL:
317                    cancelClearInterlock(EntryExitPairs.CANCELROUTE);
318                    break;
319                case EntryExitPairs.AUTOCLEAR:
320                    cancelClearInterlock(EntryExitPairs.CLEARROUTE);
321                    break;
322                case EntryExitPairs.AUTOSTACK:
323                    cancelClearInterlock(EntryExitPairs.STACKROUTE);
324                    break;
325                default:
326                    cancelClearOptionBox();
327                    break;
328            }
329            log.debug("[setRoute] Cancel/Clear/Stack route, dp = {}", getUserName());
330            return;
331        }
332        if (manager.isRouteStacked(this, false)) {
333            manager.cancelStackedRoute(this, false);
334        }
335        /* We put the setting of the route into a seperate thread and put a glass pane in front of the layout editor.
336         The swing thread for flashing the icons will carry on without interuption. */
337        final List<Color> realColorStd = new ArrayList<>();
338        final List<Color> realColorXtra = new ArrayList<>();
339        final List<LayoutBlock> routeBlocks = new ArrayList<>();
340        if (manager.useDifferentColorWhenSetting()) {
341            boolean first = true;
342            for (LayoutBlock lbk : routeDetails) {
343                if (first) {
344                    // Don't change the color for the facing block
345                    first = false;
346                    continue;
347                }
348                routeBlocks.add(lbk);
349                realColorXtra.add(lbk.getBlockExtraColor());
350                realColorStd.add(lbk.getBlockTrackColor());
351                lbk.setBlockExtraColor(manager.getSettingRouteColor());
352                lbk.setBlockTrackColor(manager.getSettingRouteColor());
353            }
354            //Force a redraw, to reflect color change
355            src.getPoint().getPanel().redrawPanel();
356        }
357        ActiveTrain tmpat = null;
358        if (manager.getDispatcherIntegration() && InstanceManager.getNullableDefault(DispatcherFrame.class) != null) {
359            DispatcherFrame df = InstanceManager.getDefault(DispatcherFrame.class);
360            for (ActiveTrain atl : df.getActiveTrainsList()) {
361                if (atl.getEndBlock() == src.getStart().getBlock()) {
362                    if (atl.getLastAllocatedSection() == atl.getEndBlockSection()) {
363                        if (!atl.getReverseAtEnd() && !atl.getResetWhenDone()) {
364                            tmpat = atl;
365                            break;
366                        }
367                        log.warn("Interlock will not be added to existing Active Train as it is set for back and forth operation");  // NOI18N
368                    }
369                }
370            }
371        }
372        final ActiveTrain at = tmpat;
373        Runnable setRouteRun = new Runnable() {
374            @Override
375            public void run() {
376                src.getPoint().getPanel().getGlassPane().setVisible(true);
377
378                try {
379                    Hashtable<Turnout, Integer> turnoutSettings = new Hashtable<>();
380
381                    ConnectivityUtil connection = new ConnectivityUtil(point.getPanel());
382//                     log.info("@@ New ConnectivityUtil = '{}'", point.getPanel().getLayoutName());
383
384                    // This for loop was after the if statement
385                    // Last block in the route is the one that we are protecting at the last sensor/signalmast
386                    for (int i = 0; i < routeDetails.size(); i++) {
387                        //if we are not using the dispatcher and the signal logic is dynamic, then set the turnouts
388                        if (at == null && isSignalLogicDynamic()) {
389                            if (i > 0) {
390                                List<LayoutTrackExpectedState<LayoutTurnout>> turnoutlist;
391                                int nxtBlk = i + 1;
392                                int preBlk = i - 1;
393                                if (i < routeDetails.size() - 1) {
394                                    turnoutlist = connection.getTurnoutList(routeDetails.get(i).getBlock(), routeDetails.get(preBlk).getBlock(), routeDetails.get(nxtBlk).getBlock());
395                                    for (int x = 0; x < turnoutlist.size(); x++) {
396                                        if (turnoutlist.get(x).getObject() instanceof LayoutSlip) {
397                                            int slipState = turnoutlist.get(x).getExpectedState();
398                                            LayoutSlip ls = (LayoutSlip) turnoutlist.get(x).getObject();
399                                            int taState = ls.getTurnoutState(slipState);
400                                            Turnout t = ls.getTurnout();
401                                            if (t==null) {
402                                                log.warn("Found unexpected Turnout reference at {}: {}",i,ls);
403                                                continue; // not sure what else do to here
404                                            }
405                                            turnoutSettings.put(t, taState);
406
407                                            int tbState = ls.getTurnoutBState(slipState);
408                                            ls.getTurnoutB().setCommandedState(tbState);
409                                            turnoutSettings.put(ls.getTurnoutB(), tbState);
410                                        } else {
411                                            String t = turnoutlist.get(x).getObject().getTurnoutName();
412                                            Turnout turnout = InstanceManager.turnoutManagerInstance().getTurnout(t);
413                                            if (turnout != null) {
414                                                turnoutSettings.put(turnout, turnoutlist.get(x).getExpectedState());
415                                                if (turnoutlist.get(x).getObject().getSecondTurnout() != null) {
416                                                    turnoutSettings.put(turnoutlist.get(x).getObject().getSecondTurnout(),
417                                                            turnoutlist.get(x).getExpectedState());
418                                                }
419                                            }
420                                        }
421                                    }
422                                }
423                            }
424                        }
425                        if ((getEntryExitType() == EntryExitPairs.FULLINTERLOCK)) {
426                            routeDetails.get(i).getBlock().addPropertyChangeListener(propertyBlockListener); // was set against occupancy sensor
427                            if (i > 0) {
428                                routeDetails.get(i).setUseExtraColor(true);
429                            }
430                        } else {
431                            routeDetails.get(i).getBlock().removePropertyChangeListener(propertyBlockListener); // was set against occupancy sensor
432                        }
433                    }
434                    if (at == null) {
435                        if (!isSignalLogicDynamic()) {
436                            SignalMastLogic tmSml = InstanceManager.getDefault(SignalMastLogicManager.class).getSignalMastLogic((SignalMast) src.sourceSignal);
437                            for (Turnout t : tmSml.getAutoTurnouts((SignalMast) getSignal())) {
438                                turnoutSettings.put(t, tmSml.getAutoTurnoutState(t, (SignalMast) getSignal()));
439                            }
440                        }
441                        for (Map.Entry< Turnout, Integer> entry : turnoutSettings.entrySet()) {
442                            entry.getKey().setCommandedState(entry.getValue());
443//                             log.info("**> Set turnout '{}'", entry.getKey().getDisplayName());
444                            Runnable r = new Runnable() {
445                                @Override
446                                public void run() {
447                                    try {
448                                        Thread.sleep(250 + manager.turnoutSetDelay);
449                                    } catch (InterruptedException ex) {
450                                        Thread.currentThread().interrupt();
451                                    }
452                                }
453                            };
454                            Thread thr = ThreadingUtil.newThread(r, "Entry Exit Route: Turnout Setting");  // NOI18N
455                            thr.start();
456                            try {
457                                thr.join();
458                            } catch (InterruptedException ex) {
459                                //            log.info("interrupted at join " + ex);
460                            }
461                        }
462                    }
463                    src.getPoint().getPanel().redrawPanel();
464                    if (getEntryExitType() != EntryExitPairs.SETUPTURNOUTSONLY) {
465                        if (getEntryExitType() == EntryExitPairs.FULLINTERLOCK) {
466                            //If our start block is already active we will set it as our lastSeenActiveBlock.
467                            if (src.getStart().getState() == Block.OCCUPIED) {
468                                src.getStart().removePropertyChangeListener(propertyBlockListener);
469                                lastSeenActiveBlockObject = src.getStart().getBlock().getValue();
470                                log.debug("Last seen value {}", lastSeenActiveBlockObject);
471                            }
472                        }
473                        if ((src.sourceSignal instanceof SignalMast) && (getSignal() instanceof SignalMast)) {
474                            SignalMast smSource = (SignalMast) src.sourceSignal;
475                            SignalMast smDest = (SignalMast) getSignal();
476                            synchronized (this) {
477                                sml = InstanceManager.getDefault(SignalMastLogicManager.class).newSignalMastLogic(smSource);
478                                if (!sml.isDestinationValid(smDest)) {
479                                    //if no signalmastlogic existed then created it, but set it not to be stored.
480                                    sml.setDestinationMast(smDest);
481                                    sml.setStore(SignalMastLogic.STORENONE, smDest);
482                                }
483                            }
484
485                            //Remove the first block as it is our start block
486                            routeDetails.remove(0);
487
488                            synchronized (this) {
489                                releaseMast(smSource, turnoutSettings);
490                                //Only change the block and turnout details if this a temp signalmast logic
491                                if (sml.getStoreState(smDest) == SignalMastLogic.STORENONE) {
492                                    LinkedHashMap<Block, Integer> blks = new LinkedHashMap<>();
493                                    for (int i = 0; i < routeDetails.size(); i++) {
494                                        if (routeDetails.get(i).getBlock().getState() == Block.UNKNOWN) {
495                                            routeDetails.get(i).getBlock().setState(Block.UNOCCUPIED);
496                                        }
497                                        blks.put(routeDetails.get(i).getBlock(), Block.UNOCCUPIED);
498                                    }
499                                    sml.setAutoBlocks(blks, smDest);
500                                    sml.setAutoTurnouts(turnoutSettings, smDest);
501                                    sml.initialise(smDest);
502                                }
503                            }
504                            smSource.addPropertyChangeListener(new PropertyChangeListener() {
505                                @Override
506                                public void propertyChange(PropertyChangeEvent e) {
507                                    SignalMast source = (SignalMast) e.getSource();
508                                    source.removePropertyChangeListener(this);
509                                    setRouteFrom(true);
510                                    setRouteTo(true);
511                                }
512                            });
513                            src.pd.extendedtime = true;
514                            point.extendedtime = true;
515                        } else {
516                            if (src.sourceSignal instanceof SignalMast) {
517                                SignalMast mast = (SignalMast) src.sourceSignal;
518                                releaseMast(mast, turnoutSettings);
519                            } else if (src.sourceSignal instanceof SignalHead) {
520                                SignalHead head = (SignalHead) src.sourceSignal;
521                                head.setHeld(false);
522                            }
523                            setRouteFrom(true);
524                            setRouteTo(true);
525                        }
526                    }
527                    if (manager.useDifferentColorWhenSetting()) {
528                        //final List<Color> realColorXtra = realColorXtra;
529                        javax.swing.Timer resetColorBack = new javax.swing.Timer(manager.getSettingTimer(), new java.awt.event.ActionListener() {
530                            @Override
531                            public void actionPerformed(java.awt.event.ActionEvent e) {
532                                for (int i = 0; i < routeBlocks.size(); i++) {
533                                    LayoutBlock lbk = routeBlocks.get(i);
534                                    lbk.setBlockExtraColor(realColorXtra.get(i));
535                                    lbk.setBlockTrackColor(realColorStd.get(i));
536                                }
537                                src.getPoint().getPanel().redrawPanel();
538                            }
539                        });
540                        resetColorBack.setRepeats(false);
541                        resetColorBack.start();
542                    }
543
544                    if (at != null) {
545                        Section sec;
546                        if (sml != null && sml.getAssociatedSection((SignalMast) getSignal()) != null) {
547                            sec = sml.getAssociatedSection((SignalMast) getSignal());
548                        } else {
549                            String secUserName = src.getPoint().getDisplayName() + ":" + point.getDisplayName();
550                            sec = InstanceManager.getDefault(SectionManager.class).getSection(secUserName);
551                            if (sec == null) {
552                                sec = InstanceManager.getDefault(SectionManager.class).createNewSection(secUserName);
553                                sec.setSectionType(Section.DYNAMICADHOC);
554                            }
555                            if (sec.getSectionType() == Section.DYNAMICADHOC) {
556                                sec.removeAllBlocksFromSection();
557                                for (LayoutBlock key : routeDetails) {
558                                    if (key != src.getStart()) {
559                                        sec.addBlock(key.getBlock());
560                                    }
561                                }
562                                String dir = Path.decodeDirection(src.getStart().getNeighbourDirection(routeDetails.get(0).getBlock()));
563                                EntryPoint ep = new EntryPoint(routeDetails.get(0).getBlock(), src.getStart().getBlock(), dir);
564                                ep.setTypeForward();
565                                sec.addToForwardList(ep);
566
567                                LayoutBlock proDestLBlock = point.getProtecting().get(0);
568                                if (proDestLBlock != null) {
569                                    dir = Path.decodeDirection(proDestLBlock.getNeighbourDirection(point.getFacing()));
570                                    ep = new EntryPoint(point.getFacing().getBlock(), proDestLBlock.getBlock(), dir);
571                                    ep.setTypeReverse();
572                                    sec.addToReverseList(ep);
573                                }
574                            }
575                        }
576                        InstanceManager.getDefault(DispatcherFrame.class).extendActiveTrainsPath(sec, at, src.getPoint().getPanel());
577                    }
578
579                    src.pd.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE);
580                    point.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE);
581                } catch (RuntimeException ex) {
582                    log.error("An error occurred while setting the route", ex);  // NOI18N
583                    src.pd.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE);
584                    point.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE);
585                    if (manager.useDifferentColorWhenSetting()) {
586                        for (int i = 0; i < routeBlocks.size(); i++) {
587                            LayoutBlock lbk = routeBlocks.get(i);
588                            lbk.setBlockExtraColor(realColorXtra.get(i));
589                            lbk.setBlockTrackColor(realColorStd.get(i));
590                        }
591                    }
592                    src.getPoint().getPanel().redrawPanel();
593                }
594                src.getPoint().getPanel().getGlassPane().setVisible(false);
595                //src.setMenuEnabled(true);
596            }
597        };
598        Thread thrMain = ThreadingUtil.newThread(setRouteRun, "Entry Exit Set Route");  // NOI18N
599        thrMain.start();
600        try {
601            thrMain.join();
602        } catch (InterruptedException e) {
603            log.error("Interuption exception {}", e.toString());  // NOI18N
604        }
605        log.debug("[setRoute] Done, dp = {}", getUserName());
606    }
607
608    /**
609     * Remove the hold on the mast when all of the turnouts have completed moving.
610     * This only applies to turnouts using ONESENSOR feedback.  TWOSENSOR has an
611     * intermediate inconsistent state which prevents erroneous signal aspects.
612     * The maximum wait time is 10 seconds.
613     *
614     * @since 4.11.1
615     * @param mast The signal mast that will be released.
616     * @param turnoutSettings The turnouts that are being set for the current NX route.
617     */
618    private void releaseMast(SignalMast mast, Hashtable<Turnout, Integer> turnoutSettings) {
619        Hashtable<Turnout, Integer> turnoutList = new Hashtable<>(turnoutSettings);
620        Runnable r = new Runnable() {
621            @Override
622            public void run() {
623                try {
624                    for (int i = 20; i > 0; i--) {
625                        int active = 0;
626                        for (Map.Entry< Turnout, Integer> entry : turnoutList.entrySet()) {
627                            Turnout tout = entry.getKey();
628                            if (tout.getFeedbackMode() == Turnout.ONESENSOR) {
629                                // Check state
630                                if (tout.getKnownState() != tout.getCommandedState()) {
631                                    active += 1;
632                                }
633                            }
634                        }
635                        if (active == 0) {
636                            break;
637                        }
638                        Thread.sleep(500);
639                    }
640                    log.debug("[releaseMast] mast = {}", mast.getDisplayName());
641                    mast.setHeld(false);
642                } catch (InterruptedException ex) {
643                    Thread.currentThread().interrupt();
644                }
645            }
646        };
647        Thread thr = ThreadingUtil.newThread(r, "Entry Exit Route: Release Mast");  // NOI18N
648        thr.start();
649    }
650
651    private boolean isSignalLogicDynamic() {
652        if ((src.sourceSignal instanceof SignalMast) && (getSignal() instanceof SignalMast)) {
653            SignalMast smSource = (SignalMast) src.sourceSignal;
654            SignalMast smDest = (SignalMast) getSignal();
655            if (InstanceManager.getDefault(SignalMastLogicManager.class).getSignalMastLogic(smSource) != null
656                    && InstanceManager.getDefault(SignalMastLogicManager.class).getSignalMastLogic(smSource).getStoreState(smDest) != SignalMastLogic.STORENONE) {
657                return false;
658            }
659        }
660        return true;
661
662    }
663
664    private JFrame cancelClearFrame;
665    transient private Thread threadAutoClearFrame = null;
666    JButton jButton_Stack = new JButton(Bundle.getMessage("Stack"));  // NOI18N
667
668    void cancelClearOptionBox() {
669        if (cancelClearFrame == null) {
670            JButton jButton_Clear = new JButton(Bundle.getMessage("ClearDown"));  // NOI18N
671            JButton jButton_Cancel = new JButton(Bundle.getMessage("ButtonCancel"));  // NOI18N
672
673            JButton jButton_Exit = new JButton(Bundle.getMessage("Exit"));  // NOI18N
674            JLabel jLabel = new JLabel(Bundle.getMessage("InterlockPrompt"));  // NOI18N
675            JLabel jIcon = new JLabel(javax.swing.UIManager.getIcon("OptionPane.questionIcon"));  // NOI18N
676            cancelClearFrame = new JFrame(Bundle.getMessage("Interlock"));  // NOI18N
677            Container cont = cancelClearFrame.getContentPane();
678            JPanel qPanel = new JPanel();
679            qPanel.add(jIcon);
680            qPanel.add(jLabel);
681            cont.add(qPanel, BorderLayout.CENTER);
682            JPanel buttonsPanel = new JPanel();
683            buttonsPanel.add(jButton_Cancel);
684            buttonsPanel.add(jButton_Clear);
685            buttonsPanel.add(jButton_Stack);
686            buttonsPanel.add(jButton_Exit);
687            cont.add(buttonsPanel, BorderLayout.SOUTH);
688            cancelClearFrame.pack();
689
690            jButton_Clear.addActionListener(new ActionListener() {
691                @Override
692                public void actionPerformed(ActionEvent e) {
693                    cancelClearFrame.setVisible(false);
694                    threadAutoClearFrame.interrupt();
695                    cancelClearInterlock(EntryExitPairs.CLEARROUTE);
696                }
697            });
698            jButton_Cancel.addActionListener(new ActionListener() {
699                @Override
700                public void actionPerformed(ActionEvent e) {
701                    cancelClearFrame.setVisible(false);
702                    threadAutoClearFrame.interrupt();
703                    cancelClearInterlock(EntryExitPairs.CANCELROUTE);
704                }
705            });
706            jButton_Stack.addActionListener(new ActionListener() {
707                @Override
708                public void actionPerformed(ActionEvent e) {
709                    cancelClearFrame.setVisible(false);
710                    threadAutoClearFrame.interrupt();
711                    cancelClearInterlock(EntryExitPairs.STACKROUTE);
712                }
713            });
714            jButton_Exit.addActionListener(new ActionListener() {
715                @Override
716                public void actionPerformed(ActionEvent e) {
717                    cancelClearFrame.setVisible(false);
718                    threadAutoClearFrame.interrupt();
719                    cancelClearInterlock(EntryExitPairs.EXITROUTE);
720                    firePropertyChange("noChange", null, null);  // NOI18N
721                }
722            });
723            src.getPoint().getPanel().setGlassPane(manager.getGlassPane());
724
725        }
726        cancelClearFrame.setTitle(getUserName());
727        if (manager.isRouteStacked(this, false)) {
728            jButton_Stack.setEnabled(false);
729        } else {
730            jButton_Stack.setEnabled(true);
731        }
732
733        if (cancelClearFrame.isVisible()) {
734            return;
735        }
736        src.pd.extendedtime = true;
737        point.extendedtime = true;
738
739        class MessageTimeOut implements Runnable {
740
741            MessageTimeOut() {
742            }
743
744            @Override
745            public void run() {
746                try {
747                    //Set a timmer before this window is automatically closed to 30 seconds
748                    Thread.sleep(NXMESSAGEBOXCLEARTIMEOUT * 1000);
749                    cancelClearFrame.setVisible(false);
750                    cancelClearInterlock(EntryExitPairs.EXITROUTE);
751                } catch (InterruptedException ex) {
752                    log.debug("Flash timer cancelled");  // NOI18N
753                }
754            }
755        }
756        MessageTimeOut mt = new MessageTimeOut();
757        threadAutoClearFrame = ThreadingUtil.newThread(mt, "NX Button Clear Message Timeout ");  // NOI18N
758        threadAutoClearFrame.start();
759        cancelClearFrame.setAlwaysOnTop(true);
760        src.getPoint().getPanel().getGlassPane().setVisible(true);
761        int w = cancelClearFrame.getSize().width;
762        int h = cancelClearFrame.getSize().height;
763        int x = (int) src.getPoint().getPanel().getLocation().getX() + ((src.getPoint().getPanel().getSize().width - w) / 2);
764        int y = (int) src.getPoint().getPanel().getLocation().getY() + ((src.getPoint().getPanel().getSize().height - h) / 2);
765        cancelClearFrame.setLocation(x, y);
766        cancelClearFrame.setVisible(true);
767    }
768
769    void cancelClearInterlock(int cancelClear) {
770        if ((cancelClear == EntryExitPairs.EXITROUTE) || (cancelClear == EntryExitPairs.STACKROUTE)) {
771            src.pd.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE);
772            point.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE);
773            src.getPoint().getPanel().getGlassPane().setVisible(false);
774            if (cancelClear == EntryExitPairs.STACKROUTE) {
775                manager.stackNXRoute(this, false);
776            }
777            return;
778        }
779
780        if (cancelClear == EntryExitPairs.CANCELROUTE) {
781            if (manager.getDispatcherIntegration() && InstanceManager.getNullableDefault(DispatcherFrame.class) != null) {
782                DispatcherFrame df = InstanceManager.getDefault(DispatcherFrame.class);
783                ActiveTrain at = null;
784                for (ActiveTrain atl : df.getActiveTrainsList()) {
785                    if (atl.getEndBlock() == point.getFacing().getBlock()) {
786                        if (atl.getLastAllocatedSection() == atl.getEndBlockSection()) {
787                            at = atl;
788                            break;
789                        }
790                    }
791                }
792                if (at != null) {
793                    Section sec;
794                    synchronized (this) {
795                        if (sml != null && sml.getAssociatedSection((SignalMast) getSignal()) != null) {
796                            sec = sml.getAssociatedSection((SignalMast) getSignal());
797                        } else {
798                            sec = InstanceManager.getDefault(SectionManager.class).getSection(src.getPoint().getDisplayName() + ":" + point.getDisplayName());
799                        }
800                    }
801                    if (sec != null) {
802                        if (!df.removeFromActiveTrainPath(sec, at, src.getPoint().getPanel())) {
803                            log.error("Unable to remove allocation from dispathcer, leave interlock in place");  // NOI18N
804                            src.pd.cancelNXButtonTimeOut();
805                            point.cancelNXButtonTimeOut();
806                            src.getPoint().getPanel().getGlassPane().setVisible(false);
807                            return;
808                        }
809                        if (sec.getSectionType() == Section.DYNAMICADHOC) {
810                            sec.removeAllBlocksFromSection();
811                        }
812                    }
813                }
814            }
815        }
816        src.setMenuEnabled(false);
817        if (src.sourceSignal instanceof SignalMast) {
818            SignalMast mast = (SignalMast) src.sourceSignal;
819            mast.setAspect(mast.getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.DANGER));
820            if (!manager.isAbsSignalMode()) {
821                mast.setHeld(true);
822            }
823        } else if (src.sourceSignal instanceof SignalHead) {
824            SignalHead head = (SignalHead) src.sourceSignal;
825            if (!manager.isAbsSignalMode()) {
826                head.setHeld(true);
827            }
828        } else {
829            log.debug("No signal found");  // NOI18N
830        }
831
832        //Get rid of the signal mast logic to the destination mast.
833        synchronized (this) {
834            if ((getSignal() instanceof SignalMast) && (sml != null)) {
835                SignalMast mast = (SignalMast) getSignal();
836                if (sml.getStoreState(mast) == SignalMastLogic.STORENONE) {
837                    sml.removeDestination(mast);
838                }
839            }
840            sml = null;
841        }
842
843        if (routeDetails == null) {
844            return;
845        }
846
847        // The block list for an interlocking NX still has the facing block if there are no signals.
848        boolean facing = getSource().getStart().getUseExtraColor();
849        for (LayoutBlock blk : routeDetails) {
850            if (facing) {
851                // skip the facing block when there is an active NX pair immediately before this one.
852                facing = false;
853                continue;
854            }
855            if ((getEntryExitType() == EntryExitPairs.FULLINTERLOCK)) {
856                blk.setUseExtraColor(false);
857            }
858            blk.getBlock().removePropertyChangeListener(propertyBlockListener); // was set against occupancy sensor
859        }
860
861        if (cancelClear == EntryExitPairs.CLEARROUTE) {
862            if (routeDetails.isEmpty()) {
863                if (log.isDebugEnabled()) {
864                    log.debug("{}  all blocks have automatically been cleared down", getUserName());  // NOI18N
865                }
866            } else {
867                if (log.isDebugEnabled()) {
868                    log.debug("{}  No blocks were cleared down {}", getUserName(), routeDetails.size());  // NOI18N
869                }
870                try {
871                    if (log.isDebugEnabled()) {
872                        log.debug("{}  set first block as active so that we can manually clear this down {}", getUserName(), routeDetails.get(0).getBlock().getUserName());  // NOI18N
873                    }
874                    if (routeDetails.get(0).getOccupancySensor() != null) {
875                        routeDetails.get(0).getOccupancySensor().setState(Sensor.ACTIVE);
876                    } else {
877                        routeDetails.get(0).getBlock().goingActive();
878                    }
879
880                    if (src.getStart().getOccupancySensor() != null) {
881                        src.getStart().getOccupancySensor().setState(Sensor.INACTIVE);
882                    } else {
883                        src.getStart().getBlock().goingInactive();
884                    }
885                } catch (java.lang.NullPointerException e) {
886                    log.error("error in clear route A", e);  // NOI18N
887                } catch (JmriException e) {
888                    log.error("error in clear route A", e);  // NOI18N
889                }
890                if (log.isDebugEnabled()) {
891                    log.debug("{}  Going to clear routeDetails down {}", getUserName(), routeDetails.size());  // NOI18N
892                    for (int i = 0; i < routeDetails.size(); i++) {
893                        log.debug("Block at {} {}", i, routeDetails.get(i).getDisplayName());
894                    }
895                }
896                if (routeDetails.size() > 1) {
897                    //We will remove the propertychange listeners on the sensors as we will now manually clear things down.
898                    //Should we just be usrc.pdating the block status and not the sensor
899                    for (int i = 1; i < routeDetails.size() - 1; i++) {
900                        if (log.isDebugEnabled()) {
901                            log.debug("{} in loop Set active {} {}", getUserName(), routeDetails.get(i).getDisplayName(), routeDetails.get(i).getBlock().getSystemName());  // NOI18N
902                        }
903                        try {
904                            if (routeDetails.get(i).getOccupancySensor() != null) {
905                                routeDetails.get(i).getOccupancySensor().setState(Sensor.ACTIVE);
906                            } else {
907                                routeDetails.get(i).getBlock().goingActive();
908                            }
909
910                            if (log.isDebugEnabled()) {
911                                log.debug("{} in loop Set inactive {} {}", getUserName(), routeDetails.get(i - 1).getDisplayName(), routeDetails.get(i - 1).getBlock().getSystemName());  // NOI18N
912                            }
913                            if (routeDetails.get(i - 1).getOccupancySensor() != null) {
914                                routeDetails.get(i - 1).getOccupancySensor().setState(Sensor.INACTIVE);
915                            } else {
916                                routeDetails.get(i - 1).getBlock().goingInactive();
917                            }
918                        } catch (NullPointerException | JmriException e) {
919                            log.error("error in clear route b ", e);  // NOI18N
920                        }
921                        // NOI18N
922
923                    }
924                    try {
925                        if (log.isDebugEnabled()) {
926                            log.debug("{} out of loop Set active {} {}", getUserName(), routeDetails.get(routeDetails.size() - 1).getDisplayName(), routeDetails.get(routeDetails.size() - 1).getBlock().getSystemName());  // NOI18N
927                        }
928                        //Get the last block an set it active.
929                        if (routeDetails.get(routeDetails.size() - 1).getOccupancySensor() != null) {
930                            routeDetails.get(routeDetails.size() - 1).getOccupancySensor().setState(Sensor.ACTIVE);
931                        } else {
932                            routeDetails.get(routeDetails.size() - 1).getBlock().goingActive();
933                        }
934                        if (log.isDebugEnabled()) {
935                            log.debug("{} out of loop Set inactive {} {}", getUserName(), routeDetails.get(routeDetails.size() - 2).getUserName(), routeDetails.get(routeDetails.size() - 2).getBlock().getSystemName());  // NOI18N
936                        }
937                        if (routeDetails.get(routeDetails.size() - 2).getOccupancySensor() != null) {
938                            routeDetails.get(routeDetails.size() - 2).getOccupancySensor().setState(Sensor.INACTIVE);
939                        } else {
940                            routeDetails.get(routeDetails.size() - 2).getBlock().goingInactive();
941                        }
942                    } catch (java.lang.NullPointerException e) {
943                        log.error("error in clear route c", e);  // NOI18N
944                    } catch (java.lang.ArrayIndexOutOfBoundsException e) {
945                        log.error("error in clear route c", e);  // NOI18N
946                    } catch (JmriException e) {
947                        log.error("error in clear route c", e);  // NOI18N
948                    }
949                }
950            }
951        }
952        setActiveEntryExit(false);
953        setRouteFrom(false);
954        setRouteTo(false);
955        routeDetails = null;
956        synchronized (this) {
957            lastSeenActiveBlockObject = null;
958        }
959        src.pd.cancelNXButtonTimeOut();
960        point.cancelNXButtonTimeOut();
961        src.getPoint().getPanel().getGlassPane().setVisible(false);
962
963    }
964
965    public void setInterlockRoute(boolean reverseDirection) {
966        if (activeEntryExit) {
967            return;
968        }
969        activeBean(reverseDirection, false);
970    }
971
972    void activeBean(boolean reverseDirection) {
973        activeBean(reverseDirection, true);
974    }
975
976    synchronized void activeBean(boolean reverseDirection, boolean showMessage) {
977        // Clear any previous memory message
978        MemoryManager mgr = InstanceManager.getDefault(MemoryManager.class);
979        Memory nxMem = mgr.getMemory(manager.getMemoryOption());
980        if (nxMem != null) {
981            nxMem.setValue("");
982        }
983
984        if (!isEnabled()) {
985            JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("RouteDisabled", getDisplayName()));  // NOI18N
986            src.pd.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE);
987            point.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE);
988            return;
989        }
990        if (activeEntryExit) {
991            // log.debug(getUserName() + "  Our route is active so this would go for a clear down but we need to check that the we can clear it down" + activeEndPoint);
992            if (!isEnabled()) {
993                log.debug("A disabled entry exit has been called will bomb out");  // NOI18N
994                return;
995            }
996            log.debug("{}  We have a valid match on our end point so we can clear down", getUserName());  // NOI18N
997            //setRouteTo(false);
998            //src.pd.setRouteFrom(false);
999            setRoute(false);
1000        } else {
1001            if (isRouteToPointSet()) {
1002                log.debug("{}  route to this point is set therefore can not set another to it ", getUserName());  // NOI18N
1003                if (showMessage && !manager.isRouteStacked(this, false)) {
1004                    handleNoCurrentRoute(reverseDirection, "Route already set to the destination point");  // NOI18N
1005                }
1006                src.pd.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE);
1007                point.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE);
1008                return;
1009            } else {
1010                LayoutBlock startlBlock = src.getStart();
1011                class BestPath {
1012
1013                    LayoutBlock srcProtecting = null;
1014                    LayoutBlock srcStart = null;
1015                    LayoutBlock destination = null;
1016
1017                    BestPath(LayoutBlock startPro, LayoutBlock sourceProtecting, LayoutBlock destinationBlock, List<LayoutBlock> blocks) {
1018                        srcStart = startPro;
1019                        srcProtecting = sourceProtecting;
1020                        destination = destinationBlock;
1021                        listOfBlocks = blocks;
1022                    }
1023
1024                    LayoutBlock getStartBlock() {
1025                        return srcStart;
1026                    }
1027
1028                    LayoutBlock getProtectingBlock() {
1029                        return srcProtecting;
1030                    }
1031
1032                    LayoutBlock getDestinationBlock() {
1033                        return destination;
1034                    }
1035
1036                    List<LayoutBlock> listOfBlocks = new ArrayList<>(0);
1037                    String errorMessage = "";
1038
1039                    List<LayoutBlock> getListOfBlocks() {
1040                        return listOfBlocks;
1041                    }
1042
1043                    void setErrorMessage(String msg) {
1044                        errorMessage = msg;
1045                    }
1046
1047                    String getErrorMessage() {
1048                        return errorMessage;
1049                    }
1050                }
1051                List<BestPath> pathList = new ArrayList<>(2);
1052                LayoutBlock protectLBlock;
1053                LayoutBlock destinationLBlock;
1054                //Need to work out around here the best one.
1055                for (LayoutBlock srcProLBlock : src.getSourceProtecting()) {
1056                    protectLBlock = srcProLBlock;
1057                    if (!reverseDirection) {
1058                        //We have a problem, the destination point is already setup with a route, therefore we would need to
1059                        //check some how that a route hasn't been set to it.
1060                        destinationLBlock = getFacing();
1061                        List<LayoutBlock> blocks = new ArrayList<>();
1062                        String errorMessage = null;
1063                        try {
1064                            blocks = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlockConnectivityTools().getLayoutBlocks(startlBlock, destinationLBlock, protectLBlock, false, LayoutBlockConnectivityTools.Routing.MASTTOMAST);
1065                        } catch (Exception e) {
1066                            errorMessage = e.getMessage();
1067                            //can be considered normal if no free route is found
1068                        }
1069                        BestPath toadd = new BestPath(startlBlock, protectLBlock, destinationLBlock, blocks);
1070                        toadd.setErrorMessage(errorMessage);
1071                        pathList.add(toadd);
1072                    } else {
1073                        // Handle reversed direction - Only used when Both Way is enabled.
1074                        // The controlling block references are flipped
1075                        startlBlock = point.getProtecting().get(0);
1076                        protectLBlock = point.getFacing();
1077
1078                        destinationLBlock = src.getSourceProtecting().get(0);
1079                        if (log.isDebugEnabled()) {
1080                            log.debug("reverse set destination is set going for {} {} {}", startlBlock.getDisplayName(), destinationLBlock.getDisplayName(), protectLBlock.getDisplayName());  // NOI18N
1081                        }
1082                        try {
1083                            LayoutBlock srcPro = src.getSourceProtecting().get(0);  //Don't care what block the facing is protecting
1084                            //Need to add a check for the lengths of the returned lists, then choose the most appropriate
1085                            if (!InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlockConnectivityTools().checkValidDest(startlBlock, protectLBlock, srcPro, src.getStart(), LayoutBlockConnectivityTools.Routing.SENSORTOSENSOR)) {
1086                                startlBlock = getFacing();
1087                                protectLBlock = srcProLBlock;
1088                                if (log.isDebugEnabled()) {
1089                                    log.debug("That didn't work so try  {} {} {}", startlBlock.getDisplayName(), destinationLBlock.getDisplayName(), protectLBlock.getDisplayName());  // NOI18N
1090                                }
1091                                if (!InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlockConnectivityTools().checkValidDest(startlBlock, protectLBlock, srcPro, src.getStart(), LayoutBlockConnectivityTools.Routing.SENSORTOSENSOR)) {
1092                                    log.error("No route found");  // NOI18N
1093                                    JmriJOptionPane.showMessageDialog(null, "No Valid path found");  // NOI18N
1094                                    src.pd.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE);
1095                                    point.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE);
1096                                    return;
1097                                } else {
1098                                    List<LayoutBlock> blocks = new ArrayList<>();
1099                                    String errorMessage = null;
1100                                    try {
1101                                        blocks = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlockConnectivityTools().getLayoutBlocks(startlBlock, destinationLBlock, protectLBlock, false, LayoutBlockConnectivityTools.Routing.MASTTOMAST);
1102                                    } catch (Exception e) {
1103                                        errorMessage = e.getMessage();
1104                                        //can be considered normal if no free route is found
1105                                    }
1106                                    BestPath toadd = new BestPath(startlBlock, protectLBlock, destinationLBlock, blocks);
1107                                    toadd.setErrorMessage(errorMessage);
1108                                    pathList.add(toadd);
1109                                }
1110                            } else if (InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlockConnectivityTools().checkValidDest(getFacing(), srcProLBlock, srcPro, src.getStart(), LayoutBlockConnectivityTools.Routing.SENSORTOSENSOR)) {
1111                                //Both paths are valid, so will go for setting the shortest
1112                                int distance = startlBlock.getBlockHopCount(destinationLBlock.getBlock(), protectLBlock.getBlock());
1113                                int distance2 = getFacing().getBlockHopCount(destinationLBlock.getBlock(), srcProLBlock.getBlock());
1114                                if (distance > distance2) {
1115                                    //The alternative route is shorter we shall use that
1116                                    startlBlock = getFacing();
1117                                    protectLBlock = srcProLBlock;
1118                                }
1119                                List<LayoutBlock> blocks = new ArrayList<>();
1120                                String errorMessage = "";
1121                                try {
1122                                    blocks = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlockConnectivityTools().getLayoutBlocks(startlBlock, destinationLBlock, protectLBlock, false, LayoutBlockConnectivityTools.Routing.NONE);
1123                                } catch (Exception e) {
1124                                    //can be considered normal if no free route is found
1125                                    errorMessage = e.getMessage();
1126                                }
1127                                BestPath toadd = new BestPath(startlBlock, protectLBlock, destinationLBlock, blocks);
1128                                toadd.setErrorMessage(errorMessage);
1129                                pathList.add(toadd);
1130                            } else {
1131                                List<LayoutBlock> blocks = new ArrayList<>();
1132                                String errorMessage = "";
1133                                try {
1134                                    blocks = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlockConnectivityTools().getLayoutBlocks(startlBlock, destinationLBlock, protectLBlock, false, LayoutBlockConnectivityTools.Routing.NONE);
1135                                } catch (Exception e) {
1136                                    //can be considered normal if no free route is found
1137                                    errorMessage = e.getMessage();
1138                                }
1139                                BestPath toadd = new BestPath(startlBlock, protectLBlock, destinationLBlock, blocks);
1140                                toadd.setErrorMessage(errorMessage);
1141                                pathList.add(toadd);
1142                            }
1143                        } catch (JmriException ex) {
1144                            log.error("Exception {}", ex.getMessage());  // NOI18N
1145                            if (showMessage) {
1146                                JmriJOptionPane.showMessageDialog(null, ex.getMessage());
1147                            }
1148                            src.pd.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE);
1149                            point.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE);
1150                            return;
1151                        }
1152                    }
1153                }
1154                if (pathList.isEmpty()) {
1155                    log.debug("Path list empty so exiting");  // NOI18N
1156                    return;
1157                }
1158                BestPath pathToUse = null;
1159                if (pathList.size() == 1) {
1160                    if (!pathList.get(0).getListOfBlocks().isEmpty()) {
1161                        pathToUse = pathList.get(0);
1162                    }
1163                } else {
1164                    /*Need to filter out the remaining routes, in theory this should only ever be two.
1165                     We simply pick at this stage the one with the least number of blocks as being preferred.
1166                     This could be expanded at some stage to look at either the length or the metric*/
1167                    int noOfBlocks = 0;
1168                    for (BestPath bp : pathList) {
1169                        if (!bp.getListOfBlocks().isEmpty()) {
1170                            if (noOfBlocks == 0 || bp.getListOfBlocks().size() < noOfBlocks) {
1171                                noOfBlocks = bp.getListOfBlocks().size();
1172                                pathToUse = bp;
1173                            }
1174                        }
1175                    }
1176                }
1177                if (pathToUse == null) {
1178                    //No valid paths found so will quit
1179                    if (pathList.get(0).getListOfBlocks().isEmpty()) {
1180                        if (showMessage) {
1181                            //Considered normal if not a valid through path, provide an option to stack
1182                            handleNoCurrentRoute(reverseDirection, pathList.get(0).getErrorMessage());
1183                            src.pd.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE);
1184                            point.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE);
1185                        }
1186                        return;
1187                    }
1188                    pathToUse = pathList.get(0);
1189                }
1190                startlBlock = pathToUse.getStartBlock();
1191                protectLBlock = pathToUse.getProtectingBlock();
1192                destinationLBlock = pathToUse.getDestinationBlock();
1193                routeDetails = pathToUse.getListOfBlocks();
1194
1195                synchronized (this) {
1196                    destination = destinationLBlock;
1197                }
1198
1199                if (log.isDebugEnabled()) {
1200                    log.debug("[activeBean] Path chosen start = {}, dest = {}, protect = {}", startlBlock.getDisplayName(),  // NOI18N
1201                            destinationLBlock.getDisplayName(), protectLBlock.getDisplayName());
1202                    for (LayoutBlock blk : routeDetails) {
1203                        log.debug("  block {}", blk.getDisplayName());
1204                    }
1205                }
1206
1207                if (getEntryExitType() == EntryExitPairs.FULLINTERLOCK) {
1208                    setActiveEntryExit(true, reverseDirection);
1209                }
1210
1211                log.debug("[activeBean] Start setRoute thread, dp = {}", getUserName());
1212                ThreadingUtil.newThread(() -> {
1213                    try {
1214                        setRoute(true);
1215                    } catch (Exception e) {
1216                        log.error("[activeBean] setRoute thread exception: {}", e.getMessage());
1217                    }
1218                }).start();
1219            }
1220        }
1221    }
1222
1223    void handleNoCurrentRoute(boolean reverse, String message) {
1224        int opt = manager.getOverlapOption();
1225
1226        if (opt == EntryExitPairs.PROMPTUSER) {
1227            Object[] options = {
1228                    Bundle.getMessage("ButtonYes"),  // NOI18N
1229                    Bundle.getMessage("ButtonNo")};  // NOI18N
1230            int ans = JmriJOptionPane.showOptionDialog(null,
1231                    message + "\n" + Bundle.getMessage("StackRouteAsk"), Bundle.getMessage("RouteNotClear"), // NOI18N
1232                    JmriJOptionPane.DEFAULT_OPTION,
1233                    JmriJOptionPane.QUESTION_MESSAGE,
1234                    null,
1235                    options,
1236                    options[1]);
1237            if (ans == 0) { // array position 0 Yes
1238                opt = EntryExitPairs.OVERLAP_STACK;
1239            } else { // array position 1 or Dialog closed
1240                opt = EntryExitPairs.OVERLAP_CANCEL;
1241            }
1242        }
1243
1244        if (opt == EntryExitPairs.OVERLAP_STACK) {
1245            manager.stackNXRoute(this, reverse);
1246            firePropertyChange("stacked", null, null);  // NOI18N
1247        } else {
1248            firePropertyChange("failed", null, null);  // NOI18N
1249        }
1250
1251        // Set memory value if requested
1252        MemoryManager mgr = InstanceManager.getDefault(MemoryManager.class);
1253        Memory nxMem = mgr.getMemory(manager.getMemoryOption());
1254        if (nxMem != null) {
1255            String optString = (opt == EntryExitPairs.OVERLAP_STACK)
1256                    ? Bundle.getMessage("StackRoute")       // NOI18N
1257                    : Bundle.getMessage("CancelRoute");     // NOI18N
1258            nxMem.setValue(Bundle.getMessage("MemoryMessage", message, optString));  // NOI18N
1259
1260            // Check for auto memory clear delay
1261            int delay = manager.getMemoryClearDelay() * 1000;
1262            if (delay > 0) {
1263                javax.swing.Timer memoryClear = new javax.swing.Timer(delay, new java.awt.event.ActionListener() {
1264                    @Override
1265                    public void actionPerformed(java.awt.event.ActionEvent e) {
1266                        nxMem.setValue("");
1267                    }
1268                });
1269                memoryClear.setRepeats(false);
1270                memoryClear.start();
1271            }
1272        }
1273    }
1274
1275    @Override
1276    public void dispose() {
1277        enabled = false;
1278        setActiveEntryExit(false);
1279        cancelClearInterlock(EntryExitPairs.CANCELROUTE);
1280        setRouteFrom(false);
1281        setRouteTo(false);
1282        point.removeDestination(this);
1283        synchronized (this) {
1284            lastSeenActiveBlockObject = null;
1285        }
1286        disposed = true;
1287        super.dispose();
1288    }
1289
1290    @Override
1291    public int getState() {
1292        if (activeEntryExit) {
1293            return 0x02;
1294        }
1295        return 0x04;
1296    }
1297
1298    public boolean isActive() {
1299        return activeEntryExit;
1300    }
1301
1302    public boolean isReversed() {
1303        return activeEntryExitReversed;
1304    }
1305
1306    public boolean isUniDirection() {
1307        return uniDirection;
1308    }
1309
1310    @Override
1311    public void setState(int state) {
1312    }
1313
1314    protected void setActiveEntryExit(boolean boo) {
1315        setActiveEntryExit(boo, false);
1316    }
1317
1318    protected void setActiveEntryExit(boolean boo, boolean reversed) {
1319        int oldvalue = getState();
1320        activeEntryExit = boo;
1321        activeEntryExitReversed = reversed;
1322        src.setMenuEnabled(boo);
1323        firePropertyChange("active", oldvalue, getState());  // NOI18N
1324    }
1325
1326    @Override
1327    public List<NamedBeanUsageReport> getUsageReport(NamedBean bean) {
1328        List<NamedBeanUsageReport> report = new ArrayList<>();
1329        if (bean != null) {
1330            if (bean.equals(getSource().getPoint().getSensor())) {
1331                report.add(new NamedBeanUsageReport("EntryExitSourceSensor"));  // NOI18N
1332            }
1333            if (bean.equals(getSource().getPoint().getSignal())) {
1334                report.add(new NamedBeanUsageReport("EntryExitSourceSignal"));  // NOI18N
1335            }
1336            if (bean.equals(getDestPoint().getSensor())) {
1337                report.add(new NamedBeanUsageReport("EntryExitDestinationSensor"));  // NOI18N
1338            }
1339            if (bean.equals(getDestPoint().getSignal())) {
1340                report.add(new NamedBeanUsageReport("EntryExitDesinationSignal"));  // NOI18N
1341            }
1342        }
1343        return report;
1344    }
1345
1346    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DestinationPoints.class);
1347
1348}