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