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