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