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