001package jmri.jmrit.display.layoutEditor; 002 003import java.util.ArrayList; 004import java.util.List; 005import java.util.stream.Collectors; 006import javax.annotation.CheckForNull; 007import javax.annotation.CheckReturnValue; 008import javax.annotation.Nonnull; 009import jmri.Block; 010import jmri.EntryPoint; 011import jmri.InstanceManager; 012import jmri.SignalHead; 013import jmri.SignalMast; 014import jmri.Turnout; 015import jmri.jmrit.blockboss.BlockBossLogic; 016import jmri.jmrit.blockboss.BlockBossLogicProvider; 017 018/** 019 * ConnectivityUtil provides methods supporting use of layout connectivity 020 * available in Layout Editor panels. These tools allow outside classes to 021 * inquire into connectivity information contained in a specified Layout Editor 022 * panel. 023 * <p> 024 * Connectivity information is stored in the track diagram of a Layout Editor 025 * panel. The "connectivity graph" of the layout consists of nodes 026 * (LayoutTurnouts, LevelXings, and PositionablePoints) connected by lines 027 * (TrackSegments). These methods extract information from the connection graph 028 * and make it available. Each instance of ConnectivityUtil is associated with a 029 * specific Layout Editor panel, and is accessed via that LayoutEditor panel's 030 * 'getConnectivityUtil' method. 031 * <p> 032 * The methods in this module do not modify the Layout in any way, or change the 033 * state of items on the layout. They only provide information to allow other 034 * modules to do so as appropriate. For example, the "getTurnoutList" method 035 * provides information about the turnouts in a block, but does not test the 036 * state, or change the state, of any turnout. 037 * <p> 038 * The methods in this module are accessed via direct calls from the inquiring 039 * method. 040 * <p> 041 * A single object of this type, obtained via {@link LayoutEditor#getConnectivityUtil()} 042 * is shared across all instances of {@link LayoutBlock}. 043 * 044 * @author Dave Duchamp Copyright (c) 2009 045 * @author George Warner Copyright (c) 2017-2018 046 */ 047final public class ConnectivityUtil { 048 049 // constants 050 // operational instance variables 051 final private LayoutEditor layoutEditor; 052 final private LayoutEditorAuxTools auxTools; 053 final private LayoutBlockManager layoutBlockManager; 054 055 private final int TRACKNODE_CONTINUING = 0; 056 private final int TRACKNODE_DIVERGING = 1; 057 private final int TRACKNODE_DIVERGING_2ND_3WAY = 2; 058 059 private final BlockBossLogicProvider blockBossLogicProvider; 060 061 // constructor method 062 public ConnectivityUtil(LayoutEditor thePanel) { 063 layoutEditor = thePanel; 064 auxTools = layoutEditor.getLEAuxTools(); 065 layoutBlockManager = InstanceManager.getDefault(LayoutBlockManager.class); 066 blockBossLogicProvider = InstanceManager.getDefault(BlockBossLogicProvider.class); 067 } 068 069 private TrackSegment trackSegment = null; 070 private HitPointType prevConnectType = HitPointType.NONE; 071 private LayoutTrack prevConnectTrack = null; 072 private LayoutBlock currLayoutBlock = null; 073 private LayoutBlock nextLayoutBlock = null; 074 075 /** 076 * Provide a list of LayoutTurnouts in the specified Block, in order, 077 * beginning at the connection to the specified previous Block and 078 * continuing to the specified next Block. Also compiles a companion list of 079 * how the turnout should be set for the specified connectivity. The 080 * companion list can be accessed by "getTurnoutSettingList" immediately 081 * after this method returns. 082 * 083 * @param currBlock the block to list LayoutTurnouts in 084 * @param prevBlock the previous block 085 * @param nextBlock the following block 086 * @return the list of all turnouts in the block if prevBlock or nextBlock 087 * are null or the list of all turnouts required to transit 088 * currBlock between prevBlock and nextBlock; returns an empty list 089 * if prevBlock and nextBlock are not null and are not connected 090 */ 091 @Nonnull 092 public List<LayoutTrackExpectedState<LayoutTurnout>> getTurnoutList( 093 @CheckForNull Block currBlock, 094 @CheckForNull Block prevBlock, 095 @CheckForNull Block nextBlock) { 096 return getTurnoutList(currBlock, prevBlock, nextBlock, false); 097 } 098 099 /** 100 * Provide a list of LayoutTurnouts in the specified Block, in order, 101 * beginning at the connection to the specified previous Block and 102 * continuing to the specified next Block. Also compiles a companion list of 103 * how the turnout should be set for the specified connectivity. The 104 * companion list can be accessed by "getTurnoutSettingList" immediately 105 * after this method returns. 106 * 107 * @param currBlock the block to list LayoutTurnouts in 108 * @param prevBlock the previous block 109 * @param nextBlock the following block 110 * @param suppress true to prevent errors from being logged; false 111 * otherwise 112 * @return the list of all turnouts in the block if prevBlock or nextBlock 113 * are null or the list of all turnouts required to transit 114 * currBlock between prevBlock and nextBlock; returns an empty list 115 * if prevBlock and nextBlock are not null and are not connected 116 */ 117 @Nonnull 118 public List<LayoutTrackExpectedState<LayoutTurnout>> getTurnoutList( 119 @CheckForNull Block currBlock, 120 @CheckForNull Block prevBlock, 121 @CheckForNull Block nextBlock, 122 boolean suppress) { 123 List<LayoutTrackExpectedState<LayoutTurnout>> result = new ArrayList<>(); 124 125 // initialize 126 currLayoutBlock = null; 127 String currUserName = null; 128 if (currBlock != null) { 129 currUserName = currBlock.getUserName(); 130 if ((currUserName != null) && !currUserName.isEmpty()) { 131 currLayoutBlock = layoutBlockManager.getByUserName(currUserName); 132 } 133 } 134 135 LayoutBlock prevLayoutBlock = null; 136 if (prevBlock != null) { 137 String prevUserName = prevBlock.getUserName(); 138 if ((prevUserName != null) && !prevUserName.isEmpty()) { 139 prevLayoutBlock = layoutBlockManager.getByUserName(prevUserName); 140 } 141 } 142 143 nextLayoutBlock = null; 144 if (nextBlock != null) { 145 String nextUserName = nextBlock.getUserName(); 146 if ((nextUserName != null) && !nextUserName.isEmpty()) { 147 nextLayoutBlock = layoutBlockManager.getByUserName(nextUserName); 148 } 149 } 150 151 turnoutConnectivity = true; 152 if ((prevLayoutBlock == null) || (nextLayoutBlock == null)) { 153 // special search with partial information - not as good, order not assured 154 List<LayoutTurnout> allTurnouts = getAllTurnoutsThisBlock(currLayoutBlock); 155 for (LayoutTurnout lt : allTurnouts) { 156 result.add(new LayoutTrackExpectedState<>(lt, 157 lt.getConnectivityStateForLayoutBlocks( 158 currLayoutBlock, prevLayoutBlock, nextLayoutBlock, true))); 159 } 160 return result; 161 } 162 163 List<LayoutConnectivity> cList = auxTools.getConnectivityList(currLayoutBlock); 164 HitPointType cType; 165 // initialize the connectivity search, processing a turnout in this block if it is present 166 boolean notFound = true; 167 for (int i = 0; (i < cList.size()) && notFound; i++) { 168 LayoutConnectivity lc = cList.get(i); 169 if ((lc.getXover() != null) && (((lc.getBlock1() == currLayoutBlock) && (lc.getBlock2() == prevLayoutBlock)) 170 || ((lc.getBlock1() == prevLayoutBlock) && (lc.getBlock2() == currLayoutBlock)))) { 171 // have a block boundary in a crossover turnout, add turnout to the List 172 LayoutTurnout xt = lc.getXover(); 173 int setting = Turnout.THROWN; 174 // determine setting and setup track segment if there is one 175 trackSegment = null; 176 prevConnectTrack = xt; 177 switch (lc.getXoverBoundaryType()) { 178 case LayoutConnectivity.XOVER_BOUNDARY_AB: { 179 setting = Turnout.CLOSED; 180 if (((TrackSegment) xt.getConnectA() != null) && (currLayoutBlock == ((TrackSegment) xt.getConnectA()).getLayoutBlock())) { 181 // block exits Xover at A 182 trackSegment = (TrackSegment) xt.getConnectA(); 183 prevConnectType = HitPointType.TURNOUT_A; 184 } else if (((TrackSegment) xt.getConnectB() != null) && (currLayoutBlock == ((TrackSegment) xt.getConnectB()).getLayoutBlock())) { 185 // block exits Xover at B 186 trackSegment = (TrackSegment) xt.getConnectB(); 187 prevConnectType = HitPointType.TURNOUT_B; 188 } 189 break; 190 } 191 case LayoutConnectivity.XOVER_BOUNDARY_CD: { 192 setting = Turnout.CLOSED; 193 if (((TrackSegment) xt.getConnectC() != null) && (currLayoutBlock == ((TrackSegment) xt.getConnectC()).getLayoutBlock())) { 194 // block exits Xover at C 195 trackSegment = (TrackSegment) xt.getConnectC(); 196 prevConnectType = HitPointType.TURNOUT_C; 197 } else if (((TrackSegment) xt.getConnectD() != null) && (currLayoutBlock == ((TrackSegment) xt.getConnectD()).getLayoutBlock())) { 198 // block exits Xover at D 199 trackSegment = (TrackSegment) xt.getConnectD(); 200 prevConnectType = HitPointType.TURNOUT_D; 201 } 202 break; 203 } 204 case LayoutConnectivity.XOVER_BOUNDARY_AC: { 205 if (((TrackSegment) xt.getConnectA() != null) && (currLayoutBlock == ((TrackSegment) xt.getConnectA()).getLayoutBlock())) { 206 // block exits Xover at A 207 trackSegment = (TrackSegment) xt.getConnectA(); 208 prevConnectType = HitPointType.TURNOUT_A; 209 } else if (((TrackSegment) xt.getConnectC() != null) && (currLayoutBlock == ((TrackSegment) xt.getConnectC()).getLayoutBlock())) { 210 // block exits Xover at C 211 trackSegment = (TrackSegment) xt.getConnectC(); 212 prevConnectType = HitPointType.TURNOUT_C; 213 } 214 break; 215 } 216 case LayoutConnectivity.XOVER_BOUNDARY_BD: { 217 if (((TrackSegment) xt.getConnectB() != null) && (currLayoutBlock == ((TrackSegment) xt.getConnectB()).getLayoutBlock())) { 218 // block exits Xover at B 219 trackSegment = (TrackSegment) xt.getConnectB(); 220 prevConnectType = HitPointType.TURNOUT_B; 221 } else if (((TrackSegment) xt.getConnectD() != null) && (currLayoutBlock == ((TrackSegment) xt.getConnectD()).getLayoutBlock())) { 222 // block exits Xover at D 223 trackSegment = (TrackSegment) xt.getConnectD(); 224 prevConnectType = HitPointType.TURNOUT_D; 225 } 226 break; 227 } 228 default: { 229 log.error("Unhandled crossover boundary type: {}", lc.getXoverBoundaryType()); 230 break; 231 } 232 } 233 result.add(new LayoutTrackExpectedState<>(xt, setting)); 234 notFound = false; 235 } else if ((lc.getBlock1() == currLayoutBlock) && (lc.getBlock2() == prevLayoutBlock)) { 236 // no turnout or level crossing at the beginning of this block 237 trackSegment = lc.getTrackSegment(); 238 if (lc.getConnectedType() == HitPointType.TRACK) { 239 prevConnectType = HitPointType.POS_POINT; 240 prevConnectTrack = lc.getAnchor(); 241 } else { 242 prevConnectType = lc.getConnectedType(); 243 prevConnectTrack = lc.getConnectedObject(); 244 } 245 notFound = false; 246 } else if ((lc.getBlock2() == currLayoutBlock) && (lc.getBlock1() == prevLayoutBlock)) { 247 cType = lc.getConnectedType(); 248 // check for connection to a track segment 249 if (cType == HitPointType.TRACK) { 250 trackSegment = (TrackSegment) lc.getConnectedObject(); 251 prevConnectType = HitPointType.POS_POINT; 252 prevConnectTrack = lc.getAnchor(); 253 } // check for a level crossing 254 else if (HitPointType.isLevelXingHitType(cType)) { 255 // entering this Block at a level crossing, skip over it an initialize the next 256 // TrackSegment if there is one in this Block 257 setupOpposingTrackSegment((LevelXing) lc.getConnectedObject(), cType); 258 } // check for turnout 259 else if (HitPointType.isTurnoutHitType(cType)) { 260 // add turnout to list 261 result.add(new LayoutTrackExpectedState<>((LayoutTurnout) lc.getConnectedObject(), 262 getTurnoutSetting((LayoutTurnout) lc.getConnectedObject(), cType, suppress))); 263 } else if (HitPointType.isSlipHitType(cType)) { 264 result.add(new LayoutTrackExpectedState<>((LayoutTurnout) lc.getConnectedObject(), 265 getTurnoutSetting((LayoutTurnout) lc.getConnectedObject(), cType, suppress))); 266 } 267 notFound = false; 268 } 269 } 270 if (notFound) { 271 if (prevBlock != null) { // could not initialize the connectivity search 272 if (!suppress) { 273 log.warn("Could not find connection between Blocks {} and {}", currUserName, prevBlock.getUserName()); 274 } 275 } else if (!suppress) { 276 log.warn("Could not find connection between Blocks {}, prevBock is null!", currUserName); 277 } 278 return result; 279 } 280 // search connectivity for turnouts by following TrackSegments to end of Block 281 while (trackSegment != null) { 282 LayoutTrack cObject; 283 // identify next connection 284 if ((trackSegment.getConnect1() == prevConnectTrack) && (trackSegment.getType1() == prevConnectType)) { 285 cType = trackSegment.getType2(); 286 cObject = trackSegment.getConnect2(); 287 } else if ((trackSegment.getConnect2() == prevConnectTrack) && (trackSegment.getType2() == prevConnectType)) { 288 cType = trackSegment.getType1(); 289 cObject = trackSegment.getConnect1(); 290 } else { 291 if (!suppress) { 292 log.error("Connectivity error when searching turnouts in Block {}", currLayoutBlock.getDisplayName()); 293 log.warn("Track segment connected to {{}, {}} and {{}, {}} but previous object was {{}, {}}", 294 trackSegment.getConnect1(), trackSegment.getType1().name(), 295 trackSegment.getConnect2(), trackSegment.getType2().name(), 296 prevConnectTrack, prevConnectType); 297 } 298 trackSegment = null; 299 break; 300 } 301 if (cType == HitPointType.POS_POINT) { 302 // reached anchor point or end bumper 303 if (((PositionablePoint) cObject).getType() == PositionablePoint.PointType.END_BUMPER) { 304 // end of line 305 trackSegment = null; 306 } else if (((PositionablePoint) cObject).getType() == PositionablePoint.PointType.ANCHOR || (((PositionablePoint) cObject).getType() == PositionablePoint.PointType.EDGE_CONNECTOR)) { 307 // proceed to next track segment if within the same Block 308 if (((PositionablePoint) cObject).getConnect1() == trackSegment) { 309 trackSegment = ((PositionablePoint) cObject).getConnect2(); 310 } else { 311 trackSegment = ((PositionablePoint) cObject).getConnect1(); 312 } 313 if ((trackSegment == null) || (trackSegment.getLayoutBlock() != currLayoutBlock)) { 314 // track segment is not in this block 315 trackSegment = null; 316 } else { 317 prevConnectType = cType; 318 prevConnectTrack = cObject; 319 } 320 } 321 } else if (HitPointType.isLevelXingHitType(cType)) { 322 // reached a level crossing, is it within this block? 323 switch (cType) { 324 case LEVEL_XING_A: 325 case LEVEL_XING_C: { 326 if (((LevelXing) cObject).getLayoutBlockAC() != currLayoutBlock) { 327 // outside of block 328 trackSegment = null; 329 } else { 330 // same block 331 setupOpposingTrackSegment((LevelXing) cObject, cType); 332 } 333 break; 334 } 335 case LEVEL_XING_B: 336 case LEVEL_XING_D: { 337 if (((LevelXing) cObject).getLayoutBlockBD() != currLayoutBlock) { 338 // outside of block 339 trackSegment = null; 340 } else { 341 // same block 342 setupOpposingTrackSegment((LevelXing) cObject, cType); 343 } 344 break; 345 } 346 default: { 347 log.warn("Unhandled Level Crossing type: {}", cType); 348 break; 349 } 350 } 351 } else if (HitPointType.isTurnoutHitType(cType)) { 352 // reached a turnout 353 LayoutTurnout lt = (LayoutTurnout) cObject; 354 LayoutTurnout.TurnoutType tType = lt.getTurnoutType(); 355 // is this turnout a crossover turnout at least partly within this block? 356 if (LayoutTurnout.isTurnoutTypeXover(tType)) { 357 // reached a crossover turnout 358 switch (cType) { 359 case TURNOUT_A: 360 if ((lt.getLayoutBlock()) != currLayoutBlock) { 361 // connection is outside of the current block 362 trackSegment = null; 363 } else if (lt.getLayoutBlockB() == nextLayoutBlock) { 364 // exits Block at B 365 result.add(new LayoutTrackExpectedState<>((LayoutTurnout) cObject, Turnout.CLOSED)); 366 trackSegment = null; 367 } else if ((lt.getLayoutBlockC() == nextLayoutBlock) && (tType != LayoutTurnout.TurnoutType.LH_XOVER)) { 368 // exits Block at C, either Double or RH 369 result.add(new LayoutTrackExpectedState<>((LayoutTurnout) cObject, Turnout.THROWN)); 370 trackSegment = null; 371 } else if (lt.getLayoutBlockB() == currLayoutBlock) { 372 // block continues at B 373 result.add(new LayoutTrackExpectedState<>((LayoutTurnout) cObject, Turnout.CLOSED)); 374 trackSegment = (TrackSegment) lt.getConnectB(); 375 prevConnectType = HitPointType.TURNOUT_B; 376 prevConnectTrack = cObject; 377 } else if ((lt.getLayoutBlockC() == currLayoutBlock) && (tType != LayoutTurnout.TurnoutType.LH_XOVER)) { 378 // block continues at C, either Double or RH 379 result.add(new LayoutTrackExpectedState<>((LayoutTurnout) cObject, Turnout.THROWN)); 380 trackSegment = (TrackSegment) lt.getConnectC(); 381 prevConnectType = HitPointType.TURNOUT_C; 382 prevConnectTrack = cObject; 383 } else if (lt.getLayoutBlock() == currLayoutBlock && currLayoutBlock == nextLayoutBlock) { 384 //we are at our final destination so not an error such 385 trackSegment = null; 386 } else { 387 // no legal outcome found, print error 388 if (!suppress) { 389 log.warn("Connectivity mismatch at A in turnout {}", lt.getTurnoutName()); 390 } 391 trackSegment = null; 392 } 393 break; 394 case TURNOUT_B: 395 if ((lt.getLayoutBlockB()) != currLayoutBlock) { 396 // connection is outside of the current block 397 trackSegment = null; 398 } else if (lt.getLayoutBlock() == nextLayoutBlock) { 399 // exits Block at A 400 result.add(new LayoutTrackExpectedState<>((LayoutTurnout) cObject, Turnout.CLOSED)); 401 trackSegment = null; 402 } else if ((lt.getLayoutBlockD() == nextLayoutBlock) && (tType != LayoutTurnout.TurnoutType.RH_XOVER)) { 403 // exits Block at D, either Double or LH 404 result.add(new LayoutTrackExpectedState<>((LayoutTurnout) cObject, Turnout.THROWN)); 405 trackSegment = null; 406 } else if (lt.getLayoutBlock() == currLayoutBlock) { 407 // block continues at A 408 result.add(new LayoutTrackExpectedState<>((LayoutTurnout) cObject, Turnout.CLOSED)); 409 trackSegment = (TrackSegment) lt.getConnectA(); 410 prevConnectType = HitPointType.TURNOUT_A; 411 prevConnectTrack = cObject; 412 } else if ((lt.getLayoutBlockD() == currLayoutBlock) && (tType != LayoutTurnout.TurnoutType.RH_XOVER)) { 413 // block continues at D, either Double or LH 414 result.add(new LayoutTrackExpectedState<>((LayoutTurnout) cObject, Turnout.THROWN)); 415 trackSegment = (TrackSegment) lt.getConnectD(); 416 prevConnectType = HitPointType.TURNOUT_D; 417 prevConnectTrack = cObject; 418 } else if (lt.getLayoutBlockB() == currLayoutBlock && currLayoutBlock == nextLayoutBlock) { 419 //we are at our final destination so not an error such 420 trackSegment = null; 421 } else { 422 // no legal outcome found, print error 423 if (!suppress) { 424 log.warn("Connectivity mismatch at B in turnout {}", lt.getTurnoutName()); 425 } 426 trackSegment = null; 427 } 428 break; 429 case TURNOUT_C: 430 if ((lt.getLayoutBlockC()) != currLayoutBlock) { 431 // connection is outside of the current block 432 trackSegment = null; 433 } else if (lt.getLayoutBlockD() == nextLayoutBlock) { 434 // exits Block at D 435 result.add(new LayoutTrackExpectedState<>((LayoutTurnout) cObject, Turnout.CLOSED)); 436 trackSegment = null; 437 } else if ((lt.getLayoutBlock() == nextLayoutBlock) && (tType != LayoutTurnout.TurnoutType.LH_XOVER)) { 438 // exits Block at A, either Double or RH 439 result.add(new LayoutTrackExpectedState<>((LayoutTurnout) cObject, Turnout.THROWN)); 440 trackSegment = null; 441 } else if (lt.getLayoutBlockD() == currLayoutBlock) { 442 // block continues at D 443 result.add(new LayoutTrackExpectedState<>((LayoutTurnout) cObject, Turnout.CLOSED)); 444 trackSegment = (TrackSegment) lt.getConnectD(); 445 prevConnectType = HitPointType.TURNOUT_D; 446 prevConnectTrack = cObject; 447 } else if ((lt.getLayoutBlock() == currLayoutBlock) && (tType != LayoutTurnout.TurnoutType.LH_XOVER)) { 448 // block continues at A, either Double or RH 449 result.add(new LayoutTrackExpectedState<>((LayoutTurnout) cObject, Turnout.THROWN)); 450 trackSegment = (TrackSegment) lt.getConnectA(); 451 prevConnectType = HitPointType.TURNOUT_A; 452 prevConnectTrack = cObject; 453 } else if (lt.getLayoutBlockC() == currLayoutBlock && currLayoutBlock == nextLayoutBlock) { 454 //we are at our final destination so not an error such 455 trackSegment = null; 456 } else { 457 // no legal outcome found, print error 458 if (!suppress) { 459 log.warn("Connectivity mismatch at C in turnout {}", lt.getTurnoutName()); 460 } 461 trackSegment = null; 462 } 463 break; 464 case TURNOUT_D: 465 if ((lt.getLayoutBlockD()) != currLayoutBlock) { 466 // connection is outside of the current block 467 trackSegment = null; 468 } else if (lt.getLayoutBlockC() == nextLayoutBlock) { 469 // exits Block at C 470 result.add(new LayoutTrackExpectedState<>((LayoutTurnout) cObject, Turnout.CLOSED)); 471 trackSegment = null; 472 } else if ((lt.getLayoutBlockB() == nextLayoutBlock) && (tType != LayoutTurnout.TurnoutType.RH_XOVER)) { 473 // exits Block at B, either Double or LH 474 result.add(new LayoutTrackExpectedState<>((LayoutTurnout) cObject, Turnout.THROWN)); 475 trackSegment = null; 476 } else if (lt.getLayoutBlockC() == currLayoutBlock) { 477 // block continues at C 478 result.add(new LayoutTrackExpectedState<>((LayoutTurnout) cObject, Turnout.CLOSED)); 479 trackSegment = (TrackSegment) lt.getConnectC(); 480 prevConnectType = HitPointType.TURNOUT_C; 481 prevConnectTrack = cObject; 482 } else if ((lt.getLayoutBlockB() == currLayoutBlock) && (tType != LayoutTurnout.TurnoutType.RH_XOVER)) { 483 // block continues at B, either Double or LH 484 result.add(new LayoutTrackExpectedState<>((LayoutTurnout) cObject, Turnout.THROWN)); 485 trackSegment = (TrackSegment) lt.getConnectB(); 486 prevConnectType = HitPointType.TURNOUT_B; 487 prevConnectTrack = cObject; 488 } else if (lt.getLayoutBlockD() == currLayoutBlock && currLayoutBlock == nextLayoutBlock) { 489 //we are at our final destination so not an error such 490 trackSegment = null; 491 } else { 492 // no legal outcome found, print error 493 if (!suppress) { 494 log.warn("Connectivity mismatch at D in turnout {}", lt.getTurnoutName()); 495 } 496 trackSegment = null; 497 } 498 break; 499 default: { 500 log.warn("Unhandled crossover type: {}", cType); 501 break; 502 } 503 } 504 } else if (LayoutTurnout.isTurnoutTypeTurnout(tType)) { 505 // reached RH. LH, or WYE turnout, is it in the current Block? 506 if (lt.getLayoutBlock() != currLayoutBlock) { 507 // turnout is outside of current block 508 trackSegment = null; 509 } else { 510 // turnout is inside current block, add it to the list 511 result.add(new LayoutTrackExpectedState<>((LayoutTurnout) cObject, getTurnoutSetting(lt, cType, suppress))); 512 } 513 } 514 } else if (HitPointType.isSlipHitType(cType)) { 515 // reached a LayoutSlip 516 LayoutSlip ls = (LayoutSlip) cObject; 517 if (((cType == HitPointType.SLIP_A) && (ls.getLayoutBlock() != currLayoutBlock)) 518 || ((cType == HitPointType.SLIP_B) && (ls.getLayoutBlockB() != currLayoutBlock)) 519 || ((cType == HitPointType.SLIP_C) && (ls.getLayoutBlockC() != currLayoutBlock)) 520 || ((cType == HitPointType.SLIP_D) && (ls.getLayoutBlockD() != currLayoutBlock))) { 521 //Slip is outside of the current block 522 trackSegment = null; 523 } else { 524 // turnout is inside current block, add it to the list 525 result.add(new LayoutTrackExpectedState<>(ls, getTurnoutSetting(ls, cType, suppress))); 526 } 527 } else if (HitPointType.isTurntableRayHitType(cType)) { 528 // Declare arrival at a turntable ray to be the end of the block 529 trackSegment = null; 530 } 531 } 532 return result; 533 } 534 535 /** 536 * Get a list of all Blocks connected to a specified Block. 537 * 538 * @param block the block to get connections for 539 * @return connected blocks or an empty list if none 540 */ 541 @Nonnull 542 public List<Block> getConnectedBlocks(@Nonnull Block block 543 ) { 544 List<Block> result = new ArrayList<>(); 545 // 546 //TODO: Dead-code strip (after 4.9.x) 547 // dissusion: lBlock could be used to match against getBlock1 & 2... 548 // instead of matching against block == getBlock() 549 // 550 //String userName = block.getUserName(); 551 //LayoutBlock lBlock = null; 552 //if ((userName != null) && !userName.isEmpty()) { 553 // lBlock = layoutBlockManager.getByUserName(userName); 554 //} 555 List<LayoutConnectivity> cList = auxTools.getConnectivityList(currLayoutBlock); 556 for (LayoutConnectivity lc : cList) { 557 if (lc.getBlock1().getBlock() == block) { 558 result.add((lc.getBlock2()).getBlock()); 559 } else if (lc.getBlock2().getBlock() == block) { 560 result.add((lc.getBlock1()).getBlock()); 561 } 562 } 563 return result; 564 } 565 566 /** 567 * Get a list of all anchor point boundaries involving the specified Block. 568 * 569 * @param block the block to get anchor point boundaries for 570 * @return a list of anchor point boundaries 571 */ 572 @Nonnull 573 public List<PositionablePoint> getAnchorBoundariesThisBlock( 574 @Nonnull Block block 575 ) { 576 List<PositionablePoint> result = new ArrayList<>(); 577 String userName = block.getUserName(); 578 LayoutBlock lBlock = null; 579 if ((userName != null) && !userName.isEmpty()) { 580 lBlock = layoutBlockManager.getByUserName(userName); 581 } 582 for (PositionablePoint p : layoutEditor.getPositionablePoints()) { 583 if ((p.getConnect2() != null) && (p.getConnect1() != null)) { 584 if ((p.getConnect2().getLayoutBlock() != null) 585 && (p.getConnect1().getLayoutBlock() != null)) { 586 if ((((p.getConnect1()).getLayoutBlock() == lBlock) 587 && ((p.getConnect2()).getLayoutBlock() != lBlock)) 588 || (((p.getConnect1()).getLayoutBlock() != lBlock) 589 && ((p.getConnect2()).getLayoutBlock() == lBlock))) { 590 result.add(p); 591 } 592 } 593 } 594 } 595 return result; 596 } 597 598 /** 599 * Get a list of all LevelXings involving the specified Block. To be listed, 600 * a LevelXing must have all its four connections and all blocks must be 601 * assigned. If any connection is missing, or if a block assignment is 602 * missing, an error message is printed and the level crossing is not added 603 * to the list. 604 * 605 * @param block the block to check 606 * @return a list of all complete LevelXings 607 */ 608 @Nonnull 609 public List<LevelXing> getLevelCrossingsThisBlock(@Nonnull Block block 610 ) { 611 List<LevelXing> result = new ArrayList<>(); 612 String userName = block.getUserName(); 613 LayoutBlock lBlock = null; 614 if ((userName != null) && !userName.isEmpty()) { 615 lBlock = layoutBlockManager.getByUserName(userName); 616 } 617 for (LevelXing x : layoutEditor.getLevelXings()) { 618 boolean found = false; 619 if ((x.getLayoutBlockAC() == lBlock) || (x.getLayoutBlockBD() == lBlock)) { 620 found = true; 621 } else if ((x.getConnectA() != null) && (((TrackSegment) x.getConnectA()).getLayoutBlock() == lBlock)) { 622 found = true; 623 } else if ((x.getConnectB() != null) && (((TrackSegment) x.getConnectB()).getLayoutBlock() == lBlock)) { 624 found = true; 625 } else if ((x.getConnectC() != null) && (((TrackSegment) x.getConnectC()).getLayoutBlock() == lBlock)) { 626 found = true; 627 } else if ((x.getConnectD() != null) && (((TrackSegment) x.getConnectD()).getLayoutBlock() == lBlock)) { 628 found = true; 629 } 630 if (found) { 631 if ((x.getConnectA() != null) && (((TrackSegment) x.getConnectA()).getLayoutBlock() != null) 632 && (x.getConnectB() != null) && (((TrackSegment) x.getConnectB()).getLayoutBlock() != null) 633 && (x.getConnectC() != null) && (((TrackSegment) x.getConnectC()).getLayoutBlock() != null) 634 && (x.getConnectD() != null) && (((TrackSegment) x.getConnectD()).getLayoutBlock() != null) 635 && (x.getLayoutBlockAC() != null) && (x.getLayoutBlockBD() != null)) { 636 result.add(x); 637 } else { 638 log.error("Missing connection or block assignment at Level Crossing in Block {}", block.getDisplayName()); 639 } 640 } 641 } 642 return result; 643 } 644 645 /** 646 * Get a list of all layout turnouts involving the specified Block. 647 * 648 * @param block the Block to get layout turnouts for 649 * @return the list of associated layout turnouts or an empty list if none 650 */ 651 @Nonnull 652 public List<LayoutTurnout> getLayoutTurnoutsThisBlock(@Nonnull Block block 653 ) { 654 List<LayoutTurnout> result = new ArrayList<>(); 655 String userName = block.getUserName(); 656 LayoutBlock lBlock = null; 657 if ((userName != null) && !userName.isEmpty()) { 658 lBlock = layoutBlockManager.getByUserName(userName); 659 } 660 for (LayoutTurnout t : layoutEditor.getLayoutTurnouts()) { 661 if ((t.getBlockName().equals(userName)) || (t.getBlockBName().equals(userName)) 662 || (t.getBlockCName().equals(userName)) || (t.getBlockDName().equals(userName))) { 663 result.add(t); 664 } else if ((t.getConnectA() != null) && (((TrackSegment) t.getConnectA()).getLayoutBlock() == lBlock)) { 665 result.add(t); 666 } else if ((t.getConnectB() != null) && (((TrackSegment) t.getConnectB()).getLayoutBlock() == lBlock)) { 667 result.add(t); 668 } else if ((t.getConnectC() != null) && (((TrackSegment) t.getConnectC()).getLayoutBlock() == lBlock)) { 669 result.add(t); 670 } else if ((t.getConnectD() != null) && (((TrackSegment) t.getConnectD()).getLayoutBlock() == lBlock)) { 671 result.add(t); 672 } 673 } 674 for (LayoutTurnout ls : layoutEditor.getLayoutTurnouts()) { 675 if (ls.getBlockName().equals(userName)) { 676 result.add(ls); 677 } else if ((ls.getConnectA() != null) && (((TrackSegment) ls.getConnectA()).getLayoutBlock() == lBlock)) { 678 result.add(ls); 679 } else if ((ls.getConnectB() != null) && (((TrackSegment) ls.getConnectB()).getLayoutBlock() == lBlock)) { 680 result.add(ls); 681 } else if ((ls.getConnectC() != null) && (((TrackSegment) ls.getConnectC()).getLayoutBlock() == lBlock)) { 682 result.add(ls); 683 } else if ((ls.getConnectD() != null) && (((TrackSegment) ls.getConnectD()).getLayoutBlock() == lBlock)) { 684 result.add(ls); 685 } 686 } 687 if (log.isTraceEnabled()) { 688 StringBuilder txt = new StringBuilder("Turnouts for Block "); 689 txt.append(block.getUserName()).append(" - "); 690 for (int k = 0; k < result.size(); k++) { 691 if (k > 0) { 692 txt.append(", "); 693 } 694 if ((result.get(k)).getTurnout() != null) { 695 txt.append((result.get(k)).getTurnout().getSystemName()); 696 } else { 697 txt.append("???"); 698 } 699 } 700 log.error("Turnouts for Block {}", txt.toString()); 701 } 702 return result; 703 } 704 705 /** 706 * Check if specified LayoutTurnout has required signals. 707 * 708 * @param t the LayoutTurnout to check 709 * @return true if specified LayoutTurnout has required signal heads; false 710 * otherwise 711 */ 712 public boolean layoutTurnoutHasRequiredSignals(@Nonnull LayoutTurnout t) { 713 switch (t.getLinkType()) { 714 case NO_LINK: 715 if ((t.isTurnoutTypeTurnout())) { 716 return (!t.getSignalA1Name().isEmpty() 717 && !t.getSignalB1Name().isEmpty() 718 && !t.getSignalC1Name().isEmpty()); 719 } else if (t.isTurnoutTypeSlip()) { 720 if (!t.getSignalA1Name().isEmpty() 721 && !t.getSignalA2Name().isEmpty() 722 && !t.getSignalB1Name().isEmpty() 723 && !t.getSignalC1Name().isEmpty() 724 && !t.getSignalD1Name().isEmpty() 725 && !t.getSignalD2Name().isEmpty()) { 726 if (t.getTurnoutType() == LayoutTurnout.TurnoutType.SINGLE_SLIP) { 727 return true; 728 } 729 if (t.getTurnoutType() == LayoutTurnout.TurnoutType.DOUBLE_SLIP) { 730 if (!t.getSignalB2Name().isEmpty() 731 && !t.getSignalC2Name().isEmpty()) { 732 return true; 733 } 734 } 735 } 736 return false; 737 } else { 738 return !t.getSignalA1Name().isEmpty() 739 && !t.getSignalB1Name().isEmpty() 740 && !t.getSignalC1Name().isEmpty() 741 && !t.getSignalD1Name().isEmpty(); 742 } 743 case FIRST_3_WAY: 744 return (!t.getSignalA1Name().isEmpty() 745 && !t.getSignalC1Name().isEmpty()); 746 case SECOND_3_WAY: 747 case THROAT_TO_THROAT: 748 return (!t.getSignalB1Name().isEmpty() 749 && !t.getSignalC1Name().isEmpty()); 750 default: 751 break; 752 } 753 return false; 754 } 755 756 /** 757 * Get the SignalHead at the Anchor block boundary. 758 * 759 * @param p the anchor with the signal head 760 * @param block the adjacent block 761 * @param facing true if SignalHead facing towards block should be returned; 762 * false if SignalHead facing away from block should be 763 * returned 764 * @return a SignalHead facing away from or towards block depending on value 765 * of facing; may be null 766 */ 767 @CheckReturnValue 768 @CheckForNull 769 public SignalHead getSignalHeadAtAnchor(@CheckForNull PositionablePoint p, 770 @CheckForNull Block block, boolean facing) { 771 if ((p == null) || (block == null)) { 772 log.error("null arguments in call to getSignalHeadAtAnchor"); 773 return null; 774 } 775 String userName = block.getUserName(); 776 LayoutBlock lBlock = null; 777 if ((userName != null) && !userName.isEmpty()) { 778 lBlock = layoutBlockManager.getByUserName(userName); 779 } 780 if (((p.getConnect1()).getLayoutBlock() == lBlock) && ((p.getConnect2()).getLayoutBlock() != lBlock)) { 781 if ((LayoutEditorTools.isAtWestEndOfAnchor(layoutEditor, p.getConnect2(), p) && facing) 782 || ((!LayoutEditorTools.isAtWestEndOfAnchor(layoutEditor, p.getConnect2(), p)) && (!facing))) { 783 return (InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(p.getWestBoundSignal())); 784 } else { 785 return (InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(p.getEastBoundSignal())); 786 } 787 } else if (((p.getConnect1()).getLayoutBlock() != lBlock) && ((p.getConnect2()).getLayoutBlock() == lBlock)) { 788 if ((LayoutEditorTools.isAtWestEndOfAnchor(layoutEditor, p.getConnect1(), p) && facing) 789 || ((!LayoutEditorTools.isAtWestEndOfAnchor(layoutEditor, p.getConnect1(), p)) && (!facing))) { 790 return (InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(p.getWestBoundSignal())); 791 } else { 792 return (InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(p.getEastBoundSignal())); 793 } 794 } else { 795 // should never happen 796 return null; 797 } 798 } 799 800 /** 801 * Get the SignalMast at the Anchor block boundary. 802 * 803 * @param p the anchor with the signal head 804 * @param block the adjacent block 805 * @param facing true if SignalMast facing towards block should be returned; 806 * false if SignalMast facing away from block should be 807 * returned 808 * @return a SignalMast facing away from or towards block depending on value 809 * of facing; may be null 810 */ 811 @CheckReturnValue 812 @CheckForNull 813 public SignalMast getSignalMastAtAnchor(@CheckForNull PositionablePoint p, 814 @CheckForNull Block block, boolean facing) { 815 if ((p == null) || (block == null)) { 816 log.error("null arguments in call to getSignalHeadAtAnchor"); 817 return null; 818 } 819 String userName = block.getUserName(); 820 LayoutBlock lBlock = null; 821 if ((userName != null) && !userName.isEmpty()) { 822 lBlock = layoutBlockManager.getByUserName(userName); 823 } 824 if (((p.getConnect1()).getLayoutBlock() == lBlock) && ((p.getConnect2()).getLayoutBlock() != lBlock)) { 825 if ((LayoutEditorTools.isAtWestEndOfAnchor(layoutEditor, p.getConnect2(), p) && facing) 826 || ((!LayoutEditorTools.isAtWestEndOfAnchor(layoutEditor, p.getConnect2(), p)) && (!facing))) { 827 return (InstanceManager.getDefault(jmri.SignalMastManager.class).getSignalMast(p.getWestBoundSignalMastName())); 828 } else { 829 return (InstanceManager.getDefault(jmri.SignalMastManager.class).getSignalMast(p.getEastBoundSignalMastName())); 830 } 831 } else if (((p.getConnect1()).getLayoutBlock() != lBlock) && ((p.getConnect2()).getLayoutBlock() == lBlock)) { 832 if ((LayoutEditorTools.isAtWestEndOfAnchor(layoutEditor, p.getConnect1(), p) && facing) 833 || ((!LayoutEditorTools.isAtWestEndOfAnchor(layoutEditor, p.getConnect1(), p)) && (!facing))) { 834 return (InstanceManager.getDefault(jmri.SignalMastManager.class).getSignalMast(p.getWestBoundSignalMastName())); 835 } else { 836 return (InstanceManager.getDefault(jmri.SignalMastManager.class).getSignalMast(p.getEastBoundSignalMastName())); 837 } 838 } else { 839 // should never happen 840 return null; 841 } 842 } 843 844 //Signalmasts are only valid or required on the boundary to a block. 845 public boolean layoutTurnoutHasSignalMasts(@Nonnull LayoutTurnout t) { 846 String[] turnoutBlocks = t.getBlockBoundaries(); 847 boolean valid = true; 848 if (turnoutBlocks[0] != null && (t.getSignalAMastName().isEmpty())) { 849 valid = false; 850 } 851 if (turnoutBlocks[1] != null && (t.getSignalBMastName().isEmpty())) { 852 valid = false; 853 } 854 if (turnoutBlocks[2] != null && (t.getSignalCMastName().isEmpty())) { 855 valid = false; 856 } 857 if (turnoutBlocks[3] != null && (t.getSignalDMastName().isEmpty())) { 858 valid = false; 859 } 860 return valid; 861 } 862 863 /** 864 * Get the SignalHead at the level crossing. 865 * 866 * @param x the level crossing 867 * @param block the adjacent block 868 * @param facing true if SignalHead facing towards block should be returned; 869 * false if SignalHead facing away from block should be 870 * returned 871 * @return a SignalHead facing away from or towards block depending on value 872 * of facing; may be null 873 */ 874 @CheckReturnValue 875 @CheckForNull 876 public SignalHead getSignalHeadAtLevelXing(@CheckForNull LevelXing x, 877 @CheckForNull Block block, boolean facing) { 878 if ((x == null) || (block == null)) { 879 log.error("null arguments in call to getSignalHeadAtLevelXing"); 880 return null; 881 } 882 String userName = block.getUserName(); 883 LayoutBlock lBlock = null; 884 if ((userName != null) && !userName.isEmpty()) { 885 lBlock = layoutBlockManager.getByUserName(userName); 886 } 887 if ((x.getConnectA() == null) || (x.getConnectB() == null) 888 || (x.getConnectC() == null) || (x.getConnectD() == null)) { 889 log.error("Missing track around level crossing near Block {}", block.getUserName()); 890 return null; 891 } 892 if (((TrackSegment) x.getConnectA()).getLayoutBlock() == lBlock) { 893 if (facing) { 894 return InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(x.getSignalCName()); 895 } else { 896 return InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(x.getSignalAName()); 897 } 898 } 899 if (((TrackSegment) x.getConnectB()).getLayoutBlock() == lBlock) { 900 if (facing) { 901 return InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(x.getSignalDName()); 902 } else { 903 return InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(x.getSignalBName()); 904 } 905 } 906 if (((TrackSegment) x.getConnectC()).getLayoutBlock() == lBlock) { 907 if (facing) { 908 return InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(x.getSignalAName()); 909 } else { 910 return InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(x.getSignalCName()); 911 } 912 } 913 if (((TrackSegment) x.getConnectD()).getLayoutBlock() == lBlock) { 914 if (facing) { 915 return InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(x.getSignalBName()); 916 } else { 917 return InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(x.getSignalDName()); 918 } 919 } 920 return null; 921 } 922 923 /** 924 * Check if block is internal to a level crossing. 925 * 926 * @param x the level crossing to check 927 * @param block the block to check 928 * @return true if block is internal to x; false if block is external or 929 * contains a connecting track segment 930 */ 931 public boolean blockInternalToLevelXing( 932 @CheckForNull LevelXing x, 933 @CheckForNull Block block) { 934 if ((x == null) || (block == null)) { 935 return false; 936 } 937 String userName = block.getUserName(); 938 LayoutBlock lBlock = null; 939 if ((userName != null) && !userName.isEmpty()) { 940 lBlock = layoutBlockManager.getByUserName(userName); 941 } 942 if (lBlock == null) { 943 return false; 944 } 945 if ((x.getConnectA() == null) || (x.getConnectB() == null) 946 || (x.getConnectC() == null) || (x.getConnectD() == null)) { 947 return false; 948 } 949 if ((x.getLayoutBlockAC() != lBlock) && (x.getLayoutBlockBD() != lBlock)) { 950 return false; 951 } 952 if (((TrackSegment) x.getConnectA()).getLayoutBlock() == lBlock) { 953 return false; 954 } 955 if (((TrackSegment) x.getConnectB()).getLayoutBlock() == lBlock) { 956 return false; 957 } 958 if (((TrackSegment) x.getConnectC()).getLayoutBlock() == lBlock) { 959 return false; 960 } 961 return (((TrackSegment) x.getConnectD()).getLayoutBlock() != lBlock); 962 } 963 964 /** 965 * Get the direction of the block boundary anchor point p. If 966 * {@link EntryPoint#UNKNOWN} is returned, it indicates that p is entirely 967 * internal or external to the Section. 968 * 969 * @param mForwardEntryPoints list of forward entry points 970 * @param mReverseEntryPoints list of reverse entry points 971 * @param p anchor point to match against one of the 972 * points in the specified lists 973 * @return the direction specified in the matching entry point or 974 * {@link EntryPoint#UNKNOWN} 975 */ 976 public int getDirectionFromAnchor( 977 @Nonnull List<EntryPoint> mForwardEntryPoints, 978 @Nonnull List<EntryPoint> mReverseEntryPoints, 979 @Nonnull PositionablePoint p) { 980 Block block1 = p.getConnect1().getLayoutBlock().getBlock(); 981 Block block2 = p.getConnect2().getLayoutBlock().getBlock(); 982 for (EntryPoint ep : mForwardEntryPoints) { 983 if (((ep.getBlock() == block1) && (ep.getFromBlock() == block2)) 984 || ((ep.getBlock() == block2) && (ep.getFromBlock() == block1))) { 985 return EntryPoint.FORWARD; 986 } 987 } 988 for (EntryPoint ep : mReverseEntryPoints) { 989 if (((ep.getBlock() == block1) && (ep.getFromBlock() == block2)) 990 || ((ep.getBlock() == block2) && (ep.getFromBlock() == block1))) { 991 return EntryPoint.REVERSE; 992 } 993 } 994 return EntryPoint.UNKNOWN; 995 } 996 997 /** 998 * Check if the AC track of a Level Crossing and its two connecting Track 999 * Segments are internal to the specified block. 1000 * <p> 1001 * Note: if two connecting track segments are in the block, but the internal 1002 * connecting track is not, that is an error in the Layout Editor panel. If 1003 * found, an error message is generated and this method returns false. 1004 * 1005 * @param x the level crossing to check 1006 * @param block the block to check 1007 * @return true if the A and C track segments of LevelXing x is in Block 1008 * block; false otherwise 1009 */ 1010 public boolean isInternalLevelXingAC( 1011 @Nonnull LevelXing x, @Nonnull Block block) { 1012 String userName = block.getUserName(); 1013 LayoutBlock lBlock = null; 1014 if ((userName != null) && !userName.isEmpty()) { 1015 lBlock = layoutBlockManager.getByUserName(userName); 1016 } 1017 if ((((TrackSegment) x.getConnectA()).getLayoutBlock() == lBlock) 1018 && (((TrackSegment) x.getConnectC()).getLayoutBlock() == lBlock)) { 1019 if (x.getLayoutBlockAC() == lBlock) { 1020 return true; 1021 } else { 1022 log.error("Panel blocking error at AC of Level Crossing in Block {}", block.getUserName()); 1023 return false; 1024 } 1025 } 1026 return false; 1027 } 1028 1029 /** 1030 * Check if the BD track of a Level Crossing and its two connecting Track 1031 * Segments are internal to the specified block. 1032 * <p> 1033 * Note: if two connecting track segments are in the block, but the internal 1034 * connecting track is not, that is an error in the Layout Editor panel. If 1035 * found, an error message is generated and this method returns false. 1036 * 1037 * @param x the level crossing to check 1038 * @param block the block to check 1039 * @return true if the B and D track segments of LevelXing x is in Block 1040 * block; false otherwise 1041 */ 1042 public boolean isInternalLevelXingBD( 1043 @Nonnull LevelXing x, @Nonnull Block block) { 1044 String userName = block.getUserName(); 1045 LayoutBlock lBlock = null; 1046 if ((userName != null) && !userName.isEmpty()) { 1047 lBlock = layoutBlockManager.getByUserName(userName); 1048 } 1049 if ((((TrackSegment) x.getConnectB()).getLayoutBlock() == lBlock) 1050 && (((TrackSegment) x.getConnectD()).getLayoutBlock() == lBlock)) { 1051 if (x.getLayoutBlockBD() == lBlock) { 1052 return true; 1053 } else { 1054 log.error("Panel blocking error at BD of Level Crossing in Block {}", block.getDisplayName()); 1055 return false; 1056 } 1057 } 1058 return false; 1059 } 1060 1061 /* 1062 * Defines where to place sensor in a FACING mode SSL 1063 */ 1064 public static final int OVERALL = 0x00; 1065 public static final int CONTINUING = 0x01; 1066 public static final int DIVERGING = 0x02; 1067 1068 /** 1069 * Add the specified sensor ('name') to the SSL for the specified signal 1070 * head 'name' should be the system name for the sensor. 1071 * <p> 1072 * If the SSL has not been set up yet, the sensor is not added, an error 1073 * message is output and 'false' is returned. 1074 * 1075 * @param name sensor name 1076 * @param sh signal head 1077 * @param where should be one of DIVERGING if the sensor is being added to 1078 * the diverging (second) part of a facing mode SSL, CONTINUING 1079 * if the sensor is being added to the continuing (first) part 1080 * of a facing mode SSL, OVERALL if the sensor is being added 1081 * to the overall sensor list of a facing mode SSL. 'where' is 1082 * ignored if not a facing mode SSL 1083 * @return 'true' if the sensor was already in the signal head SSL or if it 1084 * has been added successfully; 'false' and logs an error if not. 1085 */ 1086 public boolean addSensorToSignalHeadLogic( 1087 @CheckForNull String name, 1088 @CheckForNull SignalHead sh, 1089 int where) { 1090 if (sh == null) { 1091 log.error("Null signal head on entry to addSensorToSignalHeadLogic"); 1092 return false; 1093 } 1094 if ((name == null) || name.isEmpty()) { 1095 log.error("Null string for sensor name on entry to addSensorToSignalHeadLogic"); 1096 return false; 1097 } 1098 BlockBossLogic bbLogic = BlockBossLogic.getStoppedObject(sh.getSystemName()); 1099 1100 int mode = bbLogic.getMode(); 1101 if (((mode == BlockBossLogic.SINGLEBLOCK) || (mode == BlockBossLogic.TRAILINGMAIN) 1102 || (mode == BlockBossLogic.TRAILINGDIVERGING)) || ((mode == BlockBossLogic.FACING) 1103 && (where == OVERALL))) { 1104 if (((bbLogic.getSensor1() != null) && (bbLogic.getSensor1()).equals(name)) 1105 || ((bbLogic.getSensor2() != null) && (bbLogic.getSensor2()).equals(name)) 1106 || ((bbLogic.getSensor3() != null) && (bbLogic.getSensor3()).equals(name)) 1107 || ((bbLogic.getSensor4() != null) && (bbLogic.getSensor4()).equals(name)) 1108 || ((bbLogic.getSensor5() != null) && (bbLogic.getSensor5()).equals(name))) { 1109 blockBossLogicProvider.register(bbLogic); 1110 bbLogic.start(); 1111 return true; 1112 } 1113 if (bbLogic.getSensor1() == null) { 1114 bbLogic.setSensor1(name); 1115 } else if (bbLogic.getSensor2() == null) { 1116 bbLogic.setSensor2(name); 1117 } else if (bbLogic.getSensor3() == null) { 1118 bbLogic.setSensor3(name); 1119 } else if (bbLogic.getSensor4() == null) { 1120 bbLogic.setSensor4(name); 1121 } else if (bbLogic.getSensor5() == null) { 1122 bbLogic.setSensor5(name); 1123 } else { 1124 log.error("could not add sensor to SSL for signal head {} because there is no room in the SSL.", sh.getDisplayName()); 1125 blockBossLogicProvider.register(bbLogic); 1126 bbLogic.start(); 1127 return false; 1128 } 1129 } else if (mode == BlockBossLogic.FACING) { 1130 switch (where) { 1131 case DIVERGING: 1132 if (((bbLogic.getWatchedSensor2() != null) && (bbLogic.getWatchedSensor2()).equals(name)) 1133 || ((bbLogic.getWatchedSensor2Alt() != null) && (bbLogic.getWatchedSensor2Alt()).equals(name))) { 1134 blockBossLogicProvider.register(bbLogic); 1135 bbLogic.start(); 1136 return true; 1137 } 1138 if (bbLogic.getWatchedSensor2() == null) { 1139 bbLogic.setWatchedSensor2(name); 1140 } else if (bbLogic.getWatchedSensor2Alt() == null) { 1141 bbLogic.setWatchedSensor2Alt(name); 1142 } else { 1143 log.error("could not add watched sensor to SSL for signal head {} because there is no room in the facing SSL diverging part.", sh.getSystemName()); 1144 blockBossLogicProvider.register(bbLogic); 1145 bbLogic.start(); 1146 return false; 1147 } 1148 break; 1149 case CONTINUING: 1150 if (((bbLogic.getWatchedSensor1() != null) && (bbLogic.getWatchedSensor1()).equals(name)) 1151 || ((bbLogic.getWatchedSensor1Alt() != null) && (bbLogic.getWatchedSensor1Alt()).equals(name))) { 1152 blockBossLogicProvider.register(bbLogic); 1153 bbLogic.start(); 1154 return true; 1155 } 1156 if (bbLogic.getWatchedSensor1() == null) { 1157 bbLogic.setWatchedSensor1(name); 1158 } else if (bbLogic.getWatchedSensor1Alt() == null) { 1159 bbLogic.setWatchedSensor1Alt(name); 1160 } else { 1161 log.error("could not add watched sensor to SSL for signal head {} because there is no room in the facing SSL continuing part.", sh.getSystemName()); 1162 blockBossLogicProvider.register(bbLogic); 1163 bbLogic.start(); 1164 return false; 1165 } 1166 break; 1167 default: 1168 log.error("could not add watched sensor to SSL for signal head {}because 'where' to place the sensor was not correctly designated.", sh.getSystemName()); 1169 blockBossLogicProvider.register(bbLogic); 1170 bbLogic.start(); 1171 return false; 1172 } 1173 } else { 1174 log.error("SSL has not been set up for signal head {}. Could not add sensor - {}.", sh.getDisplayName(), name); 1175 return false; 1176 } 1177 blockBossLogicProvider.register(bbLogic); 1178 bbLogic.start(); 1179 return true; 1180 } 1181 1182 /** 1183 * Remove the specified sensors from the SSL for the specified signal head 1184 * if any of the sensors is currently in the SSL. 1185 * 1186 * @param names the names of the sensors to remove 1187 * @param sh the signal head to remove the sensors from 1188 * @return true if successful; false otherwise 1189 */ 1190 public boolean removeSensorsFromSignalHeadLogic( 1191 @CheckForNull List<String> names, @CheckForNull SignalHead sh) { 1192 if (sh == null) { 1193 log.error("Null signal head on entry to removeSensorsFromSignalHeadLogic"); 1194 return false; 1195 } 1196 if (names == null) { 1197 log.error("Null List of sensor names on entry to removeSensorsFromSignalHeadLogic"); 1198 return false; 1199 } 1200 BlockBossLogic bbLogic = BlockBossLogic.getStoppedObject(sh.getSystemName()); 1201 1202 for (String name : names) { 1203 if ((bbLogic.getSensor1() != null) && (bbLogic.getSensor1()).equals(name)) { 1204 bbLogic.setSensor1(null); 1205 } 1206 if ((bbLogic.getSensor2() != null) && (bbLogic.getSensor2()).equals(name)) { 1207 bbLogic.setSensor2(null); 1208 } 1209 if ((bbLogic.getSensor3() != null) && (bbLogic.getSensor3()).equals(name)) { 1210 bbLogic.setSensor3(null); 1211 } 1212 if ((bbLogic.getSensor4() != null) && (bbLogic.getSensor4()).equals(name)) { 1213 bbLogic.setSensor4(null); 1214 } 1215 if ((bbLogic.getSensor5() != null) && (bbLogic.getSensor5()).equals(name)) { 1216 bbLogic.setSensor5(null); 1217 } 1218 if (bbLogic.getMode() == BlockBossLogic.FACING) { 1219 if ((bbLogic.getWatchedSensor1() != null) && (bbLogic.getWatchedSensor1()).equals(name)) { 1220 bbLogic.setWatchedSensor1(null); 1221 } 1222 if ((bbLogic.getWatchedSensor1Alt() != null) && (bbLogic.getWatchedSensor1Alt()).equals(name)) { 1223 bbLogic.setWatchedSensor1Alt(null); 1224 } 1225 if ((bbLogic.getWatchedSensor2() != null) && (bbLogic.getWatchedSensor2()).equals(name)) { 1226 bbLogic.setWatchedSensor2(null); 1227 } 1228 if ((bbLogic.getWatchedSensor2Alt() != null) && (bbLogic.getWatchedSensor2Alt()).equals(name)) { 1229 bbLogic.setWatchedSensor2Alt(null); 1230 } 1231 } 1232 } 1233 if (bbLogic.getMode() == 0) { 1234 // this to avoid Unexpected mode ERROR message at startup 1235 bbLogic.setMode(BlockBossLogic.SINGLEBLOCK); 1236 } 1237 blockBossLogicProvider.register(bbLogic); 1238 bbLogic.start(); 1239 return true; 1240 } 1241 1242 /** 1243 * Get the next TrackNode following the specified TrackNode. 1244 * 1245 * @param currentNode the current node 1246 * @param currentNodeType the possible path to follow (for example, if the 1247 * current node is a turnout entered at its throat, 1248 * the path could be the thrown or closed path) 1249 * @return the next TrackNode following currentNode for the given state or 1250 * null if unable to follow the track 1251 */ 1252 @CheckReturnValue 1253 @CheckForNull 1254 public TrackNode getNextNode(@CheckForNull TrackNode currentNode, int currentNodeType) { 1255 if (currentNode == null) { 1256 log.error("getNextNode called with a null Track Node"); 1257 return null; 1258 } 1259 if (currentNode.reachedEndOfTrack()) { 1260 log.error("getNextNode - attempt to search past endBumper"); 1261 return null; 1262 } 1263 return (getTrackNode(currentNode.getNode(), currentNode.getNodeType(), currentNode.getTrackSegment(), currentNodeType)); 1264 } 1265 1266 /** 1267 * Get the next TrackNode following the specified TrackNode, assuming that 1268 * TrackNode was reached via the specified TrackSegment. 1269 * <p> 1270 * If the specified track node can lead to different paths to the next node, 1271 * for example, if the specified track node is a turnout entered at its 1272 * throat, then "currentNodeType" must be specified to choose between the 1273 * possible paths. If currentNodeType = 0, the search will follow the 1274 * 'continuing' track; if currentNodeType = 1, the search will follow the 1275 * 'diverging' track; if currentNodeType = 2 (3-way turnouts only), the 1276 * search will follow the second 'diverging' track. 1277 * <p> 1278 * In determining which track is the 'continuing' track for RH, LH, and WYE 1279 * turnouts, this search routine uses the layout turnout's 1280 * 'continuingState'. 1281 * <p> 1282 * When following track, this method skips over anchor points that are not 1283 * block boundaries. 1284 * <p> 1285 * When following track, this method treats a modeled 3-way turnout as a 1286 * single turnout. It also treats two THROAT_TO_THROAT turnouts as a single 1287 * turnout, but with each turnout having a continuing sense. 1288 * 1289 * @param currentNode the current node 1290 * @param currentNodeType the type of node 1291 * @param currentTrackSegment the followed track segment 1292 * @param currentNodeState the possible path to follow (for example, if 1293 * the current node is a turnout entered at its 1294 * throat, the path could be the thrown or closed 1295 * path) 1296 * @return the next TrackNode following currentNode for the given state if a 1297 * node or end_of-track is reached or null if unable to follow the 1298 * track 1299 */ 1300 //TODO: cTrack parameter isn't used in this method; is this a bug? 1301 //TODO: prevTrackType local variable is set but never used; dead-code strip? 1302 @CheckReturnValue 1303 @CheckForNull 1304 public TrackNode getTrackNode( 1305 @Nonnull LayoutTrack currentNode, 1306 HitPointType currentNodeType, 1307 @CheckForNull TrackSegment currentTrackSegment, 1308 int currentNodeState) { 1309 // initialize 1310 //LayoutEditor.HitPointType prevTrackType = currentNodeType; 1311 LayoutTrack prevTrack = currentNode; 1312 TrackSegment nextTrackSegment = currentTrackSegment; 1313 switch (currentNodeType) { 1314 case POS_POINT: 1315 if (currentNode instanceof PositionablePoint) { 1316 PositionablePoint p = (PositionablePoint) currentNode; 1317 if (p.getType() == PositionablePoint.PointType.END_BUMPER) { 1318 log.warn("Attempt to search beyond end of track"); 1319 return null; 1320 } 1321 nextTrackSegment = p.getConnect1(); 1322 if (nextTrackSegment == null) { 1323 nextTrackSegment = p.getConnect2(); 1324 } 1325 } else { 1326 log.warn("currentNodeType wrong for currentNode"); 1327 } 1328 break; 1329 case TURNOUT_A: { 1330 if (currentNode instanceof LayoutTurnout) { 1331 LayoutTurnout lt = (LayoutTurnout) currentNode; 1332 if (lt.isTurnoutTypeTurnout()) { 1333 if ((lt.getLinkedTurnoutName() == null) 1334 || (lt.getLinkedTurnoutName().isEmpty())) { 1335 // Standard turnout - node type A 1336 if (lt.getContinuingSense() == Turnout.CLOSED) { 1337 switch (currentNodeState) { 1338 case TRACKNODE_CONTINUING: 1339 nextTrackSegment = (TrackSegment) lt.getConnectB(); 1340 //prevTrackType = LayoutEditor.HitPointType.TURNOUT_B; 1341 break; 1342 case TRACKNODE_DIVERGING: 1343 nextTrackSegment = (TrackSegment) lt.getConnectC(); 1344 //prevTrackType = LayoutEditor.HitPointType.TURNOUT_C; 1345 break; 1346 default: 1347 log.error("Bad currentNodeState when searching track-std. normal"); 1348 return null; 1349 } 1350 } else { 1351 switch (currentNodeState) { 1352 case TRACKNODE_CONTINUING: 1353 nextTrackSegment = (TrackSegment) lt.getConnectC(); 1354 //prevTrackType = LayoutEditor.HitPointType.TURNOUT_C; 1355 break; 1356 case TRACKNODE_DIVERGING: 1357 nextTrackSegment = (TrackSegment) lt.getConnectB(); 1358 //prevTrackType = LayoutEditor.HitPointType.TURNOUT_B; 1359 break; 1360 default: 1361 log.error("Bad currentNodeType argument when searching track-std reversed"); 1362 return null; 1363 } 1364 } 1365 } else { 1366 // linked turnout - node type A 1367 LayoutTurnout lto = layoutEditor.getFinder().findLayoutTurnoutByName(lt.getLinkedTurnoutName()); 1368 if (lt.getLinkType() == LayoutTurnout.LinkType.THROAT_TO_THROAT) { 1369 switch (currentNodeState) { 1370 case TRACKNODE_CONTINUING: 1371 if (lto.getContinuingSense() == Turnout.CLOSED) { 1372 nextTrackSegment = (TrackSegment) lto.getConnectB(); 1373 //prevTrackType = LayoutEditor.HitPointType.TURNOUT_B; 1374 } else { 1375 nextTrackSegment = (TrackSegment) lto.getConnectC(); 1376 //prevTrackType = LayoutEditor.HitPointType.TURNOUT_C; 1377 } 1378 break; 1379 case TRACKNODE_DIVERGING: 1380 if (lto.getContinuingSense() == Turnout.CLOSED) { 1381 nextTrackSegment = (TrackSegment) lto.getConnectC(); 1382 //prevTrackType = LayoutEditor.HitPointType.TURNOUT_C; 1383 } else { 1384 nextTrackSegment = (TrackSegment) lto.getConnectB(); 1385 //prevTrackType = LayoutEditor.HitPointType.TURNOUT_B; 1386 } 1387 break; 1388 default: 1389 log.error("Bad currentNodeType argument when searching track - THROAT_TO_THROAT"); 1390 return null; 1391 } 1392 prevTrack = lto; 1393 } else if (lt.getLinkType() == LayoutTurnout.LinkType.FIRST_3_WAY) { 1394 switch (currentNodeState) { 1395 case TRACKNODE_CONTINUING: 1396 if (lto.getContinuingSense() == Turnout.CLOSED) { 1397 nextTrackSegment = (TrackSegment) lto.getConnectB(); 1398 //prevTrackType = LayoutEditor.HitPointType.TURNOUT_B; 1399 } else { 1400 nextTrackSegment = (TrackSegment) lto.getConnectC(); 1401 //prevTrackType = LayoutEditor.HitPointType.TURNOUT_C; 1402 } 1403 prevTrack = lto; 1404 break; 1405 case TRACKNODE_DIVERGING: 1406 if (lt.getContinuingSense() == Turnout.CLOSED) { 1407 nextTrackSegment = (TrackSegment) lt.getConnectC(); 1408 //prevTrackType = LayoutEditor.HitPointType.TURNOUT_C; 1409 } else { 1410 nextTrackSegment = (TrackSegment) lt.getConnectB(); 1411 //prevTrackType = LayoutEditor.HitPointType.TURNOUT_B; 1412 } 1413 break; 1414 case TRACKNODE_DIVERGING_2ND_3WAY: 1415 if (lto.getContinuingSense() == Turnout.CLOSED) { 1416 nextTrackSegment = (TrackSegment) lto.getConnectC(); 1417 //prevTrackType = LayoutEditor.HitPointType.TURNOUT_C; 1418 } else { 1419 nextTrackSegment = (TrackSegment) lto.getConnectB(); 1420 //prevTrackType = LayoutEditor.HitPointType.TURNOUT_B; 1421 } 1422 prevTrack = lto; 1423 break; 1424 default: 1425 log.error("Bad currentNodeType argument when searching track - FIRST_3_WAY"); 1426 return null; 1427 } 1428 } 1429 } 1430 } else if (lt.isTurnoutTypeXover()) { 1431 // crossover turnout - node type A 1432 switch (currentNodeState) { 1433 case TRACKNODE_CONTINUING: 1434 nextTrackSegment = (TrackSegment) lt.getConnectB(); 1435 //prevTrackType = LayoutEditor.HitPointType.TURNOUT_B; 1436 break; 1437 case TRACKNODE_DIVERGING: 1438 if ((currentNodeType == HitPointType.TURNOUT_A) 1439 && (!(lt.getTurnoutType() == LayoutTurnout.TurnoutType.LH_XOVER))) { 1440 nextTrackSegment = (TrackSegment) lt.getConnectC(); 1441 //prevTrackType = LayoutEditor.HitPointType.TURNOUT_C; 1442 } else { 1443 log.error("Request to follow not allowed switch setting at LH_XOVER or RH_OVER"); 1444 return null; 1445 } 1446 break; 1447 default: 1448 log.error("Bad currentNodeType argument when searching track- XOVER A"); 1449 return null; 1450 } 1451 } 1452 } else { 1453 log.error("currentNodeType wrong for currentNode"); 1454 } 1455 break; 1456 } 1457 case TURNOUT_B: 1458 case TURNOUT_C: { 1459 if (currentNode instanceof LayoutTurnout) { 1460 LayoutTurnout lt = (LayoutTurnout) currentNode; 1461 if (lt.isTurnoutTypeTurnout()) { 1462 if ((lt.getLinkedTurnoutName() == null) 1463 || (lt.getLinkedTurnoutName().isEmpty()) 1464 || (lt.getLinkType() == LayoutTurnout.LinkType.FIRST_3_WAY)) { 1465 nextTrackSegment = (TrackSegment) (lt.getConnectA()); 1466 //prevTrackType = LayoutEditor.HitPointType.TURNOUT_A; 1467 } else { 1468 LayoutTurnout lto = layoutEditor.getFinder().findLayoutTurnoutByName(lt.getLinkedTurnoutName()); 1469 if (lt.getLinkType() == LayoutTurnout.LinkType.SECOND_3_WAY) { 1470 nextTrackSegment = (TrackSegment) (lto.getConnectA()); 1471 //prevTrackType = LayoutEditor.HitPointType.TURNOUT_A; 1472 } else if (lt.getLinkType() == LayoutTurnout.LinkType.THROAT_TO_THROAT) { 1473 switch (currentNodeState) { 1474 case TRACKNODE_CONTINUING: 1475 if (lto.getContinuingSense() == Turnout.CLOSED) { 1476 nextTrackSegment = (TrackSegment) lto.getConnectB(); 1477 //prevTrackType = LayoutEditor.HitPointType.TURNOUT_B; 1478 } else { 1479 nextTrackSegment = (TrackSegment) lto.getConnectC(); 1480 //prevTrackType = LayoutEditor.HitPointType.TURNOUT_C; 1481 } 1482 break; 1483 case TRACKNODE_DIVERGING: 1484 if (lto.getContinuingSense() == Turnout.CLOSED) { 1485 nextTrackSegment = (TrackSegment) lto.getConnectC(); 1486 //prevTrackType = LayoutEditor.HitPointType.TURNOUT_C; 1487 } else { 1488 nextTrackSegment = (TrackSegment) lto.getConnectB(); 1489 //prevTrackType = LayoutEditor.HitPointType.TURNOUT_B; 1490 } 1491 break; 1492 default: 1493 log.error("Bad currentNodeType argument when searching track - THROAT_TO_THROAT - 2"); 1494 return null; 1495 } 1496 } 1497 prevTrack = lto; 1498 } 1499 } else if (lt.isTurnoutTypeXover()) { 1500 switch (currentNodeState) { 1501 case TRACKNODE_CONTINUING: 1502 if (currentNodeType == HitPointType.TURNOUT_B) { 1503 nextTrackSegment = (TrackSegment) lt.getConnectA(); 1504 //prevTrackType = LayoutEditor.HitPointType.TURNOUT_A; 1505 } else if (currentNodeType == HitPointType.TURNOUT_C) { 1506 nextTrackSegment = (TrackSegment) lt.getConnectD(); 1507 //prevTrackType = LayoutEditor.HitPointType.TURNOUT_D; 1508 } 1509 break; 1510 case TRACKNODE_DIVERGING: 1511 if ((currentNodeType == HitPointType.TURNOUT_C) 1512 && (!(lt.getTurnoutType() == LayoutTurnout.TurnoutType.LH_XOVER))) { 1513 nextTrackSegment = (TrackSegment) lt.getConnectA(); 1514 //prevTrackType = LayoutEditor.HitPointType.TURNOUT_A; 1515 } else if ((currentNodeType == HitPointType.TURNOUT_B) 1516 && (!(lt.getTurnoutType() == LayoutTurnout.TurnoutType.RH_XOVER))) { 1517 nextTrackSegment = (TrackSegment) lt.getConnectD(); 1518 //prevTrackType = LayoutEditor.HitPointType.TURNOUT_D; 1519 } else { 1520 log.error("Request to follow not allowed switch setting at LH_XOVER or RH_OVER"); 1521 return null; 1522 } 1523 break; 1524 default: 1525 log.error("Bad currentNodeType argument when searching track - XOVER B or C"); 1526 return null; 1527 } 1528 } 1529 } else { 1530 log.error("currentNodeType wrong for currentNode"); 1531 } 1532 break; 1533 } 1534 case TURNOUT_D: { 1535 if (currentNode instanceof LayoutTurnout) { 1536 LayoutTurnout lt = (LayoutTurnout) currentNode; 1537 if (lt.isTurnoutTypeXover()) { 1538 switch (currentNodeState) { 1539 case TRACKNODE_CONTINUING: 1540 nextTrackSegment = (TrackSegment) lt.getConnectC(); 1541 //prevTrackType = LayoutEditor.HitPointType.TURNOUT_C; 1542 break; 1543 case TRACKNODE_DIVERGING: 1544 if (!(lt.getTurnoutType() == LayoutTurnout.TurnoutType.RH_XOVER)) { 1545 nextTrackSegment = (TrackSegment) lt.getConnectB(); 1546 //prevTrackType = LayoutEditor.HitPointType.TURNOUT_B; 1547 } else { 1548 log.error("Request to follow not allowed switch setting at LH_XOVER or RH_OVER"); 1549 return null; 1550 } 1551 break; 1552 default: 1553 log.error("Bad currentNodeType argument when searching track - XOVER D"); 1554 return null; 1555 } 1556 } else { 1557 log.error("Bad traak node type - TURNOUT_D, but not a crossover turnout"); 1558 return null; 1559 } 1560 } else { 1561 log.error("currentNodeType wrong for currentNode"); 1562 } 1563 break; 1564 } 1565 case LEVEL_XING_A: 1566 if (currentNode instanceof LevelXing) { 1567 nextTrackSegment = (TrackSegment) ((LevelXing) currentNode).getConnectC(); 1568 //prevTrackType = LayoutEditor.HitPointType.LEVEL_XING_C; 1569 } else { 1570 log.error("currentNodeType wrong for currentNode"); 1571 } 1572 break; 1573 case LEVEL_XING_B: 1574 if (currentNode instanceof LevelXing) { 1575 nextTrackSegment = (TrackSegment) ((LevelXing) currentNode).getConnectD(); 1576 //prevTrackType = LayoutEditor.HitPointType.LEVEL_XING_D; 1577 } else { 1578 log.error("currentNodeType wrong for currentNode"); 1579 } 1580 break; 1581 case LEVEL_XING_C: 1582 if (currentNode instanceof LevelXing) { 1583 nextTrackSegment = (TrackSegment) ((LevelXing) currentNode).getConnectA(); 1584 //prevTrackType = LayoutEditor.HitPointType.LEVEL_XING_A; 1585 } else { 1586 log.error("currentNodeType wrong for currentNode"); 1587 } 1588 break; 1589 case LEVEL_XING_D: 1590 if (currentNode instanceof LevelXing) { 1591 nextTrackSegment = (TrackSegment) ((LevelXing) currentNode).getConnectB(); 1592 //prevTrackType = LayoutEditor.HitPointType.LEVEL_XING_B; 1593 } else { 1594 log.error("currentNodeType wrong for currentNode"); 1595 } 1596 break; 1597 case SLIP_A: { 1598 if (currentNode instanceof LayoutSlip) { 1599 LayoutSlip ls = (LayoutSlip) currentNode; 1600 if (currentNodeState == TRACKNODE_CONTINUING) { 1601 nextTrackSegment = (TrackSegment) ls.getConnectC(); 1602 //prevTrackType = LayoutEditor.HitPointType.SLIP_C; 1603 } else if (currentNodeState == TRACKNODE_DIVERGING) { 1604 nextTrackSegment = (TrackSegment) ls.getConnectD(); 1605 //prevTrackType = LayoutEditor.HitPointType.SLIP_D; 1606 } 1607 } else { 1608 log.error("currentNodeType wrong for currentNode"); 1609 } 1610 break; 1611 } 1612 case SLIP_B: { 1613 if (currentNode instanceof LayoutSlip) { 1614 LayoutSlip ls = (LayoutSlip) currentNode; 1615 if (currentNodeState == TRACKNODE_CONTINUING) { 1616 nextTrackSegment = (TrackSegment) ls.getConnectD(); 1617 //prevTrackType = LayoutEditor.HitPointType.SLIP_D; 1618 } else if ((currentNodeState == TRACKNODE_DIVERGING) 1619 && (ls.getTurnoutType() == LayoutTurnout.TurnoutType.DOUBLE_SLIP)) { 1620 nextTrackSegment = (TrackSegment) ls.getConnectC(); 1621 //prevTrackType = LayoutEditor.HitPointType.SLIP_C; 1622 } else { 1623 log.error("Request to follow not allowed on a single slip"); 1624 return null; 1625 } 1626 } else { 1627 log.error("currentNodeType wrong for currentNode"); 1628 } 1629 break; 1630 } 1631 case SLIP_C: { 1632 if (currentNode instanceof LayoutSlip) { 1633 LayoutSlip ls = (LayoutSlip) currentNode; 1634 if (currentNodeState == TRACKNODE_CONTINUING) { 1635 nextTrackSegment = (TrackSegment) ls.getConnectA(); 1636 //prevTrackType = LayoutEditor.HitPointType.SLIP_A; 1637 } else if ((currentNodeState == TRACKNODE_DIVERGING) 1638 && (ls.getTurnoutType() == LayoutTurnout.TurnoutType.DOUBLE_SLIP)) { 1639 nextTrackSegment = (TrackSegment) ls.getConnectB(); 1640 //prevTrackType = LayoutEditor.HitPointType.SLIP_B; 1641 } else { 1642 log.error("Request to follow not allowed on a single slip"); 1643 return null; 1644 } 1645 } else { 1646 log.error("currentNodeType wrong for currentNode"); 1647 } 1648 break; 1649 } 1650 case SLIP_D: { 1651 if (currentNode instanceof LayoutSlip) { 1652 LayoutSlip ls = (LayoutSlip) currentNode; 1653 if (currentNodeState == TRACKNODE_CONTINUING) { 1654 nextTrackSegment = (TrackSegment) ls.getConnectB(); 1655 //prevTrackType = LayoutEditor.HitPointType.SLIP_B; 1656 } else if (currentNodeState == TRACKNODE_DIVERGING) { 1657 nextTrackSegment = (TrackSegment) ls.getConnectA(); 1658 //prevTrackType = LayoutEditor.HitPointType.SLIP_A; 1659 } 1660 } else { 1661 log.error("currentNodeType wrong for currentNode"); 1662 } 1663 break; 1664 } 1665 default: 1666 log.error("Unable to initiate 'getTrackNode'. Probably bad input Track Node."); 1667 return null; 1668 } 1669 1670 if (nextTrackSegment == null) { 1671 log.error("Error nextTrackSegment is null!"); 1672 return null; 1673 } 1674 1675 // follow track to next node (anchor block boundary, turnout, or level crossing) 1676 LayoutTrack node = null; 1677 HitPointType nodeType = HitPointType.NONE; 1678 TrackSegment nodeTrackSegment = null; 1679 1680 boolean hitEnd = false; 1681 boolean hasNode = false; 1682 while (!hasNode) { 1683 LayoutTrack nextLayoutTrack = null; 1684 HitPointType nextType = HitPointType.NONE; 1685 1686 if (nextTrackSegment.getConnect1() == prevTrack) { 1687 nextLayoutTrack = nextTrackSegment.getConnect2(); 1688 nextType = nextTrackSegment.getType2(); 1689 } else if (nextTrackSegment.getConnect2() == prevTrack) { 1690 nextLayoutTrack = nextTrackSegment.getConnect1(); 1691 nextType = nextTrackSegment.getType1(); 1692 } 1693 if (nextLayoutTrack == null) { 1694 log.error("Error while following track {} looking for next node", nextTrackSegment.getName()); 1695 return null; 1696 } 1697 1698 if (nextType == HitPointType.POS_POINT) { 1699 PositionablePoint p = (PositionablePoint) nextLayoutTrack; 1700 if (p.getType() == PositionablePoint.PointType.END_BUMPER) { 1701 hitEnd = true; 1702 hasNode = true; 1703 } else { 1704 TrackSegment con1 = p.getConnect1(); 1705 TrackSegment con2 = p.getConnect2(); 1706 if ((con1 == null) || (con2 == null)) { 1707 log.error("Breakin connectivity at Anchor Point when searching for track node"); 1708 return null; 1709 } 1710 if (con1.getLayoutBlock() == con2.getLayoutBlock()) { 1711 if (con1 == nextTrackSegment) { 1712 nextTrackSegment = con2; 1713 } else if (con2 == nextTrackSegment) { 1714 nextTrackSegment = con1; 1715 } else { 1716 log.error("Breakin connectivity at Anchor Point when searching for track node"); 1717 return null; 1718 } 1719 prevTrack = nextLayoutTrack; 1720 } else { 1721 node = nextLayoutTrack; 1722 nodeType = nextType; 1723 nodeTrackSegment = nextTrackSegment; 1724 hasNode = true; 1725 } 1726 } 1727 } else { 1728 node = nextLayoutTrack; 1729 nodeType = nextType; 1730 nodeTrackSegment = nextTrackSegment; 1731 hasNode = true; 1732 } 1733 } 1734 return (new TrackNode(node, nodeType, nodeTrackSegment, hitEnd, currentNodeState)); 1735 } 1736 1737 /** 1738 * Get an "exit block" for the specified track node if there is one, else 1739 * returns null. An "exit block" must be different from the block of the 1740 * track segment in the node. If the node is a PositionablePoint, it is 1741 * assumed to be a block boundary anchor point. 1742 * 1743 * @param node the node to get the exit block for 1744 * @param excludedBlock blocks not to be considered as exit blocks 1745 * @return the exit block for node or null if none exists 1746 */ 1747 @CheckReturnValue 1748 @CheckForNull 1749 public Block getExitBlockForTrackNode( 1750 @CheckForNull TrackNode node, 1751 @CheckForNull Block excludedBlock) { 1752 if ((node == null) || node.reachedEndOfTrack()) { 1753 return null; 1754 } 1755 Block block = null; 1756 switch (node.getNodeType()) { 1757 case POS_POINT: 1758 PositionablePoint p = (PositionablePoint) node.getNode(); 1759 block = p.getConnect1().getLayoutBlock().getBlock(); 1760 if (block == node.getTrackSegment().getLayoutBlock().getBlock()) { 1761 block = p.getConnect2().getLayoutBlock().getBlock(); 1762 } 1763 break; 1764 case TURNOUT_A: 1765 LayoutTurnout lt = (LayoutTurnout) node.getNode(); 1766 Block tBlock = ((TrackSegment) lt.getConnectB()).getLayoutBlock().getBlock(); 1767 if ((tBlock != node.getTrackSegment().getLayoutBlock().getBlock()) 1768 && (tBlock != excludedBlock)) { 1769 block = tBlock; 1770 } else if (lt.getTurnoutType() != LayoutTurnout.TurnoutType.LH_XOVER) { 1771 tBlock = ((TrackSegment) lt.getConnectC()).getLayoutBlock().getBlock(); 1772 if ((tBlock != node.getTrackSegment().getLayoutBlock().getBlock()) 1773 && (tBlock != excludedBlock)) { 1774 block = tBlock; 1775 } 1776 } 1777 break; 1778 case TURNOUT_B: 1779 lt = (LayoutTurnout) node.getNode(); 1780 tBlock = ((TrackSegment) lt.getConnectA()).getLayoutBlock().getBlock(); 1781 if ((tBlock != node.getTrackSegment().getLayoutBlock().getBlock()) 1782 && (tBlock != excludedBlock)) { 1783 block = tBlock; 1784 } else if ((lt.getTurnoutType() == LayoutTurnout.TurnoutType.LH_XOVER) 1785 || (lt.getTurnoutType() == LayoutTurnout.TurnoutType.DOUBLE_XOVER)) { 1786 tBlock = ((TrackSegment) lt.getConnectD()).getLayoutBlock().getBlock(); 1787 if ((tBlock != node.getTrackSegment().getLayoutBlock().getBlock()) 1788 && (tBlock != excludedBlock)) { 1789 block = tBlock; 1790 } 1791 } 1792 break; 1793 case TURNOUT_C: 1794 lt = (LayoutTurnout) node.getNode(); 1795 if (lt.getTurnoutType() != LayoutTurnout.TurnoutType.LH_XOVER) { 1796 tBlock = ((TrackSegment) lt.getConnectA()).getLayoutBlock().getBlock(); 1797 if ((tBlock != node.getTrackSegment().getLayoutBlock().getBlock()) 1798 && (tBlock != excludedBlock)) { 1799 block = tBlock; 1800 } 1801 } 1802 if ((block == null) && ((lt.getTurnoutType() == LayoutTurnout.TurnoutType.LH_XOVER) 1803 || (lt.getTurnoutType() == LayoutTurnout.TurnoutType.DOUBLE_XOVER))) { 1804 tBlock = ((TrackSegment) lt.getConnectD()).getLayoutBlock().getBlock(); 1805 if ((tBlock != node.getTrackSegment().getLayoutBlock().getBlock()) 1806 && (tBlock != excludedBlock)) { 1807 block = tBlock; 1808 } 1809 } 1810 break; 1811 case TURNOUT_D: 1812 lt = (LayoutTurnout) node.getNode(); 1813 if ((lt.getTurnoutType() == LayoutTurnout.TurnoutType.LH_XOVER) 1814 || (lt.getTurnoutType() == LayoutTurnout.TurnoutType.DOUBLE_XOVER)) { 1815 tBlock = ((TrackSegment) lt.getConnectB()).getLayoutBlock().getBlock(); 1816 if ((tBlock != node.getTrackSegment().getLayoutBlock().getBlock()) 1817 && (tBlock != excludedBlock)) { 1818 block = tBlock; 1819 } 1820 } 1821 break; 1822 case LEVEL_XING_A: 1823 LevelXing x = (LevelXing) node.getNode(); 1824 tBlock = ((TrackSegment) x.getConnectC()).getLayoutBlock().getBlock(); 1825 if (tBlock != node.getTrackSegment().getLayoutBlock().getBlock()) { 1826 block = tBlock; 1827 } 1828 break; 1829 case LEVEL_XING_B: 1830 x = (LevelXing) node.getNode(); 1831 tBlock = ((TrackSegment) x.getConnectD()).getLayoutBlock().getBlock(); 1832 if (tBlock != node.getTrackSegment().getLayoutBlock().getBlock()) { 1833 block = tBlock; 1834 } 1835 break; 1836 case LEVEL_XING_C: 1837 x = (LevelXing) node.getNode(); 1838 tBlock = ((TrackSegment) x.getConnectA()).getLayoutBlock().getBlock(); 1839 if (tBlock != node.getTrackSegment().getLayoutBlock().getBlock()) { 1840 block = tBlock; 1841 } 1842 break; 1843 case LEVEL_XING_D: 1844 x = (LevelXing) node.getNode(); 1845 tBlock = ((TrackSegment) x.getConnectB()).getLayoutBlock().getBlock(); 1846 if (tBlock != node.getTrackSegment().getLayoutBlock().getBlock()) { 1847 block = tBlock; 1848 } 1849 break; 1850 case SLIP_A: 1851 LayoutSlip ls = (LayoutSlip) node.getNode(); 1852 tBlock = ((TrackSegment) ls.getConnectC()).getLayoutBlock().getBlock(); 1853 if ((tBlock != node.getTrackSegment().getLayoutBlock().getBlock()) 1854 && (tBlock != excludedBlock)) { 1855 block = tBlock; 1856 } else { 1857 tBlock = ((TrackSegment) ls.getConnectD()).getLayoutBlock().getBlock(); 1858 if ((tBlock != node.getTrackSegment().getLayoutBlock().getBlock()) 1859 && (tBlock != excludedBlock)) { 1860 block = tBlock; 1861 } 1862 } 1863 break; 1864 case SLIP_B: 1865 ls = (LayoutSlip) node.getNode(); 1866 tBlock = ((TrackSegment) ls.getConnectD()).getLayoutBlock().getBlock(); 1867 if (ls.getTurnoutType() == LayoutSlip.TurnoutType.DOUBLE_SLIP) { 1868 //Double slip 1869 if ((tBlock != node.getTrackSegment().getLayoutBlock().getBlock()) 1870 && (tBlock != excludedBlock)) { 1871 block = tBlock; 1872 } else { 1873 tBlock = ((TrackSegment) ls.getConnectC()).getLayoutBlock().getBlock(); 1874 if ((tBlock != node.getTrackSegment().getLayoutBlock().getBlock()) 1875 && (tBlock != excludedBlock)) { 1876 block = tBlock; 1877 } 1878 } 1879 } else { 1880 if (tBlock != node.getTrackSegment().getLayoutBlock().getBlock()) { 1881 block = tBlock; 1882 } 1883 } 1884 break; 1885 case SLIP_C: 1886 ls = (LayoutSlip) node.getNode(); 1887 tBlock = ((TrackSegment) ls.getConnectA()).getLayoutBlock().getBlock(); 1888 if (ls.getTurnoutType() == LayoutSlip.TurnoutType.DOUBLE_SLIP) { 1889 if ((tBlock != node.getTrackSegment().getLayoutBlock().getBlock()) 1890 && (tBlock != excludedBlock)) { 1891 block = tBlock; 1892 } else { 1893 tBlock = ((TrackSegment) ls.getConnectB()).getLayoutBlock().getBlock(); 1894 if ((tBlock != node.getTrackSegment().getLayoutBlock().getBlock()) 1895 && (tBlock != excludedBlock)) { 1896 block = tBlock; 1897 } 1898 } 1899 } else { 1900 if (tBlock != node.getTrackSegment().getLayoutBlock().getBlock()) { 1901 block = tBlock; 1902 } 1903 } 1904 break; 1905 case SLIP_D: 1906 ls = (LayoutSlip) node.getNode(); 1907 tBlock = ((TrackSegment) ls.getConnectB()).getLayoutBlock().getBlock(); 1908 if ((tBlock != node.getTrackSegment().getLayoutBlock().getBlock()) 1909 && (tBlock != excludedBlock)) { 1910 block = tBlock; 1911 } else { 1912 tBlock = ((TrackSegment) ls.getConnectA()).getLayoutBlock().getBlock(); 1913 if ((tBlock != node.getTrackSegment().getLayoutBlock().getBlock()) 1914 && (tBlock != excludedBlock)) { 1915 block = tBlock; 1916 } 1917 } 1918 break; 1919 default: 1920 break; 1921 } 1922 return block; 1923 } 1924 1925 // support methods 1926 1927 /** 1928 * Provide the "neither branch leads to next block" warning message if relevant 1929 */ 1930 private void neitherBranchWarning(LayoutTurnout layoutTurnout, LayoutBlock nextLayoutBlock, boolean suppress) { 1931 if (!suppress) { 1932 String layoutTrackInfo = layoutTurnout.toString(); 1933 if (layoutTurnout.namedTurnout != null && layoutTurnout.namedTurnout.getBean() != null) { 1934 Turnout turnout = layoutTurnout.namedTurnout.getBean(); 1935 String turnoutSystemName = turnout.getSystemName(); 1936 String turnoutUserName = turnout.getUserName(); 1937 layoutTrackInfo = layoutTrackInfo+ " turnout: "+turnoutUserName+" ("+turnoutSystemName+")"; 1938 } 1939 String layoutBlockSystemName = nextLayoutBlock.getSystemName(); 1940 String layoutBlockUserName = nextLayoutBlock.getUserName(); 1941 1942 log.warn("Neither branch at {} leads to next block {} ({})", 1943 layoutTrackInfo, 1944 layoutBlockUserName, 1945 layoutBlockSystemName); 1946 } 1947 } 1948 1949 /** 1950 * Initialize the setting (as an object), sets the new track segment (if in 1951 * Block), and sets the prevConnectType. 1952 */ 1953 private Integer getTurnoutSetting( 1954 @Nonnull LayoutTurnout layoutTurnout, HitPointType cType, boolean suppress) { 1955 prevConnectTrack = layoutTurnout; 1956 int setting = Turnout.THROWN; 1957 LayoutTurnout.TurnoutType tType = layoutTurnout.getTurnoutType(); 1958 if (layoutTurnout instanceof LayoutSlip) { 1959 setting = LayoutSlip.UNKNOWN; 1960 LayoutSlip layoutSlip = (LayoutSlip) layoutTurnout; 1961 tType = layoutSlip.getTurnoutType(); 1962 LayoutBlock layoutBlockA = ((TrackSegment) layoutSlip.getConnectA()).getLayoutBlock(); 1963 LayoutBlock layoutBlockB = ((TrackSegment) layoutSlip.getConnectB()).getLayoutBlock(); 1964 LayoutBlock layoutBlockC = ((TrackSegment) layoutSlip.getConnectC()).getLayoutBlock(); 1965 LayoutBlock layoutBlockD = ((TrackSegment) layoutSlip.getConnectD()).getLayoutBlock(); 1966 switch (cType) { 1967 case SLIP_A: 1968 if (nextLayoutBlock == layoutBlockC) { 1969 // exiting block at C 1970 prevConnectType = HitPointType.SLIP_C; 1971 setting = LayoutSlip.STATE_AC; 1972 trackSegment = (TrackSegment) layoutSlip.getConnectC(); 1973 } else if (nextLayoutBlock == layoutBlockD) { 1974 // exiting block at D 1975 prevConnectType = HitPointType.SLIP_D; 1976 setting = LayoutSlip.STATE_AD; 1977 trackSegment = (TrackSegment) layoutSlip.getConnectD(); 1978 } else if (currLayoutBlock == layoutBlockC 1979 && currLayoutBlock != layoutBlockD) { 1980 // block continues at C only 1981 trackSegment = (TrackSegment) layoutTurnout.getConnectC(); 1982 setting = LayoutSlip.STATE_AC; 1983 prevConnectType = HitPointType.SLIP_C; 1984 1985 } else if (currLayoutBlock == layoutBlockD 1986 && currLayoutBlock != layoutBlockC) { 1987 // block continues at D only 1988 setting = LayoutSlip.STATE_AD; 1989 trackSegment = (TrackSegment) layoutTurnout.getConnectD(); 1990 prevConnectType = HitPointType.SLIP_D; 1991 } else { // both connecting track segments continue in current block, must search further 1992 if ((layoutSlip.getConnectC() != null) && trackSegmentLeadsTo((TrackSegment) layoutSlip.getConnectC(), layoutSlip)) { 1993 prevConnectType = HitPointType.SLIP_C; 1994 setting = LayoutSlip.STATE_AC; 1995 trackSegment = (TrackSegment) layoutTurnout.getConnectC(); 1996 } else if ((layoutSlip.getConnectD() != null) && trackSegmentLeadsTo((TrackSegment) layoutSlip.getConnectD(), layoutSlip)) { 1997 prevConnectType = HitPointType.SLIP_D; 1998 setting = LayoutSlip.STATE_AD; 1999 trackSegment = (TrackSegment) layoutTurnout.getConnectD(); 2000 } else { 2001 neitherBranchWarning(layoutTurnout, nextLayoutBlock, suppress); 2002 trackSegment = null; 2003 } 2004 } 2005 break; 2006 case SLIP_B: 2007 if (nextLayoutBlock == layoutBlockD) { 2008 // exiting block at D 2009 prevConnectType = HitPointType.SLIP_D; 2010 setting = LayoutSlip.STATE_BD; 2011 trackSegment = (TrackSegment) layoutSlip.getConnectD(); 2012 } else if (nextLayoutBlock == layoutBlockC 2013 && tType == LayoutSlip.TurnoutType.DOUBLE_SLIP) { 2014 // exiting block at C 2015 prevConnectType = HitPointType.SLIP_C; 2016 setting = LayoutSlip.STATE_BC; 2017 trackSegment = (TrackSegment) layoutSlip.getConnectC(); 2018 } else { 2019 if (tType == LayoutSlip.TurnoutType.DOUBLE_SLIP) { 2020 if (currLayoutBlock == layoutBlockD 2021 && currLayoutBlock != layoutBlockC) { 2022 //Found continuing at D only 2023 trackSegment = (TrackSegment) layoutTurnout.getConnectD(); 2024 setting = LayoutSlip.STATE_BD; 2025 prevConnectType = HitPointType.SLIP_D; 2026 2027 } else if (currLayoutBlock == layoutBlockC 2028 && currLayoutBlock != layoutBlockD) { 2029 //Found continuing at C only 2030 trackSegment = (TrackSegment) layoutTurnout.getConnectC(); 2031 setting = LayoutSlip.STATE_BC; 2032 prevConnectType = HitPointType.SLIP_C; 2033 } else { // both connecting track segments continue in current block, must search further 2034 if ((layoutSlip.getConnectD() != null) && trackSegmentLeadsTo((TrackSegment) layoutSlip.getConnectD(), layoutSlip)) { 2035 prevConnectType = HitPointType.SLIP_D; 2036 setting = LayoutSlip.STATE_BD; 2037 trackSegment = (TrackSegment) layoutTurnout.getConnectD(); 2038 } else if ((layoutSlip.getConnectC() != null) && trackSegmentLeadsTo((TrackSegment) layoutSlip.getConnectC(), layoutSlip)) { 2039 prevConnectType = HitPointType.SLIP_C; 2040 setting = LayoutSlip.STATE_BC; 2041 trackSegment = (TrackSegment) layoutTurnout.getConnectC(); 2042 } else { 2043 neitherBranchWarning(layoutTurnout, nextLayoutBlock, suppress); 2044 trackSegment = null; 2045 } 2046 } 2047 } else { 2048 if (currLayoutBlock == layoutBlockD) { 2049 //Found continuing at D only 2050 trackSegment = (TrackSegment) layoutTurnout.getConnectD(); 2051 setting = LayoutSlip.STATE_BD; 2052 prevConnectType = HitPointType.SLIP_D; 2053 } else { 2054 trackSegment = null; 2055 } 2056 } 2057 } 2058 break; 2059 case SLIP_C: 2060 if (nextLayoutBlock == layoutBlockA) { 2061 // exiting block at A 2062 prevConnectType = HitPointType.SLIP_A; 2063 setting = LayoutSlip.STATE_AC; 2064 trackSegment = (TrackSegment) layoutSlip.getConnectA(); 2065 } else if (nextLayoutBlock == layoutBlockB 2066 && tType == LayoutSlip.TurnoutType.DOUBLE_SLIP) { 2067 // exiting block at B 2068 prevConnectType = HitPointType.SLIP_B; 2069 setting = LayoutSlip.STATE_BC; 2070 trackSegment = (TrackSegment) layoutSlip.getConnectB(); 2071 } else { 2072 if (tType == LayoutSlip.TurnoutType.DOUBLE_SLIP) { 2073 if (currLayoutBlock == layoutBlockA 2074 && currLayoutBlock != layoutBlockB) { 2075 //Found continuing at A only 2076 trackSegment = (TrackSegment) layoutTurnout.getConnectA(); 2077 setting = LayoutSlip.STATE_AC; 2078 prevConnectType = HitPointType.SLIP_A; 2079 } else if (currLayoutBlock == layoutBlockB 2080 && currLayoutBlock != layoutBlockA) { 2081 //Found continuing at B only 2082 trackSegment = (TrackSegment) layoutTurnout.getConnectB(); 2083 setting = LayoutSlip.STATE_BC; 2084 prevConnectType = HitPointType.SLIP_B; 2085 } else { // both connecting track segments continue in current block, must search further 2086 if ((layoutSlip.getConnectA() != null) && trackSegmentLeadsTo((TrackSegment) layoutSlip.getConnectA(), layoutSlip)) { 2087 prevConnectType = HitPointType.SLIP_A; 2088 setting = LayoutSlip.STATE_AC; 2089 trackSegment = (TrackSegment) layoutTurnout.getConnectA(); 2090 } else if ((layoutSlip.getConnectB() != null) && trackSegmentLeadsTo((TrackSegment) layoutSlip.getConnectB(), layoutSlip)) { 2091 prevConnectType = HitPointType.SLIP_B; 2092 setting = LayoutSlip.STATE_BC; 2093 trackSegment = (TrackSegment) layoutTurnout.getConnectB(); 2094 } else { 2095 neitherBranchWarning(layoutTurnout, nextLayoutBlock, suppress); 2096 trackSegment = null; 2097 } 2098 } 2099 } else { 2100 if (currLayoutBlock == layoutBlockA) { 2101 //Found continuing at A only 2102 trackSegment = (TrackSegment) layoutTurnout.getConnectA(); 2103 setting = LayoutSlip.STATE_AC; 2104 prevConnectType = HitPointType.SLIP_A; 2105 } else { 2106 trackSegment = null; 2107 } 2108 } 2109 } 2110 break; 2111 case SLIP_D: 2112 if (nextLayoutBlock == layoutBlockB) { 2113 // exiting block at B 2114 prevConnectType = HitPointType.SLIP_B; 2115 setting = LayoutSlip.STATE_BD; 2116 trackSegment = (TrackSegment) layoutSlip.getConnectB(); 2117 } else if (nextLayoutBlock == layoutBlockA) { 2118 // exiting block at B 2119 prevConnectType = HitPointType.SLIP_A; 2120 setting = LayoutSlip.STATE_AD; 2121 trackSegment = (TrackSegment) layoutSlip.getConnectA(); 2122 } else if (currLayoutBlock == layoutBlockB 2123 && currLayoutBlock != layoutBlockA) { 2124 //Found continuing at B only 2125 trackSegment = (TrackSegment) layoutTurnout.getConnectB(); 2126 setting = LayoutSlip.STATE_BD; 2127 prevConnectType = HitPointType.SLIP_B; 2128 2129 } else if (currLayoutBlock == layoutBlockA 2130 && currLayoutBlock != layoutBlockB) { 2131 //Found continuing at A only 2132 setting = LayoutSlip.STATE_AD; 2133 trackSegment = (TrackSegment) layoutTurnout.getConnectA(); 2134 prevConnectType = HitPointType.SLIP_A; 2135 } else { // both connecting track segments continue in current block, must search further 2136 if ((layoutSlip.getConnectA() != null) && trackSegmentLeadsTo((TrackSegment) layoutSlip.getConnectA(), layoutSlip)) { 2137 prevConnectType = HitPointType.SLIP_A; 2138 setting = LayoutSlip.STATE_AD; 2139 trackSegment = (TrackSegment) layoutTurnout.getConnectA(); 2140 } else if ((layoutSlip.getConnectB() != null) && trackSegmentLeadsTo((TrackSegment) layoutSlip.getConnectB(), layoutSlip)) { 2141 prevConnectType = HitPointType.SLIP_B; 2142 setting = LayoutSlip.STATE_BD; 2143 trackSegment = (TrackSegment) layoutTurnout.getConnectB(); 2144 } else { 2145 neitherBranchWarning(layoutTurnout, nextLayoutBlock, suppress); 2146 trackSegment = null; 2147 } 2148 } 2149 break; 2150 default: 2151 break; 2152 } 2153 if ((trackSegment != null) && (trackSegment.getLayoutBlock() != currLayoutBlock)) { 2154 // continuing track segment is not in this block 2155 trackSegment = null; 2156 } else if (trackSegment == null) { 2157 if (!suppress) { 2158 log.warn("Connectivity not complete at {}", layoutSlip.getDisplayName()); 2159 } 2160 turnoutConnectivity = false; 2161 } 2162 } else { 2163 switch (cType) { 2164 case TURNOUT_A: 2165 // check for left-handed crossover 2166 if (tType == LayoutTurnout.TurnoutType.LH_XOVER) { 2167 // entering at a continuing track of a left-handed crossover 2168 prevConnectType = HitPointType.TURNOUT_B; 2169 setting = Turnout.CLOSED; 2170 trackSegment = (TrackSegment) layoutTurnout.getConnectB(); 2171 } // entering at a throat, determine exit by checking block of connected track segment 2172 else if ((nextLayoutBlock == layoutTurnout.getLayoutBlockB()) || ((layoutTurnout.getConnectB() != null) 2173 && (nextLayoutBlock == ((TrackSegment) layoutTurnout.getConnectB()).getLayoutBlock()))) { 2174 // exiting block at continuing track 2175 prevConnectType = HitPointType.TURNOUT_B; 2176 setting = Turnout.CLOSED; 2177 trackSegment = (TrackSegment) layoutTurnout.getConnectB(); 2178 } else if ((nextLayoutBlock == layoutTurnout.getLayoutBlockC()) || ((layoutTurnout.getConnectC() != null) 2179 && (nextLayoutBlock == ((TrackSegment) layoutTurnout.getConnectC()).getLayoutBlock()))) { 2180 // exiting block at diverging track 2181 prevConnectType = HitPointType.TURNOUT_C; 2182 trackSegment = (TrackSegment) layoutTurnout.getConnectC(); 2183 } // must stay in block after turnout - check if only one track continues in block 2184 else if ((layoutTurnout.getConnectB() != null) && (currLayoutBlock == ((TrackSegment) layoutTurnout.getConnectB()).getLayoutBlock()) 2185 && (layoutTurnout.getConnectC() != null) && (currLayoutBlock != ((TrackSegment) layoutTurnout.getConnectC()).getLayoutBlock())) { 2186 // continuing in block on continuing track only 2187 prevConnectType = HitPointType.TURNOUT_B; 2188 setting = Turnout.CLOSED; 2189 trackSegment = (TrackSegment) layoutTurnout.getConnectB(); 2190 } else if ((layoutTurnout.getConnectC() != null) && (currLayoutBlock == ((TrackSegment) layoutTurnout.getConnectC()).getLayoutBlock()) 2191 && (layoutTurnout.getConnectB() != null) && (currLayoutBlock != ((TrackSegment) layoutTurnout.getConnectB()).getLayoutBlock())) { 2192 // continuing in block on diverging track only 2193 prevConnectType = HitPointType.TURNOUT_C; 2194 trackSegment = (TrackSegment) layoutTurnout.getConnectC(); 2195 } else { // both connecting track segments continue in current block, must search further 2196 // check if continuing track leads to the next block 2197 if ((layoutTurnout.getConnectB() != null) && trackSegmentLeadsTo((TrackSegment) layoutTurnout.getConnectB(), layoutTurnout)) { 2198 prevConnectType = HitPointType.TURNOUT_B; 2199 setting = Turnout.CLOSED; 2200 trackSegment = (TrackSegment) layoutTurnout.getConnectB(); 2201 } // check if diverging track leads to the next block 2202 else if ((layoutTurnout.getConnectC() != null) && trackSegmentLeadsTo((TrackSegment) layoutTurnout.getConnectC(), layoutTurnout)) { 2203 prevConnectType = HitPointType.TURNOUT_C; 2204 trackSegment = (TrackSegment) layoutTurnout.getConnectC(); 2205 } else { 2206 neitherBranchWarning(layoutTurnout, nextLayoutBlock, suppress); 2207 trackSegment = null; 2208 } 2209 } 2210 break; 2211 case TURNOUT_B: 2212 if ((tType == LayoutTurnout.TurnoutType.LH_XOVER) || (tType == LayoutTurnout.TurnoutType.DOUBLE_XOVER)) { 2213 // entering at a throat of a double crossover or a left-handed crossover 2214 if ((nextLayoutBlock == layoutTurnout.getLayoutBlock()) || ((layoutTurnout.getConnectA() != null) 2215 && (nextLayoutBlock == ((TrackSegment) layoutTurnout.getConnectA()).getLayoutBlock()))) { 2216 // exiting block at continuing track 2217 prevConnectType = HitPointType.TURNOUT_A; 2218 setting = Turnout.CLOSED; 2219 trackSegment = (TrackSegment) layoutTurnout.getConnectB(); 2220 } else if ((nextLayoutBlock == layoutTurnout.getLayoutBlockD()) || ((layoutTurnout.getConnectD() != null) 2221 && (nextLayoutBlock == ((TrackSegment) layoutTurnout.getConnectD()).getLayoutBlock()))) { 2222 // exiting block at diverging track 2223 prevConnectType = HitPointType.TURNOUT_D; 2224 trackSegment = (TrackSegment) layoutTurnout.getConnectD(); 2225 } // must stay in block after turnout 2226 else if (((layoutTurnout.getConnectA() != null) && (currLayoutBlock == ((TrackSegment) layoutTurnout.getConnectA()).getLayoutBlock())) 2227 && ((layoutTurnout.getConnectD() != null) && (currLayoutBlock != ((TrackSegment) layoutTurnout.getConnectD()).getLayoutBlock()))) { 2228 // continuing in block on continuing track only 2229 prevConnectType = HitPointType.TURNOUT_A; 2230 setting = Turnout.CLOSED; 2231 trackSegment = (TrackSegment) layoutTurnout.getConnectA(); 2232 } else if (((layoutTurnout.getConnectD() != null) && (currLayoutBlock == ((TrackSegment) layoutTurnout.getConnectD()).getLayoutBlock())) 2233 && ((layoutTurnout.getConnectA() != null) && (currLayoutBlock != ((TrackSegment) layoutTurnout.getConnectA()).getLayoutBlock()))) { 2234 // continuing in block on diverging track only 2235 prevConnectType = HitPointType.TURNOUT_D; 2236 trackSegment = (TrackSegment) layoutTurnout.getConnectD(); 2237 } else { // both connecting track segments continue in current block, must search further 2238 // check if continuing track leads to the next block 2239 if ((layoutTurnout.getConnectA() != null) && trackSegmentLeadsTo((TrackSegment) layoutTurnout.getConnectA(), layoutTurnout)) { 2240 prevConnectType = HitPointType.TURNOUT_A; 2241 setting = Turnout.CLOSED; 2242 trackSegment = (TrackSegment) layoutTurnout.getConnectA(); 2243 } // check if diverging track leads to the next block 2244 else if ((layoutTurnout.getConnectD() != null) && trackSegmentLeadsTo((TrackSegment) layoutTurnout.getConnectD(), layoutTurnout)) { 2245 prevConnectType = HitPointType.TURNOUT_D; 2246 trackSegment = (TrackSegment) layoutTurnout.getConnectD(); 2247 } else { 2248 neitherBranchWarning(layoutTurnout, nextLayoutBlock, suppress); 2249 trackSegment = null; 2250 } 2251 } 2252 } else { 2253 // entering at continuing track, must exit at throat 2254 prevConnectType = HitPointType.TURNOUT_A; 2255 setting = Turnout.CLOSED; 2256 trackSegment = (TrackSegment) layoutTurnout.getConnectA(); 2257 } 2258 break; 2259 case TURNOUT_C: 2260 if ((tType == LayoutTurnout.TurnoutType.RH_XOVER) || (tType == LayoutTurnout.TurnoutType.DOUBLE_XOVER)) { 2261 // entering at a throat of a double crossover or a right-handed crossover 2262 if ((nextLayoutBlock == layoutTurnout.getLayoutBlockD()) || ((layoutTurnout.getConnectD() != null) 2263 && (nextLayoutBlock == ((TrackSegment) layoutTurnout.getConnectD()).getLayoutBlock()))) { 2264 // exiting block at continuing track 2265 prevConnectType = HitPointType.TURNOUT_D; 2266 setting = Turnout.CLOSED; 2267 trackSegment = (TrackSegment) layoutTurnout.getConnectD(); 2268 } else if ((nextLayoutBlock == layoutTurnout.getLayoutBlock()) || ((layoutTurnout.getConnectA() != null) 2269 && (nextLayoutBlock == ((TrackSegment) layoutTurnout.getConnectA()).getLayoutBlock()))) { 2270 // exiting block at diverging track 2271 prevConnectType = HitPointType.TURNOUT_A; 2272 trackSegment = (TrackSegment) layoutTurnout.getConnectA(); 2273 } // must stay in block after turnout 2274 else if (((layoutTurnout.getConnectD() != null) && (currLayoutBlock == ((TrackSegment) layoutTurnout.getConnectD()).getLayoutBlock())) 2275 && ((layoutTurnout.getConnectA() != null) && (currLayoutBlock != ((TrackSegment) layoutTurnout.getConnectA()).getLayoutBlock()))) { 2276 // continuing in block on continuing track 2277 prevConnectType = HitPointType.TURNOUT_D; 2278 setting = Turnout.CLOSED; 2279 trackSegment = (TrackSegment) layoutTurnout.getConnectD(); 2280 } else if (((layoutTurnout.getConnectA() != null) && (currLayoutBlock == ((TrackSegment) layoutTurnout.getConnectA()).getLayoutBlock())) 2281 && ((layoutTurnout.getConnectD() != null) && (currLayoutBlock != ((TrackSegment) layoutTurnout.getConnectD()).getLayoutBlock()))) { 2282 // continuing in block on diverging track 2283 prevConnectType = HitPointType.TURNOUT_A; 2284 trackSegment = (TrackSegment) layoutTurnout.getConnectA(); 2285 } else { // both connecting track segments continue in current block, must search further 2286 // check if continuing track leads to the next block 2287 if ((layoutTurnout.getConnectD() != null) && trackSegmentLeadsTo((TrackSegment) layoutTurnout.getConnectD(), layoutTurnout)) { 2288 prevConnectType = HitPointType.TURNOUT_D; 2289 setting = Turnout.CLOSED; 2290 trackSegment = (TrackSegment) layoutTurnout.getConnectD(); 2291 } // check if diverging track leads to the next block 2292 else if ((layoutTurnout.getConnectA() != null) && trackSegmentLeadsTo((TrackSegment) layoutTurnout.getConnectA(), layoutTurnout)) { 2293 prevConnectType = HitPointType.TURNOUT_A; 2294 trackSegment = (TrackSegment) layoutTurnout.getConnectA(); 2295 } else { 2296 neitherBranchWarning(layoutTurnout, nextLayoutBlock, suppress); 2297 trackSegment = null; 2298 } 2299 } 2300 } else if (tType == LayoutTurnout.TurnoutType.LH_XOVER) { 2301 // entering at continuing track, must exit at throat 2302 prevConnectType = HitPointType.TURNOUT_D; 2303 trackSegment = (TrackSegment) layoutTurnout.getConnectD(); 2304 setting = Turnout.CLOSED; 2305 } else { 2306 // entering at diverging track, must exit at throat 2307 prevConnectType = HitPointType.TURNOUT_A; 2308 trackSegment = (TrackSegment) layoutTurnout.getConnectA(); 2309 } 2310 break; 2311 case TURNOUT_D: 2312 if ((tType == LayoutTurnout.TurnoutType.LH_XOVER) || (tType == LayoutTurnout.TurnoutType.DOUBLE_XOVER)) { 2313 // entering at a throat of a double crossover or a left-handed crossover 2314 if ((nextLayoutBlock == layoutTurnout.getLayoutBlockC()) || ((layoutTurnout.getConnectC() != null) 2315 && (nextLayoutBlock == ((TrackSegment) layoutTurnout.getConnectC()).getLayoutBlock()))) { 2316 // exiting block at continuing track 2317 prevConnectType = HitPointType.TURNOUT_C; 2318 setting = Turnout.CLOSED; 2319 trackSegment = (TrackSegment) layoutTurnout.getConnectC(); 2320 } else if ((nextLayoutBlock == layoutTurnout.getLayoutBlockB()) || ((layoutTurnout.getConnectB() != null) 2321 && (nextLayoutBlock == ((TrackSegment) layoutTurnout.getConnectB()).getLayoutBlock()))) { 2322 // exiting block at diverging track 2323 prevConnectType = HitPointType.TURNOUT_B; 2324 trackSegment = (TrackSegment) layoutTurnout.getConnectB(); 2325 } // must stay in block after turnout 2326 else if (((layoutTurnout.getConnectC() != null) && (currLayoutBlock == ((TrackSegment) layoutTurnout.getConnectC()).getLayoutBlock())) 2327 && ((layoutTurnout.getConnectB() != null) && (currLayoutBlock != ((TrackSegment) layoutTurnout.getConnectB()).getLayoutBlock()))) { 2328 // continuing in block on continuing track 2329 prevConnectType = HitPointType.TURNOUT_C; 2330 setting = Turnout.CLOSED; 2331 trackSegment = (TrackSegment) layoutTurnout.getConnectC(); 2332 } else if (((layoutTurnout.getConnectB() != null) && (currLayoutBlock == ((TrackSegment) layoutTurnout.getConnectB()).getLayoutBlock())) 2333 && ((layoutTurnout.getConnectC() != null) && (currLayoutBlock != ((TrackSegment) layoutTurnout.getConnectC()).getLayoutBlock()))) { 2334 // continuing in block on diverging track 2335 prevConnectType = HitPointType.TURNOUT_B; 2336 trackSegment = (TrackSegment) layoutTurnout.getConnectB(); 2337 } else { // both connecting track segments continue in current block, must search further 2338 // check if continuing track leads to the next block 2339 if ((layoutTurnout.getConnectC() != null) && trackSegmentLeadsTo((TrackSegment) layoutTurnout.getConnectC(), layoutTurnout)) { 2340 prevConnectType = HitPointType.TURNOUT_C; 2341 setting = Turnout.CLOSED; 2342 trackSegment = (TrackSegment) layoutTurnout.getConnectC(); 2343 } // check if diverging track leads to the next block 2344 else if ((layoutTurnout.getConnectB() != null) && trackSegmentLeadsTo((TrackSegment) layoutTurnout.getConnectB(), layoutTurnout)) { 2345 prevConnectType = HitPointType.TURNOUT_B; 2346 trackSegment = (TrackSegment) layoutTurnout.getConnectB(); 2347 } else { 2348 neitherBranchWarning(layoutTurnout, nextLayoutBlock, suppress); 2349 trackSegment = null; 2350 } 2351 } 2352 } else if (tType == LayoutTurnout.TurnoutType.RH_XOVER) { 2353 // entering at through track of a right-handed crossover, must exit at throat 2354 prevConnectType = HitPointType.TURNOUT_C; 2355 trackSegment = (TrackSegment) layoutTurnout.getConnectC(); 2356 setting = Turnout.CLOSED; 2357 } else { 2358 // entering at diverging track of a right-handed crossover, must exit at throat 2359 prevConnectType = HitPointType.TURNOUT_A; 2360 trackSegment = (TrackSegment) layoutTurnout.getConnectA(); 2361 } 2362 break; 2363 default: { 2364 log.warn("getTurnoutSetting() unknown cType: {}", cType); 2365 break; 2366 } 2367 } // switch (cType) 2368 2369 if ((trackSegment != null) && (trackSegment.getLayoutBlock() != currLayoutBlock)) { 2370 // continuing track segment is not in this block 2371 trackSegment = null; 2372 } else if (trackSegment == null) { 2373 if (!suppress) { 2374 log.warn("Connectivity not complete at {}", layoutTurnout.getTurnoutName()); 2375 } 2376 turnoutConnectivity = false; 2377 } 2378 if (layoutTurnout.getContinuingSense() != Turnout.CLOSED) { 2379 if (setting == Turnout.THROWN) { 2380 setting = Turnout.CLOSED; 2381 } else if (setting == Turnout.CLOSED) { 2382 setting = Turnout.THROWN; 2383 } 2384 } 2385 } 2386 return (setting); 2387 } 2388 2389 /** 2390 * Follow the track from a beginning track segment to its exits from the 2391 * current LayoutBlock 'currLayoutBlock' until the track connects to the 2392 * designated Block 'nextLayoutBlock' or all exit points have been tested. 2393 * 2394 * @return 'true' if designated Block is connected; 'false' if not 2395 */ 2396 private boolean trackSegmentLeadsTo( 2397 @CheckForNull TrackSegment trackSegment, @CheckForNull LayoutTrack layoutTrack) { 2398 if ((trackSegment == null) || (layoutTrack == null)) { 2399 log.error("Null argument on entry to trackSegmentLeadsTo"); 2400 return false; 2401 } 2402 TrackSegment curTrackSegment = trackSegment; 2403 LayoutTrack curLayoutTrack = layoutTrack; 2404 2405 if (log.isDebugEnabled()) { 2406 log.info("trackSegmentLeadsTo({}, {}): entry", curTrackSegment.getName(), curLayoutTrack.getName()); 2407 } 2408 2409 // post process track segment and conObj lists 2410 List<TrackSegment> postTrackSegments = new ArrayList<>(); 2411 List<LayoutTrack> postLayoutTracks = new ArrayList<>(); 2412 2413 HitPointType conType; 2414 LayoutTrack conLayoutTrack; 2415 2416 // follow track to all exit points outside this block 2417 while (curTrackSegment != null) { 2418 // if the current track segment is in the next block... 2419 if (curTrackSegment.getLayoutBlock() == nextLayoutBlock) { 2420 return true; // ... we're done! 2421 } 2422 2423 // if the current track segment is in the current block... 2424 if (curTrackSegment.getLayoutBlock() == currLayoutBlock) { 2425 // identify next destination along track 2426 if (curTrackSegment.getConnect1() == curLayoutTrack) { 2427 // entered through 1, leaving through 2 2428 conType = curTrackSegment.getType2(); 2429 conLayoutTrack = curTrackSegment.getConnect2(); 2430 } else if (curTrackSegment.getConnect2() == curLayoutTrack) { 2431 // entered through 2, leaving through 1 2432 conType = curTrackSegment.getType1(); 2433 conLayoutTrack = curTrackSegment.getConnect1(); 2434 } else { 2435 log.error("Connectivity error when following track {} in Block {}", curTrackSegment.getName(), currLayoutBlock.getUserName()); 2436 log.warn("{} not connected to {} (connects: {} & {})", 2437 curLayoutTrack.getName(), 2438 curTrackSegment.getName(), 2439 curTrackSegment.getConnect1Name(), 2440 curTrackSegment.getConnect2Name()); 2441 return false; 2442 } 2443 2444 if (log.isDebugEnabled()) { 2445 log.info("In block {}, going from {} thru {} to {} (conType: {}), nextLayoutBlock: {}", 2446 currLayoutBlock.getUserName(), 2447 conLayoutTrack.getName(), 2448 curTrackSegment.getName(), 2449 curLayoutTrack.getName(), 2450 conType.name(), 2451 nextLayoutBlock.getId()); 2452 } 2453 2454 // follow track according to next destination type 2455 // this is a positionable point 2456 if (conType == HitPointType.POS_POINT) { 2457 // reached anchor point or end bumper 2458 if (((PositionablePoint) conLayoutTrack).getType() == PositionablePoint.PointType.END_BUMPER) { 2459 // end of line without reaching 'nextLayoutBlock' 2460 if (log.isDebugEnabled()) { 2461 log.info("end of line without reaching {}", nextLayoutBlock.getId()); 2462 } 2463 curTrackSegment = null; 2464 } else if (((PositionablePoint) conLayoutTrack).getType() == PositionablePoint.PointType.ANCHOR 2465 || ((PositionablePoint) conLayoutTrack).getType() == PositionablePoint.PointType.EDGE_CONNECTOR) { 2466 // proceed to next track segment if within the same Block 2467 if (((PositionablePoint) conLayoutTrack).getConnect1() == curTrackSegment) { 2468 curTrackSegment = (((PositionablePoint) conLayoutTrack).getConnect2()); 2469 } else { 2470 curTrackSegment = (((PositionablePoint) conLayoutTrack).getConnect1()); 2471 } 2472 curLayoutTrack = conLayoutTrack; 2473 } 2474 } else if (HitPointType.isLevelXingHitType(conType)) { 2475 // reached a level crossing 2476 if ((conType == HitPointType.LEVEL_XING_A) || (conType == HitPointType.LEVEL_XING_C)) { 2477 if (((LevelXing) conLayoutTrack).getLayoutBlockAC() != currLayoutBlock) { 2478 if (((LevelXing) conLayoutTrack).getLayoutBlockAC() == nextLayoutBlock) { 2479 return true; 2480 } else { 2481 curTrackSegment = null; 2482 } 2483 } else if (conType == HitPointType.LEVEL_XING_A) { 2484 curTrackSegment = (TrackSegment) ((LevelXing) conLayoutTrack).getConnectC(); 2485 } else { 2486 curTrackSegment = (TrackSegment) ((LevelXing) conLayoutTrack).getConnectA(); 2487 } 2488 } else { 2489 if (((LevelXing) conLayoutTrack).getLayoutBlockBD() != currLayoutBlock) { 2490 if (((LevelXing) conLayoutTrack).getLayoutBlockBD() == nextLayoutBlock) { 2491 return true; 2492 } else { 2493 curTrackSegment = null; 2494 } 2495 } else if (conType == HitPointType.LEVEL_XING_B) { 2496 curTrackSegment = (TrackSegment) ((LevelXing) conLayoutTrack).getConnectD(); 2497 } else { 2498 curTrackSegment = (TrackSegment) ((LevelXing) conLayoutTrack).getConnectB(); 2499 } 2500 } 2501 curLayoutTrack = conLayoutTrack; 2502 } else if (HitPointType.isTurnoutHitType(conType)) { 2503 // reached a turnout 2504 LayoutTurnout lt = (LayoutTurnout) conLayoutTrack; 2505 LayoutTurnout.TurnoutType tType = lt.getTurnoutType(); 2506 2507 // RH, LH or DOUBLE _XOVER 2508 if (lt.isTurnoutTypeXover()) { 2509 // reached a crossover turnout 2510 switch (conType) { 2511 case TURNOUT_A: 2512 if ((lt.getLayoutBlock()) != currLayoutBlock) { 2513 if (lt.getLayoutBlock() == nextLayoutBlock) { 2514 return true; 2515 } else { 2516 curTrackSegment = null; 2517 } 2518 } else if ((lt.getLayoutBlockB() == nextLayoutBlock) || ((tType != LayoutTurnout.TurnoutType.LH_XOVER) 2519 && (lt.getLayoutBlockC() == nextLayoutBlock))) { 2520 return true; 2521 } else if (lt.getLayoutBlockB() == currLayoutBlock) { 2522 curTrackSegment = (TrackSegment) lt.getConnectB(); 2523 if ((tType != LayoutTurnout.TurnoutType.LH_XOVER) && (lt.getLayoutBlockC() == currLayoutBlock)) { 2524 postTrackSegments.add((TrackSegment) lt.getConnectC()); 2525 postLayoutTracks.add(conLayoutTrack); 2526 } 2527 } else if ((tType != LayoutTurnout.TurnoutType.LH_XOVER) && (lt.getLayoutBlockC() == currLayoutBlock)) { 2528 curTrackSegment = (TrackSegment) lt.getConnectC(); 2529 } else { 2530 curTrackSegment = null; 2531 } 2532 break; 2533 case TURNOUT_B: 2534 if ((lt.getLayoutBlockB()) != currLayoutBlock) { 2535 if (lt.getLayoutBlockB() == nextLayoutBlock) { 2536 return true; 2537 } else { 2538 curTrackSegment = null; 2539 } 2540 } else if ((lt.getLayoutBlock() == nextLayoutBlock) || ((tType != LayoutTurnout.TurnoutType.RH_XOVER) 2541 && (lt.getLayoutBlockD() == nextLayoutBlock))) { 2542 return true; 2543 } else if (lt.getLayoutBlock() == currLayoutBlock) { 2544 curTrackSegment = (TrackSegment) lt.getConnectA(); 2545 if ((tType != LayoutTurnout.TurnoutType.RH_XOVER) && (lt.getLayoutBlockD() == currLayoutBlock)) { 2546 postTrackSegments.add((TrackSegment) lt.getConnectD()); 2547 postLayoutTracks.add(conLayoutTrack); 2548 } 2549 } else if ((tType != LayoutTurnout.TurnoutType.RH_XOVER) && (lt.getLayoutBlockD() == currLayoutBlock)) { 2550 curTrackSegment = (TrackSegment) lt.getConnectD(); 2551 } else { 2552 curTrackSegment = null; 2553 } 2554 break; 2555 case TURNOUT_C: 2556 if ((lt.getLayoutBlockC()) != currLayoutBlock) { 2557 if (lt.getLayoutBlockC() == nextLayoutBlock) { 2558 return true; 2559 } else { 2560 curTrackSegment = null; 2561 } 2562 } else if ((lt.getLayoutBlockD() == nextLayoutBlock) || ((tType != LayoutTurnout.TurnoutType.LH_XOVER) 2563 && (lt.getLayoutBlock() == nextLayoutBlock))) { 2564 return true; 2565 } else if (lt.getLayoutBlockD() == currLayoutBlock) { 2566 curTrackSegment = (TrackSegment) lt.getConnectD(); 2567 if ((tType != LayoutTurnout.TurnoutType.LH_XOVER) && (lt.getLayoutBlock() == currLayoutBlock)) { 2568 postTrackSegments.add((TrackSegment) lt.getConnectA()); 2569 postLayoutTracks.add(conLayoutTrack); 2570 } 2571 } else if ((tType != LayoutTurnout.TurnoutType.LH_XOVER) && (lt.getLayoutBlock() == currLayoutBlock)) { 2572 curTrackSegment = (TrackSegment) lt.getConnectA(); 2573 } else { 2574 curTrackSegment = null; 2575 } 2576 break; 2577 case TURNOUT_D: 2578 if ((lt.getLayoutBlockD()) != currLayoutBlock) { 2579 if (lt.getLayoutBlockD() == nextLayoutBlock) { 2580 return true; 2581 } else { 2582 curTrackSegment = null; 2583 } 2584 } else if ((lt.getLayoutBlockC() == nextLayoutBlock) || ((tType != LayoutTurnout.TurnoutType.RH_XOVER) 2585 && (lt.getLayoutBlockB() == nextLayoutBlock))) { 2586 return true; 2587 } else if (lt.getLayoutBlockC() == currLayoutBlock) { 2588 curTrackSegment = (TrackSegment) lt.getConnectC(); 2589 if ((tType != LayoutTurnout.TurnoutType.RH_XOVER) && (lt.getLayoutBlockB() == currLayoutBlock)) { 2590 postTrackSegments.add((TrackSegment) lt.getConnectB()); 2591 postLayoutTracks.add(conLayoutTrack); 2592 } 2593 } else if ((tType != LayoutTurnout.TurnoutType.RH_XOVER) && (lt.getLayoutBlockB() == currLayoutBlock)) { 2594 curTrackSegment = (TrackSegment) lt.getConnectB(); 2595 } else { 2596 curTrackSegment = null; 2597 } 2598 break; 2599 default: { 2600 log.warn("trackSegmentLeadsTo() unknown conType: {}", conType); 2601 break; 2602 } 2603 } // switch (conType) 2604 curLayoutTrack = conLayoutTrack; 2605 } else // if RH, LH or DOUBLE _XOVER 2606 if (LayoutTurnout.isTurnoutTypeTurnout(tType)) { 2607 // reached RH. LH, or WYE turnout 2608 if (lt.getLayoutBlock() != currLayoutBlock) { // if not in the last block... 2609 if (lt.getLayoutBlock() == nextLayoutBlock) { // if in the next block 2610 return true; //(Yes!) done 2611 } else { 2612 curTrackSegment = null; //(nope) dead end 2613 } 2614 } else { 2615 if (conType == HitPointType.TURNOUT_A) { 2616 // if the connect B or C are in the next block... 2617 if ((((TrackSegment) lt.getConnectB()).getLayoutBlock() == nextLayoutBlock) 2618 || (((TrackSegment) lt.getConnectC()).getLayoutBlock() == nextLayoutBlock)) { 2619 return true; //(yes!) done! 2620 } else // if connect B is in this block... 2621 if (((TrackSegment) lt.getConnectB()).getLayoutBlock() == currLayoutBlock) { 2622 curTrackSegment = (TrackSegment) lt.getConnectB(); 2623 //if connect C is in this block 2624 if (((TrackSegment) lt.getConnectC()).getLayoutBlock() == currLayoutBlock) { 2625 // add it to our post processing list 2626 postTrackSegments.add((TrackSegment) lt.getConnectC()); 2627 postLayoutTracks.add(conLayoutTrack); 2628 } 2629 } else { 2630 curTrackSegment = (TrackSegment) lt.getConnectC(); 2631 } 2632 } else { 2633 curTrackSegment = (TrackSegment) lt.getConnectA(); 2634 } 2635 curLayoutTrack = conLayoutTrack; 2636 } 2637 } // if RH, LH or WYE _TURNOUT 2638 } else if (HitPointType.isSlipHitType(conType)) { 2639 LayoutSlip ls = (LayoutSlip) conLayoutTrack; 2640 LayoutTurnout.TurnoutType tType = ls.getTurnoutType(); 2641 2642 if (ls.getLayoutBlock() != currLayoutBlock) { // if not in the last block 2643 if (ls.getLayoutBlock() == nextLayoutBlock) { // if in the next block 2644 return true; //(yes!) done 2645 } else { 2646 curTrackSegment = null; //(nope) dead end 2647 } 2648 } else { // still in the last block 2649 LayoutBlock layoutBlockA = ((TrackSegment) ls.getConnectA()).getLayoutBlock(); 2650 LayoutBlock layoutBlockB = ((TrackSegment) ls.getConnectB()).getLayoutBlock(); 2651 LayoutBlock layoutBlockC = ((TrackSegment) ls.getConnectC()).getLayoutBlock(); 2652 LayoutBlock layoutBlockD = ((TrackSegment) ls.getConnectD()).getLayoutBlock(); 2653 switch (conType) { 2654 case SLIP_A: 2655 if (layoutBlockC == nextLayoutBlock) { 2656 //Leg A-D has next currLayoutBlock 2657 return true; 2658 } 2659 if (layoutBlockD == nextLayoutBlock) { 2660 //Leg A-C has next currLayoutBlock 2661 return true; 2662 } 2663 if (layoutBlockC == currLayoutBlock) { 2664 curTrackSegment = (TrackSegment) ls.getConnectC(); 2665 if (layoutBlockD == currLayoutBlock) { 2666 postTrackSegments.add((TrackSegment) ls.getConnectD()); 2667 postLayoutTracks.add(conLayoutTrack); 2668 } 2669 } else { 2670 curTrackSegment = (TrackSegment) ls.getConnectD(); 2671 } 2672 break; 2673 case SLIP_B: 2674 if (tType == LayoutSlip.TurnoutType.SINGLE_SLIP) { 2675 curTrackSegment = (TrackSegment) ls.getConnectD(); 2676 break; 2677 } 2678 if (layoutBlockC == nextLayoutBlock) { 2679 //Leg B-C has next currLayoutBlock 2680 return true; 2681 } 2682 if (layoutBlockD == nextLayoutBlock) { 2683 //Leg D-B has next currLayoutBlock 2684 return true; 2685 } 2686 if (layoutBlockC == currLayoutBlock) { 2687 curTrackSegment = (TrackSegment) ls.getConnectC(); 2688 if (layoutBlockD == currLayoutBlock) { 2689 postTrackSegments.add((TrackSegment) ls.getConnectD()); 2690 postLayoutTracks.add(conLayoutTrack); 2691 } 2692 } else { 2693 curTrackSegment = (TrackSegment) ls.getConnectD(); 2694 } 2695 break; 2696 case SLIP_C: 2697 // if this is a single slip... 2698 if (tType == LayoutSlip.TurnoutType.SINGLE_SLIP) { 2699 curTrackSegment = (TrackSegment) ls.getConnectA(); 2700 break; 2701 } 2702 //if connect A is in the next block 2703 if (layoutBlockA == nextLayoutBlock) { 2704 return true; //(Yes!) Leg A-C has next block 2705 } 2706 //if connect B is in the next block 2707 if (layoutBlockB == nextLayoutBlock) { 2708 return true; //(Yes!) Leg B-C has next block 2709 } 2710 2711 //if connect B is in this block... 2712 if (layoutBlockB == currLayoutBlock) { 2713 curTrackSegment = (TrackSegment) ls.getConnectB(); 2714 //if connect A is in this block... 2715 if (layoutBlockA == currLayoutBlock) { 2716 // add it to our post processing list 2717 postTrackSegments.add((TrackSegment) ls.getConnectA()); 2718 postLayoutTracks.add(conLayoutTrack); 2719 } 2720 } else { //if connect A is in this block... 2721 if (layoutBlockA == currLayoutBlock) { 2722 curTrackSegment = (TrackSegment) ls.getConnectA(); 2723 } else { 2724 log.debug("{} not connected to {} (connections: {} & {})", 2725 currLayoutBlock.getUserName(), ls.getName(), 2726 ls.getConnectA().getName(), 2727 ls.getConnectB().getName()); 2728 } 2729 } 2730 break; 2731 case SLIP_D: 2732 if (layoutBlockA == nextLayoutBlock) { 2733 //Leg D-A has next currLayoutBlock 2734 return true; 2735 } 2736 if (layoutBlockB == nextLayoutBlock) { 2737 //Leg D-B has next currLayoutBlock 2738 return true; 2739 } 2740 if (layoutBlockB == currLayoutBlock) { 2741 curTrackSegment = (TrackSegment) ls.getConnectB(); 2742 if (layoutBlockA == currLayoutBlock) { 2743 postTrackSegments.add((TrackSegment) ls.getConnectA()); 2744 postLayoutTracks.add(conLayoutTrack); 2745 } 2746 } else { 2747 curTrackSegment = (TrackSegment) ls.getConnectA(); 2748 } 2749 break; 2750 default: { 2751 log.warn("trackSegmentLeadsTo() unknown conType: {}", conType); 2752 break; 2753 } 2754 } //switch (conType) 2755 curLayoutTrack = conLayoutTrack; 2756 } // if (ls.getLayoutBlock() != currLayoutBlock 2757 } //else if (LayoutEditor.HitPointType.isSlipHitType(conType)) 2758 } else { 2759 curTrackSegment = null; 2760 } 2761 2762 if (curTrackSegment == null) { 2763 // reached an end point outside this block that was not 'nextLayoutBlock' - any other paths to follow? 2764 if (postTrackSegments.size() > 0) { 2765 // paths remain, initialize the next one 2766 curTrackSegment = postTrackSegments.get(0); 2767 curLayoutTrack = postLayoutTracks.get(0); 2768 // remove it from the list of unexplored paths 2769 postTrackSegments.remove(0); 2770 postLayoutTracks.remove(0); 2771 } 2772 } 2773 } // while (curTS != null) 2774 2775 // searched all possible paths in this block, 'currLayoutBlock', without finding the desired exit block, 'nextLayoutBlock' 2776 return false; 2777 } 2778 2779 private boolean turnoutConnectivity = true; 2780 2781 /** 2782 * Check if the connectivity of the turnouts has been completed in the block 2783 * after calling getTurnoutList(). 2784 * 2785 * @return true if turnout connectivity is complete; otherwise false 2786 */ 2787 public boolean isTurnoutConnectivityComplete() { 2788 return turnoutConnectivity; 2789 } 2790 2791 private void setupOpposingTrackSegment(@Nonnull LevelXing x, HitPointType cType) { 2792 switch (cType) { 2793 case LEVEL_XING_A: 2794 trackSegment = (TrackSegment) x.getConnectC(); 2795 prevConnectType = HitPointType.LEVEL_XING_C; 2796 break; 2797 case LEVEL_XING_B: 2798 trackSegment = (TrackSegment) x.getConnectD(); 2799 prevConnectType = HitPointType.LEVEL_XING_D; 2800 break; 2801 case LEVEL_XING_C: 2802 trackSegment = (TrackSegment) x.getConnectA(); 2803 prevConnectType = HitPointType.LEVEL_XING_A; 2804 break; 2805 case LEVEL_XING_D: 2806 trackSegment = (TrackSegment) x.getConnectB(); 2807 prevConnectType = HitPointType.LEVEL_XING_B; 2808 break; 2809 default: 2810 break; 2811 } 2812 if (trackSegment.getLayoutBlock() != currLayoutBlock) { 2813 // track segment is not in this block 2814 trackSegment = null; 2815 } else { 2816 // track segment is in this block 2817 prevConnectTrack = x; 2818 } 2819 } 2820 2821 @Nonnull 2822 public List<LayoutTurnout> getAllTurnoutsThisBlock( 2823 @Nonnull LayoutBlock currLayoutBlock) { 2824 return layoutEditor.getLayoutTracks().stream() 2825 .filter((o) -> (o instanceof LayoutTurnout)) // this includes LayoutSlips 2826 .map(LayoutTurnout.class::cast) 2827 .filter((lt) -> ((lt.getLayoutBlock() == currLayoutBlock) 2828 || (lt.getLayoutBlockB() == currLayoutBlock) 2829 || (lt.getLayoutBlockC() == currLayoutBlock) 2830 || (lt.getLayoutBlockD() == currLayoutBlock))) 2831 .map(LayoutTurnout.class::cast) 2832 .collect(Collectors.toCollection(ArrayList::new)); 2833 } 2834 2835 // initialize logging 2836 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ConnectivityUtil.class); 2837}