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