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