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