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    static final 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        LayoutBlock facing = getSource().getStart();
848        for (LayoutBlock blk : routeDetails) {
849            if (blk == facing) {
850                // Skip the facing block if it is still in the block list.
851                continue;
852            }
853            if ((getEntryExitType() == EntryExitPairs.FULLINTERLOCK)) {
854                blk.setUseExtraColor(false);
855            }
856            blk.getBlock().removePropertyChangeListener(propertyBlockListener); // was set against occupancy sensor
857        }
858
859        if (cancelClear == EntryExitPairs.CLEARROUTE) {
860            if (routeDetails.isEmpty()) {
861                if (log.isDebugEnabled()) {
862                    log.debug("{}  all blocks have automatically been cleared down", getUserName());  // NOI18N
863                }
864            } else {
865                if (log.isDebugEnabled()) {
866                    log.debug("{}  No blocks were cleared down {}", getUserName(), routeDetails.size());  // NOI18N
867                }
868                try {
869                    if (log.isDebugEnabled()) {
870                        log.debug("{}  set first block as active so that we can manually clear this down {}", getUserName(), routeDetails.get(0).getBlock().getUserName());  // NOI18N
871                    }
872                    if (routeDetails.get(0).getOccupancySensor() != null) {
873                        routeDetails.get(0).getOccupancySensor().setState(Sensor.ACTIVE);
874                    } else {
875                        routeDetails.get(0).getBlock().goingActive();
876                    }
877
878                    if (src.getStart().getOccupancySensor() != null) {
879                        src.getStart().getOccupancySensor().setState(Sensor.INACTIVE);
880                    } else {
881                        src.getStart().getBlock().goingInactive();
882                    }
883                } catch (java.lang.NullPointerException e) {
884                    log.error("error in clear route A", e);  // NOI18N
885                } catch (JmriException e) {
886                    log.error("error in clear route A", e);  // NOI18N
887                }
888                if (log.isDebugEnabled()) {
889                    log.debug("{}  Going to clear routeDetails down {}", getUserName(), routeDetails.size());  // NOI18N
890                    for (int i = 0; i < routeDetails.size(); i++) {
891                        log.debug("Block at {} {}", i, routeDetails.get(i).getDisplayName());
892                    }
893                }
894                if (routeDetails.size() > 1) {
895                    //We will remove the propertychange listeners on the sensors as we will now manually clear things down.
896                    //Should we just be usrc.pdating the block status and not the sensor
897                    for (int i = 1; i < routeDetails.size() - 1; i++) {
898                        if (log.isDebugEnabled()) {
899                            log.debug("{} in loop Set active {} {}", getUserName(), routeDetails.get(i).getDisplayName(), routeDetails.get(i).getBlock().getSystemName());  // NOI18N
900                        }
901                        try {
902                            if (routeDetails.get(i).getOccupancySensor() != null) {
903                                routeDetails.get(i).getOccupancySensor().setState(Sensor.ACTIVE);
904                            } else {
905                                routeDetails.get(i).getBlock().goingActive();
906                            }
907
908                            if (log.isDebugEnabled()) {
909                                log.debug("{} in loop Set inactive {} {}", getUserName(), routeDetails.get(i - 1).getDisplayName(), routeDetails.get(i - 1).getBlock().getSystemName());  // NOI18N
910                            }
911                            if (routeDetails.get(i - 1).getOccupancySensor() != null) {
912                                routeDetails.get(i - 1).getOccupancySensor().setState(Sensor.INACTIVE);
913                            } else {
914                                routeDetails.get(i - 1).getBlock().goingInactive();
915                            }
916                        } catch (NullPointerException | JmriException e) {
917                            log.error("error in clear route b ", e);  // NOI18N
918                        }
919                        // NOI18N
920
921                    }
922                    try {
923                        if (log.isDebugEnabled()) {
924                            log.debug("{} out of loop Set active {} {}", getUserName(), routeDetails.get(routeDetails.size() - 1).getDisplayName(), routeDetails.get(routeDetails.size() - 1).getBlock().getSystemName());  // NOI18N
925                        }
926                        //Get the last block an set it active.
927                        if (routeDetails.get(routeDetails.size() - 1).getOccupancySensor() != null) {
928                            routeDetails.get(routeDetails.size() - 1).getOccupancySensor().setState(Sensor.ACTIVE);
929                        } else {
930                            routeDetails.get(routeDetails.size() - 1).getBlock().goingActive();
931                        }
932                        if (log.isDebugEnabled()) {
933                            log.debug("{} out of loop Set inactive {} {}", getUserName(), routeDetails.get(routeDetails.size() - 2).getUserName(), routeDetails.get(routeDetails.size() - 2).getBlock().getSystemName());  // NOI18N
934                        }
935                        if (routeDetails.get(routeDetails.size() - 2).getOccupancySensor() != null) {
936                            routeDetails.get(routeDetails.size() - 2).getOccupancySensor().setState(Sensor.INACTIVE);
937                        } else {
938                            routeDetails.get(routeDetails.size() - 2).getBlock().goingInactive();
939                        }
940                    } catch (java.lang.NullPointerException e) {
941                        log.error("error in clear route c", e);  // NOI18N
942                    } catch (java.lang.ArrayIndexOutOfBoundsException e) {
943                        log.error("error in clear route c", e);  // NOI18N
944                    } catch (JmriException e) {
945                        log.error("error in clear route c", e);  // NOI18N
946                    }
947                }
948            }
949        }
950        setActiveEntryExit(false);
951        setRouteFrom(false);
952        setRouteTo(false);
953        routeDetails = null;
954        synchronized (this) {
955            lastSeenActiveBlockObject = null;
956        }
957        src.pd.cancelNXButtonTimeOut();
958        point.cancelNXButtonTimeOut();
959        src.getPoint().getPanel().getGlassPane().setVisible(false);
960
961    }
962
963    public void setInterlockRoute(boolean reverseDirection) {
964        if (activeEntryExit) {
965            return;
966        }
967        activeBean(reverseDirection, false);
968    }
969
970    void activeBean(boolean reverseDirection) {
971        activeBean(reverseDirection, true);
972    }
973
974    synchronized void activeBean(boolean reverseDirection, boolean showMessage) {
975        // Clear any previous memory message
976        MemoryManager mgr = InstanceManager.getDefault(MemoryManager.class);
977        Memory nxMem = mgr.getMemory(manager.getMemoryOption());
978        if (nxMem != null) {
979            nxMem.setValue("");
980        }
981
982        if (!isEnabled()) {
983            JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("RouteDisabled", getDisplayName()));  // NOI18N
984            src.pd.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE);
985            point.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE);
986            return;
987        }
988        if (activeEntryExit) {
989            // 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);
990            if (!isEnabled()) {
991                log.debug("A disabled entry exit has been called will bomb out");  // NOI18N
992                return;
993            }
994            log.debug("{}  We have a valid match on our end point so we can clear down", getUserName());  // NOI18N
995            //setRouteTo(false);
996            //src.pd.setRouteFrom(false);
997            setRoute(false);
998        } else {
999            if (isRouteToPointSet()) {
1000                log.debug("{}  route to this point is set therefore can not set another to it ", getUserName());  // NOI18N
1001                if (showMessage && !manager.isRouteStacked(this, false)) {
1002                    handleNoCurrentRoute(reverseDirection, "Route already set to the destination point");  // NOI18N
1003                }
1004                src.pd.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE);
1005                point.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE);
1006                return;
1007            } else {
1008                LayoutBlock startlBlock = src.getStart();
1009
1010                List<BestPath> pathList = new ArrayList<>(2);
1011                LayoutBlock protectLBlock;
1012                LayoutBlock destinationLBlock;
1013                //Need to work out around here the best one.
1014                for (LayoutBlock srcProLBlock : src.getSourceProtecting()) {
1015                    protectLBlock = srcProLBlock;
1016                    if (!reverseDirection) {
1017                        //We have a problem, the destination point is already setup with a route, therefore we would need to
1018                        //check some how that a route hasn't been set to it.
1019                        destinationLBlock = getFacing();
1020                        List<LayoutBlock> blocks = new ArrayList<>();
1021                        String errorMessage = null;
1022                        try {
1023                            blocks = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlockConnectivityTools().getLayoutBlocks(startlBlock, destinationLBlock, protectLBlock, false, LayoutBlockConnectivityTools.Routing.MASTTOMAST);
1024                        } catch (Exception e) {
1025                            errorMessage = e.getMessage();
1026                            //can be considered normal if no free route is found
1027                        }
1028                        BestPath toadd = new BestPath(startlBlock, protectLBlock, destinationLBlock, blocks);
1029                        toadd.setErrorMessage(errorMessage);
1030                        pathList.add(toadd);
1031                    } else {
1032                        // Handle reversed direction - Only used when Both Way is enabled.
1033                        // The controlling block references are flipped
1034                        startlBlock = point.getProtecting().get(0);
1035                        protectLBlock = point.getFacing();
1036
1037                        destinationLBlock = src.getSourceProtecting().get(0);
1038                        if (log.isDebugEnabled()) {
1039                            log.debug("reverse set destination is set going for {} {} {}", startlBlock.getDisplayName(), destinationLBlock.getDisplayName(), protectLBlock.getDisplayName());  // NOI18N
1040                        }
1041                        try {
1042                            LayoutBlock srcPro = src.getSourceProtecting().get(0);  //Don't care what block the facing is protecting
1043                            //Need to add a check for the lengths of the returned lists, then choose the most appropriate
1044                            if (!InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlockConnectivityTools().checkValidDest(startlBlock, protectLBlock, srcPro, src.getStart(), LayoutBlockConnectivityTools.Routing.SENSORTOSENSOR)) {
1045                                startlBlock = getFacing();
1046                                protectLBlock = srcProLBlock;
1047                                if (log.isDebugEnabled()) {
1048                                    log.debug("That didn't work so try  {} {} {}", startlBlock.getDisplayName(), destinationLBlock.getDisplayName(), protectLBlock.getDisplayName());  // NOI18N
1049                                }
1050                                if (!InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlockConnectivityTools().checkValidDest(startlBlock, protectLBlock, srcPro, src.getStart(), LayoutBlockConnectivityTools.Routing.SENSORTOSENSOR)) {
1051                                    log.error("No route found");  // NOI18N
1052                                    JmriJOptionPane.showMessageDialog(null, "No Valid path found");  // NOI18N
1053                                    src.pd.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE);
1054                                    point.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE);
1055                                    return;
1056                                } else {
1057                                    List<LayoutBlock> blocks = new ArrayList<>();
1058                                    String errorMessage = null;
1059                                    try {
1060                                        blocks = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlockConnectivityTools().getLayoutBlocks(startlBlock, destinationLBlock, protectLBlock, false, LayoutBlockConnectivityTools.Routing.MASTTOMAST);
1061                                    } catch (Exception e) {
1062                                        errorMessage = e.getMessage();
1063                                        //can be considered normal if no free route is found
1064                                    }
1065                                    BestPath toadd = new BestPath(startlBlock, protectLBlock, destinationLBlock, blocks);
1066                                    toadd.setErrorMessage(errorMessage);
1067                                    pathList.add(toadd);
1068                                }
1069                            } else if (InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlockConnectivityTools().checkValidDest(getFacing(), srcProLBlock, srcPro, src.getStart(), LayoutBlockConnectivityTools.Routing.SENSORTOSENSOR)) {
1070                                //Both paths are valid, so will go for setting the shortest
1071                                int distance = startlBlock.getBlockHopCount(destinationLBlock.getBlock(), protectLBlock.getBlock());
1072                                int distance2 = getFacing().getBlockHopCount(destinationLBlock.getBlock(), srcProLBlock.getBlock());
1073                                if (distance > distance2) {
1074                                    //The alternative route is shorter we shall use that
1075                                    startlBlock = getFacing();
1076                                    protectLBlock = srcProLBlock;
1077                                }
1078                                List<LayoutBlock> blocks = new ArrayList<>();
1079                                String errorMessage = "";
1080                                try {
1081                                    blocks = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlockConnectivityTools().getLayoutBlocks(startlBlock, destinationLBlock, protectLBlock, false, LayoutBlockConnectivityTools.Routing.NONE);
1082                                } catch (Exception e) {
1083                                    //can be considered normal if no free route is found
1084                                    errorMessage = e.getMessage();
1085                                }
1086                                BestPath toadd = new BestPath(startlBlock, protectLBlock, destinationLBlock, blocks);
1087                                toadd.setErrorMessage(errorMessage);
1088                                pathList.add(toadd);
1089                            } else {
1090                                List<LayoutBlock> blocks = new ArrayList<>();
1091                                String errorMessage = "";
1092                                try {
1093                                    blocks = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlockConnectivityTools().getLayoutBlocks(startlBlock, destinationLBlock, protectLBlock, false, LayoutBlockConnectivityTools.Routing.NONE);
1094                                } catch (Exception e) {
1095                                    //can be considered normal if no free route is found
1096                                    errorMessage = e.getMessage();
1097                                }
1098                                BestPath toadd = new BestPath(startlBlock, protectLBlock, destinationLBlock, blocks);
1099                                toadd.setErrorMessage(errorMessage);
1100                                pathList.add(toadd);
1101                            }
1102                        } catch (JmriException ex) {
1103                            log.error("Exception {}", ex.getMessage());  // NOI18N
1104                            if (showMessage) {
1105                                JmriJOptionPane.showMessageDialog(null, ex.getMessage());
1106                            }
1107                            src.pd.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE);
1108                            point.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE);
1109                            return;
1110                        }
1111                    }
1112                }
1113                if (pathList.isEmpty()) {
1114                    log.debug("Path list empty so exiting");  // NOI18N
1115                    return;
1116                }
1117                BestPath pathToUse = null;
1118                if (pathList.size() == 1) {
1119                    if (!pathList.get(0).getListOfBlocks().isEmpty()) {
1120                        pathToUse = pathList.get(0);
1121                    }
1122                } else {
1123                    /*Need to filter out the remaining routes, in theory this should only ever be two.
1124                     We simply pick at this stage the one with the least number of blocks as being preferred.
1125                     This could be expanded at some stage to look at either the length or the metric*/
1126                    int noOfBlocks = 0;
1127                    for (BestPath bp : pathList) {
1128                        if (!bp.getListOfBlocks().isEmpty()) {
1129                            if (noOfBlocks == 0 || bp.getListOfBlocks().size() < noOfBlocks) {
1130                                noOfBlocks = bp.getListOfBlocks().size();
1131                                pathToUse = bp;
1132                            }
1133                        }
1134                    }
1135                }
1136                if (pathToUse == null) {
1137                    //No valid paths found so will quit
1138                    if (pathList.get(0).getListOfBlocks().isEmpty()) {
1139                        if (showMessage) {
1140                            //Considered normal if not a valid through path, provide an option to stack
1141                            handleNoCurrentRoute(reverseDirection, pathList.get(0).getErrorMessage());
1142                            src.pd.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE);
1143                            point.setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE);
1144                        }
1145                        return;
1146                    }
1147                    pathToUse = pathList.get(0);
1148                }
1149                startlBlock = pathToUse.getStartBlock();
1150                protectLBlock = pathToUse.getProtectingBlock();
1151                destinationLBlock = pathToUse.getDestinationBlock();
1152                routeDetails = pathToUse.getListOfBlocks();
1153
1154                synchronized (this) {
1155                    destination = destinationLBlock;
1156                }
1157
1158                if (log.isDebugEnabled()) {
1159                    log.debug("[activeBean] Path chosen start = {}, dest = {}, protect = {}", startlBlock.getDisplayName(),  // NOI18N
1160                            destinationLBlock.getDisplayName(), protectLBlock.getDisplayName());
1161                    for (LayoutBlock blk : routeDetails) {
1162                        log.debug("  block {}", blk.getDisplayName());
1163                    }
1164                }
1165
1166                if (getEntryExitType() == EntryExitPairs.FULLINTERLOCK) {
1167                    setActiveEntryExit(true, reverseDirection);
1168                }
1169
1170                if (manager.isSkipGuiFix()) {
1171                    log.debug("[activeBean] Start setRoute without thread, dp = {}", getUserName());
1172                    setRoute(true);
1173                } else {
1174                    log.debug("[activeBean] Start setRoute thread, dp = {}", getUserName());
1175                    ThreadingUtil.newThread(() -> {
1176                        try {
1177                            setRoute(true);
1178                        } catch (Exception e) {
1179                            log.error("[activeBean] setRoute thread exception: {}", e.getMessage());
1180                        }
1181                    }, "NX set route thread").start();
1182                }
1183            }
1184        }
1185    }
1186
1187    private static class BestPath {
1188
1189        LayoutBlock srcProtecting = null;
1190        LayoutBlock srcStart = null;
1191        LayoutBlock destination = null;
1192
1193        BestPath(LayoutBlock startPro, LayoutBlock sourceProtecting, LayoutBlock destinationBlock, List<LayoutBlock> blocks) {
1194            srcStart = startPro;
1195            srcProtecting = sourceProtecting;
1196            destination = destinationBlock;
1197            listOfBlocks = blocks;
1198        }
1199
1200        LayoutBlock getStartBlock() {
1201            return srcStart;
1202        }
1203
1204        LayoutBlock getProtectingBlock() {
1205            return srcProtecting;
1206        }
1207
1208        LayoutBlock getDestinationBlock() {
1209            return destination;
1210        }
1211
1212        List<LayoutBlock> listOfBlocks = new ArrayList<>(0);
1213        String errorMessage = "";
1214
1215        List<LayoutBlock> getListOfBlocks() {
1216            return listOfBlocks;
1217        }
1218
1219        void setErrorMessage(String msg) {
1220            errorMessage = msg;
1221        }
1222
1223        String getErrorMessage() {
1224            return errorMessage;
1225        }
1226    }
1227
1228    void handleNoCurrentRoute(boolean reverse, String message) {
1229        int opt = manager.getOverlapOption();
1230
1231        if (opt == EntryExitPairs.PROMPTUSER) {
1232            Object[] options = {
1233                    Bundle.getMessage("ButtonYes"),  // NOI18N
1234                    Bundle.getMessage("ButtonNo")};  // NOI18N
1235            int ans = JmriJOptionPane.showOptionDialog(null,
1236                    message + "\n" + Bundle.getMessage("StackRouteAsk"), Bundle.getMessage("RouteNotClear"), // NOI18N
1237                    JmriJOptionPane.DEFAULT_OPTION,
1238                    JmriJOptionPane.QUESTION_MESSAGE,
1239                    null,
1240                    options,
1241                    options[1]);
1242            if (ans == 0) { // array position 0 Yes
1243                opt = EntryExitPairs.OVERLAP_STACK;
1244            } else { // array position 1 or Dialog closed
1245                opt = EntryExitPairs.OVERLAP_CANCEL;
1246            }
1247        }
1248
1249        if (opt == EntryExitPairs.OVERLAP_STACK) {
1250            manager.stackNXRoute(this, reverse);
1251            firePropertyChange(PROPERTY_STACKED, null, null);
1252        } else {
1253            firePropertyChange(PROPERTY_FAILED, null, null);
1254        }
1255
1256        // Set memory value if requested
1257        MemoryManager mgr = InstanceManager.getDefault(MemoryManager.class);
1258        Memory nxMem = mgr.getMemory(manager.getMemoryOption());
1259        if (nxMem != null) {
1260            String optString = (opt == EntryExitPairs.OVERLAP_STACK)
1261                    ? Bundle.getMessage("StackRoute")       // NOI18N
1262                    : Bundle.getMessage("CancelRoute");     // NOI18N
1263            nxMem.setValue(Bundle.getMessage("MemoryMessage", message, optString));  // NOI18N
1264
1265            // Check for auto memory clear delay
1266            int delay = manager.getMemoryClearDelay() * 1000;
1267            if (delay > 0) {
1268                javax.swing.Timer memoryClear = new javax.swing.Timer(delay, new java.awt.event.ActionListener() {
1269                    @Override
1270                    public void actionPerformed(java.awt.event.ActionEvent e) {
1271                        nxMem.setValue("");
1272                    }
1273                });
1274                memoryClear.setRepeats(false);
1275                memoryClear.start();
1276            }
1277        }
1278    }
1279
1280    @Override
1281    public void dispose() {
1282        enabled = false;
1283        setActiveEntryExit(false);
1284        cancelClearInterlock(EntryExitPairs.CANCELROUTE);
1285        setRouteFrom(false);
1286        setRouteTo(false);
1287        point.removeDestination(this);
1288        synchronized (this) {
1289            lastSeenActiveBlockObject = null;
1290        }
1291        disposed = true;
1292        super.dispose();
1293    }
1294
1295    @Override
1296    public int getState() {
1297        if (activeEntryExit) {
1298            return 0x02;
1299        }
1300        return 0x04;
1301    }
1302
1303    public boolean isActive() {
1304        return activeEntryExit;
1305    }
1306
1307    public boolean isReversed() {
1308        return activeEntryExitReversed;
1309    }
1310
1311    public boolean isUniDirection() {
1312        return uniDirection;
1313    }
1314
1315    @Override
1316    public void setState(int state) {
1317    }
1318
1319    protected void setActiveEntryExit(boolean boo) {
1320        setActiveEntryExit(boo, false);
1321    }
1322
1323    protected void setActiveEntryExit(boolean boo, boolean reversed) {
1324        int oldvalue = getState();
1325        activeEntryExit = boo;
1326        activeEntryExitReversed = reversed;
1327        src.setMenuEnabled(boo);
1328        firePropertyChange(PROPERTY_ACTIVE, oldvalue, getState());
1329    }
1330
1331    @Override
1332    public List<NamedBeanUsageReport> getUsageReport(NamedBean bean) {
1333        List<NamedBeanUsageReport> report = new ArrayList<>();
1334        if (bean != null) {
1335            if (bean.equals(getSource().getPoint().getSensor())) {
1336                report.add(new NamedBeanUsageReport("EntryExitSourceSensor"));  // NOI18N
1337            }
1338            if (bean.equals(getSource().getPoint().getSignal())) {
1339                report.add(new NamedBeanUsageReport("EntryExitSourceSignal"));  // NOI18N
1340            }
1341            if (bean.equals(getDestPoint().getSensor())) {
1342                report.add(new NamedBeanUsageReport("EntryExitDestinationSensor"));  // NOI18N
1343            }
1344            if (bean.equals(getDestPoint().getSignal())) {
1345                report.add(new NamedBeanUsageReport("EntryExitDesinationSignal"));  // NOI18N
1346            }
1347        }
1348        return report;
1349    }
1350
1351    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DestinationPoints.class);
1352
1353}