001package jmri.jmrit.display.layoutEditor;
002
003import java.util.ArrayList;
004import java.util.Arrays;
005import java.util.List;
006import jmri.BeanSetting;
007import jmri.Path;
008import jmri.Turnout;
009import org.slf4j.Logger;
010import org.slf4j.LoggerFactory;
011
012/**
013 * LayoutEditorAuxTools provides tools making use of layout connectivity
014 * available in Layout Editor panels. (More tools are in
015 * LayoutEditorTools.java.)
016 * <p>
017 * This module manages block connectivity for its associated LayoutEditor.
018 * <p>
019 * A single object of this type, obtained via {@link LayoutEditor#getLEAuxTools()}
020 * is shared across all instances of {@link ConnectivityUtil}.
021 * <p>
022 * The tools in this module are accessed via the Tools menu in Layout Editor, or
023 * directly from LayoutEditor or LayoutEditor specific modules.
024 *
025 * @author Dave Duchamp Copyright (c) 2008
026 * @author George Warner Copyright (c) 2017-2018
027 */
028final public class LayoutEditorAuxTools {
029    // constants
030
031    // operational instance variables
032    final private LayoutModels models;
033    final private List<LayoutConnectivity> cList = new ArrayList<>(); // LayoutConnectivity list
034    private boolean blockConnectivityChanged = false;  // true if block connectivity may have changed
035    private boolean initialized = false;
036
037    // constructor method
038    public LayoutEditorAuxTools(LayoutModels theModels) {
039        models = theModels;
040    }
041
042    // register a change in block connectivity that may require an update of connectivity list
043    public void setBlockConnectivityChanged() {
044        blockConnectivityChanged = true;
045    }
046
047    /**
048     * Get Connectivity involving a specific Layout Block.
049     * <p>
050     * This routine returns an ArrayList of BlockConnectivity objects involving
051     * the specified LayoutBlock.
052     * @param blk the layout block.
053     * @return the layout connectivity list, not null.
054     */
055    public List<LayoutConnectivity> getConnectivityList(LayoutBlock blk) {
056        if (!initialized) {
057            initializeBlockConnectivity();
058        }
059        if (blockConnectivityChanged) {
060            updateBlockConnectivity();
061        }
062        List<LayoutConnectivity> retList = new ArrayList<>();
063        for (LayoutConnectivity lc : cList) {
064            if ((lc.getBlock1() == blk) || (lc.getBlock2() == blk)) {
065                retList.add(lc);
066            }
067        }
068        return (retList);
069    }
070
071    /**
072     * Initializes the block connectivity (block boundaries) for a Layout Editor
073     * panel.
074     * <p>
075     * This routine sets up the LayoutConnectivity objects needed to show the
076     * current connectivity. It gets its information from arrays contained in
077     * LayoutEditor.
078     * <p>
079     * One LayoutConnectivity object is created for each block boundary --
080     * connection points where two blocks join. Block boundaries can occur where
081     * ever a track segment in one block joins with: 1) a track segment in
082     * another block -OR- 2) a connection point in a layout turnout in another
083     * block -OR- 3) a connection point in a level crossing in another block.
084     * <p>
085     * The first block is always a track segment. The direction set in the
086     * LayoutConnectivity is the direction of the track segment alone for cases
087     * 2) and 3) above. For case 1), two track segments, the direction reflects
088     * an "average" over the two track segments. See LayoutConnectivity for the
089     * allowed values of direction.
090     * <p>
091     * Normally the initialization only occurs once after the panel is loaded.  When edge
092     * connectors are used, an incomplete LayoutConnectivity table can occur due to the
093     * panel loading sequence and the complexity of edge connector relationships.
094     * An additional initialization is allowed to build the final LayoutConnectivity table.
095     */
096    public void initializeBlockConnectivity() {
097        cList.clear();
098        List<LayoutConnectivity> lcs = null;
099
100        for (LayoutTrackView ltv : models.getLayoutTrackViews()) {
101            if ((ltv instanceof PositionablePointView)    // effectively, skip LevelXing and LayoutTurntable - why?
102                    || (ltv instanceof TrackSegmentView)
103                    || (ltv instanceof LayoutTurnoutView)) { // <== includes Wye. LayoutSlips, XOvers
104                lcs = ltv.getLayoutConnectivity();
105                cList.addAll(lcs); // append to list
106            }
107        }
108        initialized = true;
109    }   // initializeBlockConnectivity
110
111    /**
112     * Updates the block connectivity (block boundaries) for a Layout Editor
113     * panel after changes may have been made.
114     */
115    private void updateBlockConnectivity() {
116        int sz = cList.size();
117        boolean[] found = new boolean[sz];
118        Arrays.fill(found, false);
119
120        List<LayoutConnectivity> lcs = null;
121
122        // Check for block boundaries at positionable points.
123        for (PositionablePoint p : models.getPositionablePoints()) {
124            lcs = p.getLayoutConnectivity();
125            for (LayoutConnectivity lc : lcs) {
126                // add to list, if not already present
127                checkConnectivity(lc, found);
128            }
129        }
130
131        // Check for block boundaries at layout turnouts and level crossings
132        for (TrackSegment ts : models.getTrackSegments()) {
133            lcs = ts.getLayoutConnectivity();
134            for (LayoutConnectivity lc : lcs) {
135                // add to list, if not already present
136                checkConnectivity(lc, found);
137            }
138        }
139
140        // check for block boundaries internal to crossover turnouts
141        for (LayoutTurnout lt : models.getLayoutTurnouts()) {
142            lcs = lt.getLayoutConnectivity();
143            for (LayoutConnectivity lc : lcs) {
144                // add to list, if not already present
145                checkConnectivity(lc, found);
146            }
147        }
148
149        // check for block boundaries internal to slips
150        for (LayoutSlip ls : models.getLayoutSlips()) {
151            lcs = ls.getLayoutConnectivity();
152            for (LayoutConnectivity lc : lcs) {
153                // add to list, if not already present
154                checkConnectivity(lc, found);
155            }
156        }
157
158        // delete any LayoutConnectivity objects no longer needed
159        for (int i = sz - 1; i >= 0; i--) {
160            if (!found[i]) {
161                // djd debugging - message to list connectivity being removed
162                //    LayoutConnectivity xx = (LayoutConnectivity)cList.get(i);
163                //    log.error("  Deleting Layout Connectivity - " + xx.getBlock1().getId() + ", " + xx.getBlock2().getId());
164                // end debugging
165                cList.remove(i);
166            }
167        }
168        blockConnectivityChanged = false;
169    }   // updateBlockConnectivity
170
171    //
172    private void checkConnectivity(LayoutConnectivity c, boolean[] found) {
173        // initialize input LayoutConnectivity components
174        LayoutBlock blk1 = c.getBlock1();
175        LayoutBlock blk2 = c.getBlock2();
176
177        int dir = c.getDirection();
178        int rDir = c.getReverseDirection();
179
180        TrackSegment track = c.getTrackSegment();
181        LayoutTrack connected = c.getConnectedObject();
182        HitPointType type = c.getConnectedType();
183
184        LayoutTurnout xOver = c.getXover();
185        int xOverType = c.getXoverBoundaryType();
186
187        // loop over connectivity list, looking for this layout connectivity
188        for (int i = 0; i < cList.size(); i++) {
189            LayoutConnectivity lc = cList.get(i);
190            // compare input LayoutConnectivity with LayoutConnectivity from the list
191            if (xOver == null) {
192                // not a crossover block boundary
193                if ((blk1 == lc.getBlock1()) && (blk2 == lc.getBlock2()) && (track == lc.getTrackSegment())
194                        && (connected == lc.getConnectedObject()) && (type == lc.getConnectedType())
195                        && (dir == lc.getDirection())) {
196                    found[i] = true;
197                    break;
198                }
199            } else {
200                // boundary is in a crossover turnout
201                if ((xOver == lc.getXover()) && (xOverType == lc.getXoverBoundaryType())) {
202                    if ((blk1 == lc.getBlock1()) && (blk2 == lc.getBlock2()) && (dir == lc.getDirection())) {
203                        found[i] = true;
204                        break;
205                    } else if ((blk2 == lc.getBlock1()) && (blk1 == lc.getBlock2()) && (rDir == lc.getDirection())) {
206                        found[i] = true;
207                        break;
208                    }
209                }
210            }
211        }
212
213        // Check to see if this connectivity is already in the list
214        // This occurs for the first layout editor panel when there
215        // are multiple panels connected by edge connectors.
216        if (cList.contains(c)) {
217            log.debug("checkConnectivity: Duplicate connection: '{}'", c);  // NOI18N
218        } else {
219            cList.add(c);
220        }
221    }   // checkConnectivity
222
223    /**
224     * Searches for and adds BeanSetting's to a Path as needed.
225     * <p>
226     * This method starts at the entry point to the LayoutBlock given in the
227     * Path at the block boundary specified in the LayoutConnectivity. It
228     * follows the track looking for turnout settings that are required for a
229     * train entering on this block boundary point to exit the block. If a
230     * required turnout setting is found, the turnout and its required state are
231     * used to create a BeanSetting, which is added to the Path. Such a setting
232     * can occur, for example, if a track enters a right-handed turnout from
233     * either the diverging track or the continuing track.
234     * <p>
235     * If the track branches into two tracks (for example, by entering a
236     * right-handed turnout via the throat track), the search is stopped. The
237     * search is also stopped when the track reaches a different block (or an
238     * undefined block), or reaches an end bumper.
239     * @param p path to follow until branch.
240     * @param lc layout connectivity.
241     * @param layoutBlock the layout block.
242     */
243    public void addBeanSettings(Path p, LayoutConnectivity lc, LayoutBlock layoutBlock) {
244        p.clearSettings();
245        LayoutTrack curConnection = null;
246        LayoutTrack prevConnection = null;
247        HitPointType typeCurConnection = HitPointType.NONE;
248        BeanSetting bs = null;
249        LayoutTurnout lt = null;
250        // process track at block boundary
251        if (lc.getBlock1() == layoutBlock) {    // block1 is this LayoutBlock
252            curConnection = lc.getTrackSegment();
253            if (curConnection != null) {        // connected track in this block is a track segment
254                prevConnection = lc.getConnectedObject();
255                typeCurConnection = HitPointType.TRACK;
256                // is this Track Segment connected to a RH, LH, or WYE turnout at the continuing or diverging track?
257                if ((lc.getConnectedType() == HitPointType.TURNOUT_B
258                        || lc.getConnectedType() == HitPointType.TURNOUT_C)
259                        && ((LayoutTurnout) prevConnection).getTurnoutType() != LayoutTurnout.TurnoutType.NONE
260                        && LayoutTurnout.hasEnteringSingleTrack(((LayoutTurnout) prevConnection).getTurnoutType())) {
261                    LayoutTurnout ltx = (LayoutTurnout) prevConnection;
262                    // Track Segment connected to continuing track of turnout?
263                    if (lc.getConnectedType() == HitPointType.TURNOUT_B) {
264                        Turnout ltxto = ltx.getTurnout();
265                        if ( ltxto != null) {
266                            bs = new BeanSetting(ltxto, ltx.getTurnoutName(), ltx.getContinuingSense());
267                            p.addSetting(bs);
268                        } else {
269                            log.error("No assigned turnout (A): LTO = {}, blk = {}", ltx.getName(), ltx.getLayoutBlock().getDisplayName());  // NOI18N
270                        }
271                    } else if (lc.getConnectedType() == HitPointType.TURNOUT_C) {
272                        // is Track Segment connected to diverging track of turnout?
273                        Turnout ltxto = ltx.getTurnout();
274                        if (ltxto != null) {
275                            if (ltx.getContinuingSense() == Turnout.CLOSED) {
276                                bs = new BeanSetting(ltxto, ltx.getTurnoutName(), Turnout.THROWN);
277                            } else {
278                                bs = new BeanSetting(ltxto, ltx.getTurnoutName(), Turnout.CLOSED);
279                            }
280                            p.addSetting(bs);
281                        } else {
282                            log.error("No assigned turnout (B): LTO = {}, blk = {}", ltx.getName(), ltx.getLayoutBlock().getDisplayName());  // NOI18N
283                        }
284                    } else {
285                        log.warn("Did not decode lc.getConnectedType() of {}", lc.getConnectedType());  // NOI18N
286                    }
287                } // is this Track Segment connected to the continuing track of a RH_XOVER or LH_XOVER?
288                else if (HitPointType.isTurnoutHitType(lc.getConnectedType())
289                        && ((((LayoutTurnout) prevConnection).getTurnoutType() == LayoutTurnout.TurnoutType.RH_XOVER)
290                        || (((LayoutTurnout) prevConnection).getTurnoutType() == LayoutTurnout.TurnoutType.LH_XOVER))) {
291                    LayoutTurnout ltz = (LayoutTurnout) prevConnection;
292                    if (((ltz.getTurnoutType() == LayoutTurnout.TurnoutType.RH_XOVER)
293                            && ((lc.getConnectedType() == HitPointType.TURNOUT_B)
294                            || (lc.getConnectedType() == HitPointType.TURNOUT_D)))
295                            || ((ltz.getTurnoutType() == LayoutTurnout.TurnoutType.LH_XOVER)
296                            && ((lc.getConnectedType() == HitPointType.TURNOUT_A)
297                            || (lc.getConnectedType() == HitPointType.TURNOUT_C)))) {
298
299                        Turnout ltzto = ltz.getTurnout();
300                        if (ltzto != null) {
301                            bs = new BeanSetting(ltzto, ltz.getTurnoutName(), Turnout.CLOSED);
302                            p.addSetting(bs);
303                        } else {
304                            log.error("No assigned turnout (C): LTO = {}, blk = {}, TO type = {}, conn type = {}", // NOI18N
305                                    ltz.getName(), ltz.getLayoutBlock().getDisplayName(), ltz.getTurnoutType(), lc.getConnectedType());
306                        }
307                    }
308                } // is this track section is connected to a slip?
309                else if (HitPointType.isSlipHitType(lc.getConnectedType())) {
310                    LayoutSlip lsz = (LayoutSlip) prevConnection;
311                    if (lsz.getSlipType() == LayoutSlip.TurnoutType.SINGLE_SLIP) {
312                        if (lc.getConnectedType() == HitPointType.SLIP_C) {
313                            Turnout lszto = lsz.getTurnout();
314                            if (lszto != null) {
315                                bs = new BeanSetting(lszto, lsz.getTurnoutName(), lsz.getTurnoutState(LayoutTurnout.STATE_AC));
316                                p.addSetting(bs);
317                            } else {
318                                log.error("No assigned turnout (D): LTO = {}, blk = {}", lsz.getName(), lsz.getLayoutBlock().getDisplayName());  // NOI18N
319                            }
320                            Turnout lsztob = lsz.getTurnoutB();
321                            if (lsztob != null) {
322                                bs = new BeanSetting(lsztob, lsz.getTurnoutBName(), lsz.getTurnoutBState(LayoutTurnout.STATE_AC));
323                                p.addSetting(bs);
324                            } else {
325                                log.error("No assigned turnoutB (E): LTO = {}, blk = {}", lsz.getName(), lsz.getLayoutBlock().getDisplayName());  // NOI18N
326                            }
327                        } else if (lc.getConnectedType() == HitPointType.SLIP_B) {
328                            Turnout lszto = lsz.getTurnout();
329                            if (lszto != null) {
330                                bs = new BeanSetting(lszto, lsz.getTurnoutName(), lsz.getTurnoutState(LayoutTurnout.STATE_BD));
331                                p.addSetting(bs);
332                            } else {
333                                log.error("No assigned turnout (F): LTO = {}, blk = {}", lsz.getName(), lsz.getLayoutBlock().getDisplayName());  // NOI18N
334                            }
335
336                            Turnout lsztob = lsz.getTurnoutB();
337                            if (lsztob != null) {
338                                bs = new BeanSetting(lsztob, lsz.getTurnoutBName(), lsz.getTurnoutBState(LayoutTurnout.STATE_BD));
339                                p.addSetting(bs);
340                            } else {
341                                log.error("No assigned turnoutB (G): LTO = {}, blk = {}", lsz.getName(), lsz.getLayoutBlock().getDisplayName());  // NOI18N
342                            }
343                        } else if (lc.getConnectedType() == HitPointType.SLIP_A) {
344                            log.debug("At connection A of a single slip which could go in two different directions");  // NOI18N
345                        } else if (lc.getConnectedType() == HitPointType.SLIP_D) {
346                            log.debug("At connection D of a single slip which could go in two different directions");  // NOI18N
347                        }
348                    } else {
349                        //note: I'm adding these logs as a prequel to adding the correct code for double slips
350                        if (lc.getConnectedType() == HitPointType.SLIP_A) {
351                            log.debug("At connection A of a double slip which could go in two different directions");  // NOI18N
352                        } else if (lc.getConnectedType() == HitPointType.SLIP_B) {
353                            log.debug("At connection B of a double slip which could go in two different directions");  // NOI18N
354                        } else if (lc.getConnectedType() == HitPointType.SLIP_C) {
355                            log.debug("At connection C of a double slip which could go in two different directions");  // NOI18N
356                        } else if (lc.getConnectedType() == HitPointType.SLIP_D) {
357                            log.debug("At connection D of a double slip which could go in two different directions");  // NOI18N
358                        } else {    // this should NEVER happen (it should always be SLIP_A, _B, _C or _D.
359                            log.info("At a double slip we could go in two different directions");  // NOI18N
360                        }
361                    }
362                }
363            } else {
364                // block boundary is internal to a crossover turnout
365                lt = lc.getXover();
366                prevConnection = lt;
367                if ((lt != null) && (lt.getTurnout() != null)) {
368                    int type = lc.getXoverBoundaryType();
369                    // bs is known to be null at this point
370                    if (lt.getTurnout() != null) {
371                        if (type == LayoutConnectivity.XOVER_BOUNDARY_AB) {
372                            bs = new BeanSetting(lt.getTurnout(), lt.getTurnoutName(), Turnout.CLOSED);
373                            curConnection = lt.getConnectA();
374                        } else if (type == LayoutConnectivity.XOVER_BOUNDARY_CD) {
375                            bs = new BeanSetting(lt.getTurnout(), lt.getTurnoutName(), Turnout.CLOSED);
376                            curConnection = lt.getConnectC();
377                        } else if (type == LayoutConnectivity.XOVER_BOUNDARY_AC) {
378                            bs = new BeanSetting(lt.getTurnout(), lt.getTurnoutName(), Turnout.THROWN);
379                            curConnection = lt.getConnectA();
380                        } else if (type == LayoutConnectivity.XOVER_BOUNDARY_BD) {
381                            bs = new BeanSetting(lt.getTurnout(), lt.getTurnoutName(), Turnout.THROWN);
382                            curConnection = lt.getConnectB();
383                        } else {
384                            log.warn("failed to decode lc.getXoverBoundaryType() of {} (A)", lc.getXoverBoundaryType());  // NOI18N
385                        }
386                    }
387                    typeCurConnection = HitPointType.TRACK;
388                    if (bs != null) {
389                        p.addSetting(bs);
390                    } else {
391                        log.error("No assigned turnout (H): LTO = {}, blk = {}, type = {}", lt.getName(), lt.getLayoutBlock().getDisplayName(), type);  // NOI18N
392                    }
393                }
394            }
395        } else if (lc.getXover() != null) {
396            // first Block is not in a Track Segment, must be block boundary internal to a crossover turnout
397            lt = lc.getXover();
398            if ((lt != null) && (lt.getTurnout() != null)) {
399                int type = lc.getXoverBoundaryType();
400                // bs is known to be null at this point
401                if (lt.getTurnout() != null) {
402                    if (type == LayoutConnectivity.XOVER_BOUNDARY_AB) {
403                        bs = new BeanSetting(lt.getTurnout(), lt.getTurnoutName(), Turnout.CLOSED);
404                        curConnection = lt.getConnectB();
405                    } else if (type == LayoutConnectivity.XOVER_BOUNDARY_CD) {
406                        bs = new BeanSetting(lt.getTurnout(), lt.getTurnoutName(), Turnout.CLOSED);
407                        curConnection = lt.getConnectD();
408                    } else if (type == LayoutConnectivity.XOVER_BOUNDARY_AC) {
409                        bs = new BeanSetting(lt.getTurnout(), lt.getTurnoutName(), Turnout.THROWN);
410                        curConnection = lt.getConnectC();
411                    } else if (type == LayoutConnectivity.XOVER_BOUNDARY_BD) {
412                        bs = new BeanSetting(lt.getTurnout(), lt.getTurnoutName(), Turnout.THROWN);
413                        curConnection = lt.getConnectD();
414                    } else {
415                        log.warn("failed to decode lc.getXoverBoundaryType() of {} (B)", lc.getXoverBoundaryType());  // NOI18N
416                    }
417                }
418                typeCurConnection = HitPointType.TRACK;
419                if (bs != null) {
420                    p.addSetting(bs);
421                } else {
422                    log.error("No assigned turnout (I): LTO = {}, blk = {}, type = {}", lt.getName(), lt.getLayoutBlock().getDisplayName(), type);  // NOI18N
423                }
424            }
425        } else {
426            // block2 is this LayoutBlock, and block1 is in a track segment
427            if (lc.getConnectedObject() != null) {
428                // connected object in this block is a turnout or levelxing
429                curConnection = lc.getConnectedObject();
430                prevConnection = lc.getTrackSegment();
431                typeCurConnection = lc.getConnectedType();
432                if (HitPointType.isTurnoutHitType(typeCurConnection)) {
433                    // connected object is a turnout
434                    LayoutTurnout.TurnoutType turnoutType = ((LayoutTurnout) curConnection).getTurnoutType();
435                    if (LayoutTurnout.hasEnteringDoubleTrack(turnoutType)) {
436                        // have crossover turnout
437                        if ((turnoutType == LayoutTurnout.TurnoutType.DOUBLE_XOVER)
438                                || ((turnoutType == LayoutTurnout.TurnoutType.RH_XOVER) && ((typeCurConnection == HitPointType.TURNOUT_A) || (typeCurConnection == HitPointType.TURNOUT_C)))
439                                || ((turnoutType == LayoutTurnout.TurnoutType.LH_XOVER) && ((typeCurConnection == HitPointType.TURNOUT_B) || (typeCurConnection == HitPointType.TURNOUT_D)))) {
440                            // entering turnout at a throat, cannot follow path any further
441                            curConnection = null;
442                        } else {
443                            // entering turnout at continuing track
444                            if (((LayoutTurnout) curConnection).getTurnout() != null) {
445                                bs = new BeanSetting(((LayoutTurnout) curConnection).getTurnout(), ((LayoutTurnout) curConnection).getTurnoutName(), Turnout.CLOSED);
446                                p.addSetting(bs);
447                            } else {
448                                log.error("No assigned turnout (J): LTO = {}, blk = {}", // NOI18N
449                                        ((LayoutTurnout) curConnection).getName(), ((LayoutTurnout) curConnection).getLayoutBlock().getDisplayName());
450                            }
451                            prevConnection = curConnection;
452                            if (typeCurConnection == HitPointType.TURNOUT_A) {
453                                curConnection = ((LayoutTurnout) curConnection).getConnectB();
454                            } else if (typeCurConnection == HitPointType.TURNOUT_B) {
455                                curConnection = ((LayoutTurnout) curConnection).getConnectA();
456                            } else if (typeCurConnection == HitPointType.TURNOUT_C) {
457                                curConnection = ((LayoutTurnout) curConnection).getConnectD();
458                            } else { // typeCurConnection == LayoutEditor.HitPointTypes.TURNOUT_D per if statement 3 levels up
459                                curConnection = ((LayoutTurnout) curConnection).getConnectC();
460                            }
461                            typeCurConnection = HitPointType.TRACK;
462                        }
463                    } // must be RH, LH, or WYE turnout
464                    else if (typeCurConnection == HitPointType.TURNOUT_A) {
465                        // turnout throat, no bean setting needed and cannot follow Path any further
466                        log.debug("At connection A of a turnout which could go in two different directions");  // NOI18N
467                        curConnection = null;
468                    } else if (typeCurConnection == HitPointType.TURNOUT_B) {
469                        // continuing track of turnout
470                        if (((LayoutTurnout) curConnection).getTurnout() != null) {
471                            if (((LayoutTurnout) curConnection).getContinuingSense() == Turnout.CLOSED) {
472                                bs = new BeanSetting(((LayoutTurnout) curConnection).getTurnout(), ((LayoutTurnout) curConnection).getTurnoutName(), Turnout.CLOSED);
473                            } else {
474                                bs = new BeanSetting(((LayoutTurnout) curConnection).getTurnout(), ((LayoutTurnout) curConnection).getTurnoutName(), Turnout.THROWN);
475                            }
476                            p.addSetting(bs);
477                        } else {
478                            log.error("No assigned turnout (K): LTO = {}, blk = {}", // NOI18N
479                                    ((LayoutTurnout) curConnection).getName(), ((LayoutTurnout) curConnection).getLayoutBlock().getDisplayName());
480                        }
481                        prevConnection = curConnection;
482                        curConnection = ((LayoutTurnout) curConnection).getConnectA();
483                        typeCurConnection = HitPointType.TRACK;
484                    } else if (typeCurConnection == HitPointType.TURNOUT_C) {
485                        // diverging track of turnout
486                        if (((LayoutTurnout) curConnection).getTurnout() != null) {
487                            if (((LayoutTurnout) curConnection).getContinuingSense() == Turnout.CLOSED) {
488                                bs = new BeanSetting(((LayoutTurnout) curConnection).getTurnout(), ((LayoutTurnout) curConnection).getTurnoutName(), Turnout.THROWN);
489                            } else {
490                                bs = new BeanSetting(((LayoutTurnout) curConnection).getTurnout(), ((LayoutTurnout) curConnection).getTurnoutName(), Turnout.CLOSED);
491                            }
492                            p.addSetting(bs);
493                        } else {
494                            log.error("No assigned turnout (L): LTO = {}, blk = {}", // NOI18N
495                                    ((LayoutTurnout) curConnection).getName(), ((LayoutTurnout) curConnection).getLayoutBlock().getDisplayName());
496                        }
497                        prevConnection = curConnection;
498                        curConnection = ((LayoutTurnout) curConnection).getConnectA();
499                        typeCurConnection = HitPointType.TRACK;
500                    }
501                } // if level crossing, skip to the connected track segment on opposite side
502                else if (typeCurConnection == HitPointType.LEVEL_XING_A) {
503                    prevConnection = curConnection;
504                    curConnection = ((LevelXing) curConnection).getConnectC();
505                    typeCurConnection = HitPointType.TRACK;
506                } else if (typeCurConnection == HitPointType.LEVEL_XING_C) {
507                    prevConnection = curConnection;
508                    curConnection = ((LevelXing) curConnection).getConnectA();
509                    typeCurConnection = HitPointType.TRACK;
510                } else if (typeCurConnection == HitPointType.LEVEL_XING_B) {
511                    prevConnection = curConnection;
512                    curConnection = ((LevelXing) curConnection).getConnectD();
513                    typeCurConnection = HitPointType.TRACK;
514                } else if (typeCurConnection == HitPointType.LEVEL_XING_D) {
515                    prevConnection = curConnection;
516                    curConnection = ((LevelXing) curConnection).getConnectB();
517                    typeCurConnection = HitPointType.TRACK;
518                }
519            } else {
520                // block boundary is internal to a crossover turnout
521                lt = lc.getXover();
522                prevConnection = lt;
523                if ((lt != null) && (lt.getTurnout() != null)) {
524                    int type = lc.getXoverBoundaryType();
525                    // bs is known to be null at this point
526                    if (lt.getTurnout() != null) {
527                        if (type == LayoutConnectivity.XOVER_BOUNDARY_AB) {
528                            bs = new BeanSetting(lt.getTurnout(), lt.getTurnoutName(), Turnout.CLOSED);
529                            curConnection = lt.getConnectB();
530                        } else if (type == LayoutConnectivity.XOVER_BOUNDARY_CD) {
531                            bs = new BeanSetting(lt.getTurnout(), lt.getTurnoutName(), Turnout.CLOSED);
532                            curConnection = lt.getConnectD();
533                        } else if (type == LayoutConnectivity.XOVER_BOUNDARY_AC) {
534                            bs = new BeanSetting(lt.getTurnout(), lt.getTurnoutName(), Turnout.THROWN);
535                            curConnection = lt.getConnectC();
536                        } else if (type == LayoutConnectivity.XOVER_BOUNDARY_BD) {
537                            bs = new BeanSetting(lt.getTurnout(), lt.getTurnoutName(), Turnout.THROWN);
538                            curConnection = lt.getConnectD();
539                        }
540                    }
541                    typeCurConnection = HitPointType.TRACK;
542                    if (bs != null) {
543                        p.addSetting(bs);
544                    } else {
545                        log.error("No assigned turnout (Q): LTO = {}, blk = {}", lt.getName(), lt.getLayoutBlock().getDisplayName());  // NOI18N
546                    }
547                }
548            }
549        }
550        // follow path through this block - done when reaching another block, or a branching of Path
551        while (curConnection != null) {
552            if (typeCurConnection == HitPointType.TRACK) {
553                TrackSegment curTS = (TrackSegment) curConnection;
554                // track segment is current connection
555                if (curTS.getLayoutBlock() != layoutBlock) {
556                    curConnection = null;
557                } else {
558                    // skip over to other end of Track Segment
559                    if (curTS.getConnect1() == prevConnection) {
560                        prevConnection = curConnection;
561                        typeCurConnection = curTS.getType2();
562                        curConnection = curTS.getConnect2();
563                    } else {
564                        prevConnection = curConnection;
565                        typeCurConnection = curTS.getType1();
566                        curConnection = curTS.getConnect1();
567                    }
568                    // skip further if positionable point (possible anchor point)
569                    if (typeCurConnection == HitPointType.POS_POINT) {
570                        PositionablePoint pt = (PositionablePoint) curConnection;
571                        if (pt.getType() == PositionablePoint.PointType.END_BUMPER) {
572                            // reached end of track
573                            curConnection = null;
574                        } else {
575                            // at an anchor point, find track segment on other side
576                            TrackSegment track = null;
577                            if (pt.getConnect1() == prevConnection) {
578                                track = pt.getConnect2();
579                            } else {
580                                track = pt.getConnect1();
581                            }
582                            // check for block boundary
583                            if ((track == null) || (track.getLayoutBlock() != layoutBlock)) {
584                                // moved outside of block - anchor point was a block boundary -OR-
585                                //  reached the end of the defined track
586                                curConnection = null;
587                            } else {
588                                prevConnection = curConnection;
589                                curConnection = track;
590                                typeCurConnection = HitPointType.TRACK;
591                            }
592                        }
593                    }
594                }
595            } else if (HitPointType.isTurnoutHitType(typeCurConnection)) {
596                lt = (LayoutTurnout) curConnection;
597                // test for crossover turnout
598                if (lt.hasEnteringSingleTrack()) {
599                    // have RH, LH, or WYE turnout
600
601                    if (lt.getLayoutBlock() != layoutBlock) {
602                        curConnection = null;
603                    } else {
604                        // turnout is in current block, test connection point
605                        if (typeCurConnection == HitPointType.TURNOUT_A) {
606                            // turnout throat, no bean setting needed and cannot follow possible path any further
607                            curConnection = null;
608                        } else if (typeCurConnection == HitPointType.TURNOUT_B) {
609                            // continuing track of turnout, add a bean setting
610                            if (lt.getTurnout() != null) {
611                                if (lt.getContinuingSense() == Turnout.CLOSED) {
612                                    bs = new BeanSetting(lt.getTurnout(), lt.getTurnoutName(), Turnout.CLOSED);
613                                } else {
614                                    bs = new BeanSetting(lt.getTurnout(), lt.getTurnoutName(), Turnout.THROWN);
615                                }
616                                p.addSetting(bs);
617                            } else {
618                                log.error("No assigned turnout (R): LTO = {}, blk = {}", lt.getName(), lt.getLayoutBlock().getDisplayName());  // NOI18N
619                            }
620                            if (lt.getLayoutBlock() != layoutBlock) {
621                                curConnection = null;
622                            } else {
623                                prevConnection = curConnection;
624                                curConnection = lt.getConnectA();
625                                typeCurConnection = HitPointType.TRACK;
626                            }
627                        } else if (typeCurConnection == HitPointType.TURNOUT_C) {
628                            // diverging track of turnout
629                            if (lt.getTurnout() != null) {
630                                if (lt.getContinuingSense() == Turnout.CLOSED) {
631                                    bs = new BeanSetting(lt.getTurnout(), lt.getTurnoutName(), Turnout.THROWN);
632                                } else {
633                                    bs = new BeanSetting(lt.getTurnout(), lt.getTurnoutName(), Turnout.CLOSED);
634                                }
635                                p.addSetting(bs);
636                            } else {
637                                log.error("No assigned turnout (S): LTO = {}, blk = {}", lt.getName(), lt.getLayoutBlock().getDisplayName());  // NOI18N
638                            }
639                            if (lt.getLayoutBlock() != layoutBlock) {
640                                curConnection = null;
641                            } else {
642                                prevConnection = curConnection;
643                                curConnection = lt.getConnectA();
644                                typeCurConnection = HitPointType.TRACK;
645                            }
646                        }
647                    }
648                } else if (lt.getTurnoutType() == LayoutTurnout.TurnoutType.DOUBLE_XOVER) {
649                    // have a double crossover turnout, cannot follow possible path any further
650                    curConnection = null;
651                } else if (lt.getTurnoutType() == LayoutTurnout.TurnoutType.RH_XOVER) {
652                    // have a right-handed crossover turnout
653                    if ((typeCurConnection == HitPointType.TURNOUT_A)
654                            || (typeCurConnection == HitPointType.TURNOUT_C)) {
655                        // entry is at turnout throat, cannot follow possible path any further
656                        curConnection = null;
657                    } else if (typeCurConnection == HitPointType.TURNOUT_B) {
658                        // entry is at continuing track of turnout
659                        if (lt.getLayoutBlockB() != layoutBlock) {
660                            // cross-over block different, end of current block
661                            break;
662                        }
663                        if (lt.getTurnout() != null) {
664                            bs = new BeanSetting(lt.getTurnout(), lt.getTurnoutName(), Turnout.CLOSED);
665                            p.addSetting(bs);
666                        } else {
667                            log.error("No assigned turnout (T): LTO = {}, blk = {}", lt.getName(), lt.getLayoutBlock().getDisplayName());  // NOI18N
668                        }
669                        if (lt.getLayoutBlock() != layoutBlock) {
670                            // left current block
671                            curConnection = null;
672                        } else {
673                            prevConnection = curConnection;
674                            curConnection = lt.getConnectA();
675                            typeCurConnection = HitPointType.TRACK;
676                        }
677                    } else { // typeCurConnection == LayoutEditor.HitPointTypes.TURNOUT_D
678                        // entry is at continuing track of turnout
679                        if (lt.getLayoutBlockD() != layoutBlock) {
680                            // cross-over block different, end of current block
681                            break;
682                        }
683                        if (lt.getTurnout() != null) {
684                            bs = new BeanSetting(lt.getTurnout(), lt.getTurnoutName(), Turnout.CLOSED);
685                            p.addSetting(bs);
686                        } else {
687                            log.error("No assigned turnout (U): LTO = {}, blk = {}", lt.getName(), lt.getLayoutBlock().getDisplayName());  // NOI18N
688                        }
689                        if (lt.getLayoutBlockC() != layoutBlock) {
690                            // left current block
691                            curConnection = null;
692                        } else {
693                            prevConnection = curConnection;
694                            curConnection = lt.getConnectC();
695                            typeCurConnection = HitPointType.TRACK;
696                        }
697                    }
698                } else if (lt.getTurnoutType() == LayoutTurnout.TurnoutType.LH_XOVER) {
699                    // have a left-handed crossover turnout
700                    if ((typeCurConnection == HitPointType.TURNOUT_B)
701                            || (typeCurConnection == HitPointType.TURNOUT_D)) {
702                        // entry is at turnout throat, cannot follow possible path any further
703                        curConnection = null;
704                    } else if (typeCurConnection == HitPointType.TURNOUT_A) {
705                        // entry is at continuing track of turnout
706                        if (lt.getLayoutBlock() != layoutBlock) {
707                            // cross-over block different, end of current block
708                            break;
709                        }
710                        if (lt.getTurnout() != null) {
711                            bs = new BeanSetting(lt.getTurnout(), lt.getTurnoutName(), Turnout.CLOSED);
712                            p.addSetting(bs);
713                        } else {
714                            log.error("No assigned turnout (V): LTO = {}, blk = {}", lt.getName(), lt.getLayoutBlock().getDisplayName());  // NOI18N
715                        }
716                        if (lt.getLayoutBlockB() != layoutBlock) {
717                            // left current block
718                            curConnection = null;
719                        } else {
720                            prevConnection = curConnection;
721                            curConnection = lt.getConnectB();
722                            typeCurConnection = HitPointType.TRACK;
723                        }
724                    } else { // typeCurConnection == LayoutEditor.HitPointTypes.TURNOUT_C per if statement 2 levels up
725                        // entry is at continuing track of turnout
726                        if (lt.getLayoutBlockC() != layoutBlock) {
727                            // cross-over block different, end of current block
728                            break;
729                        }
730                        if (lt.getTurnout() != null) {
731                            bs = new BeanSetting(lt.getTurnout(), lt.getTurnoutName(), Turnout.CLOSED);
732                            p.addSetting(bs);
733                        } else {
734                            log.error("No assigned turnout (W): LTO = {}, blk = {}", lt.getName(), lt.getLayoutBlock().getDisplayName());  // NOI18N
735                        }
736                        if (lt.getLayoutBlockD() != layoutBlock) {
737                            // left current block
738                            curConnection = null;
739                        } else {
740                            prevConnection = curConnection;
741                            curConnection = lt.getConnectD();
742                            typeCurConnection = HitPointType.TRACK;
743                        }
744                    }
745                }
746            } else if (typeCurConnection == HitPointType.LEVEL_XING_A) {
747                // have a level crossing connected at A
748                if (((LevelXing) curConnection).getLayoutBlockAC() != layoutBlock) {
749                    // moved outside of this block
750                    curConnection = null;
751                } else {
752                    // move to other end of this section of this level crossing track
753                    prevConnection = curConnection;
754                    curConnection = ((LevelXing) curConnection).getConnectC();
755                    typeCurConnection = HitPointType.TRACK;
756                }
757            } else if (typeCurConnection == HitPointType.LEVEL_XING_B) {
758                // have a level crossing connected at B
759                if (((LevelXing) curConnection).getLayoutBlockBD() != layoutBlock) {
760                    // moved outside of this block
761                    curConnection = null;
762                } else {
763                    // move to other end of this section of this level crossing track
764                    prevConnection = curConnection;
765                    curConnection = ((LevelXing) curConnection).getConnectD();
766                    typeCurConnection = HitPointType.TRACK;
767                }
768            } else if (typeCurConnection == HitPointType.LEVEL_XING_C) {
769                // have a level crossing connected at C
770                if (((LevelXing) curConnection).getLayoutBlockAC() != layoutBlock) {
771                    // moved outside of this block
772                    curConnection = null;
773                } else {
774                    // move to other end of this section of this level crossing track
775                    prevConnection = curConnection;
776                    curConnection = ((LevelXing) curConnection).getConnectA();
777                    typeCurConnection = HitPointType.TRACK;
778                }
779            } else if (typeCurConnection == HitPointType.LEVEL_XING_D) {
780                // have a level crossing connected at D
781                if (((LevelXing) curConnection).getLayoutBlockBD() != layoutBlock) {
782                    // moved outside of this block
783                    curConnection = null;
784                } else {
785                    // move to other end of this section of this level crossing track
786                    prevConnection = curConnection;
787                    curConnection = ((LevelXing) curConnection).getConnectB();
788                    typeCurConnection = HitPointType.TRACK;
789                }
790            } else if (HitPointType.isSlipHitType(typeCurConnection)) {
791                LayoutSlip ls = (LayoutSlip) curConnection;
792                if (ls.getLayoutBlock() != layoutBlock) {
793                    curConnection = null;
794                } else if (ls.getSlipType() == LayoutSlip.TurnoutType.SINGLE_SLIP) {
795                    if (typeCurConnection == HitPointType.SLIP_C) {
796                        if (ls.getTurnout() != null) {
797                            bs = new BeanSetting(ls.getTurnout(), ls.getTurnoutName(), ls.getTurnoutState(LayoutTurnout.STATE_AC));
798                            p.addSetting(bs);
799                        } else {
800                            log.error("No assigned turnout (X): LTO = {}, blk = {}", ls.getName(), ls.getLayoutBlock().getDisplayName());  // NOI18N
801                        }
802                        if (ls.getTurnoutB() != null) {
803                            bs = new BeanSetting(ls.getTurnoutB(), ls.getTurnoutBName(), ls.getTurnoutBState(LayoutTurnout.STATE_AC));
804                            p.addSetting(bs);
805                        } else {
806                            log.error("No assigned turnoutB (Y): LTO = {}, blk = {}", ls.getName(), ls.getLayoutBlock().getDisplayName());  // NOI18N
807                        }
808                        prevConnection = curConnection;
809                        curConnection = ((LayoutSlip) curConnection).getConnectC();
810                        typeCurConnection = HitPointType.TRACK;
811                    } else if (typeCurConnection == HitPointType.SLIP_B) {
812                        if (ls.getTurnout() != null) {
813                            bs = new BeanSetting(ls.getTurnout(), ls.getTurnoutName(), ls.getTurnoutState(LayoutTurnout.STATE_BD));
814                            p.addSetting(bs);
815                        } else {
816                            log.error("No assigned turnout (Z): LTO = {}, blk = {}", ls.getName(), ls.getLayoutBlock().getDisplayName());  // NOI18N
817                        }
818
819                        if (ls.getTurnoutB() != null) {
820                            bs = new BeanSetting(ls.getTurnoutB(), ls.getTurnoutBName(), ls.getTurnoutBState(LayoutTurnout.STATE_BD));
821                            p.addSetting(bs);
822                        } else {
823                            log.error("No assigned turnoutB (1): LTO = {}, blk = {}", ls.getName(), ls.getLayoutBlock().getDisplayName());  // NOI18N
824                        }
825                        prevConnection = curConnection;
826                        curConnection = ((LayoutSlip) curConnection).getConnectB();
827                        typeCurConnection = HitPointType.TRACK;
828                    } else {
829                        //Else could be going in the slip direction
830                        curConnection = null;
831                    }
832
833                } else {
834                    //At double slip, can not follow any further
835                    curConnection = null;
836                }
837            } else if (HitPointType.isTurntableRayHitType(typeCurConnection)) {
838                if (log.isDebugEnabled()) {
839                    log.debug("Layout Block: {}, found track type: {}, to " // NOI18N
840                            + "Block: {}, is potentially assigned to turntable ray", // NOI18N
841                            layoutBlock.getDisplayName(),
842                            typeCurConnection,
843                            p.getBlock().getDisplayName()
844                    );
845                }
846                curConnection = null;
847            } else {
848                // catch when some new type got added
849                log.error("Layout Block: {} found unknown track type: {}" // NOI18N
850                        + " to Block: {}",
851                        layoutBlock.getDisplayName(),
852                        typeCurConnection,
853                        p.getBlock().getDisplayName()
854                );
855                break;
856            }
857        }
858    }   // addBeanSettings
859
860    // initialize logging
861    private final static Logger log
862            = LoggerFactory.getLogger(LayoutEditorAuxTools.class);
863}