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