001package jmri.jmrit.display.layoutEditor;
002
003import java.awt.Color;
004import java.awt.Font;
005import java.text.MessageFormat;
006import java.util.*;
007
008import javax.annotation.CheckForNull;
009import javax.annotation.Nonnull;
010
011import jmri.*;
012import jmri.jmrit.signalling.SignallingGuiTools;
013import jmri.jmrit.display.ToolTip;
014
015/**
016 * LayoutTurnout is the abstract base for classes representing various types of turnout on the layout.
017 * A LayoutTurnout is an
018 * extension of the standard Turnout object with drawing and connectivity
019 * information added.
020 * <p>
021 * Specific forms are represented: right-hand, left-hand, wye, double crossover,
022 * right-handed single crossover, and left-handed single crossover. Note that
023 * double-slip turnouts can be handled as two turnouts, throat to throat, and
024 * three-way turnouts can be handled as two turnouts, left-hand and right-hand,
025 * arranged throat to continuing route.
026 * <p>
027 * A LayoutTurnout has three or four connection points, designated A, B, C, and
028 * D. For right-handed or left-handed turnouts, A corresponds to the throat. At
029 * the crossing, A-B (and C-D for crossovers) is a straight segment (continuing
030 * route). A-C (and B-D for crossovers) is the diverging route. B-C (and A-D for
031 * crossovers) is an illegal condition.
032 * <br>
033 * <pre>
034 *           Turnouts
035 * Right-hand       Left-hand
036 *
037 *                        C
038 *                       //
039 * A ==**== B       A ==**== B
040 *      \\
041 *       C
042 *
043 *    Wye           Three-way
044 *
045 *       B                D
046 *      //               //
047 * A ==**           A ==**== B
048 *      \\               \\
049 *       C                C
050 *
051 *           Crossovers
052 * Right-hand            left-hand
053 * A ==**===== B      A ====**== B
054 *      \\                 //
055 *       \\               //
056 *  D ====**== C     D ==**===== C
057 *
058 *             Double
059 *        A ==**==**== B
060 *             \\//
061 *              XX
062 *             //\\
063 *        D ==**==**== C
064 * </pre>
065 * (The {@link LayoutSlip} track objects follow a different pattern. They put A-D in
066 * different places and have AD and BC as the normal-continuance parallel paths)
067 * <p>
068 * A LayoutTurnout carries Block information. For right-handed, left-handed, and
069 * wye turnouts, the entire turnout is in one block, however, a block border may
070 * occur at any connection (A,B,C,D). For a double crossover turnout, up to four
071 * blocks may be assigned, one for each connection point, but if only one block
072 * is assigned, that block applies to the entire turnout.
073 * <p>
074 * For drawing purposes, each LayoutTurnout carries a center point and
075 * displacements for B and C. For right-handed or left-handed turnouts, the
076 * displacement for A = - the displacement for B, and the center point is at the
077 * junction of the diverging route and the straight through continuing route.
078 * For double crossovers, the center point is at the center of the turnout, and
079 * the displacement for A = - the displacement for C and the displacement for D
080 * = - the displacement for B. The center point and these displacements may be
081 * adjusted by the user when in edit mode. For double crossovers, AB and BC are
082 * constrained to remain perpendicular. For single crossovers, AB and CD are
083 * constrained to remain parallel, and AC and BD are constrained to remain
084 * parallel.
085 * <p>
086 * When LayoutTurnouts are first created, a rotation (degrees) is provided. For
087 * 0.0 rotation, the turnout lies on the east-west line with A facing east.
088 * Rotations are performed in a clockwise direction.
089 * <p>
090 * When LayoutTurnouts are first created, there are no connections. Block
091 * information and connections may be added when available.
092 * <p>
093 * When a LayoutTurnout is first created, it is enabled for control of an
094 * assigned actual turnout. Clicking on the turnout center point will toggle the
095 * turnout. This can be disabled via the popup menu.
096 * <p>
097 * Signal Head names are saved here to keep track of where signals are.
098 * LayoutTurnout only serves as a storage place for signal head names. The names
099 * are placed here by tools, e.g., Set Signals at Turnout, and Set Signals at
100 * Double Crossover. Each connection point can have up to three SignalHeads and one SignalMast.
101 * <p>
102 * A LayoutWye may be linked to another LayoutTurnout to form a turnout
103 * pair.
104 *<br>
105 * Throat-To-Throat Turnouts - Two turnouts connected closely at their
106 * throats, so closely that signals are not appropriate at the their throats.
107 * This is the situation when two RH, LH, or WYE turnouts are used to model a
108 * double slip.
109 *<br>
110 * 3-Way Turnout - Two turnouts modeling a 3-way turnout, where the
111 * throat of the second turnout is closely connected to the continuing track of
112 * the first turnout. The throat will have three heads, or one head. A link is
113 * required to be able to correctly interpret the use of signal heads.
114 *
115 * @author Dave Duchamp Copyright (c) 2004-2007
116 * @author George Warner Copyright (c) 2017-2019
117 * @author Bob Jacobsen Copyright (c) 2020
118 */
119abstract public class LayoutTurnout extends LayoutTrack {
120
121    protected LayoutTurnout(@Nonnull String id,
122            @Nonnull LayoutEditor models, TurnoutType t) {
123        super(id, models);
124
125        type = t;
126        createTooltip(models);
127    }
128
129    protected LayoutTurnout(@Nonnull String id,
130            @Nonnull LayoutEditor models) {
131        this(id, models, TurnoutType.NONE);
132    }
133
134    public LayoutTurnout(@Nonnull String id, TurnoutType t,
135            @Nonnull LayoutEditor models) {
136        this(id, t, models, 1);
137    }
138
139    /**
140     * Main constructor method.
141     * @param id Layout Turnout ID.
142     * @param t type, e.g. LH_TURNOUT, WYE_TURNOUT
143     * @param models main layout editor.
144     * @param v version.
145     */
146    public LayoutTurnout(@Nonnull String id, TurnoutType t,
147            @Nonnull LayoutEditor models,
148            int v) {
149        super(id, models);
150
151        namedTurnout = null;
152        turnoutName = "";
153        mTurnoutListener = null;
154        disabled = false;
155        disableWhenOccupied = false;
156        type = t;
157        version = v;
158        createTooltip(models);
159
160    }
161
162    private void createTooltip(LayoutEditor models) {
163        var tt = new ToolTip(null, 0, 0, new Font("SansSerif", Font.PLAIN, 12),
164                Color.black, new Color(215, 225, 255), Color.black, null);
165        setToolTip(tt);
166        setShowToolTip(models.showToolTip());
167    }
168
169    // Defined constants for turnout types
170    // This is being replaced by subclasses; do not add more
171    // references to it.
172    public enum TurnoutType {
173        NONE,
174        RH_TURNOUT,
175        LH_TURNOUT,
176        WYE_TURNOUT,
177        DOUBLE_XOVER,
178        RH_XOVER,
179        LH_XOVER,
180        SINGLE_SLIP, // used for LayoutSlip which extends this class
181        DOUBLE_SLIP     // used for LayoutSlip which extends this class
182    }
183
184    /**
185     * Returns true if this is a turnout (not a crossover or slip)
186     *
187     * @param type the turnout type
188     * @return boolean true if this is a turnout
189     */
190    public static boolean isTurnoutTypeTurnout(TurnoutType type) {
191        return (type == TurnoutType.RH_TURNOUT
192                || type == TurnoutType.LH_TURNOUT
193                || type == TurnoutType.WYE_TURNOUT);
194    }
195
196    /**
197     * Returns true if this is a turnout (not a crossover or slip)
198     *
199     * @return boolean true if this is a turnout
200     */
201    public boolean isTurnoutTypeTurnout() {
202        return isTurnoutTypeTurnout(getTurnoutType());
203    }
204
205    /**
206     * Returns true if this is a crossover
207     *
208     * @param type the turnout type
209     * @return boolean true if this is a crossover
210     */
211    public static boolean isTurnoutTypeXover(TurnoutType type) {
212        return (type == TurnoutType.DOUBLE_XOVER
213                || type == TurnoutType.RH_XOVER
214                || type == TurnoutType.LH_XOVER);
215    }
216
217    /**
218     * Returns true if this is a crossover
219     *
220     * @return boolean true if this is a crossover
221     */
222    public boolean isTurnoutTypeXover() {
223        return isTurnoutTypeXover(getTurnoutType());
224    }
225
226    /**
227     * Returns true if this is a slip
228     *
229     * @param type the turnout type
230     * @return boolean true if this is a slip
231     */
232    public static boolean isTurnoutTypeSlip(TurnoutType type) {
233        return (type == TurnoutType.SINGLE_SLIP
234                || type == TurnoutType.DOUBLE_SLIP);
235    }
236
237    /**
238     * Returns true if this is a slip
239     *
240     * @return boolean true if this is a slip
241     */
242    public boolean isTurnoutTypeSlip() {
243        return isTurnoutTypeSlip(getTurnoutType());
244    }
245
246    /**
247     * Returns true if this has a single-track entrance end. (turnout or wye)
248     *
249     * @param type the turnout type
250     * @return boolean true if single track entrance
251     */
252    public static boolean hasEnteringSingleTrack(TurnoutType type) {
253        return isTurnoutTypeTurnout(type);
254    }
255
256    /**
257     * Returns true if this has a single-track entrance end. (turnout or wye)
258     *
259     * @return boolean true if single track entrance
260     */
261    public boolean hasEnteringSingleTrack() {
262        return hasEnteringSingleTrack(getTurnoutType());
263    }
264
265    /**
266     * Returns true if this has double track on the entrance end (crossover or
267     * slip)
268     *
269     * @param type the turnout type
270     * @return boolean true if double track entrance
271     */
272    public static boolean hasEnteringDoubleTrack(TurnoutType type) {
273        return isTurnoutTypeXover(type) || isTurnoutTypeSlip(type);
274    }
275
276    /**
277     * Returns true if this has double track on the entrance end (crossover or
278     * slip)
279     *
280     * @return boolean true if double track entrance
281     */
282    public boolean hasEnteringDoubleTrack() {
283        return hasEnteringDoubleTrack(getTurnoutType());
284    }
285
286    public enum LinkType {
287        NO_LINK,
288        FIRST_3_WAY, // this turnout is the first turnout of a 3-way
289        // turnout pair (closest to the throat)
290        SECOND_3_WAY, // this turnout is the second turnout of a 3-way
291        // turnout pair (furthest from the throat)
292        THROAT_TO_THROAT  // this turnout is one of two throat-to-throat
293        // turnouts - no signals at throat
294    }
295
296    // operational instance variables (not saved between sessions)
297    public static final int UNKNOWN = Turnout.UNKNOWN;
298    public static final int INCONSISTENT = Turnout.INCONSISTENT;
299    public static final int STATE_AC = 0x02;
300    public static final int STATE_BD = 0x04;
301    public static final int STATE_AD = 0x06;
302    public static final int STATE_BC = 0x08;
303
304    // program default turnout size parameters
305    public static final double turnoutBXDefault = 20.0;  // RH, LH, WYE
306    public static final double turnoutCXDefault = 20.0;
307    public static final double turnoutWidDefault = 10.0;
308    public static final double xOverLongDefault = 30.0;   // DOUBLE_XOVER, RH_XOVER, LH_XOVER
309    public static final double xOverHWidDefault = 10.0;
310    public static final double xOverShortDefault = 10.0;
311
312    // operational instance variables (not saved between sessions)
313    protected NamedBeanHandle<Turnout> namedTurnout = null;
314    // Second turnout is used to either throw a second turnout in a cross over or if one turnout address is used to throw two physical ones
315    protected NamedBeanHandle<Turnout> secondNamedTurnout = null;
316
317    private java.beans.PropertyChangeListener mTurnoutListener = null;
318
319    // persistent instances variables (saved between sessions)
320    // these should be the system or user name of an existing physical turnout
321    @Nonnull private String turnoutName = ""; // "" means none, never null
322    @Nonnull private String secondTurnoutName = ""; // "" means none, never null
323    private boolean secondTurnoutInverted = false;
324
325    // default is package protected
326    protected NamedBeanHandle<LayoutBlock> namedLayoutBlockA = null;
327    protected NamedBeanHandle<LayoutBlock> namedLayoutBlockB = null;  // Xover - second block, if there is one
328    protected NamedBeanHandle<LayoutBlock> namedLayoutBlockC = null;  // Xover - third block, if there is one
329    protected NamedBeanHandle<LayoutBlock> namedLayoutBlockD = null;  // Xover - forth block, if there is one
330
331    protected NamedBeanHandle<SignalHead> signalA1HeadNamed = null; // signal 1 (continuing) (throat for RH, LH, WYE)
332    protected NamedBeanHandle<SignalHead> signalA2HeadNamed = null; // signal 2 (diverging) (throat for RH, LH, WYE)
333    protected NamedBeanHandle<SignalHead> signalA3HeadNamed = null; // signal 3 (second diverging) (3-way turnouts only)
334    protected NamedBeanHandle<SignalHead> signalB1HeadNamed = null; // continuing (RH, LH, WYE) signal 1 (double crossover)
335    protected NamedBeanHandle<SignalHead> signalB2HeadNamed = null; // LH_Xover and double crossover only
336    protected NamedBeanHandle<SignalHead> signalC1HeadNamed = null; // diverging (RH, LH, WYE) signal 1 (double crossover)
337    protected NamedBeanHandle<SignalHead> signalC2HeadNamed = null; // RH_Xover and double crossover only
338    protected NamedBeanHandle<SignalHead> signalD1HeadNamed = null; // single or double crossover only
339    protected NamedBeanHandle<SignalHead> signalD2HeadNamed = null; // LH_Xover and double crossover only
340
341    public enum Geometry {
342        NONE,
343        POINTA1,
344        POINTA2,
345        POINTA3,
346        POINTB1,
347        POINTB2,
348        POINTC1,
349        POINTC2,
350        POINTD1,
351        POINTD2
352    }
353
354    protected NamedBeanHandle<SignalMast> signalAMastNamed = null; // Throat
355    protected NamedBeanHandle<SignalMast> signalBMastNamed = null; // Continuing
356    protected NamedBeanHandle<SignalMast> signalCMastNamed = null; // diverging
357    protected NamedBeanHandle<SignalMast> signalDMastNamed = null; // single or double crossover only
358
359    protected NamedBeanHandle<Sensor> sensorANamed = null; // Throat
360    protected NamedBeanHandle<Sensor> sensorBNamed = null; // Continuing
361    protected NamedBeanHandle<Sensor> sensorCNamed = null; // diverging
362    protected NamedBeanHandle<Sensor> sensorDNamed = null; // single or double crossover only
363
364    protected final TurnoutType type;
365
366    public LayoutTrack connectA = null;      // throat of LH, RH, RH Xover, LH Xover, and WYE turnouts
367    public LayoutTrack connectB = null;      // straight leg of LH and RH turnouts
368    public LayoutTrack connectC = null;
369    public LayoutTrack connectD = null;      // double xover, RH Xover, LH Xover only
370
371    public int continuingSense = Turnout.CLOSED;
372
373    public boolean disabled = false;
374    public boolean disableWhenOccupied = false;
375
376    private int version = 1;
377
378    @Nonnull public String linkedTurnoutName = ""; // name of the linked Turnout (as entered in tool); "" means none, never null
379    public LinkType linkType = LinkType.NO_LINK;
380
381    private final boolean useBlockSpeed = false;
382
383    /**
384     * {@inheritDoc}
385     */
386    // this should only be used for debugging...
387    @Override
388    @Nonnull
389    public String toString() {
390        return "LayoutTurnout " + getName();
391    }
392
393    /**
394     * Get the Version.
395     * @return turnout version.
396     */
397    public int getVersion() {
398        return version;
399    }
400
401    public void setVersion(int v) {
402        version = v;
403    }
404
405    public boolean useBlockSpeed() {
406        return useBlockSpeed;
407    }
408
409    @Nonnull
410    public String getTurnoutName() {
411        if (namedTurnout != null) {
412            turnoutName = namedTurnout.getName();
413        }
414        return turnoutName;
415    }
416
417    @Nonnull
418    public String getSecondTurnoutName() {
419        if (secondNamedTurnout != null) {
420            secondTurnoutName = secondNamedTurnout.getName();
421        }
422        return secondTurnoutName;
423    }
424
425    public boolean isSecondTurnoutInverted() {
426        return secondTurnoutInverted;
427    }
428
429    @Nonnull
430    public String getBlockName() {
431        String result = null;
432        if (namedLayoutBlockA != null) {
433            result = namedLayoutBlockA.getName();
434        }
435        return ((result == null) ? "" : result);
436    }
437
438    @Nonnull
439    public String getBlockBName() {
440        String result = getBlockName();
441        if (namedLayoutBlockB != null) {
442            result = namedLayoutBlockB.getName();
443        }
444        return result;
445    }
446
447    @Nonnull
448    public String getBlockCName() {
449        String result = getBlockName();
450        if (namedLayoutBlockC != null) {
451            result = namedLayoutBlockC.getName();
452        }
453        return result;
454    }
455
456    @Nonnull
457    public String getBlockDName() {
458        String result = getBlockName();
459        if (namedLayoutBlockD != null) {
460            result = namedLayoutBlockD.getName();
461        }
462        return result;
463    }
464
465    @CheckForNull
466    public SignalHead getSignalHead(Geometry loc) {
467        NamedBeanHandle<SignalHead> signalHead = null;
468        switch (loc) {
469            case POINTA1:
470                signalHead = signalA1HeadNamed;
471                break;
472            case POINTA2:
473                signalHead = signalA2HeadNamed;
474                break;
475            case POINTA3:
476                signalHead = signalA3HeadNamed;
477                break;
478            case POINTB1:
479                signalHead = signalB1HeadNamed;
480                break;
481            case POINTB2:
482                signalHead = signalB2HeadNamed;
483                break;
484            case POINTC1:
485                signalHead = signalC1HeadNamed;
486                break;
487            case POINTC2:
488                signalHead = signalC2HeadNamed;
489                break;
490            case POINTD1:
491                signalHead = signalD1HeadNamed;
492                break;
493            case POINTD2:
494                signalHead = signalD2HeadNamed;
495                break;
496            default:
497                log.warn("{}.getSignalHead({}); Unhandled point type", getName(), loc);
498                break;
499        }
500        if (signalHead != null) {
501            return signalHead.getBean();
502        }
503        return null;
504    }
505
506    @CheckForNull
507    public SignalHead getSignalA1() {
508        return signalA1HeadNamed != null ? signalA1HeadNamed.getBean() : null;
509    }
510
511    @Nonnull
512    public String getSignalA1Name() {
513        if (signalA1HeadNamed != null) {
514            return signalA1HeadNamed.getName();
515        }
516        return "";
517    }
518
519    public void setSignalA1Name(@CheckForNull String signalHead) {
520        if (signalHead == null || signalHead.isEmpty()) {
521            signalA1HeadNamed = null;
522            return;
523        }
524
525        SignalHead head = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(signalHead);
526        if (head != null) {
527            signalA1HeadNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalHead, head);
528        } else {
529            signalA1HeadNamed = null;
530            log.error("{}.setSignalA1Name({}); not fournd for turnout {}", getName(), signalHead, getTurnoutName());
531        }
532    }
533
534    @CheckForNull
535    public SignalHead getSignalA2() {
536        return signalA2HeadNamed != null ? signalA2HeadNamed.getBean() : null;
537    }
538
539    @Nonnull
540    public String getSignalA2Name() {
541        if (signalA2HeadNamed != null) {
542            return signalA2HeadNamed.getName();
543        }
544        return "";
545    }
546
547    public void setSignalA2Name(@CheckForNull String signalHead) {
548        if (signalHead == null || signalHead.isEmpty()) {
549            signalA2HeadNamed = null;
550            return;
551        }
552
553        SignalHead head = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(signalHead);
554        if (head != null) {
555            signalA2HeadNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalHead, head);
556        } else {
557            signalA2HeadNamed = null;
558            log.error("{}.setSignalA2Name({}); not fournd for turnout {}", getName(), signalHead, getTurnoutName());
559        }
560    }
561
562    @CheckForNull
563    public SignalHead getSignalA3() {
564        return signalA3HeadNamed != null ? signalA3HeadNamed.getBean() : null;
565    }
566
567    @Nonnull
568    public String getSignalA3Name() {
569        if (signalA3HeadNamed != null) {
570            return signalA3HeadNamed.getName();
571        }
572        return "";
573    }
574
575    public void setSignalA3Name(@CheckForNull String signalHead) {
576        if (signalHead == null || signalHead.isEmpty()) {
577            signalA3HeadNamed = null;
578            return;
579        }
580
581        SignalHead head = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(signalHead);
582        if (head != null) {
583            signalA3HeadNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalHead, head);
584        } else {
585            signalA3HeadNamed = null;
586            log.error("{}.setSignalA3Name({}); not fournd for turnout {}", getName(), signalHead, getTurnoutName());
587        }
588    }
589
590    @CheckForNull
591    public SignalHead getSignalB1() {
592        return signalB1HeadNamed != null ? signalB1HeadNamed.getBean() : null;
593    }
594
595    @Nonnull
596    public String getSignalB1Name() {
597        if (signalB1HeadNamed != null) {
598            return signalB1HeadNamed.getName();
599        }
600        return "";
601    }
602
603    public void setSignalB1Name(@CheckForNull String signalHead) {
604        if (signalHead == null || signalHead.isEmpty()) {
605            signalB1HeadNamed = null;
606            return;
607        }
608
609        SignalHead head = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(signalHead);
610        if (head != null) {
611            signalB1HeadNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalHead, head);
612        } else {
613            signalB1HeadNamed = null;
614            log.error("{}.setSignalB1Name({}); not fournd for turnout {}", getName(), signalHead, getTurnoutName());
615        }
616    }
617
618    @CheckForNull
619    public SignalHead getSignalB2() {
620        return signalB2HeadNamed != null ? signalB2HeadNamed.getBean() : null;
621    }
622
623    @Nonnull
624    public String getSignalB2Name() {
625        if (signalB2HeadNamed != null) {
626            return signalB2HeadNamed.getName();
627        }
628        return "";
629    }
630
631    public void setSignalB2Name(@CheckForNull String signalHead) {
632        if (signalHead == null || signalHead.isEmpty()) {
633            signalB2HeadNamed = null;
634            return;
635        }
636
637        SignalHead head = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(signalHead);
638        if (head != null) {
639            signalB2HeadNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalHead, head);
640        } else {
641            signalB2HeadNamed = null;
642            log.error("{}.setSignalB2Name({}); not fournd for turnout {}", getName(), signalHead, getTurnoutName());
643        }
644    }
645
646    @CheckForNull
647    public SignalHead getSignalC1() {
648        return signalC1HeadNamed != null ? signalC1HeadNamed.getBean() : null;
649    }
650
651    @Nonnull
652    public String getSignalC1Name() {
653        if (signalC1HeadNamed != null) {
654            return signalC1HeadNamed.getName();
655        }
656        return "";
657    }
658
659    public void setSignalC1Name(@CheckForNull String signalHead) {
660        if (signalHead == null || signalHead.isEmpty()) {
661            signalC1HeadNamed = null;
662            return;
663        }
664
665        SignalHead head = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(signalHead);
666        if (head != null) {
667            signalC1HeadNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalHead, head);
668        } else {
669            signalC1HeadNamed = null;
670            log.error("{}.setSignalC1Name({}); not fournd for turnout {}", getName(), signalHead, getTurnoutName());
671        }
672    }
673
674    @CheckForNull
675    public SignalHead getSignalC2() {
676        return signalC2HeadNamed != null ? signalC2HeadNamed.getBean() : null;
677    }
678
679    @Nonnull
680    public String getSignalC2Name() {
681        if (signalC2HeadNamed != null) {
682            return signalC2HeadNamed.getName();
683        }
684        return "";
685    }
686
687    public void setSignalC2Name(@CheckForNull String signalHead) {
688        if (signalHead == null || signalHead.isEmpty()) {
689            signalC2HeadNamed = null;
690            return;
691        }
692
693        SignalHead head = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(signalHead);
694        if (head != null) {
695            signalC2HeadNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalHead, head);
696        } else {
697            signalC2HeadNamed = null;
698            log.error("{}.setSignalC2Name({}); not fournd for turnout {}", getName(), signalHead, getTurnoutName());
699        }
700    }
701
702    @CheckForNull
703    public SignalHead getSignalD1() {
704        return signalD1HeadNamed != null ? signalD1HeadNamed.getBean() : null;
705    }
706
707    @Nonnull
708    public String getSignalD1Name() {
709        if (signalD1HeadNamed != null) {
710            return signalD1HeadNamed.getName();
711        }
712        return "";
713    }
714
715    public void setSignalD1Name(@CheckForNull String signalHead) {
716        if (signalHead == null || signalHead.isEmpty()) {
717            signalD1HeadNamed = null;
718            return;
719        }
720
721        SignalHead head = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(signalHead);
722        if (head != null) {
723            signalD1HeadNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalHead, head);
724        } else {
725            signalD1HeadNamed = null;
726            log.error("{}.setSignalD1Name({}); not fournd for turnout {}", getName(), signalHead, getTurnoutName());
727        }
728    }
729
730    @CheckForNull
731    public SignalHead getSignalD2() {
732        return signalD2HeadNamed != null ? signalD2HeadNamed.getBean() : null;
733    }
734
735    @Nonnull
736    public String getSignalD2Name() {
737        if (signalD2HeadNamed != null) {
738            return signalD2HeadNamed.getName();
739        }
740        return "";
741    }
742
743    public void setSignalD2Name(@CheckForNull String signalHead) {
744        if (signalHead == null || signalHead.isEmpty()) {
745            signalD2HeadNamed = null;
746            return;
747        }
748
749        SignalHead head = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(signalHead);
750        if (head != null) {
751            signalD2HeadNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalHead, head);
752        } else {
753            signalD2HeadNamed = null;
754            log.error("{}.setSignalD2Name({}); not fournd for turnout {}", getName(), signalHead, getTurnoutName());
755        }
756    }
757
758    public void removeBeanReference(@CheckForNull jmri.NamedBean nb) {
759        if (nb == null) {
760            return;
761        }
762        if (nb instanceof SignalMast) {
763            if (nb.equals(getSignalAMast())) {
764                setSignalAMast(null);
765                return;
766            }
767            if (nb.equals(getSignalBMast())) {
768                setSignalBMast(null);
769                return;
770            }
771            if (nb.equals(getSignalCMast())) {
772                setSignalCMast(null);
773                return;
774            }
775            if (nb.equals(getSignalDMast())) {
776                setSignalDMast(null);
777            }
778        } else if (nb instanceof Sensor) {
779            if (nb.equals(getSensorA())) {
780                setSensorA(null);
781                return;
782            }
783            if (nb.equals(getSensorB())) {
784                setSensorB(null);
785                return;
786            }
787            if (nb.equals(getSensorC())) {
788                setSensorC(null);
789                return;
790            }
791            if (nb.equals(getSensorB())) {
792                setSensorD(null);
793            }
794        } else if (nb instanceof SignalHead) {
795            if (nb.equals(getSignalHead(Geometry.POINTA1))) {
796                setSignalA1Name(null);
797            }
798            if (nb.equals(getSignalHead(Geometry.POINTA2))) {
799                setSignalA2Name(null);
800            }
801            if (nb.equals(getSignalHead(Geometry.POINTA3))) {
802                setSignalA3Name(null);
803            }
804            if (nb.equals(getSignalHead(Geometry.POINTB1))) {
805                setSignalB1Name(null);
806            }
807            if (nb.equals(getSignalHead(Geometry.POINTB2))) {
808                setSignalB2Name(null);
809            }
810            if (nb.equals(getSignalHead(Geometry.POINTC1))) {
811                setSignalC1Name(null);
812            }
813            if (nb.equals(getSignalHead(Geometry.POINTC2))) {
814                setSignalC2Name(null);
815            }
816            if (nb.equals(getSignalHead(Geometry.POINTD1))) {
817                setSignalD1Name(null);
818            }
819            if (nb.equals(getSignalHead(Geometry.POINTD2))) {
820                setSignalD2Name(null);
821            }
822        }
823    }
824
825    /**
826     * {@inheritDoc}
827     */
828    @Override
829    public boolean canRemove() {
830        ArrayList<String> beanReferences = getBeanReferences("All");  // NOI18N
831        if (!beanReferences.isEmpty()) {
832            models.displayRemoveWarning(this, beanReferences, "BeanNameTurnout");  // NOI18N
833        }
834        return beanReferences.isEmpty();
835    }
836
837    /**
838     * Build a list of sensors, signal heads, and signal masts attached to a
839     * turnout point.
840     *
841     * @param pointName Specify the point (A-D) or all (All) points.
842     * @return a list of bean reference names.
843     */
844    @Nonnull
845    public ArrayList<String> getBeanReferences(String pointName) {
846        ArrayList<String> references = new ArrayList<>();
847        if (pointName.equals("A") || pointName.equals("All")) {  // NOI18N
848            if (!getSignalAMastName().isEmpty()) {
849                references.add(getSignalAMastName());
850            }
851            if (!getSensorAName().isEmpty()) {
852                references.add(getSensorAName());
853            }
854            if (!getSignalA1Name().isEmpty()) {
855                references.add(getSignalA1Name());
856            }
857            if (!getSignalA2Name().isEmpty()) {
858                references.add(getSignalA2Name());
859            }
860            if (!getSignalA3Name().isEmpty()) {
861                references.add(getSignalA3Name());
862            }
863        }
864        if (pointName.equals("B") || pointName.equals("All")) {  // NOI18N
865            if (!getSignalBMastName().isEmpty()) {
866                references.add(getSignalBMastName());
867            }
868            if (!getSensorBName().isEmpty()) {
869                references.add(getSensorBName());
870            }
871            if (!getSignalB1Name().isEmpty()) {
872                references.add(getSignalB1Name());
873            }
874            if (!getSignalB2Name().isEmpty()) {
875                references.add(getSignalB2Name());
876            }
877        }
878        if (pointName.equals("C") || pointName.equals("All")) {  // NOI18N
879            if (!getSignalCMastName().isEmpty()) {
880                references.add(getSignalCMastName());
881            }
882            if (!getSensorCName().isEmpty()) {
883                references.add(getSensorCName());
884            }
885            if (!getSignalC1Name().isEmpty()) {
886                references.add(getSignalC1Name());
887            }
888            if (!getSignalC2Name().isEmpty()) {
889                references.add(getSignalC2Name());
890            }
891        }
892        if (pointName.equals("D") || pointName.equals("All")) {  // NOI18N
893            if (!getSignalDMastName().isEmpty()) {
894                references.add(getSignalDMastName());
895            }
896            if (!getSensorDName().isEmpty()) {
897                references.add(getSensorDName());
898            }
899            if (!getSignalD1Name().isEmpty()) {
900                references.add(getSignalD1Name());
901            }
902            if (!getSignalD2Name().isEmpty()) {
903                references.add(getSignalD2Name());
904            }
905        }
906        return references;
907    }
908
909    @Nonnull
910    public String getSignalAMastName() {
911        if (signalAMastNamed != null) {
912            return signalAMastNamed.getName();
913        }
914        return "";
915    }
916
917    // @CheckForNull temporary until we get central error check
918    public SignalMast getSignalAMast() {
919        if (signalAMastNamed != null) {
920            return signalAMastNamed.getBean();
921        }
922        return null;
923    }
924
925    public void setSignalAMast(@CheckForNull String signalMast) {
926        if (signalMast == null || signalMast.isEmpty()) {
927            signalAMastNamed = null;
928            return;
929        }
930
931        SignalMast mast = InstanceManager.getDefault(jmri.SignalMastManager.class).getSignalMast(signalMast);
932        if (mast != null) {
933            signalAMastNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalMast, mast);
934        } else {
935            signalAMastNamed = null;
936            log.error("{}.setSignalAMast({}); not fournd for turnout {}", getName(), signalMast, getTurnoutName());
937        }
938    }
939
940    @Nonnull
941    public String getSignalBMastName() {
942        if (signalBMastNamed != null) {
943            return signalBMastNamed.getName();
944        }
945        return "";
946    }
947
948    // @CheckForNull temporary until we get central error check
949    public SignalMast getSignalBMast() {
950        if (signalBMastNamed != null) {
951            return signalBMastNamed.getBean();
952        }
953        return null;
954    }
955
956    public void setSignalBMast(@CheckForNull String signalMast) {
957        if (signalMast == null || signalMast.isEmpty()) {
958            signalBMastNamed = null;
959            return;
960        }
961
962        SignalMast mast = InstanceManager.getDefault(jmri.SignalMastManager.class).getSignalMast(signalMast);
963        if (mast != null) {
964            signalBMastNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalMast, mast);
965        } else {
966            signalBMastNamed = null;
967            log.error("{}.setSignalBMast({}); not fournd for turnout {}", getName(), signalMast, getTurnoutName());
968        }
969    }
970
971    @Nonnull
972    public String getSignalCMastName() {
973        if (signalCMastNamed != null) {
974            return signalCMastNamed.getName();
975        }
976        return "";
977    }
978
979    // @CheckForNull temporary until we get central error check
980    public SignalMast getSignalCMast() {
981        if (signalCMastNamed != null) {
982            return signalCMastNamed.getBean();
983        }
984        return null;
985    }
986
987    public void setSignalCMast(@CheckForNull String signalMast) {
988        if (signalMast == null || signalMast.isEmpty()) {
989            signalCMastNamed = null;
990            return;
991        }
992
993        SignalMast mast = InstanceManager.getDefault(jmri.SignalMastManager.class).getSignalMast(signalMast);
994        if (mast != null) {
995            signalCMastNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalMast, mast);
996        } else {
997            log.error("{}.setSignalCMast({}); not fournd for turnout {}", getName(), signalMast, getTurnoutName());
998            signalCMastNamed = null;
999        }
1000    }
1001
1002    @Nonnull
1003    public String getSignalDMastName() {
1004        if (signalDMastNamed != null) {
1005            return signalDMastNamed.getName();
1006        }
1007        return "";
1008    }
1009
1010    // @CheckForNull temporary until we get central error check
1011    public SignalMast getSignalDMast() {
1012        if (signalDMastNamed != null) {
1013            return signalDMastNamed.getBean();
1014        }
1015        return null;
1016    }
1017
1018    public void setSignalDMast(@CheckForNull String signalMast) {
1019        if (signalMast == null || signalMast.isEmpty()) {
1020            signalDMastNamed = null;
1021            return;
1022        }
1023
1024        SignalMast mast = InstanceManager.getDefault(jmri.SignalMastManager.class).getSignalMast(signalMast);
1025        if (mast != null) {
1026            signalDMastNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalMast, mast);
1027        } else {
1028            log.error("{}.setSignalDMast({}); not fournd for turnout {}", getName(), signalMast, getTurnoutName());
1029            signalDMastNamed = null;
1030        }
1031    }
1032
1033    @Nonnull
1034    public String getSensorAName() {
1035        if (sensorANamed != null) {
1036            return sensorANamed.getName();
1037        }
1038        return "";
1039    }
1040
1041    @CheckForNull
1042    public Sensor getSensorA() {
1043        if (sensorANamed != null) {
1044            return sensorANamed.getBean();
1045        }
1046        return null;
1047    }
1048
1049    public void setSensorA(@CheckForNull String sensorName) {
1050        if (sensorName == null || sensorName.isEmpty()) {
1051            sensorANamed = null;
1052            return;
1053        }
1054
1055        try {
1056            Sensor sensor = InstanceManager.sensorManagerInstance().provideSensor(sensorName);
1057            sensorANamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(sensorName, sensor);
1058        } catch (IllegalArgumentException ex) {
1059            sensorANamed = null;
1060        }
1061    }
1062
1063    @Nonnull
1064    public String getSensorBName() {
1065        if (sensorBNamed != null) {
1066            return sensorBNamed.getName();
1067        }
1068        return "";
1069    }
1070
1071    @CheckForNull
1072    public Sensor getSensorB() {
1073        if (sensorBNamed != null) {
1074            return sensorBNamed.getBean();
1075        }
1076        return null;
1077    }
1078
1079    public void setSensorB(@CheckForNull String sensorName) {
1080        if (sensorName == null || sensorName.isEmpty()) {
1081            sensorBNamed = null;
1082            return;
1083        }
1084
1085        try {
1086            Sensor sensor = InstanceManager.sensorManagerInstance().provideSensor(sensorName);
1087            sensorBNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(sensorName, sensor);
1088        } catch (IllegalArgumentException ex) {
1089            sensorBNamed = null;
1090        }
1091    }
1092
1093    @Nonnull
1094    public String getSensorCName() {
1095        if (sensorCNamed != null) {
1096            return sensorCNamed.getName();
1097        }
1098        return "";
1099    }
1100
1101    @CheckForNull
1102    public Sensor getSensorC() {
1103        if (sensorCNamed != null) {
1104            return sensorCNamed.getBean();
1105        }
1106        return null;
1107    }
1108
1109    public void setSensorC(@CheckForNull String sensorName) {
1110        if (sensorName == null || sensorName.isEmpty()) {
1111            sensorCNamed = null;
1112            return;
1113        }
1114
1115        try {
1116            Sensor sensor = InstanceManager.sensorManagerInstance().provideSensor(sensorName);
1117            sensorCNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(sensorName, sensor);
1118        } catch (IllegalArgumentException ex) {
1119            sensorCNamed = null;
1120        }
1121    }
1122
1123    @Nonnull
1124    public String getSensorDName() {
1125        if (sensorDNamed != null) {
1126            return sensorDNamed.getName();
1127        }
1128        return "";
1129    }
1130
1131    @CheckForNull
1132    public Sensor getSensorD() {
1133        if (sensorDNamed != null) {
1134            return sensorDNamed.getBean();
1135        }
1136        return null;
1137    }
1138
1139    public void setSensorD(@CheckForNull String sensorName) {
1140        if (sensorName == null || sensorName.isEmpty()) {
1141            sensorDNamed = null;
1142            return;
1143        }
1144
1145        try {
1146            Sensor sensor = InstanceManager.sensorManagerInstance().provideSensor(sensorName);
1147            sensorDNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(sensorName, sensor);
1148        } catch (IllegalArgumentException ex) {
1149            sensorDNamed = null;
1150        }
1151    }
1152
1153    @Nonnull
1154    public String getLinkedTurnoutName() {
1155        return linkedTurnoutName;
1156    }
1157
1158    public void setLinkedTurnoutName(@Nonnull String s) {
1159        linkedTurnoutName = s;
1160    }  // Could be done with changing over to a NamedBeanHandle
1161
1162    public LinkType getLinkType() {
1163        return linkType;
1164    }
1165
1166    public void setLinkType(LinkType ltype) {
1167        linkType = ltype;
1168    }
1169
1170    public TurnoutType getTurnoutType() {
1171        return type;
1172    }
1173
1174    public LayoutTrack getConnectA() {
1175        return connectA;
1176    }
1177
1178    public LayoutTrack getConnectB() {
1179        return connectB;
1180    }
1181
1182    public LayoutTrack getConnectC() {
1183        return connectC;
1184    }
1185
1186    public LayoutTrack getConnectD() {
1187        return connectD;
1188    }
1189
1190    /**
1191     * Perhaps confusingly, this returns an actual Turnout reference
1192     * or null for the turnout associated with this is LayoutTurnout.
1193     * This is different from {@link #setTurnout(String)}, which
1194     * takes a name (system or user) or an empty string.
1195     * @return Null if no Turnout set
1196     */
1197    // @CheckForNull temporary - want to restore once better handled
1198    public Turnout getTurnout() {
1199        if (namedTurnout == null) {
1200            // set physical turnout if possible and needed
1201            setTurnout(turnoutName);
1202            if (namedTurnout == null) {
1203                return null;
1204            }
1205        }
1206        return namedTurnout.getBean();
1207    }
1208
1209    public int getContinuingSense() {
1210        return continuingSense;
1211    }
1212
1213    /**
1214     *
1215     * @return true is the continuingSense matches the known state
1216     */
1217    public boolean isInContinuingSenseState() {
1218        return getState() == continuingSense;
1219    }
1220
1221    /**
1222     * Perhaps confusingly, this takes a Turnout name (system or user)
1223     * to locate and set the turnout associated with this is LayoutTurnout.
1224     * This is different from {@link #getTurnout()}, which returns an
1225     * actual Turnout reference or null.
1226     * @param tName provide empty string for none; never null
1227     */
1228    public void setTurnout(@Nonnull String tName) {
1229        assert tName != null;
1230        if (namedTurnout != null) {
1231            deactivateTurnout();
1232        }
1233        turnoutName = tName;
1234        Turnout turnout = null;
1235        if (!turnoutName.isEmpty()) {
1236            turnout = InstanceManager.turnoutManagerInstance().getTurnout(turnoutName);
1237        }
1238        if (turnout != null) {
1239            namedTurnout = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(turnoutName, turnout);
1240            activateTurnout();
1241        } else {
1242            turnoutName = "";
1243            namedTurnout = null;
1244            setDisableWhenOccupied(false);
1245        }
1246        Turnout secondTurnout = getSecondTurnout();
1247        if (secondTurnout != null && secondTurnout.getFeedbackMode() == Turnout.DIRECT) {
1248            secondTurnout.setLeadingTurnout(turnout, false);
1249        }
1250    }
1251
1252    // @CheckForNull temporary until we have central paradigm for null
1253    public Turnout getSecondTurnout() {
1254        Turnout result = null;
1255        if (secondNamedTurnout == null) {
1256            // set physical turnout if possible and needed
1257            setSecondTurnout(secondTurnoutName);
1258        }
1259        if (secondNamedTurnout != null) {
1260            result = secondNamedTurnout.getBean();
1261        }
1262        return result;
1263    }
1264
1265    /**
1266     * @param tName provide empty string for none (not null)
1267     */
1268    public void setSecondTurnout(@Nonnull String tName) {
1269        assert tName != null;
1270        if (tName.equals(secondTurnoutName)) { // haven't changed anything
1271            return;
1272        }
1273
1274        if (secondNamedTurnout != null) {
1275            deactivateTurnout();
1276            Turnout turnout = secondNamedTurnout.getBean();
1277            if (turnout.getLeadingTurnout() == namedTurnout.getBean()) {
1278                turnout.setLeadingTurnout(null);
1279            }
1280        }
1281        String oldSecondTurnoutName = secondTurnoutName;
1282        secondTurnoutName = tName;
1283        Turnout turnout = null;
1284        if (! tName.isEmpty()) {
1285            turnout = InstanceManager.turnoutManagerInstance().getTurnout(secondTurnoutName);
1286        }
1287        if (turnout != null) {
1288            secondNamedTurnout = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(secondTurnoutName, turnout);
1289            if (turnout.getFeedbackMode() == Turnout.DIRECT) {
1290                turnout.setLeadingTurnout(getTurnout(), false);
1291            }
1292        } else {
1293            secondTurnoutName = "";
1294            secondNamedTurnout = null;
1295        }
1296        activateTurnout(); // Even if secondary is null, the primary Turnout may still need to be re-activated
1297        if (isTurnoutTypeTurnout()) {
1298            LayoutEditorFindItems lf = new LayoutEditorFindItems(models);
1299            if (oldSecondTurnoutName != null && !oldSecondTurnoutName.isEmpty()) {
1300                Turnout oldTurnout = InstanceManager.turnoutManagerInstance().getTurnout(oldSecondTurnoutName);
1301                String oldSystemName = (oldTurnout == null) ? null : oldTurnout.getSystemName();
1302                LayoutTurnout oldLinked = (oldSystemName == null) ? null
1303                        : lf.findLayoutTurnoutByTurnoutName(oldSystemName);
1304                if (oldLinked == null) {
1305                    String oldUserName = (oldTurnout == null) ? null : oldTurnout.getUserName();
1306                    oldLinked = (oldUserName == null) ? null
1307                            : lf.findLayoutTurnoutByTurnoutName(oldUserName);
1308                }
1309                if ((oldLinked != null) && oldLinked.getSecondTurnout() == getTurnout()) {
1310                    oldLinked.setSecondTurnout("");
1311                }
1312            }
1313            if (turnout != null) {
1314                LayoutTurnout newLinked = lf.findLayoutTurnoutByTurnoutName(turnout.getSystemName());
1315                if (newLinked == null) {
1316                    newLinked = lf.findLayoutTurnoutByTurnoutName(turnout.getUserName());
1317                }
1318                if (newLinked != null) {
1319                    newLinked.setSecondTurnout(turnoutName);
1320                }
1321            }
1322        }
1323    }
1324
1325    public void setSecondTurnoutInverted(boolean inverted) {
1326        secondTurnoutInverted = inverted;
1327    }
1328
1329    public void setContinuingSense(int sense) {
1330        continuingSense = sense;
1331    }
1332
1333    public void setDisabled(boolean state) {
1334        if (disabled != state) {
1335            disabled = state;
1336            if (models != null) {
1337                models.redrawPanel();
1338            }
1339        }
1340    }
1341
1342    public boolean isDisabled() {
1343        return disabled;
1344    }
1345
1346    public void setDisableWhenOccupied(boolean state) {
1347        if (disableWhenOccupied != state) {
1348            disableWhenOccupied = state;
1349            if (models != null) {
1350                models.redrawPanel();
1351            }
1352        }
1353    }
1354
1355    public boolean isDisabledWhenOccupied() {
1356        return disableWhenOccupied;
1357    }
1358
1359    /**
1360     * {@inheritDoc}
1361     */
1362    @Override
1363    @CheckForNull
1364    public LayoutTrack getConnection(HitPointType connectionType) {
1365        LayoutTrack result = null;
1366        switch (connectionType) {
1367            case TURNOUT_A: {
1368                result = connectA;
1369                break;
1370            }
1371            case TURNOUT_B: {
1372                result = connectB;
1373                break;
1374            }
1375            case TURNOUT_C: {
1376                result = connectC;
1377                break;
1378            }
1379            case TURNOUT_D: {
1380                result = connectD;
1381                break;
1382            }
1383            default: {
1384                String errString = MessageFormat.format("{0}.getConnection({1}); Invalid Connection Type",
1385                        getName(), connectionType); // I18IN
1386                log.error("will throw {}", errString);
1387                throw new IllegalArgumentException(errString);
1388            }
1389        }
1390        return result;
1391    }
1392
1393    /**
1394     * {@inheritDoc}
1395     */
1396    @Override
1397    public void setConnection(HitPointType connectionType, @CheckForNull LayoutTrack o, HitPointType type) throws jmri.JmriException {
1398        if ((type != HitPointType.TRACK) && (type != HitPointType.NONE)) {
1399            String errString = MessageFormat.format("{0}.setConnection({1}, {2}, {3}); unexpected type",
1400                    getName(), connectionType, (o == null) ? "null" : o.getName(), type, new Exception("traceback")); // I18IN
1401            log.error("will throw {}", errString);
1402            throw new jmri.JmriException(errString);
1403        }
1404        switch (connectionType) {
1405            case TURNOUT_A:
1406                connectA = o;
1407                break;
1408            case TURNOUT_B:
1409                connectB = o;
1410                break;
1411            case TURNOUT_C:
1412                connectC = o;
1413                break;
1414            case TURNOUT_D:
1415                connectD = o;
1416                break;
1417            default:
1418                String errString = MessageFormat.format("{0}.setConnection({1}, {2}, {3}); Invalid Connection Type",
1419                        getName(), connectionType, (o == null) ? "null" : o.getName(), type); // I18IN
1420                log.error("will throw {}", errString);
1421                throw new jmri.JmriException(errString);
1422        }
1423    }
1424
1425    public void setConnectA(@CheckForNull LayoutTrack o, HitPointType type) {
1426        connectA = o;
1427        if ((type != HitPointType.TRACK) && (type != HitPointType.NONE)) {
1428            log.error("{}.setConnectA({}, {}); unexpected type",
1429                    getName(), (o == null) ? "null" : o.getName(), type);
1430        }
1431    }
1432
1433    public void setConnectB(@CheckForNull LayoutTrack o, HitPointType type) {
1434        connectB = o;
1435        if ((type != HitPointType.TRACK) && (type != HitPointType.NONE)) {
1436            log.error("{}.setConnectB({}, {}); unexpected type",
1437                    getName(), (o == null) ? "null" : o.getName(), type);
1438        }
1439    }
1440
1441    public void setConnectC(@CheckForNull LayoutTrack o, HitPointType type) {
1442        connectC = o;
1443        if ((type != HitPointType.TRACK) && (type != HitPointType.NONE)) {
1444            log.error("{}.setConnectC({}, {}); unexpected type",
1445                    getName(), (o == null) ? "null" : o.getName(), type);
1446        }
1447    }
1448
1449    public void setConnectD(@CheckForNull LayoutTrack o, HitPointType type) {
1450        connectD = o;
1451        if ((type != HitPointType.TRACK) && (type != HitPointType.NONE)) {
1452            log.error("{}.setConnectD({}, {}); unexpected type",
1453                    getName(), (o == null) ? "null" : o.getName(), type);
1454        }
1455    }
1456
1457    // @CheckForNull - temporary, until we can centralize protection for this
1458    public LayoutBlock getLayoutBlock() {
1459        return (namedLayoutBlockA != null) ? namedLayoutBlockA.getBean() : null;
1460    }
1461
1462    // @CheckForNull - temporary, until we can centralize protection for this
1463    public LayoutBlock getLayoutBlockB() {
1464        return (namedLayoutBlockB != null) ? namedLayoutBlockB.getBean() : getLayoutBlock();
1465    }
1466
1467    // @CheckForNull - temporary, until we can centralize protection for this
1468    public LayoutBlock getLayoutBlockC() {
1469        return (namedLayoutBlockC != null) ? namedLayoutBlockC.getBean() : getLayoutBlock();
1470    }
1471
1472    // @CheckForNull - temporary, until we can centralize protection for this
1473    public LayoutBlock getLayoutBlockD() {
1474        return (namedLayoutBlockD != null) ? namedLayoutBlockD.getBean() : getLayoutBlock();
1475    }
1476
1477    // updates connectivity for blocks assigned to this turnout and connected track segments
1478    public void updateBlockInfo() {
1479        LayoutBlock bA = null;
1480        LayoutBlock bB = null;
1481        LayoutBlock bC = null;
1482        LayoutBlock bD = null;
1483        models.getLEAuxTools().setBlockConnectivityChanged();
1484        if (getLayoutBlock() != null) {
1485            getLayoutBlock().updatePaths();
1486        }
1487        if (connectA != null) {
1488            bA = ((TrackSegment) connectA).getLayoutBlock();
1489            if ((bA != null) && (bA != getLayoutBlock())) {
1490                bA.updatePaths();
1491            }
1492        }
1493        if ((getLayoutBlockB() != null)
1494                && (getLayoutBlockB() != getLayoutBlock())
1495                && (getLayoutBlockB() != bA)) {
1496            getLayoutBlockB().updatePaths();
1497        }
1498        if (connectB != null) {
1499            bB = ((TrackSegment) connectB).getLayoutBlock();
1500            if ((bB != null) && (bB != getLayoutBlock()) && (bB != bA)
1501                    && (bB != getLayoutBlockB())) {
1502                bB.updatePaths();
1503            }
1504        }
1505        if ((getLayoutBlockC() != null)
1506                && (getLayoutBlockC() != getLayoutBlock())
1507                && (getLayoutBlockC() != bA)
1508                && (getLayoutBlockC() != bB)
1509                && (getLayoutBlockC() != getLayoutBlockB())) {
1510            getLayoutBlockC().updatePaths();
1511        }
1512        if (connectC != null) {
1513            bC = ((TrackSegment) connectC).getLayoutBlock();
1514            if ((bC != null) && (bC != getLayoutBlock())
1515                    && (bC != bA) && (bC != getLayoutBlockB())
1516                    && (bC != bB)
1517                    && (bC != getLayoutBlockC())) {
1518                bC.updatePaths();
1519            }
1520        }
1521        if ((getLayoutBlockD() != null)
1522                && (getLayoutBlockD() != getLayoutBlock())
1523                && (getLayoutBlockD() != bA)
1524                && (getLayoutBlockD() != bB)
1525                && (getLayoutBlockD() != getLayoutBlockB())
1526                && (getLayoutBlockD() != bC)
1527                && (getLayoutBlockD() != getLayoutBlockC())) {
1528            getLayoutBlockD().updatePaths();
1529        }
1530        if (connectD != null) {
1531            bD = ((TrackSegment) connectD).getLayoutBlock();
1532            if ((bD != null) && (bD != getLayoutBlock())
1533                    && (bD != bA) && (bD != getLayoutBlockB())
1534                    && (bD != bB) && (bD != getLayoutBlockC())
1535                    && (bD != bC) && (bD != getLayoutBlockD())) {
1536                bD.updatePaths();
1537            }
1538        }
1539    }
1540
1541
1542    /**
1543     * Set up Layout Block(s) for this Turnout.
1544     * @param newLayoutBlock the new layout block.
1545     */
1546    protected void setLayoutBlock(LayoutBlock newLayoutBlock) {
1547        LayoutBlock blockA = getLayoutBlock();
1548        LayoutBlock blockB = getLayoutBlockB();
1549        LayoutBlock blockC = getLayoutBlockC();
1550        LayoutBlock blockD = getLayoutBlockD();
1551        if (blockA != newLayoutBlock) {
1552            // block has changed, if old block exists, decrement use
1553            if ((blockA != null)
1554                    && (blockA != blockB)
1555                    && (blockA != blockC)
1556                    && (blockA != blockD)) {
1557                blockA.decrementUse();
1558            }
1559
1560            blockA = newLayoutBlock;
1561            if (newLayoutBlock != null) {
1562                String userName = newLayoutBlock.getUserName();
1563                if (userName != null) {
1564                    namedLayoutBlockA = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(userName, newLayoutBlock);
1565                }
1566            } else {
1567                namedLayoutBlockA = null;
1568                setDisableWhenOccupied(false);
1569            }
1570            // decrement use if block was already counted
1571            if ((blockA != null)
1572                    && ((blockA == blockB) || (blockA == blockC) || (blockA == blockD))) {
1573                blockA.decrementUse();
1574            }
1575        }
1576    }
1577
1578    protected void setLayoutBlockB(LayoutBlock newLayoutBlock) {
1579        if (getLayoutBlock() == null) {
1580            setLayoutBlock(newLayoutBlock);
1581        }
1582        if (isTurnoutTypeXover() || isTurnoutTypeSlip()) {
1583            LayoutBlock blockA = getLayoutBlock();
1584            LayoutBlock blockB = getLayoutBlockB();
1585            LayoutBlock blockC = getLayoutBlockC();
1586            LayoutBlock blockD = getLayoutBlockD();
1587            if (blockB != newLayoutBlock) {
1588                // block has changed, if old block exists, decrement use
1589                if ((blockB != null)
1590                        && (blockB != blockA)
1591                        && (blockB != blockC)
1592                        && (blockB != blockD)) {
1593                    blockB.decrementUse();
1594                }
1595                blockB = newLayoutBlock;
1596                if (newLayoutBlock != null) {
1597                    String userName = newLayoutBlock.getUserName();
1598                    if (userName != null) {
1599                        namedLayoutBlockB = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(userName, newLayoutBlock);
1600                    }
1601                } else {
1602                    namedLayoutBlockB = null;
1603                }
1604                // decrement use if block was already counted
1605                if ((blockB != null)
1606                        && ((blockB == blockA) || (blockB == blockC) || (blockB == blockD))) {
1607                    blockB.decrementUse();
1608                }
1609            }
1610        } else {
1611            log.error("{}.setLayoutBlockB({}); not a crossover/slip", getName(), newLayoutBlock.getUserName());
1612        }
1613    }
1614
1615    protected void setLayoutBlockC(@CheckForNull LayoutBlock newLayoutBlock) {
1616        if (getLayoutBlock() == null) {
1617            setLayoutBlock(newLayoutBlock);
1618        }
1619        if (isTurnoutTypeXover() || isTurnoutTypeSlip()) {
1620            LayoutBlock blockA = getLayoutBlock();
1621            LayoutBlock blockB = getLayoutBlockB();
1622            LayoutBlock blockC = getLayoutBlockC();
1623            LayoutBlock blockD = getLayoutBlockD();
1624            if (blockC != newLayoutBlock) {
1625                // block has changed, if old block exists, decrement use
1626                if ((blockC != null)
1627                        && (blockC != blockA)
1628                        && (blockC != blockB)
1629                        && (blockC != blockD)) {
1630                    blockC.decrementUse();
1631                }
1632                blockC = newLayoutBlock;
1633                if (newLayoutBlock != null) {
1634                    String userName = newLayoutBlock.getUserName();
1635                    if (userName != null) {
1636                        namedLayoutBlockC = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(userName, newLayoutBlock);
1637                    }
1638                } else {
1639                    namedLayoutBlockC = null;
1640                }
1641                // decrement use if block was already counted
1642                if ((blockC != null)
1643                        && ((blockC == blockA) || (blockC == blockB) || (blockC == blockD))) {
1644                    blockC.decrementUse();
1645                }
1646            }
1647        } else {
1648            log.error("{}.setLayoutBlockC({}); not a crossover/slip", getName(), newLayoutBlock.getUserName());
1649        }
1650    }
1651
1652    protected void setLayoutBlockD(LayoutBlock newLayoutBlock) {
1653        if (getLayoutBlock() == null) {
1654            setLayoutBlock(newLayoutBlock);
1655        }
1656        if (isTurnoutTypeXover() || isTurnoutTypeSlip()) {
1657            LayoutBlock blockA = getLayoutBlock();
1658            LayoutBlock blockB = getLayoutBlockB();
1659            LayoutBlock blockC = getLayoutBlockC();
1660            LayoutBlock blockD = getLayoutBlockD();
1661            if (blockD != newLayoutBlock) {
1662                // block has changed, if old block exists, decrement use
1663                if ((blockD != null)
1664                        && (blockD != blockA)
1665                        && (blockD != blockB)
1666                        && (blockD != blockC)) {
1667                    blockD.decrementUse();
1668                }
1669                blockD = newLayoutBlock;
1670                if (newLayoutBlock != null) {
1671                    String userName = newLayoutBlock.getUserName();
1672                    if (userName != null) {
1673                        namedLayoutBlockD = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(userName, newLayoutBlock);
1674                    }
1675                } else {
1676                    namedLayoutBlockD = null;
1677                }
1678                // decrement use if block was already counted
1679                if ((blockD != null)
1680                        && ((blockD == blockA) || (blockD == blockB) || (blockD == blockC))) {
1681                    blockD.decrementUse();
1682                }
1683            }
1684        } else {
1685            log.error("{}.setLayoutBlockD({}); not a crossover/slip", getName(), newLayoutBlock.getUserName());
1686        }
1687    }
1688
1689    public void setLayoutBlockByName(@Nonnull String name) {
1690        setLayoutBlock(models.provideLayoutBlock(name));
1691    }
1692
1693    public void setLayoutBlockBByName(@Nonnull String name) {
1694        if (isTurnoutTypeXover() || isTurnoutTypeSlip()) {
1695            setLayoutBlockB(models.provideLayoutBlock(name));
1696        } else {
1697            log.error("{}.setLayoutBlockBByName({}); not a crossover/slip", getName(), name);
1698        }
1699    }
1700
1701    public void setLayoutBlockCByName(@Nonnull String name) {
1702        if (isTurnoutTypeXover() || isTurnoutTypeSlip()) {
1703            setLayoutBlockC(models.provideLayoutBlock(name));
1704        } else {
1705            log.error("{}.setLayoutBlockCByName({}); not a crossover/slip", getName(), name);
1706        }
1707    }
1708
1709    public void setLayoutBlockDByName(@Nonnull String name) {
1710        if (isTurnoutTypeXover() || isTurnoutTypeSlip()) {
1711            setLayoutBlockD(models.provideLayoutBlock(name));
1712        } else {
1713            log.error("{}.setLayoutBlockDByName({}); not a crossover/slip", getName(), name);
1714        }
1715    }
1716
1717    /**
1718     * Test if turnout legs are mainline track or not.
1719     *
1720     * @return true if connecting track segment is mainline; Defaults to not
1721     *         mainline if connecting track segment is missing
1722     */
1723    public boolean isMainlineA() {
1724        if (connectA != null) {
1725            return ((TrackSegment) connectA).isMainline();
1726        } else {
1727            // if no connection, depends on type of turnout
1728            if (isTurnoutTypeXover()) {
1729                // All crossovers - straight continuing is B
1730                if (connectB != null) {
1731                    return ((TrackSegment) connectB).isMainline();
1732                }
1733            } else if (isTurnoutTypeSlip()) {
1734                if (connectD != null) {
1735                    return ((TrackSegment) connectD).isMainline();
1736                }
1737            } // must be RH, LH, or WYE turnout - A is the switch throat
1738            else if (((connectB != null)
1739                    && (((TrackSegment) connectB).isMainline()))
1740                    || ((connectC != null)
1741                    && (((TrackSegment) connectC).isMainline()))) {
1742                return true;
1743            }
1744        }
1745        return false;
1746    }
1747
1748    public boolean isMainlineB() {
1749        if (connectB != null) {
1750            return ((TrackSegment) connectB).isMainline();
1751        } else {
1752            // if no connection, depends on type of turnout
1753            if (isTurnoutTypeXover()) {
1754                // All crossovers - straight continuing is A
1755                if (connectA != null) {
1756                    return ((TrackSegment) connectA).isMainline();
1757                }
1758            } else if (getTurnoutType() == TurnoutType.DOUBLE_SLIP) {
1759                if (connectD != null) {
1760                    return ((TrackSegment) connectD).isMainline();
1761                }
1762            } // must be RH, LH, or WYE turnout - A is the switch throat,
1763            //      B is normally the continuing straight
1764            else if (continuingSense == Turnout.CLOSED) {
1765                // user hasn't changed the continuing turnout state
1766                if (connectA != null) { // if throat is mainline, this leg must be also
1767                    return ((TrackSegment) connectA).isMainline();
1768                }
1769            }
1770        }
1771        return false;
1772    }
1773
1774    public boolean isMainlineC() {
1775        if (connectC != null) {
1776            return ((TrackSegment) connectC).isMainline();
1777        } else {
1778            // if no connection, depends on type of turnout
1779            if (isTurnoutTypeXover()) {
1780                // All crossovers - straight continuing is D
1781                if (connectD != null) {
1782                    return ((TrackSegment) connectD).isMainline();
1783                }
1784            } else if (getTurnoutType() == TurnoutType.DOUBLE_SLIP) {
1785                if (connectB != null) {
1786                    return ((TrackSegment) connectB).isMainline();
1787                }
1788            } // must be RH, LH, or WYE turnout - A is the switch throat,
1789            //      B is normally the continuing straight
1790            else if (continuingSense == Turnout.THROWN) {
1791                // user has changed the continuing turnout state
1792                if (connectA != null) { // if throat is mainline, this leg must be also
1793                    return ((TrackSegment) connectA).isMainline();
1794                }
1795            }
1796        }
1797        return false;
1798    }
1799
1800    public boolean isMainlineD() {
1801        // this is a crossover turnout
1802        if (connectD != null) {
1803            return ((TrackSegment) connectD).isMainline();
1804        } else if (isTurnoutTypeSlip()) {
1805            if (connectB != null) {
1806                return ((TrackSegment) connectB).isMainline();
1807            }
1808        } else if (connectC != null) {
1809            return ((TrackSegment) connectC).isMainline();
1810        }
1811        return false;
1812    }
1813
1814    /**
1815     * {@inheritDoc}
1816     */
1817    @Override
1818    public boolean isMainline() {
1819        return (isMainlineA() || isMainlineB() || isMainlineC() || isMainlineD());
1820    }
1821
1822    /**
1823     * Activate/Deactivate turnout to redraw when turnout state changes
1824     */
1825    private void activateTurnout() {
1826        deactivateTurnout();
1827        if (namedTurnout != null) {
1828            namedTurnout.getBean().addPropertyChangeListener(
1829                    mTurnoutListener = (java.beans.PropertyChangeEvent e) -> {
1830                        if (e.getNewValue() == null) {
1831                            return;
1832                        }
1833                        if (disableWhenOccupied && isOccupied()) {
1834                            return;
1835                        }
1836                        if (secondNamedTurnout != null) {
1837                            int t1state = namedTurnout.getBean().getCommandedState();
1838                            int t2state = secondNamedTurnout.getBean().getCommandedState();
1839                            if (e.getSource().equals(namedTurnout.getBean())
1840                            && e.getNewValue().equals(t1state)) {
1841                                if (secondTurnoutInverted) {
1842                                    t1state = Turnout.invertTurnoutState(t1state);
1843                                }
1844                                if (secondNamedTurnout.getBean().getCommandedState() != t1state) {
1845                                    secondNamedTurnout.getBean().setCommandedState(t1state);
1846                                }
1847                            } else if (e.getSource().equals(secondNamedTurnout.getBean())
1848                            && e.getNewValue().equals(t2state)) {
1849                                if (secondTurnoutInverted) {
1850                                    t2state = Turnout.invertTurnoutState(t2state);
1851                                }
1852                                if (namedTurnout.getBean().getCommandedState() != t2state) {
1853                                    namedTurnout.getBean().setCommandedState(t2state);
1854                                }
1855                            }
1856                        }
1857                        models.redrawPanel();
1858                    },
1859                    namedTurnout.getName(),
1860                    "Layout Editor Turnout"
1861            );
1862        }
1863        if (secondNamedTurnout != null) {
1864            secondNamedTurnout.getBean().addPropertyChangeListener(mTurnoutListener, secondNamedTurnout.getName(), "Layout Editor Turnout");
1865        }
1866    }
1867
1868    private void deactivateTurnout() {
1869        if (mTurnoutListener != null) {
1870            if (namedTurnout != null) {
1871                namedTurnout.getBean().removePropertyChangeListener(mTurnoutListener);
1872            }
1873            if (secondNamedTurnout != null) {
1874                secondNamedTurnout.getBean().removePropertyChangeListener(mTurnoutListener);
1875            }
1876            mTurnoutListener = null;
1877        }
1878    }
1879
1880    /**
1881     * Toggle turnout if clicked on, physical turnout exists, and not disabled.
1882     */
1883    public void toggleTurnout() {
1884        if (getTurnout() != null) {
1885            // toggle turnout
1886            if (getTurnout().getCommandedState() == Turnout.CLOSED) {
1887                setState(Turnout.THROWN);
1888            } else {
1889                setState(Turnout.CLOSED);
1890            }
1891        } else {
1892            log.debug("Turnout Icon not associated with a Turnout");
1893        }
1894    }
1895
1896    /**
1897     * Set the LayoutTurnout state. Used for sending the toggle command Checks
1898     * not disabled, disable when occupied Also sets secondary Turnout commanded
1899     * state
1900     *
1901     * @param state New state to set, eg Turnout.CLOSED
1902     */
1903    public void setState(int state) {
1904        if ((getTurnout() != null) && !disabled) {
1905            if (disableWhenOccupied && isOccupied()) {
1906                log.debug("Turnout not changed as Block is Occupied");
1907            } else {
1908                getTurnout().setCommandedState(state);
1909                Turnout secondTurnout = getSecondTurnout();
1910                if (secondTurnout != null) {
1911                    if (secondTurnoutInverted) {
1912                        secondTurnout.setCommandedState(Turnout.invertTurnoutState(state));
1913                    } else {
1914                        secondTurnout.setCommandedState(state);
1915                    }
1916                }
1917            }
1918        }
1919    }
1920
1921    /**
1922     * Get the LayoutTurnout state
1923     * <p>
1924     * Ensures the secondary Turnout state matches the primary
1925     *
1926     * @return the state, eg Turnout.CLOSED or Turnout.INCONSISTENT
1927     */
1928    public int getState() {
1929        int result = UNKNOWN;
1930        if (getTurnout() != null) {
1931            result = getTurnout().getKnownState();
1932        }
1933        if (getSecondTurnout() != null) {
1934            int t2state = getSecondTurnout().getKnownState();
1935            if (secondTurnoutInverted) {
1936                t2state = Turnout.invertTurnoutState(t2state);
1937            }
1938            if (result != t2state) {
1939                return INCONSISTENT;
1940            }
1941        }
1942        return result;
1943    }
1944
1945    /**
1946     * Is this turnout occupied?
1947     *
1948     * @return true if occupied
1949     */
1950    boolean isOccupied() {
1951        if (isTurnoutTypeTurnout()) {
1952            if (getLayoutBlock().getOccupancy() == LayoutBlock.OCCUPIED) {
1953                log.debug("Block {} is Occupied", getBlockName());
1954                return true;
1955            }
1956        } else if (isTurnoutTypeXover()) {
1957            // If the turnout is set for straight over, we need to deal with the straight over connecting blocks
1958            if (getTurnout().getKnownState() == Turnout.CLOSED) {
1959                if ((getLayoutBlock().getOccupancy() == LayoutBlock.OCCUPIED)
1960                        && (getLayoutBlockB().getOccupancy() == LayoutBlock.OCCUPIED)) {
1961                    log.debug("Blocks {} & {} are Occupied", getBlockName(), getBlockBName());
1962                    return true;
1963                }
1964                if ((getLayoutBlockC().getOccupancy() == LayoutBlock.OCCUPIED)
1965                        && (getLayoutBlockD().getOccupancy() == LayoutBlock.OCCUPIED)) {
1966                    log.debug("Blocks {} & {} are Occupied", getBlockCName(), getBlockDName());
1967                    return true;
1968                }
1969            }
1970
1971        }
1972        if ((getTurnoutType() == TurnoutType.DOUBLE_XOVER)
1973                || (getTurnoutType() == TurnoutType.LH_XOVER)) {
1974            if (getTurnout().getKnownState() == Turnout.THROWN) {
1975                if ((getLayoutBlockB().getOccupancy() == LayoutBlock.OCCUPIED)
1976                        && (getLayoutBlockD().getOccupancy() == LayoutBlock.OCCUPIED)) {
1977                    log.debug("Blocks {} & {} are Occupied", getBlockBName(), getBlockDName());
1978                    return true;
1979                }
1980            }
1981        }
1982
1983        if ((getTurnoutType() == TurnoutType.DOUBLE_XOVER)
1984                || (getTurnoutType() == TurnoutType.RH_XOVER)) {
1985            if (getTurnout().getKnownState() == Turnout.THROWN) {
1986                if ((getLayoutBlock().getOccupancy() == LayoutBlock.OCCUPIED)
1987                        && (getLayoutBlockC().getOccupancy() == LayoutBlock.OCCUPIED)) {
1988                    log.debug("Blocks {} & {} are Occupied", getLayoutBlock(), getBlockCName());
1989                    return true;
1990                }
1991            }
1992        }
1993        return false;
1994    }
1995
1996    // initialization instance variables (used when loading a LayoutEditor)
1997    public String connectAName = "";
1998    public String connectBName = "";
1999    public String connectCName = "";
2000    public String connectDName = "";
2001
2002    public String tBlockAName = "";
2003    public String tBlockBName = "";
2004    public String tBlockCName = "";
2005    public String tBlockDName = "";
2006
2007    /**
2008     * Initialization method. The above variables are initialized by
2009     * LayoutTurnoutXml, then the following method is called after the entire
2010     * LayoutEditor is loaded to set the specific TrackSegment objects.
2011     */
2012    @Override
2013    public void setObjects(@Nonnull LayoutEditor p) {
2014        connectA = p.getFinder().findTrackSegmentByName(connectAName);
2015        connectB = p.getFinder().findTrackSegmentByName(connectBName);
2016        connectC = p.getFinder().findTrackSegmentByName(connectCName);
2017        connectD = p.getFinder().findTrackSegmentByName(connectDName);
2018
2019        LayoutBlock lb;
2020        if (!tBlockAName.isEmpty()) {
2021            lb = p.provideLayoutBlock(tBlockAName);
2022            if (lb != null) {
2023                String userName = lb.getUserName();
2024                if (userName != null) {
2025                    namedLayoutBlockA = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(userName, lb);
2026                    lb.incrementUse();
2027                }
2028            } else {
2029                log.error("{}.setObjects(...); bad blockname A '{}'", getName(), tBlockAName);
2030                namedLayoutBlockA = null;
2031            }
2032            tBlockAName = null; // release this memory
2033        }
2034
2035        if (!tBlockBName.isEmpty()) {
2036            lb = p.provideLayoutBlock(tBlockBName);
2037            if (lb != null) {
2038                String userName = lb.getUserName();
2039                if (userName != null) {
2040                    namedLayoutBlockB = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(userName, lb);
2041                }
2042                if (namedLayoutBlockB != namedLayoutBlockA) {
2043                    lb.incrementUse();
2044                }
2045            } else {
2046                log.error("{}.setObjects(...); bad blockname B '{}'", getName(), tBlockBName);
2047                namedLayoutBlockB = null;
2048            }
2049            tBlockBName = null; // release this memory
2050        }
2051
2052        if (!tBlockCName.isEmpty()) {
2053            lb = p.provideLayoutBlock(tBlockCName);
2054            if (lb != null) {
2055                String userName = lb.getUserName();
2056                if (userName != null) {
2057                    namedLayoutBlockC = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(userName, lb);
2058                }
2059                if ((namedLayoutBlockC != namedLayoutBlockA)
2060                        && (namedLayoutBlockC != namedLayoutBlockB)) {
2061                    lb.incrementUse();
2062                }
2063            } else {
2064                log.error("{}.setObjects(...); bad blockname C '{}'", getName(), tBlockCName);
2065                namedLayoutBlockC = null;
2066            }
2067            tBlockCName = null; // release this memory
2068        }
2069
2070        if (!tBlockDName.isEmpty()) {
2071            lb = p.provideLayoutBlock(tBlockDName);
2072            if (lb != null) {
2073                String userName = lb.getUserName();
2074                if (userName != null) {
2075                    namedLayoutBlockD = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(userName, lb);
2076                }
2077                if ((namedLayoutBlockD != namedLayoutBlockA)
2078                        && (namedLayoutBlockD != namedLayoutBlockB)
2079                        && (namedLayoutBlockD != namedLayoutBlockC)) {
2080                    lb.incrementUse();
2081                }
2082            } else {
2083                log.error("{}.setObjects(...); bad blockname D '{}'", getName(), tBlockDName);
2084                namedLayoutBlockD = null;
2085            }
2086            tBlockDName = null; // release this memory
2087        }
2088        activateTurnout();
2089    } // setObjects
2090
2091    public String[] getBlockBoundaries() {
2092        final String[] boundaryBetween = new String[4];
2093        if (isTurnoutTypeTurnout()) {
2094            // This should only be needed where we are looking at a single turnout.
2095            if (getLayoutBlock() != null) {
2096                LayoutBlock aLBlock = null;
2097                if (connectA instanceof TrackSegment) {
2098                    aLBlock = ((TrackSegment) connectA).getLayoutBlock();
2099                    if (aLBlock != getLayoutBlock()) {
2100                        try {
2101                            boundaryBetween[0] = (aLBlock.getDisplayName() + " - " + getLayoutBlock().getDisplayName());
2102                        } catch (java.lang.NullPointerException e) {
2103                            // Can be considered normal if tracksegement hasn't yet been allocated a block
2104                            log.debug("TrackSegement at connection A doesn't contain a layout block");
2105                        }
2106                    }
2107                }
2108
2109                LayoutBlock bLBlock = null;
2110                if (connectB instanceof TrackSegment) {
2111                    bLBlock = ((TrackSegment) connectB).getLayoutBlock();
2112                    if (bLBlock != getLayoutBlock()) {
2113                        try {
2114                            boundaryBetween[1] = (bLBlock.getDisplayName() + " - " + getLayoutBlock().getDisplayName());
2115                        } catch (java.lang.NullPointerException e) {
2116                            // Can be considered normal if tracksegement hasn't yet been allocated a block
2117                            log.debug("TrackSegement at connection B doesn't contain a layout block");
2118                        }
2119                    }
2120                }
2121
2122                LayoutBlock cLBlock = null;
2123                if ((connectC instanceof TrackSegment)
2124                        && (((TrackSegment) connectC).getLayoutBlock() != getLayoutBlock())) {
2125                    cLBlock = ((TrackSegment) connectC).getLayoutBlock();
2126                    if (cLBlock != getLayoutBlock()) {
2127                        try {
2128                            boundaryBetween[2] = (cLBlock.getDisplayName() + " - " + getLayoutBlock().getDisplayName());
2129                        } catch (java.lang.NullPointerException e) {
2130                            // Can be considered normal if tracksegement hasn't yet been allocated a block
2131                            log.debug("TrackSegement at connection C doesn't contain a layout block");
2132                        }
2133                    }
2134                }
2135            }
2136        } else {
2137            LayoutBlock aLBlock = null;
2138            LayoutBlock bLBlock = null;
2139            LayoutBlock cLBlock = null;
2140            LayoutBlock dLBlock = null;
2141            if (getLayoutBlock() != null) {
2142                if (connectA instanceof TrackSegment) {
2143                    aLBlock = ((TrackSegment) connectA).getLayoutBlock();
2144                    if (aLBlock != getLayoutBlock()) {
2145                        try {
2146                            boundaryBetween[0] = (aLBlock.getDisplayName() + " - " + getLayoutBlock().getDisplayName());
2147                        } catch (java.lang.NullPointerException e) {
2148                            // Can be considered normal if tracksegement hasn't yet been allocated a block
2149                            log.debug("TrackSegement at connection A doesn't contain a layout block");
2150                        }
2151                    } else if (getLayoutBlock() != getLayoutBlockB()) {
2152                        try {
2153                            boundaryBetween[0] = (getLayoutBlock().getDisplayName() + " - " + getLayoutBlockB().getDisplayName());
2154                        } catch (java.lang.NullPointerException e) {
2155                            // Can be considered normal if tracksegement hasn't yet been allocated a block
2156                            log.debug("TrackSegement at connection A doesn't contain a layout block");
2157                        }
2158                    }
2159                }
2160
2161                if (connectB instanceof TrackSegment) {
2162                    bLBlock = ((TrackSegment) connectB).getLayoutBlock();
2163
2164                    if (bLBlock != getLayoutBlock() && bLBlock != getLayoutBlockB()) {
2165                        try {
2166                            boundaryBetween[1] = (bLBlock.getDisplayName() + " - " + getLayoutBlockB().getDisplayName());
2167                        } catch (java.lang.NullPointerException e) {
2168                            // Can be considered normal if tracksegement hasn't yet been allocated a block
2169                            log.debug("TrackSegement at connection B doesn't contain a layout block");
2170                        }
2171                    } else if (getLayoutBlock() != getLayoutBlockB()) {
2172                        // This is an interal block on the turnout
2173                        try {
2174                            boundaryBetween[1] = (getLayoutBlockB().getDisplayName() + " - " + getLayoutBlock().getDisplayName());
2175                        } catch (java.lang.NullPointerException e) {
2176                            // Can be considered normal if tracksegement hasn't yet been allocated a block
2177                            log.debug("TrackSegement at connection A doesn't contain a layout block");
2178                        }
2179                    }
2180                }
2181
2182                if (connectC instanceof TrackSegment) {
2183                    cLBlock = ((TrackSegment) connectC).getLayoutBlock();
2184                    if (cLBlock != getLayoutBlock() && cLBlock != getLayoutBlockB() && cLBlock != getLayoutBlockC()) {
2185                        try {
2186                            boundaryBetween[2] = (cLBlock.getDisplayName() + " - " + getLayoutBlockC().getDisplayName());
2187                        } catch (java.lang.NullPointerException e) {
2188                            // Can be considered normal if tracksegement hasn't yet been allocated a block
2189                            log.debug("TrackSegement at connection C doesn't contain a layout block");
2190                        }
2191                    } else if (getLayoutBlockC() != getLayoutBlockD()) {
2192                        // This is an interal block on the turnout
2193                        try {
2194                            boundaryBetween[2] = (getLayoutBlockC().getDisplayName() + " - " + getLayoutBlockD().getDisplayName());
2195                        } catch (java.lang.NullPointerException e) {
2196                            // Can be considered normal if tracksegement hasn't yet been allocated a block
2197                            log.debug("TrackSegement at connection A doesn't contain a layout block");
2198                        }
2199                    }
2200                }
2201
2202                if (connectD instanceof TrackSegment) {
2203                    dLBlock = ((TrackSegment) connectD).getLayoutBlock();
2204                    if (dLBlock != getLayoutBlock() && dLBlock != getLayoutBlockB() && dLBlock != getLayoutBlockC() && dLBlock != getLayoutBlockD()) {
2205                        try {
2206                            boundaryBetween[3] = (dLBlock.getDisplayName() + " - " + getLayoutBlockD().getDisplayName());
2207                        } catch (java.lang.NullPointerException e) {
2208                            // Can be considered normal if tracksegement hasn't yet been allocated a block
2209                            log.debug("TrackSegement at connection C doesn't contain a layout block");
2210                        }
2211                    } else if (getLayoutBlockC() != getLayoutBlockD()) {
2212                        // This is an interal block on the turnout
2213                        try {
2214                            boundaryBetween[3] = (getLayoutBlockD().getDisplayName() + " - " + getLayoutBlockC().getDisplayName());
2215                        } catch (java.lang.NullPointerException e) {
2216                            // Can be considered normal if tracksegement hasn't yet been allocated a block
2217                            log.debug("TrackSegement at connection A doesn't contain a layout block");
2218                        }
2219                    }
2220                }
2221            }
2222
2223        }
2224        return boundaryBetween;
2225    }   // getBlockBoundaries
2226
2227    @Nonnull
2228    public ArrayList<LayoutBlock> getProtectedBlocks(jmri.NamedBean bean) {
2229        ArrayList<LayoutBlock> ret = new ArrayList<>(2);
2230        if (getLayoutBlock() == null) {
2231            return ret;
2232        }
2233        if (isTurnoutTypeXover()) {
2234            if ((getTurnoutType() == TurnoutType.DOUBLE_XOVER || getTurnoutType() == TurnoutType.RH_XOVER)
2235                    && (getSignalAMast() == bean || getSignalCMast() == bean || getSensorA() == bean || getSensorC() == bean)) {
2236                if (getSignalAMast() == bean || getSensorA() == bean) {
2237                    if (connectA != null) {
2238                        if (((TrackSegment) connectA).getLayoutBlock() == getLayoutBlock()) {
2239                            if (getLayoutBlockB() != null && getLayoutBlock() != getLayoutBlockB() && getLayoutBlockC() != null && getLayoutBlock() != getLayoutBlockC()) {
2240                                ret.add(getLayoutBlockB());
2241                                ret.add(getLayoutBlockC());
2242                            }
2243                        } else {
2244                            ret.add(getLayoutBlock());
2245                        }
2246                    }
2247                } else {
2248                    if (connectC != null && getLayoutBlockC() != null) {
2249                        if (((TrackSegment) connectC).getLayoutBlock() == getLayoutBlockC()) {
2250                            if (getLayoutBlockC() != getLayoutBlock() && getLayoutBlockD() != null && getLayoutBlockC() != getLayoutBlockD()) {
2251                                ret.add(getLayoutBlock());
2252                                ret.add(getLayoutBlockD());
2253                            }
2254                        } else {
2255                            ret.add(getLayoutBlockC());
2256                        }
2257                    }
2258                }
2259            }
2260            if ((getTurnoutType() == TurnoutType.DOUBLE_XOVER || getTurnoutType() == TurnoutType.LH_XOVER)
2261                    && (getSignalBMast() == bean || getSignalDMast() == bean || getSensorB() == bean || getSensorD() == bean)) {
2262                if (getSignalBMast() == bean || getSensorB() == bean) {
2263                    if (connectB != null && getLayoutBlockB() != null) {
2264                        if (((TrackSegment) connectB).getLayoutBlock() == getLayoutBlockB()) {
2265                            if (getLayoutBlock() != getLayoutBlockB() && getLayoutBlockD() != null && getLayoutBlockB() != getLayoutBlockD()) {
2266                                ret.add(getLayoutBlock());
2267                                ret.add(getLayoutBlockD());
2268                            }
2269                        } else {
2270                            ret.add(getLayoutBlockB());
2271                        }
2272                    }
2273                } else {
2274                    if (connectD != null && getLayoutBlockD() != null) {
2275                        if (((TrackSegment) connectD).getLayoutBlock() == getLayoutBlockD()) {
2276                            if (getLayoutBlockB() != null && getLayoutBlockB() != getLayoutBlockD() && getLayoutBlockC() != null && getLayoutBlockC() != getLayoutBlockD()) {
2277                                ret.add(getLayoutBlockB());
2278                                ret.add(getLayoutBlockC());
2279                            }
2280                        } else {
2281                            ret.add(getLayoutBlockD());
2282                        }
2283                    }
2284                }
2285            }
2286            if (getTurnoutType() == TurnoutType.RH_XOVER && (getSignalBMast() == bean
2287                    || getSignalDMast() == bean || getSensorB() == bean || getSensorD() == bean)) {
2288                if (getSignalBMast() == bean || getSensorB() == bean) {
2289                    if (connectB != null && ((TrackSegment) connectB).getLayoutBlock() == getLayoutBlockB()) {
2290                        if (getLayoutBlockB() != getLayoutBlock()) {
2291                            ret.add(getLayoutBlock());
2292                        }
2293                    } else {
2294                        ret.add(getLayoutBlockB());
2295                    }
2296                } else {
2297                    if (connectD != null && ((TrackSegment) connectD).getLayoutBlock() == getLayoutBlockD()) {
2298                        if (getLayoutBlockC() != getLayoutBlockD()) {
2299                            ret.add(getLayoutBlockC());
2300                        }
2301                    } else {
2302                        ret.add(getLayoutBlockD());
2303                    }
2304                }
2305            }
2306            if (getTurnoutType() == TurnoutType.LH_XOVER && (getSensorA() == bean
2307                    || getSensorC() == bean || getSignalAMast() == bean || getSignalCMast() == bean)) {
2308                if (getSignalAMast() == bean || getSensorA() == bean) {
2309                    if (connectA != null && ((TrackSegment) connectA).getLayoutBlock() == getLayoutBlock()) {
2310                        if (getLayoutBlockB() != getLayoutBlock()) {
2311                            ret.add(getLayoutBlockB());
2312                        }
2313                    } else {
2314                        ret.add(getLayoutBlock());
2315                    }
2316                } else {
2317                    if (connectC != null && ((TrackSegment) connectC).getLayoutBlock() == getLayoutBlockC()) {
2318                        if (getLayoutBlockC() != getLayoutBlockD()) {
2319                            ret.add(getLayoutBlockD());
2320                        }
2321                    } else {
2322                        ret.add(getLayoutBlockC());
2323                    }
2324                }
2325            }
2326        } else {
2327            if (connectA != null) {
2328                if (getSignalAMast() == bean || getSensorA() == bean) {
2329                    // Mast at throat
2330                    // if the turnout is in the same block as the segment connected at the throat, then we can be protecting two blocks
2331                    if (((TrackSegment) connectA).getLayoutBlock() == getLayoutBlock()) {
2332                        if (connectB != null && connectC != null) {
2333                            if (((TrackSegment) connectB).getLayoutBlock() != getLayoutBlock()
2334                                    && ((TrackSegment) connectC).getLayoutBlock() != getLayoutBlock()) {
2335                                ret.add(((TrackSegment) connectB).getLayoutBlock());
2336                                ret.add(((TrackSegment) connectC).getLayoutBlock());
2337                            }
2338                        }
2339                    } else {
2340                        ret.add(getLayoutBlock());
2341                    }
2342                } else if (getSignalBMast() == bean || getSensorB() == bean) {
2343                    // Mast at Continuing
2344                    if (connectB != null && ((TrackSegment) connectB).getLayoutBlock() == getLayoutBlock()) {
2345                        if (((TrackSegment) connectA).getLayoutBlock() != getLayoutBlock()) {
2346                            ret.add(((TrackSegment) connectA).getLayoutBlock());
2347                        }
2348                    } else {
2349                        ret.add(getLayoutBlock());
2350                    }
2351                } else if (getSignalCMast() == bean || getSensorC() == bean) {
2352                    // Mast at Diverging
2353                    if (connectC != null && ((TrackSegment) connectC).getLayoutBlock() == getLayoutBlock()) {
2354                        if (((TrackSegment) connectA).getLayoutBlock() != getLayoutBlock()) {
2355                            ret.add(((TrackSegment) connectA).getLayoutBlock());
2356                        }
2357                    } else {
2358                        ret.add(getLayoutBlock());
2359                    }
2360                }
2361            }
2362        }
2363        return ret;
2364    }   // getProtectedBlocks
2365
2366    protected void removeSML(@CheckForNull SignalMast signalMast) {
2367        if (signalMast == null) {
2368            return;
2369
2370        }
2371        if (jmri.InstanceManager.getDefault(LayoutBlockManager.class).isAdvancedRoutingEnabled()
2372                && InstanceManager.getDefault(jmri.SignalMastLogicManager.class).isSignalMastUsed(signalMast)) {
2373
2374            SignallingGuiTools.removeSignalMastLogic(null, signalMast);
2375        }
2376    }
2377
2378    /**
2379     * Remove this object from display and persistance.
2380     */
2381    public void remove() {
2382        // if a turnout has been activated, deactivate it
2383        deactivateTurnout();
2384        // remove from persistance by flagging inactive
2385        active = false;
2386    }
2387
2388    boolean active = true;
2389
2390    /**
2391     * "active" means that the object is still displayed, and should be stored.
2392     * @return true if active, else false.
2393     */
2394    public boolean isActive() {
2395        return active;
2396    }
2397
2398
2399    /*
2400    * Used by ConnectivityUtil to determine the turnout state necessary to get
2401    * from prevLayoutBlock ==> currLayoutBlock ==> nextLayoutBlock
2402     */
2403    protected int getConnectivityStateForLayoutBlocks(
2404            LayoutBlock currLayoutBlock,
2405            LayoutBlock prevLayoutBlock,
2406            LayoutBlock nextLayoutBlock,
2407            boolean suppress) {
2408        int result = UNKNOWN;
2409
2410        LayoutBlock layoutBlockA = ((TrackSegment) getConnectA()).getLayoutBlock();
2411        LayoutBlock layoutBlockB = ((TrackSegment) getConnectB()).getLayoutBlock();
2412        LayoutBlock layoutBlockC = ((TrackSegment) getConnectC()).getLayoutBlock();
2413        // TODO: Determine if this should be being used
2414        // LayoutBlock layoutBlockD = ((TrackSegment) getConnectD()).getLayoutBlock();
2415
2416        TurnoutType tTyp = getTurnoutType();
2417        switch (tTyp) {
2418            case RH_TURNOUT:
2419            case LH_TURNOUT:
2420            case WYE_TURNOUT: {
2421                if (layoutBlockA == currLayoutBlock) {
2422                    if ((layoutBlockC == nextLayoutBlock) || (layoutBlockC == prevLayoutBlock)) {
2423                        result = Turnout.THROWN;
2424                    } else if ((layoutBlockB == nextLayoutBlock) || (layoutBlockB == prevLayoutBlock)) {
2425                        result = Turnout.CLOSED;
2426                    } else if (layoutBlockB == currLayoutBlock) {
2427                        result = Turnout.CLOSED;
2428                    } else if (layoutBlockC == currLayoutBlock) {
2429                        result = Turnout.THROWN;
2430                    } else {
2431                        if (!suppress) {
2432                            log.error("{}.getConnectivityStateForLayoutBlocks(...); Cannot determine turnout setting for {}",
2433                                    getName(), getTurnoutName());
2434                        }
2435                        result = Turnout.CLOSED;
2436                    }
2437                } else if (layoutBlockB == currLayoutBlock) {
2438                    result = Turnout.CLOSED;
2439                } else if (layoutBlockC == currLayoutBlock) {
2440                    result = Turnout.THROWN;
2441                } else {
2442                    if (!suppress) {
2443                        log.debug("lb {} nlb {} connect B {} connect C {}", currLayoutBlock, nextLayoutBlock, layoutBlockB, layoutBlockC);
2444                        log.error("{}.getConnectivityStateForLayoutBlocks(...); Cannot determine turnout setting for {}",
2445                                getName(), getTurnoutName());
2446                    }
2447                    result = Turnout.CLOSED;
2448                }
2449                break;
2450            }
2451            case RH_XOVER:
2452            case LH_XOVER:
2453            case DOUBLE_XOVER: {
2454                if (getLayoutBlock() == currLayoutBlock) {
2455                    if ((tTyp != TurnoutType.LH_XOVER)
2456                            && ((getLayoutBlockC() == nextLayoutBlock)
2457                            || (getLayoutBlockC() == prevLayoutBlock))) {
2458                        result = Turnout.THROWN;
2459                    } else if ((getLayoutBlockB() == nextLayoutBlock) || (getLayoutBlockB() == prevLayoutBlock)) {
2460                        result = Turnout.CLOSED;
2461                    } else if (getLayoutBlockB() == currLayoutBlock) {
2462                        result = Turnout.CLOSED;
2463                    } else if ((tTyp != LayoutTurnout.TurnoutType.LH_XOVER)
2464                            && (getLayoutBlockC() == currLayoutBlock)) {
2465                        result = Turnout.THROWN;
2466                    } else {
2467                        if (!suppress) {
2468                            log.error("{}.getConnectivityStateForLayoutBlocks(...); Cannot determine turnout setting for {}",
2469                                    getName(), getTurnoutName());
2470                        }
2471                        result = Turnout.CLOSED;
2472                    }
2473                } else if (getLayoutBlockB() == currLayoutBlock) {
2474                    if ((getLayoutBlock() == nextLayoutBlock) || (getLayoutBlock() == prevLayoutBlock)) {
2475                        result = Turnout.CLOSED;
2476                    } else if ((tTyp != TurnoutType.RH_XOVER)
2477                            && ((getLayoutBlockD() == nextLayoutBlock)
2478                            || (getLayoutBlockD() == prevLayoutBlock) || (getLayoutBlockD() == currLayoutBlock))) {
2479                        result = Turnout.THROWN;
2480                    } else {
2481                        if (!suppress) {
2482                            log.error("{}.getConnectivityStateForLayoutBlocks(...); Cannot determine turnout setting for {}",
2483                                    getName(), getTurnoutName());
2484                        }
2485                        result = Turnout.CLOSED;
2486                    }
2487                } else if (getLayoutBlockC() == currLayoutBlock) {
2488                    if ((tTyp != TurnoutType.LH_XOVER)
2489                            && ((getLayoutBlock() == nextLayoutBlock) || (getLayoutBlock() == prevLayoutBlock))) {
2490                        result = Turnout.THROWN;
2491                    } else if ((getLayoutBlockD() == nextLayoutBlock) || (getLayoutBlockD() == prevLayoutBlock) || (getLayoutBlockD() == currLayoutBlock)) {
2492                        result = Turnout.CLOSED;
2493                    } else if ((tTyp != TurnoutType.LH_XOVER)
2494                            && (getLayoutBlockD() == currLayoutBlock)) {
2495                        result = Turnout.THROWN;
2496                    } else {
2497                        if (!suppress) {
2498                            log.error("{}.getConnectivityStateForLayoutBlocks(...); Cannot determine turnout setting for {}",
2499                                    getName(), getTurnoutName());
2500                        }
2501                        result = Turnout.CLOSED;
2502                    }
2503                } else if (getLayoutBlockD() == currLayoutBlock) {
2504                    if ((getLayoutBlockC() == nextLayoutBlock) || (getLayoutBlockC() == prevLayoutBlock)) {
2505                        result = Turnout.CLOSED;
2506                    } else if ((tTyp != TurnoutType.RH_XOVER)
2507                            && ((getLayoutBlockB() == nextLayoutBlock) || (getLayoutBlockB() == prevLayoutBlock))) {
2508                        result = Turnout.THROWN;
2509                    } else {
2510                        if (!suppress) {
2511                            log.error("{}.getConnectivityStateForLayoutBlocks(...); Cannot determine turnout setting for {}",
2512                                    getName(), getTurnoutName());
2513                        }
2514                        result = Turnout.CLOSED;
2515                    }
2516                }
2517                break;
2518            }
2519            default: {
2520                log.warn("{}.getConnectivityStateForLayoutBlocks(...) unknown getTurnoutType: {}", getName(), tTyp);
2521                break;
2522            }
2523        }   // switch (tTyp)
2524
2525        return result;
2526    }   // getConnectivityStateForLayoutBlocks
2527
2528    /**
2529     * {@inheritDoc}
2530     */
2531    // TODO: on the cross-overs, check the internal boundary details.
2532    @Override
2533    public void reCheckBlockBoundary() {
2534        if (connectA == null && connectB == null && connectC == null) {
2535            if (isTurnoutTypeTurnout()) {
2536                if (signalAMastNamed != null) {
2537                    removeSML(getSignalAMast());
2538                }
2539                if (signalBMastNamed != null) {
2540                    removeSML(getSignalBMast());
2541                }
2542                if (signalCMastNamed != null) {
2543                    removeSML(getSignalCMast());
2544                }
2545                signalAMastNamed = null;
2546                signalBMastNamed = null;
2547                signalCMastNamed = null;
2548                sensorANamed = null;
2549                sensorBNamed = null;
2550                sensorCNamed = null;
2551                return;
2552            } else if (isTurnoutTypeXover() && connectD == null) {
2553                if (signalAMastNamed != null) {
2554                    removeSML(getSignalAMast());
2555                }
2556                if (signalBMastNamed != null) {
2557                    removeSML(getSignalBMast());
2558                }
2559                if (signalCMastNamed != null) {
2560                    removeSML(getSignalCMast());
2561                }
2562                if (signalDMastNamed != null) {
2563                    removeSML(getSignalDMast());
2564                }
2565                signalAMastNamed = null;
2566                signalBMastNamed = null;
2567                signalCMastNamed = null;
2568                signalDMastNamed = null;
2569                sensorANamed = null;
2570                sensorBNamed = null;
2571                sensorCNamed = null;
2572                sensorDNamed = null;
2573                return;
2574            }
2575        }
2576
2577        if (connectA == null || connectB == null || connectC == null) {
2578            // could still be in the process of rebuilding.
2579            return;
2580        } else if ((connectD == null) && isTurnoutTypeXover()) {
2581            // could still be in the process of rebuilding.
2582            return;
2583        }
2584
2585        TrackSegment trkA;
2586        TrackSegment trkB;
2587        TrackSegment trkC;
2588        TrackSegment trkD;
2589
2590        if (connectA instanceof TrackSegment) {
2591            trkA = (TrackSegment) connectA;
2592            if (trkA.getLayoutBlock() == getLayoutBlock()) {
2593                if (signalAMastNamed != null) {
2594                    removeSML(getSignalAMast());
2595                }
2596                signalAMastNamed = null;
2597                sensorANamed = null;
2598            }
2599        }
2600        if (connectB instanceof TrackSegment) {
2601            trkB = (TrackSegment) connectB;
2602            if (trkB.getLayoutBlock() == getLayoutBlock() || trkB.getLayoutBlock() == getLayoutBlockB()) {
2603                if (signalBMastNamed != null) {
2604                    removeSML(getSignalBMast());
2605                }
2606                signalBMastNamed = null;
2607                sensorBNamed = null;
2608
2609            }
2610        }
2611        if (connectC instanceof TrackSegment) {
2612            trkC = (TrackSegment) connectC;
2613            if (trkC.getLayoutBlock() == getLayoutBlock()
2614                    || trkC.getLayoutBlock() == getLayoutBlockB()
2615                    || trkC.getLayoutBlock() == getLayoutBlockC()) {
2616                if (signalCMastNamed != null) {
2617                    removeSML(getSignalCMast());
2618                }
2619                signalCMastNamed = null;
2620                sensorCNamed = null;
2621
2622            }
2623        }
2624        if (connectD != null && connectD instanceof TrackSegment
2625                && isTurnoutTypeXover()) {
2626            trkD = (TrackSegment) connectD;
2627            if (trkD.getLayoutBlock() == getLayoutBlock()
2628                    || trkD.getLayoutBlock() == getLayoutBlockB()
2629                    || trkD.getLayoutBlock() == getLayoutBlockC()
2630                    || trkD.getLayoutBlock() == getLayoutBlockD()) {
2631                if (signalDMastNamed != null) {
2632                    removeSML(getSignalDMast());
2633                }
2634                signalDMastNamed = null;
2635                sensorDNamed = null;
2636            }
2637        }
2638    }   // reCheckBlockBoundary
2639
2640    /**
2641     * {@inheritDoc}
2642     */
2643    @Override
2644    @Nonnull
2645    protected List<LayoutConnectivity> getLayoutConnectivity() {
2646        List<LayoutConnectivity> results = new ArrayList<>();
2647
2648        log.trace("Start in layoutTurnout.getLayoutConnectivity for {}", getName());
2649
2650        LayoutConnectivity lc = null;
2651
2652        LayoutBlock lbA = getLayoutBlock(), lbB = getLayoutBlockB(), lbC = getLayoutBlockC(), lbD = getLayoutBlockD();
2653
2654        log.trace("    type: {}", type);
2655        log.trace("     lbA: {}", lbA);
2656        log.trace("     lbB: {}", lbB);
2657        log.trace("     lbC: {}", lbC);
2658        log.trace("     lbD: {}", lbD);
2659
2660        if (hasEnteringDoubleTrack() && (lbA != null)) {
2661            // have a crossover turnout with at least one block, check for multiple blocks
2662            if ((lbA != lbB) || (lbA != lbC) || (lbA != lbD)) {
2663                // have multiple blocks and therefore internal block boundaries
2664                if (lbA != lbB) {
2665                    // have a AB block boundary, create a LayoutConnectivity
2666                    log.debug("Block boundary  ('{}'<->'{}') found at {}", lbA, lbB, this);
2667                    lc = new LayoutConnectivity(lbA, lbB);
2668                    lc.setXoverBoundary(this, LayoutConnectivity.XOVER_BOUNDARY_AB);
2669
2670                    // The following line needed to change, because it uses location of
2671                    // the points on the TurnoutView itself. Change to
2672                    // direction from connections.
2673                    //lc.setDirection(Path.computeDirection(getCoordsA(), getCoordsB()));
2674                    lc.setDirection(models.computeDirectionAB(this));
2675
2676                    log.trace("getLayoutConnectivity lbA != lbB");
2677                    log.trace("   Block boundary  ('{}'<->'{}') found at {}", lbA, lbB, this);
2678
2679                    results.add(lc);
2680                }
2681                if ((getTurnoutType() != TurnoutType.LH_XOVER) && (lbA != lbC)) {
2682                    // have a AC block boundary, create a LayoutConnectivity
2683                    log.debug("Block boundary  ('{}'<->'{}') found at {}", lbA, lbC, this);
2684                    lc = new LayoutConnectivity(lbA, lbC);
2685                    lc.setXoverBoundary(this, LayoutConnectivity.XOVER_BOUNDARY_AC);
2686
2687                    // The following line needed to change, because it uses location of
2688                    // the points on the TurnoutView itself. Change to
2689                    // direction from connections.
2690                    //lc.setDirection(Path.computeDirection(getCoordsA(), getCoordsC()));
2691                    lc.setDirection(models.computeDirectionAC(this));
2692
2693                    log.trace("getLayoutConnectivity lbA != lbC");
2694                    log.trace("   Block boundary  ('{}'<->'{}') found at {}", lbA, lbC, this);
2695
2696                    results.add(lc);
2697                }
2698                if (lbC != lbD) {
2699                    // have a CD block boundary, create a LayoutConnectivity
2700                    log.debug("Block boundary  ('{}'<->'{}') found at {}", lbC, lbD, this);
2701                    lc = new LayoutConnectivity(lbC, lbD);
2702                    lc.setXoverBoundary(this, LayoutConnectivity.XOVER_BOUNDARY_CD);
2703
2704                    // The following line needed to change, because it uses location of
2705                    // the points on the TurnoutView itself. Change to
2706                    // direction from connections.
2707                    //lc.setDirection(Path.computeDirection(getCoordsC(), getCoordsD()));
2708                    lc.setDirection(models.computeDirectionCD(this));
2709
2710                    log.trace("getLayoutConnectivity lbC != lbD");
2711                    log.trace("   Block boundary  ('{}'<->'{}') found at {}", lbC, lbD, this);
2712
2713                    results.add(lc);
2714                }
2715                if ((getTurnoutType() != TurnoutType.RH_XOVER) && (lbB != lbD)) {
2716                    // have a BD block boundary, create a LayoutConnectivity
2717                    log.debug("Block boundary  ('{}'<->'{}') found at {}", lbB, lbD, this);
2718                    lc = new LayoutConnectivity(lbB, lbD);
2719                    lc.setXoverBoundary(this, LayoutConnectivity.XOVER_BOUNDARY_BD);
2720
2721                    // The following line needed to change, because it uses location of
2722                    // the points on the TurnoutView itself. Change to
2723                    // direction from connections.
2724                    //lc.setDirection(Path.computeDirection(getCoordsB(), getCoordsD()));
2725                    lc.setDirection(models.computeDirectionBD(this));
2726
2727                    log.trace("getLayoutConnectivity lbB != lbD");
2728                    log.trace("   Block boundary  ('{}'<->'{}') found at {}", lbB, lbD, this);
2729
2730                    results.add(lc);
2731                }
2732            }
2733        }
2734        return results;
2735    }
2736
2737    /**
2738     * {@inheritDoc}
2739     */
2740    @Override
2741    @Nonnull
2742    public List<HitPointType> checkForFreeConnections() {
2743        List<HitPointType> result = new ArrayList<>();
2744
2745        // check the A connection point
2746        if (getConnectA() == null) {
2747            result.add(HitPointType.TURNOUT_A);
2748        }
2749
2750        // check the B connection point
2751        if (getConnectB() == null) {
2752            result.add(HitPointType.TURNOUT_B);
2753        }
2754
2755        // check the C connection point
2756        if (getConnectC() == null) {
2757            result.add(HitPointType.TURNOUT_C);
2758        }
2759
2760        // check the D connection point
2761        if (isTurnoutTypeXover()) {
2762            if (getConnectD() == null) {
2763                result.add(HitPointType.TURNOUT_D);
2764            }
2765        }
2766        return result;
2767    }
2768
2769    /**
2770     * {@inheritDoc}
2771     */
2772    @Override
2773    public boolean checkForUnAssignedBlocks() {
2774        // because getLayoutBlock[BCD] will return block [A] if they're null
2775        // we only need to test block [A]
2776        return (getLayoutBlock() != null);
2777    }
2778
2779    /**
2780     * {@inheritDoc}
2781     */
2782    @Override
2783    public void checkForNonContiguousBlocks(
2784            @Nonnull HashMap<String, List<Set<String>>> blockNamesToTrackNameSetsMap) {
2785        /*
2786                * For each (non-null) blocks of this track do:
2787                * #1) If it's got an entry in the blockNamesToTrackNameSetMap then
2788                * #2) If this track is already in the TrackNameSet for this block
2789                *     then return (done!)
2790                * #3) else add a new set (with this block/track) to
2791                *     blockNamesToTrackNameSetMap and check all the connections in this
2792                *     block (by calling the 2nd method below)
2793                * <p>
2794                *     Basically, we're maintaining contiguous track sets for each block found
2795                *     (in blockNamesToTrackNameSetMap)
2796         */
2797
2798        // We're only using a map here because it's convient to
2799        // use it to pair up blocks and connections
2800        Map<LayoutTrack, String> blocksAndTracksMap = new HashMap<>();
2801        if (connectA != null) {
2802            blocksAndTracksMap.put(connectA, getBlockName());
2803        }
2804        if (connectB != null) {
2805            blocksAndTracksMap.put(connectB, getBlockBName());
2806        }
2807        if (connectC != null) {
2808            blocksAndTracksMap.put(connectC, getBlockCName());
2809        }
2810        if (isTurnoutTypeXover() || isTurnoutTypeSlip()) {
2811            if (connectD != null) {
2812                blocksAndTracksMap.put(connectD, getBlockDName());
2813            }
2814        }
2815        List<Set<String>> TrackNameSets = null;
2816        Set<String> TrackNameSet = null;
2817        for (Map.Entry<LayoutTrack, String> entry : blocksAndTracksMap.entrySet()) {
2818            LayoutTrack theConnect = entry.getKey();
2819            String theBlockName = entry.getValue();
2820
2821            TrackNameSet = null;    // assume not found (pessimist!)
2822            TrackNameSets = blockNamesToTrackNameSetsMap.get(theBlockName);
2823            if (TrackNameSets != null) { // (#1)
2824                for (Set<String> checkTrackNameSet : TrackNameSets) {
2825                    if (checkTrackNameSet.contains(getName())) { // (#2)
2826                        TrackNameSet = checkTrackNameSet;
2827                        break;
2828                    }
2829                }
2830            } else {    // (#3)
2831                log.debug("*New block ('{}') trackNameSets", theBlockName);
2832                TrackNameSets = new ArrayList<>();
2833                blockNamesToTrackNameSetsMap.put(theBlockName, TrackNameSets);
2834            }
2835            if (TrackNameSet == null) {
2836                TrackNameSet = new LinkedHashSet<>();
2837                TrackNameSets.add(TrackNameSet);
2838            }
2839            if (TrackNameSet.add(getName())) {
2840                log.debug("*    Add track '{}' to trackNameSet for block '{}'", getName(), theBlockName);
2841            }
2842            theConnect.collectContiguousTracksNamesInBlockNamed(theBlockName, TrackNameSet);
2843        }
2844    }   // collectContiguousTracksNamesInBlockNamed
2845
2846    /**
2847     * {@inheritDoc}
2848     */
2849    @Override
2850    public void collectContiguousTracksNamesInBlockNamed(
2851            @Nonnull String blockName,
2852            @Nonnull Set<String> TrackNameSet) {
2853        if (!TrackNameSet.contains(getName())) {
2854
2855            // create list of our connects
2856            List<LayoutTrack> connects = new ArrayList<>();
2857            if (getBlockName().equals(blockName)
2858                    && (connectA != null)) {
2859                connects.add(connectA);
2860            }
2861            if (getBlockBName().equals(blockName)
2862                    && (connectB != null)) {
2863                connects.add(connectB);
2864            }
2865            if (getBlockCName().equals(blockName)
2866                    && (connectC != null)) {
2867                connects.add(connectC);
2868            }
2869            if (isTurnoutTypeXover() || isTurnoutTypeSlip()) {
2870                if (getBlockDName().equals(blockName)
2871                        && (connectD != null)) {
2872                    connects.add(connectD);
2873                }
2874            }
2875
2876            for (LayoutTrack connect : connects) {
2877                // if we are added to the TrackNameSet
2878                if (TrackNameSet.add(getName())) {
2879                    log.debug("*    Add track '{}' for block '{}'", getName(), blockName);
2880                }
2881                // it's time to play... flood your neighbour!
2882                connect.collectContiguousTracksNamesInBlockNamed(blockName, TrackNameSet);
2883            }
2884        }
2885    }
2886
2887    /**
2888     * {@inheritDoc}
2889     */
2890    @Override
2891    public void setAllLayoutBlocks(LayoutBlock layoutBlock) {
2892        setLayoutBlock(layoutBlock);
2893        if (isTurnoutTypeXover() || isTurnoutTypeSlip()) {
2894            setLayoutBlockB(layoutBlock);
2895            setLayoutBlockC(layoutBlock);
2896            setLayoutBlockD(layoutBlock);
2897        }
2898    }
2899
2900    /**
2901     * {@inheritDoc}
2902     */
2903    @Override
2904    public String getTypeName() {
2905        return Bundle.getMessage("TypeName_Turnout");
2906    }
2907
2908    // Tooltip support
2909    private ToolTip _tooltip;
2910    private boolean _showTooltip;
2911
2912    public void setShowToolTip(boolean set) {
2913        _showTooltip = set;
2914    }
2915
2916    public boolean showToolTip() {
2917        return _showTooltip;
2918    }
2919
2920    public void setToolTip(ToolTip tip) {
2921        _tooltip = tip;
2922    }
2923
2924    public ToolTip getToolTip() {
2925        return _tooltip;
2926    }
2927
2928    @Nonnull
2929    public  String getNameString() {
2930        var turnout = getTurnout();
2931        if (turnout != null) {
2932            return turnout.getDisplayName(jmri.NamedBean.DisplayOptions.USERNAME_SYSTEMNAME);
2933        }
2934        return getId();
2935    }
2936
2937    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LayoutTurnout.class);
2938}