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