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