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