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