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