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