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}