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