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