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