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}