001package jmri.jmrit.display.layoutEditor; 002 003import java.util.ArrayList; 004import java.util.List; 005import java.util.Set; 006import javax.annotation.CheckForNull; 007import javax.annotation.CheckReturnValue; 008import javax.annotation.Nonnull; 009 010import jmri.Block; 011import jmri.BlockManager; 012import jmri.jmrit.display.EditorManager; 013import jmri.InstanceManager; 014import jmri.JmriException; 015import jmri.Memory; 016import jmri.NamedBean; 017import jmri.NamedBeanHandle; 018import jmri.Sensor; 019import jmri.SignalHead; 020import jmri.SignalMast; 021import jmri.Turnout; 022import jmri.jmrit.roster.RosterEntry; 023import jmri.jmrix.internal.InternalSystemConnectionMemo; 024import jmri.managers.AbstractManager; 025import jmri.util.swing.JmriJOptionPane; 026import jmri.util.ThreadingUtil; 027 028/** 029 * Implementation of a Manager to handle LayoutBlocks. Note: the same 030 * LayoutBlocks may appear in multiple LayoutEditor panels. 031 * <p> 032 * This manager does not enforce any particular system naming convention. 033 * <p> 034 * LayoutBlocks are usually addressed by userName. The systemName is hidden from 035 * the user for the most part. 036 * 037 * @author Dave Duchamp Copyright (C) 2007 038 * @author George Warner Copyright (c) 2017-2018 039 */ 040public class LayoutBlockManager extends AbstractManager<LayoutBlock> implements jmri.InstanceManagerAutoDefault { 041 042 public LayoutBlockManager() { 043 super(InstanceManager.getDefault(InternalSystemConnectionMemo.class)); 044 InstanceManager.sensorManagerInstance().addVetoableChangeListener(LayoutBlockManager.this); 045 InstanceManager.memoryManagerInstance().addVetoableChangeListener(LayoutBlockManager.this); 046 } 047 048 /** 049 * String constant for advanced routing enabled. 050 */ 051 public static final String PROPERTY_ADVANCED_ROUTING_ENABLED = "advancedRoutingEnabled"; 052 053 /** 054 * String constant for the topology property. 055 */ 056 public static final String PROPERTY_TOPOLOGY = "topology"; 057 058 @Override 059 public int getXMLOrder() { 060 return jmri.Manager.LAYOUTBLOCKS; 061 } 062 063 @Override 064 public char typeLetter() { 065 return 'B'; 066 } 067 private int blkNum = 1; 068 069 /** 070 * Create a new LayoutBlock if the LayoutBlock does not exist. 071 * <p> 072 * Note that since the userName is used to address LayoutBlocks, the user 073 * name must be present. If the user name is not present, the new 074 * LayoutBlock is not created, and null is returned. 075 * 076 * @param systemName block system name. 077 * @param userName block username, must be non-empty. 078 * @return null if a LayoutBlock with the same systemName or userName 079 * already exists, or if there is trouble creating a new LayoutBlock 080 */ 081 @CheckReturnValue 082 @CheckForNull 083 public LayoutBlock createNewLayoutBlock( 084 @CheckForNull String systemName, 085 String userName) { 086 // Check that LayoutBlock does not already exist 087 LayoutBlock result; 088 089 if ((userName == null) || userName.isEmpty()) { 090 log.error("Attempt to create a LayoutBlock with no user name"); 091 092 return null; 093 } 094 result = getByUserName(userName); 095 096 if (result != null) { 097 return null; 098 } 099 100 // here if not found under user name 101 String sName = ""; 102 103 if (systemName == null) { 104 //create a new unique system name 105 boolean found = true; 106 107 while (found) { 108 sName = "ILB" + blkNum; 109 blkNum++; 110 result = getBySystemName(sName); 111 112 if (result == null) { 113 found = false; 114 } 115 } 116 } else { 117 // try the supplied system name 118 result = getBySystemName((systemName)); 119 120 if (result != null) { 121 return null; 122 } 123 sName = systemName; 124 } 125 126 // LayoutBlock does not exist, create a new LayoutBlock 127 result = new LayoutBlock(sName, userName); 128 129 //save in the maps 130 register(result); 131 132 return result; 133 } 134 135 @CheckReturnValue 136 @CheckForNull 137 public LayoutBlock createNewLayoutBlock() { 138 while (true) { 139 String sName = "ILB" + blkNum; 140 LayoutBlock block = getBySystemName(sName); 141 142 if (block == null) { 143 String uName = "AUTOBLK:" + blkNum; 144 block = new LayoutBlock(sName, uName); 145 register(block); 146 147 return block; 148 } 149 blkNum++; 150 } 151 } 152 153 /** 154 * Remove an existing LayoutBlock. 155 * @param block the block to remove. 156 */ 157 public void deleteLayoutBlock(LayoutBlock block) { 158 layoutBlockByBlock.values().remove(block); 159 deregister(block); 160 } 161 162 /** 163 * Get an existing LayoutBlock. First looks up assuming that name is a User 164 * Name. If this fails, looks up assuming that name is a System Name. 165 * 166 * @param name ideally block username, can be system name. 167 * @return LayoutBlock, or null if not found by either user name or system 168 * name 169 */ 170 @CheckReturnValue 171 @CheckForNull 172 public LayoutBlock getLayoutBlock(@Nonnull String name) { 173 LayoutBlock block = getByUserName(name); 174 175 if (block != null) { 176 return block; 177 } 178 return getBySystemName(name); 179 } 180 181 // LayoutBlock found for each Block, checked on every use as either may change 182 private final java.util.Map<Block, LayoutBlock> layoutBlockByBlock = new java.util.concurrent.ConcurrentHashMap<>(); 183 184 @CheckReturnValue 185 @CheckForNull 186 public LayoutBlock getLayoutBlock(@CheckForNull Block block) { 187 if (block != null) { 188 LayoutBlock found = layoutBlockByBlock.get(block); 189 if ((found != null) && (found.getBlock() == block) 190 && (getBySystemName(found.getSystemName()) == found)) { 191 return found; 192 } 193 } 194 for (LayoutBlock lb : getNamedBeanSet()) { 195 if (lb.getBlock() == block) { 196 if (block != null) { 197 layoutBlockByBlock.put(block, lb); 198 } 199 return lb; 200 } 201 } 202 if (block != null) { 203 layoutBlockByBlock.remove(block); 204 } 205 return null; 206 } 207 208 /** 209 * Find a LayoutBlock with a specified Sensor assigned as its occupancy 210 * sensor. 211 * 212 * @param s the sensor to search for. 213 * @return the block or null if no existing LayoutBlock has the Sensor 214 * assigned 215 */ 216 @CheckReturnValue 217 @CheckForNull 218 public LayoutBlock getBlockWithSensorAssigned(@CheckForNull Sensor s) { 219 for (LayoutBlock block : getNamedBeanSet()) { 220 if (block.getOccupancySensor() == s) { 221 return block; 222 } 223 } 224 return null; 225 } 226 227 /** 228 * Find a LayoutBlock with a specified Memory assigned as its value display. 229 * 230 * @param m the memory to search for. 231 * @return the block or null if no existing LayoutBlock has the memory 232 * assigned. 233 */ 234 @CheckReturnValue 235 @CheckForNull 236 public LayoutBlock getBlockWithMemoryAssigned(Memory m) { 237 for (LayoutBlock block : getNamedBeanSet()) { 238 if (block.getMemory() == m) { 239 return block; 240 } 241 } 242 return null; 243 } 244 245 /** 246 * Initialize/check the Paths of all Blocks associated with LayoutBlocks. 247 * <p> 248 * This routine should be called when loading panels, after all Layout 249 * Editor panels have been loaded. 250 */ 251 public void initializeLayoutBlockPaths() { 252 log.debug("start initializeLayoutBlockPaths"); 253 254 log.debug("start initializeLayoutBlockPaths getNamedBeanSet {}", getNamedBeanSet()); 255 256 // cycle through all LayoutBlocks, completing initialization of associated jmri.Blocks 257 for (LayoutBlock b : getNamedBeanSet()) { 258 log.debug("Calling block '{}({})'.initializeLayoutBlock()", b.getSystemName(), b.getDisplayName()); 259 b.initializeLayoutBlock(); 260 } 261 262 //cycle through all LayoutBlocks, updating Paths of associated jmri.Blocks 263 badBeanErrors = 0; // perhaps incremented via addBadBeanError(), but that's never called? 264 for (LayoutBlock b : getNamedBeanSet()) { 265 log.debug("Calling block '{}({})'.updatePaths()", b.getSystemName(), b.getDisplayName()); 266 267 b.updatePaths(); 268 269 if (b.getBlock().getValue() != null) { 270 b.getBlock().setValue(null); 271 } 272 } 273 274 if (badBeanErrors > 0) { // perhaps incremented via addBadBeanError(), but that's never called? 275 JmriJOptionPane.showMessageDialog(null, "" + badBeanErrors + " " + Bundle.getMessage("Warn2"), 276 Bundle.getMessage("WarningTitle"), JmriJOptionPane.ERROR_MESSAGE); 277 } 278 try { 279 new BlockValueFile().readBlockValues(); 280 } catch (org.jdom2.JDOMException jde) { 281 log.error("JDOM Exception when retreiving block values", jde); 282 } catch (java.io.IOException ioe) { 283 log.error("I/O Exception when retreiving block values", ioe); 284 } catch (RuntimeException re) { 285 // restoring the saved block values is not worth losing the routing 286 // initialization below, which would otherwise be skipped silently 287 log.error("Exception when retreiving block values", re); 288 } 289 290 //special tests for getFacingSignalHead method - comment out next three lines unless using LayoutEditorTests 291 //LayoutEditorTests layoutEditorTests = new LayoutEditorTests(); 292 //layoutEditorTests.runClinicTests(); 293 //layoutEditorTests.runTestPanel3Tests(); 294 initialized = true; 295 log.debug("start initializeLayoutBlockRouting"); 296 initializeLayoutBlockRouting(); 297 log.debug("end initializeLayoutBlockRouting and initializeLayoutBlockPaths"); 298 } 299 300 private boolean initialized = false; 301 302 // Is this ever called? 303 public void addBadBeanError() { 304 badBeanErrors++; 305 } 306 private int badBeanErrors = 0; 307 308 /** 309 * Get the Signal Head facing into a specified Block from a specified 310 * protected Block. 311 * <p> 312 * This method is primarily designed for use with scripts to get information 313 * initially residing in a Layout Editor panel. If either of the input 314 * Blocks is null, or if the two blocks do not join at a block boundary, or 315 * if either of the input Blocks are not Layout Editor panel blocks, an 316 * error message is logged, and "null" is returned. If the signal at the 317 * block boundary has two heads--is located at the facing point of a 318 * turnout-- the Signal Head that applies for the current setting of turnout 319 * (THROWN or CLOSED) is returned. If the turnout state is UNKNOWN or 320 * INCONSISTENT, an error message is logged, and "null" is returned. If the 321 * signal at the block boundary has three heads--the facing point of a 3-way 322 * turnout--the Signal Head that applies for the current settings of the two 323 * turnouts of the 3-way turnout is returned. If the turnout state of either 324 * turnout is UNKNOWN or INCONSISTENT, an error is logged and "null" is 325 * returned. "null" is returned if the block boundary is between the two 326 * turnouts of a THROAT_TO_THROAT turnout or a 3-way turnout. "null" is 327 * returned for block boundaries exiting a THROAT_TO_THROAT turnout block, 328 * since there are no signals that apply there. 329 * @param facingBlock the facing block. 330 * @param protectedBlock the protected block. 331 * @return the signal head, may be null. 332 */ 333 @CheckReturnValue 334 @CheckForNull 335 public SignalHead getFacingSignalHead( 336 @CheckForNull Block facingBlock, 337 @CheckForNull Block protectedBlock) { 338 //check input 339 if ((facingBlock == null) || (protectedBlock == null)) { 340 log.error("null block in call to getFacingSignalHead"); 341 return null; 342 } 343 344 //non-null - check if input corresponds to Blocks in a Layout Editor panel. 345 String facingBlockName = facingBlock.getUserName(); 346 if ((facingBlockName == null) || facingBlockName.isEmpty()) { 347 log.error("facingBlockName has no user name"); 348 return null; 349 } 350 351 String protectedBlockName = protectedBlock.getUserName(); 352 if ((protectedBlockName == null) || protectedBlockName.isEmpty()) { 353 log.error("protectedBlockName has no user name"); 354 return null; 355 } 356 357 LayoutBlock fLayoutBlock = getByUserName(facingBlockName); 358 LayoutBlock pLayoutBlock = getByUserName(protectedBlockName); 359 if ((fLayoutBlock == null) || (pLayoutBlock == null)) { 360 if (fLayoutBlock == null) { 361 log.error("Block {} is not on a Layout Editor panel.", facingBlock.getDisplayName()); 362 } 363 364 if (pLayoutBlock == null) { 365 log.error("Block {} is not on a Layout Editor panel.", protectedBlock.getDisplayName()); 366 } 367 return null; 368 } 369 370 //input has corresponding LayoutBlocks - does it correspond to a block boundary? 371 LayoutEditor panel = fLayoutBlock.getMaxConnectedPanel(); 372 List<LayoutConnectivity> c = panel.getLEAuxTools().getConnectivityList(fLayoutBlock); 373 LayoutConnectivity lc = null; 374 int i = 0; 375 boolean facingIsBlock1 = true; 376 377 while ((i < c.size()) && (lc == null)) { 378 LayoutConnectivity tlc = c.get(i); 379 380 if ((tlc.getBlock1() == fLayoutBlock) && (tlc.getBlock2() == pLayoutBlock)) { 381 lc = tlc; 382 } else if ((tlc.getBlock1() == pLayoutBlock) && (tlc.getBlock2() == fLayoutBlock)) { 383 lc = tlc; 384 facingIsBlock1 = false; 385 } 386 i++; 387 } 388 389 if (lc == null) { 390 log.error("Block {} ({}) is not connected to Block {}", facingBlock.getDisplayName(), 391 facingBlock.getDisplayName(), protectedBlock.getDisplayName()); 392 return null; 393 } 394 395 //blocks are connected, get connection item types 396 LayoutTurnout lt; 397 TrackSegment tr = lc.getTrackSegment(); 398 int boundaryType; 399 400 if (tr == null) { 401 // this is an internal crossover block boundary 402 lt = lc.getXover(); 403 boundaryType = lc.getXoverBoundaryType(); 404 405 switch (boundaryType) { 406 case LayoutConnectivity.XOVER_BOUNDARY_AB: { 407 if (facingIsBlock1) { 408 return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1); 409 } else { 410 return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1); 411 } 412 } 413 414 case LayoutConnectivity.XOVER_BOUNDARY_CD: { 415 if (facingIsBlock1) { 416 return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1); 417 } else { 418 return lt.getSignalHead(LayoutTurnout.Geometry.POINTD1); 419 } 420 } 421 422 case LayoutConnectivity.XOVER_BOUNDARY_AC: { 423 if (facingIsBlock1) { 424 if (lt.getSignalHead(LayoutTurnout.Geometry.POINTA2) == null) { //there is no signal head for diverging (crossed 425 //over) 426 return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1); 427 } else { //there is a diverging (crossed over) signal head, return it 428 return lt.getSignalHead(LayoutTurnout.Geometry.POINTA2); 429 } 430 } else { 431 if (lt.getSignalHead(LayoutTurnout.Geometry.POINTC2) == null) { 432 return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1); 433 } else { 434 return lt.getSignalHead(LayoutTurnout.Geometry.POINTC2); 435 } 436 } 437 } 438 439 case LayoutConnectivity.XOVER_BOUNDARY_BD: { 440 if (facingIsBlock1) { 441 if (lt.getSignalHead(LayoutTurnout.Geometry.POINTB2) == null) { 442 // there is no signal head for diverging (crossed over) 443 return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1); 444 } else { //there is a diverging (crossed over) signal head, return it 445 return lt.getSignalHead(LayoutTurnout.Geometry.POINTB2); 446 } 447 } else { 448 if (lt.getSignalHead(LayoutTurnout.Geometry.POINTD2) == null) { 449 return lt.getSignalHead(LayoutTurnout.Geometry.POINTD1); 450 } else { 451 return lt.getSignalHead(LayoutTurnout.Geometry.POINTD2); 452 } 453 } 454 } 455 456 default: { 457 log.error("Unhandled crossover connection type: {}", boundaryType); 458 break; 459 } 460 } //switch 461 462 //should never reach here, but ... 463 log.error("crossover turnout block boundary not found in getFacingSignal"); 464 465 return null; 466 } 467 468 //not internal crossover block boundary 469 LayoutTrack connected = lc.getConnectedObject(); 470 HitPointType cType = lc.getConnectedType(); 471 if (connected == null) { 472 log.error("No connectivity object found between Blocks {}, {} {}", facingBlock.getDisplayName(), 473 protectedBlock.getDisplayName(), cType); 474 475 return null; 476 } 477 478 if (cType == HitPointType.TRACK) { 479 // block boundary is at an Anchor Point 480 // LayoutEditorTools tools = panel.getLETools(); //TODO: Dead-code strip this 481 PositionablePoint p = panel.getFinder().findPositionablePointAtTrackSegments(tr, (TrackSegment) connected); 482 boolean block1IsWestEnd = LayoutEditorTools.isAtWestEndOfAnchor(panel, tr, p); 483 484 if ((block1IsWestEnd && facingIsBlock1) || (!block1IsWestEnd && !facingIsBlock1)) { 485 //block1 is on the west (north) end of the block boundary 486 return p.getEastBoundSignalHead(); 487 } else { 488 return p.getWestBoundSignalHead(); 489 } 490 } 491 492 if (cType == HitPointType.TURNOUT_A) { 493 // block boundary is at the facing point of a turnout or A connection of a crossover turnout 494 lt = (LayoutTurnout) connected; 495 496 if (lt.getLinkType() == LayoutTurnout.LinkType.NO_LINK) { 497 //standard turnout or A connection of a crossover turnout 498 if (facingIsBlock1) { 499 if (lt.getSignalHead(LayoutTurnout.Geometry.POINTA2) == null) { //there is no signal head for diverging 500 return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1); 501 } else { 502 //check if track segments at B or C are in protected block (block 2) 503 if (((TrackSegment) (lt.getConnectB())).getBlockName().equals(protectedBlock.getUserName())) { 504 //track segment connected at B matches block 2, check C 505 if (!(((TrackSegment) lt.getConnectC()).getBlockName().equals(protectedBlock.getUserName()))) { 506 //track segment connected at C is not in block2, return continuing signal head at A 507 if (lt.getContinuingSense() == Turnout.CLOSED) { 508 return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1); 509 } else { 510 return lt.getSignalHead(LayoutTurnout.Geometry.POINTA2); 511 } 512 } else { 513 //B and C both in block2, check turnout position to decide which signal head to return 514 int state = lt.getTurnout().getKnownState(); 515 516 if (((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.CLOSED)) 517 || ((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.THROWN))) { //continuing 518 return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1); 519 } else if (((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.CLOSED)) 520 || ((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.THROWN))) { //diverging 521 return lt.getSignalHead(LayoutTurnout.Geometry.POINTA2); 522 } else { 523 //turnout state is UNKNOWN or INCONSISTENT 524 log.error("Cannot choose signal head because turnout {} is in an UNKNOWN or INCONSISTENT state.", 525 lt.getTurnout().getDisplayName()); 526 527 return null; 528 } 529 } 530 } 531 532 //track segment connected at B is not in block 2 533 if ((((TrackSegment) lt.getConnectC()).getBlockName().equals(protectedBlock.getUserName()))) { 534 //track segment connected at C is in block 2, return diverging signal head 535 if (lt.getContinuingSense() == Turnout.CLOSED) { 536 return lt.getSignalHead(LayoutTurnout.Geometry.POINTA2); 537 } else { 538 return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1); 539 } 540 } else { 541 // neither track segment is in block 2 - will get here when layout turnout is the only item in block 2 542 // Return signal head based on turnout position 543 int state = lt.getTurnout().getKnownState(); 544 if (((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.CLOSED)) 545 || ((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.THROWN))) { //continuing 546 return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1); 547 } else if (((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.CLOSED)) 548 || ((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.THROWN))) { //diverging 549 return lt.getSignalHead(LayoutTurnout.Geometry.POINTA2); 550 } 551 552 // Turnout state is unknown or inconsistent 553 return null; 554 } 555 } 556 } else { 557 //check if track segments at B or C are in facing block (block 1) 558 if (((TrackSegment) (lt.getConnectB())).getBlockName().equals(facingBlock.getUserName())) { 559 //track segment connected at B matches block 1, check C 560 if (!(((TrackSegment) lt.getConnectC()).getBlockName().equals(facingBlock.getDisplayName()))) { 561 //track segment connected at C is not in block 2, return signal head at continuing end 562 return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1); 563 } else { 564 //B and C both in block 1, check turnout position to decide which signal head to return 565 int state = lt.getTurnout().getKnownState(); 566 567 if (((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.CLOSED)) 568 || ((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.THROWN))) { //continuing 569 return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1); 570 } else if (((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.CLOSED)) 571 || ((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.THROWN))) { 572 //diverging, check for second head 573 if (lt.getSignalHead(LayoutTurnout.Geometry.POINTC2) == null) { 574 return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1); 575 } else { 576 return lt.getSignalHead(LayoutTurnout.Geometry.POINTC2); 577 } 578 } else { 579 //turnout state is UNKNOWN or INCONSISTENT 580 log.error("Cannot choose signal head because turnout {} is in an UNKNOWN or INCONSISTENT state.", 581 lt.getTurnout().getDisplayName()); 582 583 return null; 584 } 585 } 586 } 587 588 //track segment connected at B is not in block 1 589 if (((TrackSegment) lt.getConnectC()).getBlockName().equals(facingBlock.getUserName())) { 590 //track segment connected at C is in block 1, return diverging signal head, check for second head 591 if (lt.getSignalHead(LayoutTurnout.Geometry.POINTC2) == null) { 592 return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1); 593 } else { 594 return lt.getSignalHead(LayoutTurnout.Geometry.POINTC2); 595 } 596 } else { 597 //neither track segment is in block 1 - should never get here unless layout turnout is 598 //the only item in block 1 599 if (!(lt.getBlockName().equals(facingBlock.getUserName()))) { 600 log.error("no signal faces block {}, and turnout is not in block either", 601 facingBlock.getDisplayName()); 602 } 603 return null; 604 } 605 } 606 } else if (lt.getLinkType() == LayoutTurnout.LinkType.THROAT_TO_THROAT) { 607 //There are no signals at the throat of a THROAT_TO_THROAT 608 609 //There should not be a block boundary here 610 return null; 611 } else if (lt.getLinkType() == LayoutTurnout.LinkType.FIRST_3_WAY) { 612 //3-way turnout is in its own block - block boundary is at the throat of the 3-way turnout 613 if (!facingIsBlock1) { 614 //facing block is within the three-way turnout's block - no signals for exit of the block 615 return null; 616 } else { 617 //select throat signal according to state of the 3-way turnout 618 int state = lt.getTurnout().getKnownState(); 619 620 if (state == Turnout.THROWN) { 621 if (lt.getSignalHead(LayoutTurnout.Geometry.POINTA2) == null) { 622 return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1); 623 } else { 624 return lt.getSignalHead(LayoutTurnout.Geometry.POINTA2); 625 } 626 } else if (state == Turnout.CLOSED) { 627 LayoutTurnout tLinked = panel.getFinder().findLayoutTurnoutByTurnoutName(lt.getLinkedTurnoutName()); 628 state = tLinked.getTurnout().getKnownState(); 629 630 if (state == Turnout.CLOSED) { 631 if (tLinked.getContinuingSense() == Turnout.CLOSED) { 632 return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1); 633 } else if (lt.getSignalHead(LayoutTurnout.Geometry.POINTA3) == null) { 634 return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1); 635 } else { 636 return lt.getSignalHead(LayoutTurnout.Geometry.POINTA3); 637 } 638 } else if (state == Turnout.THROWN) { 639 if (tLinked.getContinuingSense() == Turnout.THROWN) { 640 return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1); 641 } else if (lt.getSignalHead(LayoutTurnout.Geometry.POINTA3) == null) { 642 return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1); 643 } else { 644 return lt.getSignalHead(LayoutTurnout.Geometry.POINTA3); 645 } 646 } else { 647 //should never get here - linked turnout state is UNKNOWN or INCONSISTENT 648 log.error("Cannot choose 3-way signal head to return because turnout {} is in an UNKNOWN or INCONSISTENT state.", 649 tLinked.getTurnout().getSystemName()); 650 return null; 651 } 652 } else { 653 //should never get here - linked turnout state is UNKNOWN or INCONSISTENT 654 log.error("Cannot choose 3-way signal head to return because turnout {} is in an UNKNOWN or INCONSISTENT state.", 655 lt.getTurnout().getSystemName()); 656 return null; 657 } 658 } 659 } else if (lt.getLinkType() == LayoutTurnout.LinkType.SECOND_3_WAY) { 660 //There are no signals at the throat of the SECOND_3_WAY turnout of a 3-way turnout 661 662 //There should not be a block boundary here 663 return null; 664 } 665 } 666 667 if (cType == HitPointType.TURNOUT_B) { 668 //block boundary is at the continuing track of a turnout or B connection of a crossover turnout 669 lt = (LayoutTurnout) connected; 670 671 //check for double crossover or LH crossover 672 if (((lt.getTurnoutType() == LayoutTurnout.TurnoutType.DOUBLE_XOVER) 673 || (lt.getTurnoutType() == LayoutTurnout.TurnoutType.LH_XOVER))) { 674 if (facingIsBlock1) { 675 if (lt.getSignalHead(LayoutTurnout.Geometry.POINTB2) == null) { //there is only one signal at B, return it 676 return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1); 677 } 678 679 //check if track segments at A or D are in protected block (block 2) 680 if (((TrackSegment) (lt.getConnectA())).getBlockName().equals(protectedBlock.getUserName())) { 681 //track segment connected at A matches block 2, check D 682 if (!(((TrackSegment) lt.getConnectD()).getBlockName().equals(protectedBlock.getUserName()))) { 683 //track segment connected at D is not in block2, return continuing signal head at B 684 return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1); 685 } else { 686 //A and D both in block 2, check turnout position to decide which signal head to return 687 int state = lt.getTurnout().getKnownState(); 688 689 if (((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.CLOSED)) 690 || ((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.THROWN))) { //continuing 691 return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1); 692 } else if (((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.CLOSED)) 693 || ((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.THROWN))) { //diverging 694 //(crossed 695 696 //over) 697 return lt.getSignalHead(LayoutTurnout.Geometry.POINTB2); 698 } else { 699 //turnout state is UNKNOWN or INCONSISTENT 700 log.error("LayoutTurnout {} cannot choose signal head because turnout {} is in an UNKNOWN or INCONSISTENT state.", 701 lt, lt.getTurnout()); 702 return null; 703 } 704 } 705 } 706 707 //track segment connected at A is not in block 2 708 if ((((TrackSegment) lt.getConnectD()).getBlockName().equals(protectedBlock.getUserName()))) { //track segment 709 //connected at D 710 //is in block 2, 711 //return 712 //diverging 713 714 //signal head 715 return lt.getSignalHead(LayoutTurnout.Geometry.POINTB2); 716 } else { 717 //neither track segment is in block 2 - should never get here unless layout turnout is 718 //only item in block 2 719 if (!(lt.getBlockName().equals(protectedBlock.getUserName()))) { 720 log.error("neither signal at B protects block {}, and turnout is not in block either", 721 protectedBlock.getDisplayName()); 722 } 723 return null; 724 } 725 } else { 726 //check if track segments at A or D are in facing block (block 1) 727 if (((TrackSegment) (lt.getConnectA())).getBlockName().equals(facingBlock.getUserName())) { 728 //track segment connected at A matches block 1, check D 729 if (!(((TrackSegment) lt.getConnectD()).getBlockName().equals(facingBlock.getUserName()))) { 730 //track segment connected at D is not in block 2, return signal head at continuing end 731 return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1); 732 } else { 733 //A and D both in block 1, check turnout position to decide which signal head to return 734 int state = lt.getTurnout().getKnownState(); 735 736 if (((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.CLOSED)) 737 || ((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.THROWN))) { //continuing 738 return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1); 739 } else if (((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.CLOSED)) 740 || ((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.THROWN))) { 741 //diverging, check for second head 742 if (lt.getSignalHead(LayoutTurnout.Geometry.POINTD2) == null) { 743 return lt.getSignalHead(LayoutTurnout.Geometry.POINTD1); 744 } else { 745 return lt.getSignalHead(LayoutTurnout.Geometry.POINTD2); 746 } 747 } else { 748 //turnout state is UNKNOWN or INCONSISTENT 749 log.error("Cannot choose signal head because turnout {} is in an UNKNOWN or INCONSISTENT state.", 750 lt.getTurnout().getDisplayName()); 751 return null; 752 } 753 } 754 } 755 756 //track segment connected at A is not in block 1 757 if (((TrackSegment) lt.getConnectD()).getBlockName().equals(facingBlock.getUserName())) { 758 //track segment connected at D is in block 1, return diverging signal head, check for second head 759 if (lt.getSignalHead(LayoutTurnout.Geometry.POINTD2) == null) { 760 return lt.getSignalHead(LayoutTurnout.Geometry.POINTD1); 761 } else { 762 return lt.getSignalHead(LayoutTurnout.Geometry.POINTD2); 763 } 764 } else { 765 //neither track segment is in block 1 - should never get here unless layout turnout is 766 //the only item in block 1 767 if (!(lt.getBlockName().equals(facingBlock.getUserName()))) { 768 log.error("no signal faces block {}, and turnout is not in block either", 769 facingBlock.getDisplayName()); 770 } 771 return null; 772 } 773 } 774 } 775 776 //not double crossover or LH crossover 777 if ((lt.getLinkType() == LayoutTurnout.LinkType.NO_LINK) && (lt.getContinuingSense() == Turnout.CLOSED)) { 778 if (facingIsBlock1) { 779 return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1); 780 } else { 781 return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1); 782 } 783 } else if (lt.getLinkType() == LayoutTurnout.LinkType.NO_LINK) { 784 if (facingIsBlock1) { 785 return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1); 786 } else { 787 if (lt.getSignalHead(LayoutTurnout.Geometry.POINTA2) == null) { 788 return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1); 789 } else { 790 return lt.getSignalHead(LayoutTurnout.Geometry.POINTA2); 791 } 792 } 793 } else if (lt.getLinkType() == LayoutTurnout.LinkType.THROAT_TO_THROAT) { 794 if (!facingIsBlock1) { 795 //There are no signals at the throat of a THROAT_TO_THROAT 796 return null; 797 } 798 799 //facing block is outside of the THROAT_TO_THROAT 800 if ((lt.getContinuingSense() == Turnout.CLOSED) && (lt.getSignalHead(LayoutTurnout.Geometry.POINTB2) == null)) { 801 //there is only one signal head here - return it 802 return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1); 803 } else if ((lt.getContinuingSense() == Turnout.THROWN) && (lt.getSignalHead(LayoutTurnout.Geometry.POINTC2) == null)) { 804 //there is only one signal head here - return it 805 return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1); 806 } 807 808 //There are two signals here get linked turnout and decide which to return from linked turnout state 809 LayoutTurnout tLinked = panel.getFinder().findLayoutTurnoutByTurnoutName(lt.getLinkedTurnoutName()); 810 int state = tLinked.getTurnout().getKnownState(); 811 812 if (state == Turnout.CLOSED) { 813 if (lt.getContinuingSense() == Turnout.CLOSED) { 814 return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1); 815 } else { 816 return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1); 817 } 818 } else if (state == Turnout.THROWN) { 819 if (lt.getContinuingSense() == Turnout.CLOSED) { 820 return lt.getSignalHead(LayoutTurnout.Geometry.POINTB2); 821 } else { 822 return lt.getSignalHead(LayoutTurnout.Geometry.POINTC2); 823 } 824 } else { //should never get here - linked turnout state is UNKNOWN or INCONSISTENT 825 log.error("Cannot choose signal head to return because turnout {} is in an UNKNOWN or INCONSISTENT state.", 826 tLinked.getTurnout().getDisplayName()); 827 } 828 return null; 829 } else if (lt.getLinkType() == LayoutTurnout.LinkType.FIRST_3_WAY) { 830 //there is no signal at the FIRST_3_WAY turnout continuing track of a 3-way turnout 831 //there should not be a block boundary here 832 return null; 833 } else if (lt.getLinkType() == LayoutTurnout.LinkType.SECOND_3_WAY) { 834 if (facingIsBlock1) { 835 if (lt.getContinuingSense() == Turnout.CLOSED) { 836 return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1); 837 } else { 838 return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1); 839 } 840 } else { 841 //signal is at the linked turnout - the throat of the 3-way turnout 842 LayoutTurnout tLinked = panel.getFinder().findLayoutTurnoutByTurnoutName(lt.getLinkedTurnoutName()); 843 844 if (lt.getContinuingSense() == Turnout.CLOSED) { 845 return tLinked.getSignalHead(LayoutTurnout.Geometry.POINTA1); 846 } else { 847 if (tLinked.getSignalHead(LayoutTurnout.Geometry.POINTA3) == null) { 848 return tLinked.getSignalHead(LayoutTurnout.Geometry.POINTA1); 849 } else { 850 return tLinked.getSignalHead(LayoutTurnout.Geometry.POINTA3); 851 } 852 } 853 } 854 } 855 } 856 857 if (cType == HitPointType.TURNOUT_C) { 858 //block boundary is at the diverging track of a turnout or C connection of a crossover turnout 859 lt = (LayoutTurnout) connected; 860 861 //check for double crossover or RH crossover 862 if ((lt.getTurnoutType() == LayoutTurnout.TurnoutType.DOUBLE_XOVER) 863 || (lt.getTurnoutType() == LayoutTurnout.TurnoutType.RH_XOVER)) { 864 if (facingIsBlock1) { 865 if (lt.getSignalHead(LayoutTurnout.Geometry.POINTC2) == null) { //there is only one head at C, return it 866 return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1); 867 } 868 869 //check if track segments at A or D are in protected block (block 2) 870 if (((TrackSegment) (lt.getConnectA())).getBlockName().equals(protectedBlock.getUserName())) { 871 //track segment connected at A matches block 2, check D 872 if (!(((TrackSegment) lt.getConnectD()).getBlockName().equals(protectedBlock.getUserName()))) { 873 //track segment connected at D is not in block2, return diverging signal head at C 874 return lt.getSignalHead(LayoutTurnout.Geometry.POINTC2); 875 } else { 876 //A and D both in block 2, check turnout position to decide which signal head to return 877 int state = lt.getTurnout().getKnownState(); 878 879 if (((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.CLOSED)) 880 || ((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.THROWN))) { //continuing 881 return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1); 882 } else if (((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.CLOSED)) 883 || ((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.THROWN))) { //diverging 884 //(crossed 885 886 //over) 887 return lt.getSignalHead(LayoutTurnout.Geometry.POINTC2); 888 } else { 889 //turnout state is UNKNOWN or INCONSISTENT 890 log.error("Cannot choose signal head because turnout {} is in an UNKNOWN or INCONSISTENT state.", 891 lt.getTurnout().getDisplayName()); 892 return null; 893 } 894 } 895 } 896 897 //track segment connected at A is not in block 2 898 if ((((TrackSegment) lt.getConnectD()).getBlockName().equals(protectedBlock.getUserName()))) { 899 //track segment connected at D is in block 2, return continuing signal head 900 return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1); 901 } else { 902 //neither track segment is in block 2 - should never get here unless layout turnout is 903 //only item in block 2 904 if (!(lt.getBlockName().equals(protectedBlock.getUserName()))) { 905 log.error("neither signal at C protects block {}, and turnout is not in block either", 906 protectedBlock.getDisplayName()); 907 } 908 return null; 909 } 910 } else { 911 //check if track segments at D or A are in facing block (block 1) 912 if (((TrackSegment) (lt.getConnectD())).getBlockName().equals(facingBlock.getUserName())) { 913 //track segment connected at D matches block 1, check A 914 if (!(((TrackSegment) lt.getConnectA()).getBlockName().equals(facingBlock.getUserName()))) { 915 //track segment connected at A is not in block 2, return signal head at continuing end 916 return lt.getSignalHead(LayoutTurnout.Geometry.POINTD1); 917 } else { 918 //A and D both in block 1, check turnout position to decide which signal head to return 919 int state = lt.getTurnout().getKnownState(); 920 921 if (((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.CLOSED)) 922 || ((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.THROWN))) { //continuing 923 return lt.getSignalHead(LayoutTurnout.Geometry.POINTD1); 924 } else if (((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.CLOSED)) 925 || ((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.THROWN))) { 926 //diverging, check for second head 927 if (lt.getSignalHead(LayoutTurnout.Geometry.POINTA2) == null) { 928 return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1); 929 } else { 930 return lt.getSignalHead(LayoutTurnout.Geometry.POINTA2); 931 } 932 } else { 933 //turnout state is UNKNOWN or INCONSISTENT 934 log.error("Cannot choose signal head because turnout {} is in an UNKNOWN or INCONSISTENT state.", 935 lt.getTurnout().getDisplayName()); 936 return null; 937 } 938 } 939 } 940 941 //track segment connected at D is not in block 1 942 if (((TrackSegment) lt.getConnectA()).getBlockName().equals(facingBlock.getUserName())) { 943 //track segment connected at A is in block 1, return diverging signal head, check for second head 944 if (lt.getSignalHead(LayoutTurnout.Geometry.POINTA2) == null) { 945 return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1); 946 } else { 947 return lt.getSignalHead(LayoutTurnout.Geometry.POINTA2); 948 } 949 } else { 950 //neither track segment is in block 1 - should never get here unless layout turnout is 951 //the only item in block 1 952 if (!(lt.getBlockName().equals(facingBlock.getUserName()))) { 953 log.error("no signal faces block {}, and turnout is not in block either", 954 facingBlock.getDisplayName()); 955 } 956 return null; 957 } 958 } 959 } 960 961 //not double crossover or RH crossover 962 if ((lt.getLinkType() == LayoutTurnout.LinkType.NO_LINK) && (lt.getContinuingSense() == Turnout.CLOSED)) { 963 if (facingIsBlock1) { 964 return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1); 965 } else if (lt.getTurnoutType() == LayoutTurnout.TurnoutType.LH_XOVER) { //LH turnout - this is continuing track for D connection 966 return lt.getSignalHead(LayoutTurnout.Geometry.POINTD1); 967 } else { 968 //RH, LH or WYE turnout, this is diverging track for A connection 969 if (lt.getSignalHead(LayoutTurnout.Geometry.POINTA2) == null) { //there is no signal head at the throat for diverging 970 return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1); 971 } else { //there is a diverging head at the throat, return it 972 return lt.getSignalHead(LayoutTurnout.Geometry.POINTA2); 973 } 974 } 975 } else if (lt.getLinkType() == LayoutTurnout.LinkType.NO_LINK) { 976 if (facingIsBlock1) { 977 return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1); 978 } else { 979 return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1); 980 } 981 } else if (lt.getLinkType() == LayoutTurnout.LinkType.THROAT_TO_THROAT) { 982 if (!facingIsBlock1) { 983 //There are no signals at the throat of a THROAT_TO_THROAT 984 return null; 985 } 986 987 //facing block is outside of the THROAT_TO_THROAT 988 if ((lt.getContinuingSense() == Turnout.CLOSED) && (lt.getSignalHead(LayoutTurnout.Geometry.POINTC2) == null)) { 989 //there is only one signal head here - return it 990 return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1); 991 } else if ((lt.getContinuingSense() == Turnout.THROWN) && (lt.getSignalHead(LayoutTurnout.Geometry.POINTB2) == null)) { 992 //there is only one signal head here - return it 993 return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1); 994 } 995 996 //There are two signals here get linked turnout and decide which to return from linked turnout state 997 LayoutTurnout tLinked = panel.getFinder().findLayoutTurnoutByTurnoutName(lt.getLinkedTurnoutName()); 998 int state = tLinked.getTurnout().getKnownState(); 999 1000 if (state == Turnout.CLOSED) { 1001 if (lt.getContinuingSense() == Turnout.CLOSED) { 1002 return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1); 1003 } else { 1004 return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1); 1005 } 1006 } else if (state == Turnout.THROWN) { 1007 if (lt.getContinuingSense() == Turnout.CLOSED) { 1008 return lt.getSignalHead(LayoutTurnout.Geometry.POINTC2); 1009 } else { 1010 return lt.getSignalHead(LayoutTurnout.Geometry.POINTB2); 1011 } 1012 } else { 1013 //should never get here - linked turnout state is UNKNOWN or INCONSISTENT 1014 log.error("Cannot choose signal head to return because turnout {} is in an UNKNOWN or INCONSISTENT state.", 1015 tLinked.getTurnout().getDisplayName()); 1016 return null; 1017 } 1018 } else if (lt.getLinkType() == LayoutTurnout.LinkType.FIRST_3_WAY) { 1019 if (facingIsBlock1) { 1020 return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1); 1021 } else { 1022 if (lt.getSignalHead(LayoutTurnout.Geometry.POINTA2) == null) { 1023 return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1); 1024 } else { 1025 return lt.getSignalHead(LayoutTurnout.Geometry.POINTA2); 1026 } 1027 } 1028 } else if (lt.getLinkType() == LayoutTurnout.LinkType.SECOND_3_WAY) { 1029 if (facingIsBlock1) { 1030 if (lt.getContinuingSense() == Turnout.CLOSED) { 1031 return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1); 1032 } else { 1033 return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1); 1034 } 1035 } else { 1036 //signal is at the linked turnout - the throat of the 3-way turnout 1037 LayoutTurnout tLinked = panel.getFinder().findLayoutTurnoutByTurnoutName(lt.getLinkedTurnoutName()); 1038 1039 if (lt.getContinuingSense() == Turnout.CLOSED) { 1040 if (tLinked.getSignalHead(LayoutTurnout.Geometry.POINTA3) == null) { 1041 return tLinked.getSignalHead(LayoutTurnout.Geometry.POINTA1); 1042 } else { 1043 return tLinked.getSignalHead(LayoutTurnout.Geometry.POINTA3); 1044 } 1045 } else { 1046 if (tLinked.getSignalHead(LayoutTurnout.Geometry.POINTA2) == null) { 1047 return tLinked.getSignalHead(LayoutTurnout.Geometry.POINTA1); 1048 } else { 1049 return tLinked.getSignalHead(LayoutTurnout.Geometry.POINTA2); 1050 } 1051 } 1052 } 1053 } 1054 } 1055 1056 if (cType == HitPointType.TURNOUT_D) { 1057 //block boundary is at D connectin of a crossover turnout 1058 lt = (LayoutTurnout) connected; 1059 1060 if (lt.getTurnoutType() == LayoutTurnout.TurnoutType.RH_XOVER) { 1061 //no diverging route possible, this is continuing track for C connection 1062 if (facingIsBlock1) { 1063 return lt.getSignalHead(LayoutTurnout.Geometry.POINTD1); 1064 } else { 1065 return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1); 1066 } 1067 } 1068 1069 if (facingIsBlock1) { 1070 if (lt.getSignalHead(LayoutTurnout.Geometry.POINTD2) == null) { //there is no signal head for diverging 1071 return lt.getSignalHead(LayoutTurnout.Geometry.POINTD1); 1072 } else { 1073 //check if track segments at C or B are in protected block (block 2) 1074 if (((TrackSegment) (lt.getConnectC())).getBlockName().equals(protectedBlock.getUserName())) { 1075 //track segment connected at C matches block 2, check B 1076 if (!(((TrackSegment) lt.getConnectB()).getBlockName().equals(protectedBlock.getUserName()))) { 1077 //track segment connected at B is not in block2, return continuing signal head at D 1078 return lt.getSignalHead(LayoutTurnout.Geometry.POINTD1); 1079 } else { 1080 //C and B both in block2, check turnout position to decide which signal head to return 1081 int state = lt.getTurnout().getKnownState(); 1082 1083 if (((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.CLOSED)) 1084 || ((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.THROWN))) { //continuing 1085 return lt.getSignalHead(LayoutTurnout.Geometry.POINTD1); 1086 } else if (((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.CLOSED)) 1087 || ((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.THROWN))) { //diverging 1088 return lt.getSignalHead(LayoutTurnout.Geometry.POINTD2); 1089 } else { 1090 //turnout state is UNKNOWN or INCONSISTENT 1091 log.error("Cannot choose signal head because turnout {} is in an UNKNOWN or INCONSISTENT state.", 1092 lt.getTurnout().getDisplayName()); 1093 return null; 1094 } 1095 } 1096 } 1097 1098 //track segment connected at C is not in block 2 1099 if ((((TrackSegment) lt.getConnectB()).getBlockName().equals(protectedBlock.getUserName()))) { 1100 //track segment connected at B is in block 2, return diverging signal head 1101 return lt.getSignalHead(LayoutTurnout.Geometry.POINTD2); 1102 } else { 1103 //neither track segment is in block 2 - should never get here unless layout turnout is 1104 //the only item in block 2 1105 if (!(lt.getBlockName().equals(protectedBlock.getUserName()))) { 1106 log.error("neither signal at D protects block {}, and turnout is not in block either", 1107 protectedBlock.getDisplayName()); 1108 } 1109 return null; 1110 } 1111 } 1112 } else { 1113 //check if track segments at C or B are in facing block (block 1) 1114 if (((TrackSegment) (lt.getConnectC())).getBlockName().equals(facingBlock.getUserName())) { 1115 //track segment connected at C matches block 1, check B 1116 if (!(((TrackSegment) lt.getConnectB()).getBlockName().equals(facingBlock.getUserName()))) { 1117 //track segment connected at B is not in block 2, return signal head at continuing end 1118 return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1); 1119 } else { 1120 //C and B both in block 1, check turnout position to decide which signal head to return 1121 int state = lt.getTurnout().getKnownState(); 1122 1123 if (((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.CLOSED)) 1124 || ((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.THROWN))) { //continuing 1125 return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1); 1126 } else if (((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.CLOSED)) 1127 || ((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.THROWN))) { 1128 //diverging, check for second head 1129 if (lt.getSignalHead(LayoutTurnout.Geometry.POINTB2) == null) { 1130 return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1); 1131 } else { 1132 return lt.getSignalHead(LayoutTurnout.Geometry.POINTB2); 1133 } 1134 } else { 1135 //turnout state is UNKNOWN or INCONSISTENT 1136 log.error("Cannot choose signal head because turnout {} is in an UNKNOWN or INCONSISTENT state.", 1137 lt.getTurnout().getDisplayName()); 1138 return null; 1139 } 1140 } 1141 } 1142 1143 //track segment connected at C is not in block 1 1144 if (((TrackSegment) lt.getConnectB()).getBlockName().equals(facingBlock.getUserName())) { 1145 //track segment connected at B is in block 1, return diverging signal head, check for second head 1146 if (lt.getSignalHead(LayoutTurnout.Geometry.POINTB2) == null) { 1147 return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1); 1148 } else { 1149 return lt.getSignalHead(LayoutTurnout.Geometry.POINTB2); 1150 } 1151 } else { 1152 //neither track segment is in block 1 - should never get here unless layout turnout is 1153 //the only item in block 1 1154 if (!(lt.getBlockName().equals(facingBlock.getUserName()))) { 1155 log.error("no signal faces block {}, and turnout is not in block either", 1156 facingBlock.getDisplayName()); 1157 } 1158 return null; 1159 } 1160 } 1161 } 1162 1163 if (HitPointType.isSlipHitType(cType)) { 1164 if (!facingIsBlock1) { 1165 return null; 1166 } 1167 1168 LayoutSlip ls = (LayoutSlip) connected; 1169 1170 switch (cType) { 1171 case SLIP_A: { 1172 if (ls.getSlipState() == LayoutSlip.STATE_AD) { 1173 return ls.getSignalHead(LayoutTurnout.Geometry.POINTA2); 1174 } else { 1175 return ls.getSignalHead(LayoutTurnout.Geometry.POINTA1); 1176 } 1177 } 1178 1179 case SLIP_B: { 1180 if (ls.getTurnoutType() == LayoutSlip.TurnoutType.DOUBLE_SLIP) { 1181 if (ls.getSlipState() == LayoutSlip.STATE_BC) { 1182 return ls.getSignalHead(LayoutTurnout.Geometry.POINTB2); 1183 } else { 1184 return ls.getSignalHead(LayoutTurnout.Geometry.POINTB1); 1185 } 1186 } else { 1187 return ls.getSignalHead(LayoutTurnout.Geometry.POINTB1); 1188 } 1189 } 1190 1191 case SLIP_C: { 1192 if (ls.getTurnoutType() == LayoutSlip.TurnoutType.DOUBLE_SLIP) { 1193 if (ls.getSlipState() == LayoutSlip.STATE_BC) { 1194 return ls.getSignalHead(LayoutTurnout.Geometry.POINTC2); 1195 } else { 1196 return ls.getSignalHead(LayoutTurnout.Geometry.POINTC1); 1197 } 1198 } else { 1199 return ls.getSignalHead(LayoutTurnout.Geometry.POINTC1); 1200 } 1201 } 1202 1203 case SLIP_D: { 1204 if (ls.getSlipState() == LayoutSlip.STATE_AD) { 1205 return ls.getSignalHead(LayoutTurnout.Geometry.POINTD2); 1206 } else { 1207 return ls.getSignalHead(LayoutTurnout.Geometry.POINTD1); 1208 } 1209 } 1210 1211 default: { 1212 break; 1213 } 1214 } //switch 1215 } 1216 1217 //block boundary must be at a level crossing 1218 if (!HitPointType.isLevelXingHitType(cType)) { 1219 log.error("{} {} Block Boundary not identified correctly - Blocks {}, {}", 1220 cType, connected, facingBlock.getDisplayName(), protectedBlock.getDisplayName()); 1221 1222 return null; 1223 } 1224 LevelXing xing = (LevelXing) connected; 1225 1226 switch (cType) { 1227 case LEVEL_XING_A: { 1228 //block boundary is at the A connection of a level crossing 1229 if (facingIsBlock1) { 1230 return xing.getSignalHead(LevelXing.Geometry.POINTA); 1231 } else { 1232 return xing.getSignalHead(LevelXing.Geometry.POINTC); 1233 } 1234 } 1235 1236 case LEVEL_XING_B: { 1237 //block boundary is at the B connection of a level crossing 1238 if (facingIsBlock1) { 1239 return xing.getSignalHead(LevelXing.Geometry.POINTB); 1240 } else { 1241 return xing.getSignalHead(LevelXing.Geometry.POINTD); 1242 } 1243 } 1244 1245 case LEVEL_XING_C: { 1246 //block boundary is at the C connection of a level crossing 1247 if (facingIsBlock1) { 1248 return xing.getSignalHead(LevelXing.Geometry.POINTC); 1249 } else { 1250 return xing.getSignalHead(LevelXing.Geometry.POINTA); 1251 } 1252 } 1253 1254 case LEVEL_XING_D: { 1255 //block boundary is at the D connection of a level crossing 1256 if (facingIsBlock1) { 1257 return xing.getSignalHead(LevelXing.Geometry.POINTD); 1258 } else { 1259 return xing.getSignalHead(LevelXing.Geometry.POINTB); 1260 } 1261 } 1262 1263 default: { 1264 break; 1265 } 1266 } 1267 return null; 1268 } 1269 1270 /** 1271 * Get the named bean of either a Sensor or signalmast facing into a 1272 * specified Block from a specified protected Block. 1273 * @param facingBlock the facing block. 1274 * @param panel the main layout editor. 1275 * @return The assigned sensor or signal mast as a named bean 1276 */ 1277 @CheckReturnValue 1278 @CheckForNull 1279 public NamedBean getNamedBeanAtEndBumper( 1280 @CheckForNull Block facingBlock, 1281 @CheckForNull LayoutEditor panel) { 1282 NamedBean bean = getSignalMastAtEndBumper(facingBlock, panel); 1283 1284 if (bean != null) { 1285 return bean; 1286 } else { 1287 return getSensorAtEndBumper(facingBlock, panel); 1288 } 1289 } 1290 1291 /** 1292 * Get a Signal Mast that is assigned to a block which has an end bumper at 1293 * one end. 1294 * @param facingBlock the facing block. 1295 * @param panel the main layout editor. 1296 * @return the signal mast. 1297 */ 1298 @CheckReturnValue 1299 @CheckForNull 1300 public SignalMast getSignalMastAtEndBumper( 1301 @CheckForNull Block facingBlock, 1302 @CheckForNull LayoutEditor panel) { 1303 if (facingBlock == null) { 1304 log.error("null block in call to getFacingSignalMast"); 1305 return null; 1306 } 1307 String facingBlockName = facingBlock.getUserName(); 1308 if ((facingBlockName == null) || facingBlockName.isEmpty()) { 1309 log.error("facing block has no user name"); 1310 return null; 1311 } 1312 1313 LayoutBlock fLayoutBlock = getByUserName(facingBlockName); 1314 if (fLayoutBlock == null) { 1315 log.error("Block {} is not on a Layout Editor panel.", facingBlock.getDisplayName()); 1316 1317 return null; 1318 } 1319 1320 if (panel == null) { 1321 panel = fLayoutBlock.getMaxConnectedPanel(); 1322 } 1323 1324 for (TrackSegment t : panel.getTrackSegments()) { 1325 if (t.getLayoutBlock() == fLayoutBlock) { 1326 PositionablePoint p; 1327 1328 if (t.getType1() == HitPointType.POS_POINT) { 1329 p = (PositionablePoint) t.getConnect1(); 1330 1331 if (p.getType() == PositionablePoint.PointType.END_BUMPER) { 1332 if (p.getEastBoundSignalMast() != null) { 1333 return p.getEastBoundSignalMast(); 1334 } 1335 1336 if (p.getWestBoundSignalMast() != null) { 1337 return p.getWestBoundSignalMast(); 1338 } 1339 } 1340 } 1341 1342 if (t.getType2() == HitPointType.POS_POINT) { 1343 p = (PositionablePoint) t.getConnect2(); 1344 1345 if (p.getType() == PositionablePoint.PointType.END_BUMPER) { 1346 if (p.getEastBoundSignalMast() != null) { 1347 return p.getEastBoundSignalMast(); 1348 } 1349 1350 if (p.getWestBoundSignalMast() != null) { 1351 return p.getWestBoundSignalMast(); 1352 } 1353 } 1354 } 1355 } 1356 } 1357 return null; 1358 } 1359 1360 /** 1361 * Get a Sensor facing into a specific Block. This is used for Blocks that 1362 * have an end bumper at one end. 1363 * @param facingBlock the facing block. 1364 * @param panel the main layout editor. 1365 * @return the facing sensor. 1366 */ 1367 @CheckReturnValue 1368 @CheckForNull 1369 public Sensor getSensorAtEndBumper( 1370 @CheckForNull Block facingBlock, 1371 @CheckForNull LayoutEditor panel) { 1372 if (facingBlock == null) { 1373 log.error("null block in call to getFacingSensor"); 1374 return null; 1375 } 1376 1377 String facingBlockName = facingBlock.getUserName(); 1378 if ((facingBlockName == null) || (facingBlockName.isEmpty())) { 1379 log.error("Block {} has no user name.", facingBlock.getDisplayName()); 1380 return null; 1381 } 1382 LayoutBlock fLayoutBlock = getByUserName(facingBlockName); 1383 if (fLayoutBlock == null) { 1384 log.error("Block {} is not on a Layout Editor panel.", facingBlock.getDisplayName()); 1385 1386 return null; 1387 } 1388 1389 if (panel == null) { 1390 panel = fLayoutBlock.getMaxConnectedPanel(); 1391 } 1392 1393 for (TrackSegment t : panel.getTrackSegments()) { 1394 if (t.getLayoutBlock() == fLayoutBlock) { 1395 PositionablePoint p; 1396 1397 if (t.getType1() == HitPointType.POS_POINT) { 1398 p = (PositionablePoint) t.getConnect1(); 1399 1400 if (p.getType() == PositionablePoint.PointType.END_BUMPER) { 1401 if (p.getEastBoundSensor() != null) { 1402 return p.getEastBoundSensor(); 1403 } 1404 1405 if (p.getWestBoundSensor() != null) { 1406 return p.getWestBoundSensor(); 1407 } 1408 } 1409 } 1410 1411 if (t.getType2() == HitPointType.POS_POINT) { 1412 p = (PositionablePoint) t.getConnect2(); 1413 1414 if (p.getType() == PositionablePoint.PointType.END_BUMPER) { 1415 if (p.getEastBoundSensor() != null) { 1416 return p.getEastBoundSensor(); 1417 } 1418 1419 if (p.getWestBoundSensor() != null) { 1420 return p.getWestBoundSensor(); 1421 } 1422 } 1423 } 1424 } 1425 } 1426 return null; 1427 } 1428 1429 /** 1430 * Get the named bean of either a Sensor or signalmast facing into a 1431 * specified Block from a specified protected Block. 1432 * @param facingBlock the facing block. 1433 * @param protectedBlock the protected block. 1434 * @param panel the main layout editor. 1435 * @return The assigned sensor or signal mast as a named bean 1436 */ 1437 @CheckReturnValue 1438 @CheckForNull 1439 public NamedBean getFacingNamedBean(@CheckForNull Block facingBlock, 1440 @CheckForNull Block protectedBlock, 1441 @CheckForNull LayoutEditor panel) { 1442 NamedBean bean = getFacingBean(facingBlock, protectedBlock, panel, SignalMast.class); 1443 1444 if (bean != null) { 1445 return bean; 1446 } 1447 bean = getFacingBean(facingBlock, protectedBlock, panel, Sensor.class); 1448 1449 if (bean != null) { 1450 return bean; 1451 } 1452 return getFacingSignalHead(facingBlock, protectedBlock); 1453 } 1454 1455 @CheckReturnValue 1456 @CheckForNull 1457 public SignalMast getFacingSignalMast( 1458 @Nonnull Block facingBlock, 1459 @CheckForNull Block protectedBlock) { 1460 return getFacingSignalMast(facingBlock, protectedBlock, null); 1461 } 1462 1463 /** 1464 * Get the Signal Mast facing into a specified Block from a specified 1465 * protected Block. 1466 * 1467 * @param facingBlock the facing block. 1468 * @param protectedBlock the protected block. 1469 * @param panel the main layout editor. 1470 * @return The assigned signalMast. 1471 */ 1472 @CheckReturnValue 1473 @CheckForNull 1474 public SignalMast getFacingSignalMast( 1475 @Nonnull Block facingBlock, 1476 @CheckForNull Block protectedBlock, 1477 @CheckForNull LayoutEditor panel) { 1478 log.debug("calling getFacingMast on block '{}'", facingBlock.getDisplayName()); 1479 return (SignalMast) getFacingBean(facingBlock, protectedBlock, panel, SignalMast.class); 1480 } 1481 1482 /** 1483 * Get the Sensor facing into a specified Block from a specified protected 1484 * Block. 1485 * @param facingBlock the facing block. 1486 * @param protectedBlock the protected block. 1487 * @param panel the main layout editor. 1488 * @return The assigned sensor 1489 */ 1490 @CheckReturnValue 1491 @CheckForNull 1492 public Sensor getFacingSensor(@CheckForNull Block facingBlock, 1493 @CheckForNull Block protectedBlock, 1494 @CheckForNull LayoutEditor panel) { 1495 return (Sensor) getFacingBean(facingBlock, protectedBlock, panel, Sensor.class); 1496 } 1497 1498 /** 1499 * Get a facing bean into a specified Block from a specified protected 1500 * Block. 1501 * 1502 * @param facingBlock the facing block. 1503 * @param protectedBlock the protected block. 1504 * @param panel the layout editor panel the block is assigned, if null then 1505 * the maximum connected panel of the facing block is used 1506 * @param T The class of the item that we are looking for, either 1507 * SignalMast or Sensor 1508 * @return The assigned sensor. 1509 */ 1510 @CheckReturnValue 1511 @CheckForNull 1512 public NamedBean getFacingBean(@CheckForNull Block facingBlock, 1513 @CheckForNull Block protectedBlock, 1514 @CheckForNull LayoutEditor panel, Class< ?> T) { 1515 //check input 1516 if ((facingBlock == null) || (protectedBlock == null)) { 1517 log.error("null block in call to getFacingSignalMast"); 1518 return null; 1519 } 1520 1521 // ----- Begin Turntable Boundary Check ----- 1522 for (LayoutEditor ed : InstanceManager.getDefault(EditorManager.class).getAll(LayoutEditor.class)) { 1523 for (LayoutTurntable turntable : ed.getLayoutTurntables()) { 1524 LayoutBlock turntableBlock = turntable.getLayoutBlock(); 1525 if (turntableBlock == null) continue; 1526 1527 // Check if one of the blocks is the turntable's block 1528 if (turntableBlock.getBlock() == facingBlock || turntableBlock.getBlock() == protectedBlock) { 1529 Block otherBlock = (turntableBlock.getBlock() == facingBlock) ? protectedBlock : facingBlock; 1530 1531 for (LayoutTurntable.RayTrack ray : turntable.getRayTrackList()) { 1532 TrackSegment connectedTrack = ray.getConnect(); 1533 if (connectedTrack != null && connectedTrack.getLayoutBlock() != null && connectedTrack.getLayoutBlock().getBlock() == otherBlock) { 1534 // We found the correct ray. Now find the mast based on direction. 1535 if (turntableBlock.getBlock() == protectedBlock) { 1536 // Path 2: Moving from Ray block INTO Turntable. The facing mast is the Approach Mast. 1537 if (T.equals(SignalMast.class)) { 1538 return ray.getApproachMast(); 1539 } 1540 } else { // turntableBlock.getBlock() == facingBlock 1541 // Path 1: Moving FROM Turntable out to Ray block. The facing mast is the exit mast for that ray. 1542 if (T.equals(SignalMast.class)) { 1543 SignalMast exitMast = turntable.getExitSignalMast(); 1544 // This is the mast protecting the path from the turntable to the ray. 1545 return exitMast; 1546 } 1547 } 1548 } 1549 } 1550 } 1551 } 1552 } 1553 // ----- Begin Traverser Boundary Check ----- 1554 for (LayoutEditor ed : InstanceManager.getDefault(EditorManager.class).getAll(LayoutEditor.class)) { 1555 for (LayoutTraverser traverser : ed.getLayoutTraversers()) { 1556 LayoutBlock traverserBlock = traverser.getLayoutBlock(); 1557 if (traverserBlock == null) continue; 1558 1559 // Check if one of the blocks is the traverser's block 1560 if (traverserBlock.getBlock() == facingBlock || traverserBlock.getBlock() == protectedBlock) { 1561 Block otherBlock = (traverserBlock.getBlock() == facingBlock) ? protectedBlock : facingBlock; 1562 1563 for (LayoutTraverser.SlotTrack slot : traverser.getSlotList()) { 1564 TrackSegment connectedTrack = slot.getConnect(); 1565 if (connectedTrack != null && connectedTrack.getLayoutBlock() != null && connectedTrack.getLayoutBlock().getBlock() == otherBlock) { 1566 // We found the correct slot. Now find the mast based on direction. 1567 if (traverserBlock.getBlock() == protectedBlock) { 1568 // Path 2: Moving from Slot block INTO Traverser. The facing mast is the Approach Mast. 1569 if (T.equals(SignalMast.class)) { 1570 return slot.getApproachMast(); 1571 } 1572 } else { // traverserBlock.getBlock() == facingBlock 1573 // Path 1: Moving FROM Traverser out to Slot block. The facing mast is the exit mast for that slot. 1574 if (T.equals(SignalMast.class)) { 1575 SignalMast exitMast = traverser.getExitSignalMast(); 1576 // This is the mast protecting the path from the traverser to the slot. 1577 return exitMast; 1578 } 1579 } 1580 } 1581 } 1582 } 1583 } 1584 } 1585 // ----- End Traverser Boundary Check ----- 1586 1587 if (!T.equals(SignalMast.class) && !T.equals(Sensor.class)) { 1588 log.error("Incorrect class type called, must be either SignalMast or Sensor"); 1589 1590 return null; 1591 } 1592 1593 if (log.isDebugEnabled()) { 1594 log.debug("find signal mast between facing {} ({}) - protected {} ({})", 1595 facingBlock.getDisplayName(), facingBlock.getDisplayName(), 1596 protectedBlock.getDisplayName(), protectedBlock.getDisplayName()); 1597 } 1598 1599 //non-null - check if input corresponds to Blocks in a Layout Editor panel. 1600 String facingBlockName = facingBlock.getUserName(); 1601 if ((facingBlockName == null) || facingBlockName.isEmpty()) { 1602 log.error("facing block has no user name"); 1603 return null; 1604 } 1605 LayoutBlock fLayoutBlock = getByUserName(facingBlockName); 1606 String protectedBlockName = protectedBlock.getUserName(); 1607 LayoutBlock pLayoutBlock = (protectedBlockName == null) ? null : getByUserName(protectedBlockName); 1608 if ((fLayoutBlock == null) || (pLayoutBlock == null)) { 1609 if (fLayoutBlock == null) { 1610 log.error("Block {} is not on a Layout Editor panel.", facingBlock.getDisplayName()); 1611 } 1612 1613 if (pLayoutBlock == null) { 1614 log.error("Block {} is not on a Layout Editor panel.", protectedBlock.getDisplayName()); 1615 } 1616 return null; 1617 } 1618 1619 //input has corresponding LayoutBlocks - does it correspond to a block boundary? 1620 if (panel == null) { 1621 panel = fLayoutBlock.getMaxConnectedPanel(); 1622 } 1623 List<LayoutConnectivity> c = panel.getLEAuxTools().getConnectivityList(fLayoutBlock); 1624 LayoutConnectivity lc = null; 1625 int i = 0; 1626 boolean facingIsBlock1 = true; 1627 1628 while ((i < c.size()) && (lc == null)) { 1629 LayoutConnectivity tlc = c.get(i); 1630 1631 if ((tlc.getBlock1() == fLayoutBlock) && (tlc.getBlock2() == pLayoutBlock)) { 1632 lc = tlc; 1633 } else if ((tlc.getBlock1() == pLayoutBlock) && (tlc.getBlock2() == fLayoutBlock)) { 1634 lc = tlc; 1635 facingIsBlock1 = false; 1636 } 1637 i++; 1638 } 1639 1640 if (lc == null) { 1641 PositionablePoint p = panel.getFinder().findPositionableLinkPoint(fLayoutBlock); 1642 1643 if (p == null) { 1644 p = panel.getFinder().findPositionableLinkPoint(pLayoutBlock); 1645 } 1646 1647 if ((p != null) && (p.getLinkedEditor() != null)) { 1648 return getFacingBean(facingBlock, protectedBlock, p.getLinkedEditor(), T); 1649 } 1650 log.debug("Block {} is not connected to Block {} on panel {}", facingBlock.getDisplayName(), 1651 protectedBlock.getDisplayName(), panel.getLayoutName()); 1652 1653 return null; 1654 } 1655 LayoutTurnout lt; 1656 LayoutTrack connected = lc.getConnectedObject(); 1657 1658 TrackSegment tr = lc.getTrackSegment(); 1659 HitPointType cType = lc.getConnectedType(); 1660 1661 if (connected == null) { 1662 if (lc.getXover() != null) { 1663 if (lc.getXoverBoundaryType() == LayoutConnectivity.XOVER_BOUNDARY_AB) { 1664 if (fLayoutBlock == lc.getXover().getLayoutBlock()) { 1665 cType = HitPointType.TURNOUT_A; 1666 } else { 1667 cType = HitPointType.TURNOUT_B; 1668 } 1669 connected = lc.getXover(); 1670 } else if (lc.getXoverBoundaryType() == LayoutConnectivity.XOVER_BOUNDARY_CD) { 1671 if (fLayoutBlock == lc.getXover().getLayoutBlockC()) { 1672 cType = HitPointType.TURNOUT_C; 1673 } else { 1674 cType = HitPointType.TURNOUT_D; 1675 } 1676 connected = lc.getXover(); 1677 } else if (lc.getXoverBoundaryType() == LayoutConnectivity.XOVER_BOUNDARY_AC) { 1678 if (fLayoutBlock == lc.getXover().getLayoutBlock()) { 1679 cType = HitPointType.TURNOUT_A; 1680 } else { 1681 cType = HitPointType.TURNOUT_C; 1682 } 1683 connected = lc.getXover(); 1684 } else if (lc.getXoverBoundaryType() == LayoutConnectivity.XOVER_BOUNDARY_BD) { 1685 if (fLayoutBlock == lc.getXover().getLayoutBlockB()) { 1686 cType = HitPointType.TURNOUT_B; 1687 } else { 1688 cType = HitPointType.TURNOUT_D; 1689 } 1690 connected = lc.getXover(); 1691 } 1692 } 1693 } 1694 1695 if (connected == null) { 1696 log.error("No connectivity object found between Blocks {}, {} {}", facingBlock.getDisplayName(), 1697 protectedBlock.getDisplayName(), cType); 1698 1699 return null; 1700 } 1701 1702 if (cType == HitPointType.TRACK) { 1703 //block boundary is at an Anchor Point 1704 PositionablePoint p = panel.getFinder().findPositionablePointAtTrackSegments(tr, (TrackSegment) connected); 1705 1706 boolean block1IsWestEnd = LayoutEditorTools.isAtWestEndOfAnchor(panel, tr, p); 1707 log.debug("Track is west end? {}", block1IsWestEnd); 1708 if ((block1IsWestEnd && facingIsBlock1) || (!block1IsWestEnd && !facingIsBlock1)) { 1709 //block1 is on the west (north) end of the block boundary 1710 if (T.equals(SignalMast.class)) { 1711 return p.getEastBoundSignalMast(); 1712 } else if (T.equals(Sensor.class)) { 1713 return p.getEastBoundSensor(); 1714 } 1715 } else { 1716 if (T.equals(SignalMast.class)) { 1717 return p.getWestBoundSignalMast(); 1718 } else if (T.equals(Sensor.class)) { 1719 return p.getWestBoundSensor(); 1720 } 1721 } 1722 } 1723 1724 if (cType == HitPointType.TURNOUT_A) { 1725 lt = (LayoutTurnout) connected; 1726 1727 if ((lt.getLinkType() == LayoutTurnout.LinkType.NO_LINK) || (lt.getLinkType() == LayoutTurnout.LinkType.FIRST_3_WAY)) { 1728 if ((T.equals(SignalMast.class) && (lt.getSignalAMast() != null)) 1729 || (T.equals(Sensor.class) && (lt.getSensorA() != null))) { 1730 if (tr == null) { 1731 if (lt.getConnectA() instanceof TrackSegment) { 1732 TrackSegment t = (TrackSegment) lt.getConnectA(); 1733 1734 if ((t.getLayoutBlock() != null) && (t.getLayoutBlock() == lt.getLayoutBlock())) { 1735 if (T.equals(SignalMast.class)) { 1736 return lt.getSignalAMast(); 1737 } else if (T.equals(Sensor.class)) { 1738 return lt.getSensorA(); 1739 } 1740 } 1741 } 1742 } else if (tr.getLayoutBlock().getBlock() == facingBlock) { 1743 if (T.equals(SignalMast.class)) { 1744 return lt.getSignalAMast(); 1745 } else if (T.equals(Sensor.class)) { 1746 return lt.getSensorA(); 1747 } 1748 } 1749 } 1750 } 1751 return null; 1752 } 1753 1754 if (cType == HitPointType.TURNOUT_B) { 1755 lt = (LayoutTurnout) connected; 1756 1757 if ((T.equals(SignalMast.class) && (lt.getSignalBMast() != null)) 1758 || (T.equals(Sensor.class) && (lt.getSensorB() != null))) { 1759 if (tr == null) { 1760 if (lt.getConnectB() instanceof TrackSegment) { 1761 TrackSegment t = (TrackSegment) lt.getConnectB(); 1762 1763 if ((t.getLayoutBlock() != null) && (t.getLayoutBlock() == lt.getLayoutBlockB())) { 1764 if (T.equals(SignalMast.class)) { 1765 return lt.getSignalBMast(); 1766 } else if (T.equals(Sensor.class)) { 1767 return lt.getSensorB(); 1768 } 1769 } 1770 } 1771 } else if (tr.getLayoutBlock().getBlock() == facingBlock) { 1772 if (T.equals(SignalMast.class)) { 1773 return lt.getSignalBMast(); 1774 } else if (T.equals(Sensor.class)) { 1775 return lt.getSensorB(); 1776 } 1777 } 1778 } 1779 return null; 1780 } 1781 1782 if (cType == HitPointType.TURNOUT_C) { 1783 lt = (LayoutTurnout) connected; 1784 1785 if ((T.equals(SignalMast.class) && (lt.getSignalCMast() != null)) 1786 || (T.equals(Sensor.class) && (lt.getSensorC() != null))) { 1787 if (tr == null) { 1788 if (lt.getConnectC() instanceof TrackSegment) { 1789 TrackSegment t = (TrackSegment) lt.getConnectC(); 1790 1791 if ((t.getLayoutBlock() != null) && (t.getLayoutBlock() == lt.getLayoutBlockC())) { 1792 if (T.equals(SignalMast.class)) { 1793 return lt.getSignalCMast(); 1794 } else if (T.equals(Sensor.class)) { 1795 return lt.getSensorC(); 1796 } 1797 } 1798 } 1799 } else if (tr.getLayoutBlock().getBlock() == facingBlock) { 1800 if (T.equals(SignalMast.class)) { 1801 return lt.getSignalCMast(); 1802 } else if (T.equals(Sensor.class)) { 1803 return lt.getSensorC(); 1804 } 1805 } 1806 } 1807 return null; 1808 } 1809 1810 if (cType == HitPointType.TURNOUT_D) { 1811 lt = (LayoutTurnout) connected; 1812 1813 if ((T.equals(SignalMast.class) && (lt.getSignalDMast() != null)) 1814 || (T.equals(Sensor.class) && (lt.getSensorD() != null))) { 1815 if (tr == null) { 1816 if (lt.getConnectD() instanceof TrackSegment) { 1817 TrackSegment t = (TrackSegment) lt.getConnectD(); 1818 1819 if ((t.getLayoutBlock() != null) && (t.getLayoutBlock() == lt.getLayoutBlockD())) { 1820 if (T.equals(SignalMast.class)) { 1821 return lt.getSignalDMast(); 1822 } else if (T.equals(Sensor.class)) { 1823 return lt.getSensorD(); 1824 } 1825 } 1826 } 1827 } else if (tr.getLayoutBlock().getBlock() == facingBlock) { 1828 if (T.equals(SignalMast.class)) { 1829 return lt.getSignalDMast(); 1830 } else if (T.equals(Sensor.class)) { 1831 return lt.getSensorD(); 1832 } 1833 } 1834 } 1835 return null; 1836 } 1837 1838 if ((tr == null) || (tr.getLayoutBlock().getBlock() != facingBlock)) { 1839 return null; 1840 } 1841 1842 if (HitPointType.isSlipHitType(cType)) { 1843 LayoutSlip ls = (LayoutSlip) connected; 1844 1845 if (cType == HitPointType.SLIP_A) { 1846 if (T.equals(SignalMast.class)) { 1847 return ls.getSignalAMast(); 1848 } else if (T.equals(Sensor.class)) { 1849 return ls.getSensorA(); 1850 } 1851 } 1852 1853 if (cType == HitPointType.SLIP_B) { 1854 if (T.equals(SignalMast.class)) { 1855 return ls.getSignalBMast(); 1856 } else if (T.equals(Sensor.class)) { 1857 return ls.getSensorB(); 1858 } 1859 } 1860 1861 if (cType == HitPointType.SLIP_C) { 1862 if (T.equals(SignalMast.class)) { 1863 return ls.getSignalCMast(); 1864 } else if (T.equals(Sensor.class)) { 1865 return ls.getSensorC(); 1866 } 1867 } 1868 1869 if (cType == HitPointType.SLIP_D) { 1870 if (T.equals(SignalMast.class)) { 1871 return ls.getSignalDMast(); 1872 } else if (T.equals(Sensor.class)) { 1873 return ls.getSensorD(); 1874 } 1875 } 1876 } 1877 1878 if (!HitPointType.isLevelXingHitType(cType)) { 1879 log.error("Block Boundary not identified correctly - Blocks {}, {}", facingBlock.getDisplayName(), 1880 protectedBlock.getDisplayName()); 1881 1882 return null; 1883 } 1884 1885 /* We don't allow signal masts on the block outward facing from the level 1886 xing, nor do we consider the signal mast, that is protecting the in block on the xing */ 1887 LevelXing xing = (LevelXing) connected; 1888 1889 if (cType == HitPointType.LEVEL_XING_A) { 1890 //block boundary is at the A connection of a level crossing 1891 if (T.equals(SignalMast.class)) { 1892 return xing.getSignalAMast(); 1893 } else if (T.equals(Sensor.class)) { 1894 return xing.getSensorA(); 1895 } 1896 } 1897 1898 if (cType == HitPointType.LEVEL_XING_B) { 1899 //block boundary is at the B connection of a level crossing 1900 if (T.equals(SignalMast.class)) { 1901 return xing.getSignalBMast(); 1902 } else if (T.equals(Sensor.class)) { 1903 return xing.getSensorB(); 1904 } 1905 } 1906 1907 if (cType == HitPointType.LEVEL_XING_C) { 1908 //block boundary is at the C connection of a level crossing 1909 if (T.equals(SignalMast.class)) { 1910 return xing.getSignalCMast(); 1911 } else if (T.equals(Sensor.class)) { 1912 return xing.getSensorC(); 1913 } 1914 } 1915 1916 if (cType == HitPointType.LEVEL_XING_D) { 1917 if (T.equals(SignalMast.class)) { 1918 return xing.getSignalDMast(); 1919 } else if (T.equals(Sensor.class)) { 1920 return xing.getSensorD(); 1921 } 1922 } 1923 return null; 1924 } //getFacingBean 1925 1926 /** 1927 * In the first instance get a Signal Mast or if none exists a Signal Head 1928 * for a given facing block and protected block combination. See 1929 * #getFacingSignalMast() and #getFacingSignalHead() as to how they deal 1930 * with what each returns. 1931 * @param facingBlock the facing block to search for. 1932 * @param protectedBlock the protected block to search for. 1933 * 1934 * @return either a signalMast or signalHead 1935 */ 1936 @CheckReturnValue 1937 @CheckForNull 1938 public Object getFacingSignalObject( 1939 @Nonnull Block facingBlock, 1940 @CheckForNull Block protectedBlock) { 1941 Object sig = getFacingSignalMast(facingBlock, protectedBlock, null); 1942 1943 if (sig != null) { 1944 return sig; 1945 } 1946 sig = getFacingSignalHead(facingBlock, protectedBlock); 1947 return sig; 1948 } 1949 1950 /** 1951 * Get the block that a given bean object (Sensor, SignalMast or SignalHead) 1952 * is protecting. 1953 * 1954 * @param nb NamedBean 1955 * @param panel panel that this bean is on 1956 * @return The block that the bean object is facing 1957 */ 1958 @CheckReturnValue 1959 @CheckForNull 1960 public LayoutBlock getProtectedBlockByNamedBean( 1961 @CheckForNull NamedBean nb, 1962 @CheckForNull LayoutEditor panel) { 1963 if (nb instanceof SignalHead) { 1964 return getProtectedBlock((SignalHead) nb, panel); 1965 } 1966 List<LayoutBlock> proBlocks = getProtectingBlocksByBean(nb, panel); 1967 1968 if (proBlocks.isEmpty()) { 1969 return null; 1970 } 1971 return proBlocks.get(0); 1972 } //getProtectedBlockByNamedBean 1973 1974 @CheckReturnValue 1975 @Nonnull 1976 public List<LayoutBlock> getProtectingBlocksByNamedBean( 1977 @CheckForNull NamedBean nb, 1978 @CheckForNull LayoutEditor panel) { 1979 ArrayList<LayoutBlock> ret = new ArrayList<>(); 1980 1981 if (nb instanceof SignalHead) { 1982 ret.add(getProtectedBlock((SignalHead) nb, panel)); 1983 return ret; 1984 } 1985 return getProtectingBlocksByBean(nb, panel); 1986 } 1987 1988 /** 1989 * If the panel variable is null, search all LE panels. This was added to 1990 * support multi panel entry/exit. 1991 * 1992 * @param bean The sensor, mast or head to be located. 1993 * @param panel The panel to search. If null, search all LE panels. 1994 * @return a list of protected layout blocks. 1995 */ 1996 @Nonnull 1997 private List<LayoutBlock> getProtectingBlocksByBean( 1998 @CheckForNull NamedBean bean, 1999 @CheckForNull LayoutEditor panel) { 2000 if (panel == null) { 2001 Set<LayoutEditor> panels = InstanceManager.getDefault(EditorManager.class).getAll(LayoutEditor.class); 2002 List<LayoutBlock> protectingBlocks = new ArrayList<>(); 2003 for (LayoutEditor p : panels) { 2004 protectingBlocks = getProtectingBlocksByBeanByPanel(bean, p); 2005 if (!protectingBlocks.isEmpty()) { 2006 break; 2007 } 2008 } 2009 return protectingBlocks; 2010 } else { 2011 return getProtectingBlocksByBeanByPanel(bean, panel); 2012 } 2013 } 2014 2015 @Nonnull 2016 private List<LayoutBlock> getProtectingBlocksByBeanByPanel( 2017 @CheckForNull NamedBean bean, 2018 @Nonnull LayoutEditor panel) { 2019 List<LayoutBlock> protectingBlocks = new ArrayList<>(); 2020 2021 // Check for turntable approach masts first, as they are a special case. Ignore NX sensor beans. 2022 for (LayoutTurntable turntable : panel.getLayoutTurntables()) { 2023 if (bean instanceof SignalMast && turntable.isApproachMast((SignalMast) bean)) { 2024 if (turntable.getLayoutBlock() != null) { 2025 protectingBlocks.add(turntable.getLayoutBlock()); 2026 return protectingBlocks; 2027 } 2028 } 2029 if (bean.equals(turntable.getExitSignalMast())) { 2030 for (LayoutTurntable.RayTrack ray : turntable.getRayTrackList()) { 2031 TrackSegment connectedTrack = ray.getConnect(); 2032 if (connectedTrack != null && connectedTrack.getLayoutBlock() != null) { 2033 if (!protectingBlocks.contains(connectedTrack.getLayoutBlock())) { 2034 protectingBlocks.add(connectedTrack.getLayoutBlock()); 2035 } 2036 } 2037 } 2038 return protectingBlocks; 2039 } 2040 } 2041 2042 // Check for traverser approach masts first, as they are a special case. Ignore NX sensor beans. 2043 for (LayoutTraverser traverser : panel.getLayoutTraversers()) { 2044 if (bean instanceof SignalMast && traverser.isApproachMast((SignalMast) bean)) { 2045 if (traverser.getLayoutBlock() != null) { 2046 protectingBlocks.add(traverser.getLayoutBlock()); 2047 return protectingBlocks; 2048 } 2049 } 2050 if (bean.equals(traverser.getExitSignalMast())) { 2051 for (int i=0; i < traverser.getNumberSlots(); i++) { 2052 TrackSegment connectedTrack = traverser.getSlotConnectOrdered(i); 2053 if (connectedTrack != null && connectedTrack.getLayoutBlock() != null) { 2054 if (!protectingBlocks.contains(connectedTrack.getLayoutBlock())) { 2055 protectingBlocks.add(connectedTrack.getLayoutBlock()); 2056 } 2057 } 2058 } 2059 return protectingBlocks; 2060 } 2061 } 2062 2063 if (!(bean instanceof SignalMast) && !(bean instanceof Sensor)) { 2064 log.error("Incorrect class type called, must be either SignalMast or Sensor"); 2065 2066 return protectingBlocks; 2067 } 2068 2069 PositionablePoint pp = panel.getFinder().findPositionablePointByEastBoundBean(bean); 2070 TrackSegment tr; 2071 boolean east = true; 2072 2073 if (pp == null) { 2074 pp = panel.getFinder().findPositionablePointByWestBoundBean(bean); 2075 east = false; 2076 } 2077 2078 if (pp != null) { 2079 // LayoutEditorTools tools = panel.getLETools(); //TODO: Dead-code strip this 2080 2081 if (east) { 2082 if (LayoutEditorTools.isAtWestEndOfAnchor(panel, pp.getConnect1(), pp)) { 2083 tr = pp.getConnect2(); 2084 } else { 2085 tr = pp.getConnect1(); 2086 } 2087 } else { 2088 if (LayoutEditorTools.isAtWestEndOfAnchor(panel, pp.getConnect1(), pp)) { 2089 tr = pp.getConnect1(); 2090 } else { 2091 tr = pp.getConnect2(); 2092 } 2093 } 2094 2095 if (tr != null) { 2096 protectingBlocks.add(tr.getLayoutBlock()); 2097 2098 return protectingBlocks; 2099 } 2100 } 2101 2102 LevelXing l = panel.getFinder().findLevelXingByBean(bean); 2103 2104 if (l != null) { 2105 if (bean instanceof SignalMast) { 2106 if (l.getSignalAMast() == bean) { 2107 protectingBlocks.add(l.getLayoutBlockAC()); 2108 } else if (l.getSignalBMast() == bean) { 2109 protectingBlocks.add(l.getLayoutBlockBD()); 2110 } else if (l.getSignalCMast() == bean) { 2111 protectingBlocks.add(l.getLayoutBlockAC()); 2112 } else { 2113 protectingBlocks.add(l.getLayoutBlockBD()); 2114 } 2115 } else if (bean instanceof Sensor) { 2116 if (l.getSensorA() == bean) { 2117 protectingBlocks.add(l.getLayoutBlockAC()); 2118 } else if (l.getSensorB() == bean) { 2119 protectingBlocks.add(l.getLayoutBlockBD()); 2120 } else if (l.getSensorC() == bean) { 2121 protectingBlocks.add(l.getLayoutBlockAC()); 2122 } else { 2123 protectingBlocks.add(l.getLayoutBlockBD()); 2124 } 2125 } 2126 return protectingBlocks; 2127 } 2128 2129 LayoutSlip ls = panel.getFinder().findLayoutSlipByBean(bean); 2130 2131 if (ls != null) { 2132 protectingBlocks.add(ls.getLayoutBlock()); 2133 2134 return protectingBlocks; 2135 } 2136 2137 LayoutTurnout t = panel.getFinder().findLayoutTurnoutByBean(bean); 2138 2139 if (t != null) { 2140 return t.getProtectedBlocks(bean); 2141 } 2142 return protectingBlocks; 2143 } //getProtectingBlocksByBean 2144 2145 @CheckReturnValue 2146 @CheckForNull 2147 public LayoutBlock getProtectedBlockByMast( 2148 @CheckForNull SignalMast signalMast, 2149 @CheckForNull LayoutEditor panel) { 2150 List<LayoutBlock> proBlocks = getProtectingBlocksByBean(signalMast, panel); 2151 2152 if (proBlocks.isEmpty()) { 2153 return null; 2154 } 2155 return proBlocks.get(0); 2156 } 2157 2158 /** 2159 * Get the LayoutBlock that a given sensor is protecting. 2160 * @param sensorName the sensor name to search for. 2161 * @param panel the layout editor panel. 2162 * @return the layout block, may be null. 2163 */ 2164 @CheckReturnValue 2165 @CheckForNull 2166 public LayoutBlock getProtectedBlockBySensor( 2167 @Nonnull String sensorName, 2168 @CheckForNull LayoutEditor panel) { 2169 Sensor sensor = InstanceManager.sensorManagerInstance().getSensor(sensorName); 2170 2171 return getProtectedBlockBySensor(sensor, panel); 2172 } 2173 2174 @Nonnull 2175 public List<LayoutBlock> getProtectingBlocksBySensor( 2176 @CheckForNull Sensor sensor, @CheckForNull LayoutEditor panel) { 2177 return getProtectingBlocksByBean(sensor, panel); 2178 } 2179 2180 @Nonnull 2181 public List<LayoutBlock> getProtectingBlocksBySensorOld( 2182 @CheckForNull Sensor sensor, @Nonnull LayoutEditor panel) { 2183 List<LayoutBlock> result = new ArrayList<>(); 2184 PositionablePoint pp = panel.getFinder().findPositionablePointByEastBoundBean(sensor); 2185 TrackSegment tr; 2186 boolean east = true; 2187 2188 if (pp == null) { 2189 pp = panel.getFinder().findPositionablePointByWestBoundBean(sensor); 2190 east = false; 2191 } 2192 2193 if (pp != null) { 2194 // LayoutEditorTools tools = panel.getLETools(); //TODO: Dead-code strip this 2195 2196 if (east) { 2197 if (LayoutEditorTools.isAtWestEndOfAnchor(panel, pp.getConnect1(), pp)) { 2198 tr = pp.getConnect2(); 2199 } else { 2200 tr = pp.getConnect1(); 2201 } 2202 } else { 2203 if (LayoutEditorTools.isAtWestEndOfAnchor(panel, pp.getConnect1(), pp)) { 2204 tr = pp.getConnect1(); 2205 } else { 2206 tr = pp.getConnect2(); 2207 } 2208 } 2209 2210 if (tr != null) { 2211 result.add(tr.getLayoutBlock()); 2212 2213 return result; 2214 } 2215 } 2216 2217 LevelXing l = panel.getFinder().findLevelXingByBean(sensor); 2218 2219 if (l != null) { 2220 if (l.getSensorA() == sensor) { 2221 result.add(l.getLayoutBlockAC()); 2222 } else if (l.getSensorB() == sensor) { 2223 result.add(l.getLayoutBlockBD()); 2224 } else if (l.getSensorC() == sensor) { 2225 result.add(l.getLayoutBlockAC()); 2226 } else { 2227 result.add(l.getLayoutBlockBD()); 2228 } 2229 return result; 2230 } 2231 LayoutSlip ls = panel.getFinder().findLayoutSlipByBean(sensor); 2232 2233 if (ls != null) { 2234 result.add(ls.getLayoutBlock()); 2235 2236 return result; 2237 } 2238 LayoutTurnout t = panel.getFinder().findLayoutTurnoutByBean(sensor); 2239 2240 if (t != null) { 2241 return t.getProtectedBlocks(sensor); 2242 } 2243 return result; 2244 } //getProtectingBlocksBySensorOld 2245 2246 /** 2247 * Get the LayoutBlock that a given sensor is protecting. 2248 * @param sensor sensor to search for. 2249 * @param panel layout editor panel to search. 2250 * @return the layout block, may be null. 2251 */ 2252 @CheckReturnValue 2253 @CheckForNull 2254 public LayoutBlock getProtectedBlockBySensor( 2255 @CheckForNull Sensor sensor, @CheckForNull LayoutEditor panel) { 2256 List<LayoutBlock> proBlocks = getProtectingBlocksByBean(sensor, panel); 2257 2258 if (proBlocks.isEmpty()) { 2259 return null; 2260 } 2261 return proBlocks.get(0); 2262 } 2263 2264 /** 2265 * Get the block facing a given bean object (Sensor, SignalMast or 2266 * SignalHead). 2267 * 2268 * @param nb NamedBean 2269 * @param panel panel that this bean is on 2270 * @return The block that the bean object is facing 2271 */ 2272 @CheckReturnValue 2273 @CheckForNull 2274 public LayoutBlock getFacingBlockByNamedBean( 2275 @Nonnull NamedBean nb, @CheckForNull LayoutEditor panel) { 2276 if (nb instanceof SignalHead) { 2277 return getFacingBlock((SignalHead) nb, panel); 2278 } 2279 return getFacingBlockByBean(nb, panel); 2280 } 2281 2282 /** 2283 * Get the LayoutBlock that a given sensor is facing. 2284 * @param sensorName the sensor name. 2285 * @param panel the layout editor panel. 2286 * @return the facing layout block, may be null. 2287 */ 2288 @CheckReturnValue 2289 @CheckForNull 2290 public LayoutBlock getFacingBlockBySensor(@Nonnull String sensorName, 2291 @CheckForNull LayoutEditor panel) { 2292 LayoutBlock result = null; //assume failure (pessimist!) 2293 if (panel != null) { 2294 Sensor sensor = InstanceManager.sensorManagerInstance().getSensor(sensorName); 2295 result = (sensor == null) ? null : getFacingBlockBySensor(sensor, panel); 2296 } 2297 return result; 2298 } 2299 2300 /** 2301 * Get the LayoutBlock that a given signal is facing. 2302 * @param signalMast the signal mast to search for. 2303 * @param panel the layout editor panel. 2304 * @return the layout block, may be null. 2305 */ 2306 @CheckReturnValue 2307 @CheckForNull 2308 public LayoutBlock getFacingBlockByMast( 2309 @Nonnull SignalMast signalMast, 2310 @Nonnull LayoutEditor panel) { 2311 return getFacingBlockByBean(signalMast, panel); 2312 } 2313 2314 /** 2315 * If the panel variable is null, search all LE panels. This was added to 2316 * support multi panel entry/exit. 2317 * 2318 * @param bean The sensor, mast or head to be located. 2319 * @param panel The panel to search. Search all LE panels if null. 2320 * @return the facing layout block. 2321 */ 2322 @CheckReturnValue 2323 @CheckForNull 2324 private LayoutBlock getFacingBlockByBean( 2325 @Nonnull NamedBean bean, 2326 LayoutEditor panel) { 2327 if (panel == null) { 2328 Set<LayoutEditor> panels = InstanceManager.getDefault(EditorManager.class).getAll(LayoutEditor.class); 2329 LayoutBlock returnBlock = null; 2330 for (LayoutEditor p : panels) { 2331 returnBlock = getFacingBlockByBeanByPanel(bean, p); 2332 if (returnBlock != null) { 2333 break; 2334 } 2335 } 2336 return returnBlock; 2337 } else { 2338 return getFacingBlockByBeanByPanel(bean, panel); 2339 } 2340 } 2341 2342 @CheckReturnValue 2343 @CheckForNull 2344 private LayoutBlock getFacingBlockByBeanByPanel( 2345 @Nonnull NamedBean bean, 2346 @Nonnull LayoutEditor panel) { 2347 // Check for turntable masts first, as they are a special case. Ignore NX sensor beans. 2348 for (LayoutTurntable turntable : panel.getLayoutTurntables()) { 2349 if (bean.equals(turntable.getBufferMast())) { 2350 return turntable.getLayoutBlock(); 2351 } 2352 if (bean.equals(turntable.getExitSignalMast())) { 2353 return turntable.getLayoutBlock(); 2354 } 2355 if (bean instanceof SignalMast && turntable.isApproachMast((SignalMast) bean)) { 2356 for (LayoutTurntable.RayTrack ray : turntable.getRayTrackList()) { 2357 if (bean.equals(ray.getApproachMast())) { 2358 TrackSegment connectedTrack = ray.getConnect(); 2359 if (connectedTrack != null && connectedTrack.getLayoutBlock() != null) { 2360 return connectedTrack.getLayoutBlock(); 2361 } 2362 } 2363 } 2364 } 2365 } 2366 // Check for traverser masts, as they are a special case. Ignore NX sensor beans. 2367 for (LayoutTraverser traverser : panel.getLayoutTraversers()) { 2368 if (bean.equals(traverser.getBufferMast())) { 2369 return traverser.getLayoutBlock(); 2370 } 2371 if (bean.equals(traverser.getExitSignalMast())) { 2372 return traverser.getLayoutBlock(); 2373 } 2374 if (bean instanceof SignalMast && traverser.isApproachMast((SignalMast) bean)) { 2375 for (LayoutTraverser.SlotTrack slot : traverser.getSlotList()) { 2376 if (bean.equals(slot.getApproachMast())) { 2377 TrackSegment connectedTrack = slot.getConnect(); 2378 if (connectedTrack != null && connectedTrack.getLayoutBlock() != null) { 2379 return connectedTrack.getLayoutBlock(); 2380 } 2381 } 2382 } 2383 } 2384 } 2385 2386 PositionablePoint pp = panel.getFinder().findPositionablePointByEastBoundBean(bean); 2387 TrackSegment tr; 2388 boolean east = true; 2389 2390 //Don't think that the logic for this is the right way round 2391 if (pp == null) { 2392 pp = panel.getFinder().findPositionablePointByWestBoundBean(bean); 2393 east = false; 2394 } 2395 2396 if (pp != null) { 2397 // LayoutEditorTools tools = panel.getLETools(); //TODO: Dead-code strip this 2398 2399 if (east) { 2400 if (LayoutEditorTools.isAtWestEndOfAnchor(panel, pp.getConnect1(), pp)) { 2401 tr = pp.getConnect1(); 2402 } else { 2403 tr = pp.getConnect2(); 2404 } 2405 } else { 2406 if (LayoutEditorTools.isAtWestEndOfAnchor(panel, pp.getConnect1(), pp)) { 2407 tr = pp.getConnect2(); 2408 } else { 2409 tr = pp.getConnect1(); 2410 } 2411 } 2412 2413 if (tr != null) { 2414 log.debug("found facing block by positionable point"); 2415 2416 return tr.getLayoutBlock(); 2417 } 2418 } 2419 LayoutTurnout t = panel.getFinder().findLayoutTurnoutByBean(bean); 2420 2421 if (t != null) { 2422 log.debug("found signalmast at turnout {}", t.getTurnout().getDisplayName()); 2423 Object connect = null; 2424 2425 if (bean instanceof SignalMast) { 2426 if (t.getSignalAMast() == bean) { 2427 connect = t.getConnectA(); 2428 } else if (t.getSignalBMast() == bean) { 2429 connect = t.getConnectB(); 2430 } else if (t.getSignalCMast() == bean) { 2431 connect = t.getConnectC(); 2432 } else { 2433 connect = t.getConnectD(); 2434 } 2435 } else if (bean instanceof Sensor) { 2436 if (t.getSensorA() == bean) { 2437 connect = t.getConnectA(); 2438 } else if (t.getSensorB() == bean) { 2439 connect = t.getConnectB(); 2440 } else if (t.getSensorC() == bean) { 2441 connect = t.getConnectC(); 2442 } else { 2443 connect = t.getConnectD(); 2444 } 2445 } 2446 2447 if (connect instanceof TrackSegment) { 2448 tr = (TrackSegment) connect; 2449 log.debug("return block {}", tr.getLayoutBlock().getDisplayName()); 2450 2451 return tr.getLayoutBlock(); 2452 } 2453 } 2454 2455 LevelXing l = panel.getFinder().findLevelXingByBean(bean); 2456 2457 if (l != null) { 2458 Object connect = null; 2459 2460 if (bean instanceof SignalMast) { 2461 if (l.getSignalAMast() == bean) { 2462 connect = l.getConnectA(); 2463 } else if (l.getSignalBMast() == bean) { 2464 connect = l.getConnectB(); 2465 } else if (l.getSignalCMast() == bean) { 2466 connect = l.getConnectC(); 2467 } else { 2468 connect = l.getConnectD(); 2469 } 2470 } else if (bean instanceof Sensor) { 2471 if (l.getSensorA() == bean) { 2472 connect = l.getConnectA(); 2473 } else if (l.getSensorB() == bean) { 2474 connect = l.getConnectB(); 2475 } else if (l.getSensorC() == bean) { 2476 connect = l.getConnectC(); 2477 } else { 2478 connect = l.getConnectD(); 2479 } 2480 } 2481 2482 if (connect instanceof TrackSegment) { 2483 tr = (TrackSegment) connect; 2484 log.debug("return block {}", tr.getLayoutBlock().getDisplayName()); 2485 2486 return tr.getLayoutBlock(); 2487 } 2488 } 2489 2490 LayoutSlip ls = panel.getFinder().findLayoutSlipByBean(bean); 2491 2492 if (ls != null) { 2493 Object connect = null; 2494 2495 if (bean instanceof SignalMast) { 2496 if (ls.getSignalAMast() == bean) { 2497 connect = ls.getConnectA(); 2498 } else if (ls.getSignalBMast() == bean) { 2499 connect = ls.getConnectB(); 2500 } else if (ls.getSignalCMast() == bean) { 2501 connect = ls.getConnectC(); 2502 } else { 2503 connect = ls.getConnectD(); 2504 } 2505 } else if (bean instanceof Sensor) { 2506 if (ls.getSensorA() == bean) { 2507 connect = ls.getConnectA(); 2508 } else if (ls.getSensorB() == bean) { 2509 connect = ls.getConnectB(); 2510 } else if (ls.getSensorC() == bean) { 2511 connect = ls.getConnectC(); 2512 } else { 2513 connect = ls.getConnectD(); 2514 } 2515 } 2516 2517 if (connect instanceof TrackSegment) { 2518 tr = (TrackSegment) connect; 2519 log.debug("return block {}", tr.getLayoutBlock().getDisplayName()); 2520 2521 return tr.getLayoutBlock(); 2522 } 2523 } 2524 return null; 2525 } //getFacingBlockByBean 2526 2527 /** 2528 * Get the LayoutBlock that a given sensor is facing. 2529 * @param sensor the sensor to search for. 2530 * @param panel the layout editor panel to search. 2531 * @return the layout block, may be null. 2532 */ 2533 @CheckReturnValue 2534 @CheckForNull 2535 public LayoutBlock getFacingBlockBySensor( 2536 @Nonnull Sensor sensor, 2537 @Nonnull LayoutEditor panel) { 2538 return getFacingBlockByBean(sensor, panel); 2539 } 2540 2541 @CheckReturnValue 2542 @CheckForNull 2543 public LayoutBlock getProtectedBlock( 2544 @Nonnull SignalHead signalHead, @CheckForNull LayoutEditor panel) { 2545 LayoutBlock result = null; //assume failure (pessimist!) 2546 if (panel != null) { 2547 String userName = signalHead.getUserName(); 2548 result = (userName == null) ? null : getProtectedBlock(userName, panel); 2549 2550 if (result == null) { 2551 result = getProtectedBlock(signalHead.getSystemName(), panel); 2552 } 2553 } 2554 return result; 2555 } 2556 2557 /** 2558 * Get the LayoutBlock that a given signal is protecting. 2559 * @param signalName the signal name to search for. 2560 * @param panel the main layout editor panel. 2561 * @return the layout block, may be null. 2562 */ 2563 /* @TODO This needs to be expanded to cover turnouts and level crossings. */ 2564 @CheckReturnValue 2565 @CheckForNull 2566 public LayoutBlock getProtectedBlock( 2567 @Nonnull String signalName, @Nonnull LayoutEditor panel) { 2568 PositionablePoint pp = panel.getFinder().findPositionablePointByEastBoundSignal(signalName); 2569 TrackSegment tr; 2570 2571 if (pp == null) { 2572 pp = panel.getFinder().findPositionablePointByWestBoundSignal(signalName); 2573 2574 if (pp == null) { 2575 return null; 2576 } 2577 tr = pp.getConnect1(); 2578 } else { 2579 tr = pp.getConnect2(); 2580 } 2581 2582 //tr = pp.getConnect2(); 2583 if (tr == null) { 2584 return null; 2585 } 2586 return tr.getLayoutBlock(); 2587 } 2588 2589 @CheckReturnValue 2590 @CheckForNull 2591 public LayoutBlock getFacingBlock( 2592 @Nonnull SignalHead signalHead, @CheckForNull LayoutEditor panel) { 2593 LayoutBlock result = null; //assume failure (pessimist!) 2594 if (panel != null) { 2595 String userName = signalHead.getUserName(); 2596 result = (userName == null) ? null : getFacingBlock(userName, panel); 2597 if (result == null) { 2598 result = getFacingBlock(signalHead.getSystemName(), panel); 2599 } 2600 } 2601 return result; 2602 } 2603 2604 /** 2605 * Get the LayoutBlock that a given signal is facing. 2606 * @param signalName signal name. 2607 * @param panel layout editor panel. 2608 * @return the facing layout block. 2609 */ 2610 /* @TODO This needs to be expanded to cover turnouts and level crossings. */ 2611 @CheckReturnValue 2612 @CheckForNull 2613 public LayoutBlock getFacingBlock( 2614 @Nonnull String signalName, @Nonnull LayoutEditor panel) { 2615 PositionablePoint pp = panel.getFinder().findPositionablePointByWestBoundSignal(signalName); 2616 TrackSegment tr; 2617 2618 if (pp == null) { 2619 pp = panel.getFinder().findPositionablePointByWestBoundSignal(signalName); 2620 2621 if (pp == null) { 2622 return null; 2623 } 2624 tr = pp.getConnect1(); 2625 } else { 2626 tr = pp.getConnect2(); 2627 } 2628 2629 if (tr == null) { 2630 return null; 2631 } 2632 return tr.getLayoutBlock(); 2633 } 2634 2635 private boolean warnConnectivity = true; 2636 2637 /** 2638 * Controls switching off incompatible block connectivity messages. 2639 * <p> 2640 * Warnings are always on when program starts up. Once stopped by the user, 2641 * these messages may not be switched on again until program restarts. 2642 * @return true if connectivity warning flag set, else false. 2643 */ 2644 public boolean warn() { 2645 return warnConnectivity; 2646 } 2647 2648 public void turnOffWarning() { 2649 warnConnectivity = false; 2650 } 2651 2652 protected boolean enableAdvancedRouting = false; 2653 2654 /** 2655 * @return true if advanced layout block routing has been enabled 2656 */ 2657 public boolean isAdvancedRoutingEnabled() { 2658 return enableAdvancedRouting; 2659 } 2660 2661 /** 2662 * Enable the advanced layout block routing protocol 2663 * <p> 2664 * The block routing protocol enables each layout block to build up a list 2665 * of all reachable blocks, along with how far away they are, which 2666 * direction they are in and which of the connected blocks they are 2667 * reachable from. 2668 */ 2669 private long firstRoutingChange; 2670 2671 public void enableAdvancedRouting(boolean boo) { 2672 if (boo == enableAdvancedRouting) { 2673 return; 2674 } 2675 enableAdvancedRouting = boo; 2676 2677 if (boo && initialized) { 2678 initializeLayoutBlockRouting(); 2679 } 2680 firePropertyChange(PROPERTY_ADVANCED_ROUTING_ENABLED, !enableAdvancedRouting, enableAdvancedRouting); 2681 } 2682 2683 private void initializeLayoutBlockRouting() { 2684 if (!enableAdvancedRouting || !initialized) { 2685 log.debug("initializeLayoutBlockRouting immediate return due to {} {}", enableAdvancedRouting, initialized); 2686 2687 return; 2688 } 2689 if (!ThreadingUtil.isLayoutThread()) { 2690 // routing tables are only changed on the layout thread 2691 ThreadingUtil.runOnLayout(this::initializeLayoutBlockRouting); 2692 return; 2693 } 2694 firstRoutingChange = System.nanoTime(); 2695 2696 //cycle through all LayoutBlocks, completing initialization of the layout block routing 2697 java.util.Enumeration<LayoutBlock> en = _tsys.elements(); 2698 2699 while (en.hasMoreElements()) { 2700 en.nextElement().initializeLayoutBlockRouting(); 2701 } 2702 } 2703 2704 @Nonnull 2705 public LayoutBlockConnectivityTools getLayoutBlockConnectivityTools() { 2706 return lbct; 2707 } 2708 2709 private final LayoutBlockConnectivityTools lbct = new LayoutBlockConnectivityTools(); 2710 2711 private volatile long lastRoutingChange; 2712 2713 void setLastRoutingChange() { 2714 log.debug("setLastRoutingChange"); 2715 synchronized (stabilisedLock) { 2716 lastRoutingChange = System.nanoTime(); 2717 stabilised = false; 2718 } 2719 setRoutingStabilised(); 2720 } 2721 2722 // guards lastRoutingChange, stabilised and checking together, so that a 2723 // change can not be made between finding the tables unchanged and 2724 // reporting them as stable; the events are posted to the layout thread 2725 // while holding it, so they arrive in the order the changes were made 2726 private final Object stabilisedLock = new Object(); 2727 private boolean checking = false; 2728 volatile boolean stabilised = false; 2729 2730 // how long the routing tables must be unchanged to be reported as stable 2731 private long stabilisedDelayMsec = 2000L; 2732 2733 /** 2734 * Set how long the routing tables must be unchanged before the routing 2735 * is reported as stable. For tests. 2736 * 2737 * @param msec the time in milliseconds, 2000 by default 2738 */ 2739 void setStabilisedDelay(long msec) { 2740 stabilisedDelayMsec = msec; 2741 } 2742 2743 public void setRoutingStabilised() { 2744 synchronized (stabilisedLock) { 2745 if (checking) { 2746 return; 2747 } 2748 log.debug("routing table change has been initiated"); 2749 checking = true; 2750 stabilised = false; 2751 2752 ThreadingUtil.runOnLayoutEventually(() -> { 2753 if (namedStabilisedIndicator != null) { 2754 try { 2755 namedStabilisedIndicator.getBean().setState(Sensor.INACTIVE); 2756 } catch (JmriException ex) { 2757 log.debug("Error setting stability indicator sensor"); 2758 } 2759 } 2760 firePropertyChange(PROPERTY_TOPOLOGY, true, false); 2761 }); 2762 } 2763 long delay = stabilisedDelayMsec; 2764 Runnable r = () -> { 2765 try { 2766 long oldvalue = lastRoutingChange; 2767 2768 while (true) { 2769 Thread.sleep(delay); 2770 2771 synchronized (stabilisedLock) { 2772 if (oldvalue == lastRoutingChange) { 2773 log.debug("routing table has now been stable for {} msec", delay); 2774 checking = false; 2775 stabilised = true; 2776 ThreadingUtil.runOnLayoutEventually(this::reportRoutingStabilised); 2777 break; 2778 } 2779 } 2780 long seconds = (long) ((lastRoutingChange - firstRoutingChange) / 1e9); 2781 log.debug("routing table not stable after {} in {}", 2782 String.format("%d:%02d:%02d", seconds / 3600, (seconds / 60) % 60, seconds % 60), 2783 Thread.currentThread().getName()); 2784 oldvalue = lastRoutingChange; 2785 } 2786 } catch (InterruptedException ex) { 2787 Thread.currentThread().interrupt(); 2788 synchronized (stabilisedLock) { 2789 checking = false; 2790 } 2791 } 2792 }; 2793 thr = ThreadingUtil.newThread(r, "Routing stabilising timer"); 2794 thr.start(); 2795 } //setRoutingStabilised 2796 2797 private void reportRoutingStabilised() { 2798 firePropertyChange(PROPERTY_TOPOLOGY, false, true); 2799 2800 if (namedStabilisedIndicator != null) { 2801 log.debug("Setting StabilisedIndicator Sensor {} ACTIVE", 2802 namedStabilisedIndicator.getBean().getDisplayName()); 2803 try { 2804 namedStabilisedIndicator.getBean().setState(Sensor.ACTIVE); 2805 } catch (JmriException ex) { 2806 log.debug("Error setting stability indicator sensor"); 2807 } 2808 } else { 2809 log.debug("Stable, no sensor to set"); 2810 } 2811 } 2812 2813 private Thread thr = null; 2814 2815 private NamedBeanHandle<Sensor> namedStabilisedIndicator; 2816 2817 /** 2818 * Assign a sensor to the routing protocol, that changes state dependant 2819 * upon if the routing protocol has stabilised or is under going a change. 2820 * @param pName sensor name, will be provided if not existing. 2821 * @throws jmri.JmriException if no sensor manager. 2822 * 2823 */ 2824 public void setStabilisedSensor(@Nonnull String pName) throws JmriException { 2825 if (InstanceManager.getNullableDefault(jmri.SensorManager.class) != null) { 2826 try { 2827 Sensor sensor = InstanceManager.sensorManagerInstance().provideSensor(pName); 2828 namedStabilisedIndicator = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle( 2829 pName, 2830 sensor); 2831 try { 2832 if (stabilised) { 2833 sensor.setState(Sensor.ACTIVE); 2834 } else { 2835 sensor.setState(Sensor.INACTIVE); 2836 } 2837 } catch (JmriException ex) { 2838 log.error("Error setting stablilty indicator sensor"); 2839 } 2840 } catch (IllegalArgumentException ex) { 2841 log.error("Sensor '{}' not available", pName); 2842 throw new JmriException("Sensor '" + pName + "' not available"); 2843 } 2844 } else { 2845 log.error("No SensorManager for this protocol"); 2846 throw new JmriException("No Sensor Manager Found"); 2847 } 2848 } 2849 2850 /** 2851 * Get the sensor used to indicate if the routing protocol has stabilised or 2852 * not. 2853 * @return routing stability sensor, may be null. 2854 */ 2855 public Sensor getStabilisedSensor() { 2856 if (namedStabilisedIndicator == null) { 2857 return null; 2858 } 2859 return namedStabilisedIndicator.getBean(); 2860 } 2861 2862 /** 2863 * Get the sensor used for the stability indication. 2864 * @return stability sensor, may be null. 2865 */ 2866 @CheckReturnValue 2867 @CheckForNull 2868 public NamedBeanHandle<Sensor> getNamedStabilisedSensor() { 2869 return namedStabilisedIndicator; 2870 } 2871 2872 /** 2873 * @return true if the layout block routing protocol has stabilised 2874 */ 2875 public boolean routingStablised() { 2876 return stabilised; 2877 } 2878 2879 /** 2880 * @return the time when the last routing change was made, recorded as 2881 * System.nanoTime() 2882 */ 2883 public long getLastRoutingChange() { 2884 return lastRoutingChange; 2885 } 2886 2887 @Override 2888 @Nonnull 2889 public String getBeanTypeHandled(boolean plural) { 2890 return Bundle.getMessage(plural ? "BeanNameLayoutBlocks" : "BeanNameLayoutBlock"); 2891 } 2892 2893 /** 2894 * {@inheritDoc} 2895 */ 2896 @Override 2897 public Class<LayoutBlock> getNamedBeanClass() { 2898 return LayoutBlock.class; 2899 } 2900 2901 /** 2902 * Get a list of layout blocks which this roster entry appears to be 2903 * occupying. A layout block is assumed to contain this roster entry if the 2904 * value of the underlying block is the RosterEntry itself, or a string with 2905 * the entry's id or dcc address. 2906 * 2907 * @param re the roster entry 2908 * @return list of layout block user names 2909 */ 2910 @Nonnull 2911 public List<LayoutBlock> getLayoutBlocksOccupiedByRosterEntry( 2912 @Nonnull RosterEntry re) { 2913 List<LayoutBlock> result = new ArrayList<>(); 2914 2915 BlockManager bm = InstanceManager.getDefault(BlockManager.class); 2916 List<Block> blockList = bm.getBlocksOccupiedByRosterEntry(re); 2917 for (Block block : blockList) { 2918 String uname = block.getUserName(); 2919 if (uname != null) { 2920 LayoutBlock lb = getByUserName(uname); 2921 if (lb != null) { 2922 result.add(lb); 2923 } 2924 } 2925 } 2926 return result; 2927 } 2928 2929 @Override 2930 public void dispose(){ 2931 InstanceManager.sensorManagerInstance().removeVetoableChangeListener(this); 2932 InstanceManager.memoryManagerInstance().removeVetoableChangeListener(this); 2933 super.dispose(); 2934 } 2935 2936 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LayoutBlockManager.class); 2937 2938}