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