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