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