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