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