java/src/jmri/jmrit/display/layoutEditor/LayoutTurnout.java

Summary

Maintainability
F
1 mo
Test Coverage
F
53%
package jmri.jmrit.display.layoutEditor;

import java.awt.Color;
import java.awt.Font;
import java.text.MessageFormat;
import java.util.*;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;

import jmri.*;
import jmri.jmrit.signalling.SignallingGuiTools;
import jmri.jmrit.display.ToolTip;

/**
 * LayoutTurnout is the abstract base for classes representing various types of turnout on the layout.
 * A LayoutTurnout is an
 * extension of the standard Turnout object with drawing and connectivity
 * information added.
 * <p>
 * Specific forms are represented: right-hand, left-hand, wye, double crossover,
 * right-handed single crossover, and left-handed single crossover. Note that
 * double-slip turnouts can be handled as two turnouts, throat to throat, and
 * three-way turnouts can be handled as two turnouts, left-hand and right-hand,
 * arranged throat to continuing route.
 * <p>
 * A LayoutTurnout has three or four connection points, designated A, B, C, and
 * D. For right-handed or left-handed turnouts, A corresponds to the throat. At
 * the crossing, A-B (and C-D for crossovers) is a straight segment (continuing
 * route). A-C (and B-D for crossovers) is the diverging route. B-C (and A-D for
 * crossovers) is an illegal condition.
 * <br>
 * <pre>
 *           Turnouts
 * Right-hand       Left-hand
 *
 *                        C
 *                       //
 * A ==**== B       A ==**== B
 *      \\
 *       C
 *
 *    Wye           Three-way
 *
 *       B                D
 *      //               //
 * A ==**           A ==**== B
 *      \\               \\
 *       C                C
 *
 *           Crossovers
 * Right-hand            left-hand
 * A ==**===== B      A ====**== B
 *      \\                 //
 *       \\               //
 *  D ====**== C     D ==**===== C
 *
 *             Double
 *        A ==**==**== B
 *             \\//
 *              XX
 *             //\\
 *        D ==**==**== C
 * </pre>
 * (The {@link LayoutSlip} track objects follow a different pattern. They put A-D in
 * different places and have AD and BC as the normal-continuance parallel paths)
 * <p>
 * A LayoutTurnout carries Block information. For right-handed, left-handed, and
 * wye turnouts, the entire turnout is in one block, however, a block border may
 * occur at any connection (A,B,C,D). For a double crossover turnout, up to four
 * blocks may be assigned, one for each connection point, but if only one block
 * is assigned, that block applies to the entire turnout.
 * <p>
 * For drawing purposes, each LayoutTurnout carries a center point and
 * displacements for B and C. For right-handed or left-handed turnouts, the
 * displacement for A = - the displacement for B, and the center point is at the
 * junction of the diverging route and the straight through continuing route.
 * For double crossovers, the center point is at the center of the turnout, and
 * the displacement for A = - the displacement for C and the displacement for D
 * = - the displacement for B. The center point and these displacements may be
 * adjusted by the user when in edit mode. For double crossovers, AB and BC are
 * constrained to remain perpendicular. For single crossovers, AB and CD are
 * constrained to remain parallel, and AC and BD are constrained to remain
 * parallel.
 * <p>
 * When LayoutTurnouts are first created, a rotation (degrees) is provided. For
 * 0.0 rotation, the turnout lies on the east-west line with A facing east.
 * Rotations are performed in a clockwise direction.
 * <p>
 * When LayoutTurnouts are first created, there are no connections. Block
 * information and connections may be added when available.
 * <p>
 * When a LayoutTurnout is first created, it is enabled for control of an
 * assigned actual turnout. Clicking on the turnout center point will toggle the
 * turnout. This can be disabled via the popup menu.
 * <p>
 * Signal Head names are saved here to keep track of where signals are.
 * LayoutTurnout only serves as a storage place for signal head names. The names
 * are placed here by tools, e.g., Set Signals at Turnout, and Set Signals at
 * Double Crossover. Each connection point can have up to three SignalHeads and one SignalMast.
 * <p>
 * A LayoutWye may be linked to another LayoutTurnout to form a turnout
 * pair.
 *<br>
 * Throat-To-Throat Turnouts - Two turnouts connected closely at their
 * throats, so closely that signals are not appropriate at the their throats.
 * This is the situation when two RH, LH, or WYE turnouts are used to model a
 * double slip.
 *<br>
 * 3-Way Turnout - Two turnouts modeling a 3-way turnout, where the
 * throat of the second turnout is closely connected to the continuing track of
 * the first turnout. The throat will have three heads, or one head. A link is
 * required to be able to correctly interpret the use of signal heads.
 *
 * @author Dave Duchamp Copyright (c) 2004-2007
 * @author George Warner Copyright (c) 2017-2019
 * @author Bob Jacobsen Copyright (c) 2020
 */
abstract public class LayoutTurnout extends LayoutTrack {

    protected LayoutTurnout(@Nonnull String id,
            @Nonnull LayoutEditor models, TurnoutType t) {
        super(id, models);

        type = t;
        createTooltip(models);
    }

    protected LayoutTurnout(@Nonnull String id,
            @Nonnull LayoutEditor models) {
        this(id, models, TurnoutType.NONE);
    }

    public LayoutTurnout(@Nonnull String id, TurnoutType t,
            @Nonnull LayoutEditor models) {
        this(id, t, models, 1);
    }

    /**
     * Main constructor method.
     * @param id Layout Turnout ID.
     * @param t type, e.g. LH_TURNOUT, WYE_TURNOUT
     * @param models main layout editor.
     * @param v version.
     */
    public LayoutTurnout(@Nonnull String id, TurnoutType t,
            @Nonnull LayoutEditor models,
            int v) {
        super(id, models);

        namedTurnout = null;
        turnoutName = "";
        mTurnoutListener = null;
        disabled = false;
        disableWhenOccupied = false;
        type = t;
        version = v;
        createTooltip(models);

    }

    private void createTooltip(LayoutEditor models) {
        var tt = new ToolTip(null, 0, 0, new Font("SansSerif", Font.PLAIN, 12),
                Color.black, new Color(215, 225, 255), Color.black, null);
        setToolTip(tt);
        setShowToolTip(models.showToolTip());
    }

    // Defined constants for turnout types
    // This is being replaced by subclasses; do not add more
    // references to it.
    public enum TurnoutType {
        NONE,
        RH_TURNOUT,
        LH_TURNOUT,
        WYE_TURNOUT,
        DOUBLE_XOVER,
        RH_XOVER,
        LH_XOVER,
        SINGLE_SLIP, // used for LayoutSlip which extends this class
        DOUBLE_SLIP     // used for LayoutSlip which extends this class
    }

    /**
     * Returns true if this is a turnout (not a crossover or slip)
     *
     * @param type the turnout type
     * @return boolean true if this is a turnout
     */
    public static boolean isTurnoutTypeTurnout(TurnoutType type) {
        return (type == TurnoutType.RH_TURNOUT
                || type == TurnoutType.LH_TURNOUT
                || type == TurnoutType.WYE_TURNOUT);
    }

    /**
     * Returns true if this is a turnout (not a crossover or slip)
     *
     * @return boolean true if this is a turnout
     */
    public boolean isTurnoutTypeTurnout() {
        return isTurnoutTypeTurnout(getTurnoutType());
    }

    /**
     * Returns true if this is a crossover
     *
     * @param type the turnout type
     * @return boolean true if this is a crossover
     */
    public static boolean isTurnoutTypeXover(TurnoutType type) {
        return (type == TurnoutType.DOUBLE_XOVER
                || type == TurnoutType.RH_XOVER
                || type == TurnoutType.LH_XOVER);
    }

    /**
     * Returns true if this is a crossover
     *
     * @return boolean true if this is a crossover
     */
    public boolean isTurnoutTypeXover() {
        return isTurnoutTypeXover(getTurnoutType());
    }

    /**
     * Returns true if this is a slip
     *
     * @param type the turnout type
     * @return boolean true if this is a slip
     */
    public static boolean isTurnoutTypeSlip(TurnoutType type) {
        return (type == TurnoutType.SINGLE_SLIP
                || type == TurnoutType.DOUBLE_SLIP);
    }

    /**
     * Returns true if this is a slip
     *
     * @return boolean true if this is a slip
     */
    public boolean isTurnoutTypeSlip() {
        return isTurnoutTypeSlip(getTurnoutType());
    }

    /**
     * Returns true if this has a single-track entrance end. (turnout or wye)
     *
     * @param type the turnout type
     * @return boolean true if single track entrance
     */
    public static boolean hasEnteringSingleTrack(TurnoutType type) {
        return isTurnoutTypeTurnout(type);
    }

    /**
     * Returns true if this has a single-track entrance end. (turnout or wye)
     *
     * @return boolean true if single track entrance
     */
    public boolean hasEnteringSingleTrack() {
        return hasEnteringSingleTrack(getTurnoutType());
    }

    /**
     * Returns true if this has double track on the entrance end (crossover or
     * slip)
     *
     * @param type the turnout type
     * @return boolean true if double track entrance
     */
    public static boolean hasEnteringDoubleTrack(TurnoutType type) {
        return isTurnoutTypeXover(type) || isTurnoutTypeSlip(type);
    }

    /**
     * Returns true if this has double track on the entrance end (crossover or
     * slip)
     *
     * @return boolean true if double track entrance
     */
    public boolean hasEnteringDoubleTrack() {
        return hasEnteringDoubleTrack(getTurnoutType());
    }

    public enum LinkType {
        NO_LINK,
        FIRST_3_WAY, // this turnout is the first turnout of a 3-way
        // turnout pair (closest to the throat)
        SECOND_3_WAY, // this turnout is the second turnout of a 3-way
        // turnout pair (furthest from the throat)
        THROAT_TO_THROAT  // this turnout is one of two throat-to-throat
        // turnouts - no signals at throat
    }

    // operational instance variables (not saved between sessions)
    public static final int UNKNOWN = Turnout.UNKNOWN;
    public static final int INCONSISTENT = Turnout.INCONSISTENT;
    public static final int STATE_AC = 0x02;
    public static final int STATE_BD = 0x04;
    public static final int STATE_AD = 0x06;
    public static final int STATE_BC = 0x08;

    // program default turnout size parameters
    public static final double turnoutBXDefault = 20.0;  // RH, LH, WYE
    public static final double turnoutCXDefault = 20.0;
    public static final double turnoutWidDefault = 10.0;
    public static final double xOverLongDefault = 30.0;   // DOUBLE_XOVER, RH_XOVER, LH_XOVER
    public static final double xOverHWidDefault = 10.0;
    public static final double xOverShortDefault = 10.0;

    // operational instance variables (not saved between sessions)
    protected NamedBeanHandle<Turnout> namedTurnout = null;
    // 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
    protected NamedBeanHandle<Turnout> secondNamedTurnout = null;

    private java.beans.PropertyChangeListener mTurnoutListener = null;

    // persistent instances variables (saved between sessions)
    // these should be the system or user name of an existing physical turnout
    @Nonnull private String turnoutName = ""; // "" means none, never null
    @Nonnull private String secondTurnoutName = ""; // "" means none, never null
    private boolean secondTurnoutInverted = false;

    // default is package protected
    protected NamedBeanHandle<LayoutBlock> namedLayoutBlockA = null;
    protected NamedBeanHandle<LayoutBlock> namedLayoutBlockB = null;  // Xover - second block, if there is one
    protected NamedBeanHandle<LayoutBlock> namedLayoutBlockC = null;  // Xover - third block, if there is one
    protected NamedBeanHandle<LayoutBlock> namedLayoutBlockD = null;  // Xover - forth block, if there is one

    protected NamedBeanHandle<SignalHead> signalA1HeadNamed = null; // signal 1 (continuing) (throat for RH, LH, WYE)
    protected NamedBeanHandle<SignalHead> signalA2HeadNamed = null; // signal 2 (diverging) (throat for RH, LH, WYE)
    protected NamedBeanHandle<SignalHead> signalA3HeadNamed = null; // signal 3 (second diverging) (3-way turnouts only)
    protected NamedBeanHandle<SignalHead> signalB1HeadNamed = null; // continuing (RH, LH, WYE) signal 1 (double crossover)
    protected NamedBeanHandle<SignalHead> signalB2HeadNamed = null; // LH_Xover and double crossover only
    protected NamedBeanHandle<SignalHead> signalC1HeadNamed = null; // diverging (RH, LH, WYE) signal 1 (double crossover)
    protected NamedBeanHandle<SignalHead> signalC2HeadNamed = null; // RH_Xover and double crossover only
    protected NamedBeanHandle<SignalHead> signalD1HeadNamed = null; // single or double crossover only
    protected NamedBeanHandle<SignalHead> signalD2HeadNamed = null; // LH_Xover and double crossover only

    public enum Geometry {
        NONE,
        POINTA1,
        POINTA2,
        POINTA3,
        POINTB1,
        POINTB2,
        POINTC1,
        POINTC2,
        POINTD1,
        POINTD2
    }

    protected NamedBeanHandle<SignalMast> signalAMastNamed = null; // Throat
    protected NamedBeanHandle<SignalMast> signalBMastNamed = null; // Continuing
    protected NamedBeanHandle<SignalMast> signalCMastNamed = null; // diverging
    protected NamedBeanHandle<SignalMast> signalDMastNamed = null; // single or double crossover only

    protected NamedBeanHandle<Sensor> sensorANamed = null; // Throat
    protected NamedBeanHandle<Sensor> sensorBNamed = null; // Continuing
    protected NamedBeanHandle<Sensor> sensorCNamed = null; // diverging
    protected NamedBeanHandle<Sensor> sensorDNamed = null; // single or double crossover only

    protected final TurnoutType type;

    public LayoutTrack connectA = null;      // throat of LH, RH, RH Xover, LH Xover, and WYE turnouts
    public LayoutTrack connectB = null;      // straight leg of LH and RH turnouts
    public LayoutTrack connectC = null;
    public LayoutTrack connectD = null;      // double xover, RH Xover, LH Xover only

    public int continuingSense = Turnout.CLOSED;

    public boolean disabled = false;
    public boolean disableWhenOccupied = false;

    private int version = 1;

    @Nonnull public String linkedTurnoutName = ""; // name of the linked Turnout (as entered in tool); "" means none, never null
    public LinkType linkType = LinkType.NO_LINK;

    private final boolean useBlockSpeed = false;

    /**
     * {@inheritDoc}
     */
    // this should only be used for debugging...
    @Override
    @Nonnull
    public String toString() {
        return "LayoutTurnout " + getName();
    }

    /**
     * Get the Version.
     * @return turnout version.
     */
    public int getVersion() {
        return version;
    }

    public void setVersion(int v) {
        version = v;
    }

    public boolean useBlockSpeed() {
        return useBlockSpeed;
    }

    @Nonnull
    public String getTurnoutName() {
        if (namedTurnout != null) {
            turnoutName = namedTurnout.getName();
        }
        return turnoutName;
    }

    @Nonnull
    public String getSecondTurnoutName() {
        if (secondNamedTurnout != null) {
            secondTurnoutName = secondNamedTurnout.getName();
        }
        return secondTurnoutName;
    }

    public boolean isSecondTurnoutInverted() {
        return secondTurnoutInverted;
    }

    @Nonnull
    public String getBlockName() {
        String result = null;
        if (namedLayoutBlockA != null) {
            result = namedLayoutBlockA.getName();
        }
        return ((result == null) ? "" : result);
    }

    @Nonnull
    public String getBlockBName() {
        String result = getBlockName();
        if (namedLayoutBlockB != null) {
            result = namedLayoutBlockB.getName();
        }
        return result;
    }

    @Nonnull
    public String getBlockCName() {
        String result = getBlockName();
        if (namedLayoutBlockC != null) {
            result = namedLayoutBlockC.getName();
        }
        return result;
    }

    @Nonnull
    public String getBlockDName() {
        String result = getBlockName();
        if (namedLayoutBlockD != null) {
            result = namedLayoutBlockD.getName();
        }
        return result;
    }

    @CheckForNull
    public SignalHead getSignalHead(Geometry loc) {
        NamedBeanHandle<SignalHead> signalHead = null;
        switch (loc) {
            case POINTA1:
                signalHead = signalA1HeadNamed;
                break;
            case POINTA2:
                signalHead = signalA2HeadNamed;
                break;
            case POINTA3:
                signalHead = signalA3HeadNamed;
                break;
            case POINTB1:
                signalHead = signalB1HeadNamed;
                break;
            case POINTB2:
                signalHead = signalB2HeadNamed;
                break;
            case POINTC1:
                signalHead = signalC1HeadNamed;
                break;
            case POINTC2:
                signalHead = signalC2HeadNamed;
                break;
            case POINTD1:
                signalHead = signalD1HeadNamed;
                break;
            case POINTD2:
                signalHead = signalD2HeadNamed;
                break;
            default:
                log.warn("{}.getSignalHead({}); Unhandled point type", getName(), loc);
                break;
        }
        if (signalHead != null) {
            return signalHead.getBean();
        }
        return null;
    }

    @CheckForNull
    public SignalHead getSignalA1() {
        return signalA1HeadNamed != null ? signalA1HeadNamed.getBean() : null;
    }

    @Nonnull
    public String getSignalA1Name() {
        if (signalA1HeadNamed != null) {
            return signalA1HeadNamed.getName();
        }
        return "";
    }

    public void setSignalA1Name(@CheckForNull String signalHead) {
        if (signalHead == null || signalHead.isEmpty()) {
            signalA1HeadNamed = null;
            return;
        }

        SignalHead head = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(signalHead);
        if (head != null) {
            signalA1HeadNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalHead, head);
        } else {
            signalA1HeadNamed = null;
            log.error("{}.setSignalA1Name({}); not fournd for turnout {}", getName(), signalHead, getTurnoutName());
        }
    }

    @CheckForNull
    public SignalHead getSignalA2() {
        return signalA2HeadNamed != null ? signalA2HeadNamed.getBean() : null;
    }

    @Nonnull
    public String getSignalA2Name() {
        if (signalA2HeadNamed != null) {
            return signalA2HeadNamed.getName();
        }
        return "";
    }

    public void setSignalA2Name(@CheckForNull String signalHead) {
        if (signalHead == null || signalHead.isEmpty()) {
            signalA2HeadNamed = null;
            return;
        }

        SignalHead head = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(signalHead);
        if (head != null) {
            signalA2HeadNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalHead, head);
        } else {
            signalA2HeadNamed = null;
            log.error("{}.setSignalA2Name({}); not fournd for turnout {}", getName(), signalHead, getTurnoutName());
        }
    }

    @CheckForNull
    public SignalHead getSignalA3() {
        return signalA3HeadNamed != null ? signalA3HeadNamed.getBean() : null;
    }

    @Nonnull
    public String getSignalA3Name() {
        if (signalA3HeadNamed != null) {
            return signalA3HeadNamed.getName();
        }
        return "";
    }

    public void setSignalA3Name(@CheckForNull String signalHead) {
        if (signalHead == null || signalHead.isEmpty()) {
            signalA3HeadNamed = null;
            return;
        }

        SignalHead head = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(signalHead);
        if (head != null) {
            signalA3HeadNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalHead, head);
        } else {
            signalA3HeadNamed = null;
            log.error("{}.setSignalA3Name({}); not fournd for turnout {}", getName(), signalHead, getTurnoutName());
        }
    }

    @CheckForNull
    public SignalHead getSignalB1() {
        return signalB1HeadNamed != null ? signalB1HeadNamed.getBean() : null;
    }

    @Nonnull
    public String getSignalB1Name() {
        if (signalB1HeadNamed != null) {
            return signalB1HeadNamed.getName();
        }
        return "";
    }

    public void setSignalB1Name(@CheckForNull String signalHead) {
        if (signalHead == null || signalHead.isEmpty()) {
            signalB1HeadNamed = null;
            return;
        }

        SignalHead head = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(signalHead);
        if (head != null) {
            signalB1HeadNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalHead, head);
        } else {
            signalB1HeadNamed = null;
            log.error("{}.setSignalB1Name({}); not fournd for turnout {}", getName(), signalHead, getTurnoutName());
        }
    }

    @CheckForNull
    public SignalHead getSignalB2() {
        return signalB2HeadNamed != null ? signalB2HeadNamed.getBean() : null;
    }

    @Nonnull
    public String getSignalB2Name() {
        if (signalB2HeadNamed != null) {
            return signalB2HeadNamed.getName();
        }
        return "";
    }

    public void setSignalB2Name(@CheckForNull String signalHead) {
        if (signalHead == null || signalHead.isEmpty()) {
            signalB2HeadNamed = null;
            return;
        }

        SignalHead head = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(signalHead);
        if (head != null) {
            signalB2HeadNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalHead, head);
        } else {
            signalB2HeadNamed = null;
            log.error("{}.setSignalB2Name({}); not fournd for turnout {}", getName(), signalHead, getTurnoutName());
        }
    }

    @CheckForNull
    public SignalHead getSignalC1() {
        return signalC1HeadNamed != null ? signalC1HeadNamed.getBean() : null;
    }

    @Nonnull
    public String getSignalC1Name() {
        if (signalC1HeadNamed != null) {
            return signalC1HeadNamed.getName();
        }
        return "";
    }

    public void setSignalC1Name(@CheckForNull String signalHead) {
        if (signalHead == null || signalHead.isEmpty()) {
            signalC1HeadNamed = null;
            return;
        }

        SignalHead head = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(signalHead);
        if (head != null) {
            signalC1HeadNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalHead, head);
        } else {
            signalC1HeadNamed = null;
            log.error("{}.setSignalC1Name({}); not fournd for turnout {}", getName(), signalHead, getTurnoutName());
        }
    }

    @CheckForNull
    public SignalHead getSignalC2() {
        return signalC2HeadNamed != null ? signalC2HeadNamed.getBean() : null;
    }

    @Nonnull
    public String getSignalC2Name() {
        if (signalC2HeadNamed != null) {
            return signalC2HeadNamed.getName();
        }
        return "";
    }

    public void setSignalC2Name(@CheckForNull String signalHead) {
        if (signalHead == null || signalHead.isEmpty()) {
            signalC2HeadNamed = null;
            return;
        }

        SignalHead head = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(signalHead);
        if (head != null) {
            signalC2HeadNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalHead, head);
        } else {
            signalC2HeadNamed = null;
            log.error("{}.setSignalC2Name({}); not fournd for turnout {}", getName(), signalHead, getTurnoutName());
        }
    }

    @CheckForNull
    public SignalHead getSignalD1() {
        return signalD1HeadNamed != null ? signalD1HeadNamed.getBean() : null;
    }

    @Nonnull
    public String getSignalD1Name() {
        if (signalD1HeadNamed != null) {
            return signalD1HeadNamed.getName();
        }
        return "";
    }

    public void setSignalD1Name(@CheckForNull String signalHead) {
        if (signalHead == null || signalHead.isEmpty()) {
            signalD1HeadNamed = null;
            return;
        }

        SignalHead head = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(signalHead);
        if (head != null) {
            signalD1HeadNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalHead, head);
        } else {
            signalD1HeadNamed = null;
            log.error("{}.setSignalD1Name({}); not fournd for turnout {}", getName(), signalHead, getTurnoutName());
        }
    }

    @CheckForNull
    public SignalHead getSignalD2() {
        return signalD2HeadNamed != null ? signalD2HeadNamed.getBean() : null;
    }

    @Nonnull
    public String getSignalD2Name() {
        if (signalD2HeadNamed != null) {
            return signalD2HeadNamed.getName();
        }
        return "";
    }

    public void setSignalD2Name(@CheckForNull String signalHead) {
        if (signalHead == null || signalHead.isEmpty()) {
            signalD2HeadNamed = null;
            return;
        }

        SignalHead head = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(signalHead);
        if (head != null) {
            signalD2HeadNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalHead, head);
        } else {
            signalD2HeadNamed = null;
            log.error("{}.setSignalD2Name({}); not fournd for turnout {}", getName(), signalHead, getTurnoutName());
        }
    }

    public void removeBeanReference(@CheckForNull jmri.NamedBean nb) {
        if (nb == null) {
            return;
        }
        if (nb instanceof SignalMast) {
            if (nb.equals(getSignalAMast())) {
                setSignalAMast(null);
                return;
            }
            if (nb.equals(getSignalBMast())) {
                setSignalBMast(null);
                return;
            }
            if (nb.equals(getSignalCMast())) {
                setSignalCMast(null);
                return;
            }
            if (nb.equals(getSignalDMast())) {
                setSignalDMast(null);
            }
        } else if (nb instanceof Sensor) {
            if (nb.equals(getSensorA())) {
                setSensorA(null);
                return;
            }
            if (nb.equals(getSensorB())) {
                setSensorB(null);
                return;
            }
            if (nb.equals(getSensorC())) {
                setSensorC(null);
                return;
            }
            if (nb.equals(getSensorB())) {
                setSensorD(null);
            }
        } else if (nb instanceof SignalHead) {
            if (nb.equals(getSignalHead(Geometry.POINTA1))) {
                setSignalA1Name(null);
            }
            if (nb.equals(getSignalHead(Geometry.POINTA2))) {
                setSignalA2Name(null);
            }
            if (nb.equals(getSignalHead(Geometry.POINTA3))) {
                setSignalA3Name(null);
            }
            if (nb.equals(getSignalHead(Geometry.POINTB1))) {
                setSignalB1Name(null);
            }
            if (nb.equals(getSignalHead(Geometry.POINTB2))) {
                setSignalB2Name(null);
            }
            if (nb.equals(getSignalHead(Geometry.POINTC1))) {
                setSignalC1Name(null);
            }
            if (nb.equals(getSignalHead(Geometry.POINTC2))) {
                setSignalC2Name(null);
            }
            if (nb.equals(getSignalHead(Geometry.POINTD1))) {
                setSignalD1Name(null);
            }
            if (nb.equals(getSignalHead(Geometry.POINTD2))) {
                setSignalD2Name(null);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean canRemove() {
        ArrayList<String> beanReferences = getBeanReferences("All");  // NOI18N
        if (!beanReferences.isEmpty()) {
            models.displayRemoveWarning(this, beanReferences, "BeanNameTurnout");  // NOI18N
        }
        return beanReferences.isEmpty();
    }

    /**
     * Build a list of sensors, signal heads, and signal masts attached to a
     * turnout point.
     *
     * @param pointName Specify the point (A-D) or all (All) points.
     * @return a list of bean reference names.
     */
    @Nonnull
    public ArrayList<String> getBeanReferences(String pointName) {
        ArrayList<String> references = new ArrayList<>();
        if (pointName.equals("A") || pointName.equals("All")) {  // NOI18N
            if (!getSignalAMastName().isEmpty()) {
                references.add(getSignalAMastName());
            }
            if (!getSensorAName().isEmpty()) {
                references.add(getSensorAName());
            }
            if (!getSignalA1Name().isEmpty()) {
                references.add(getSignalA1Name());
            }
            if (!getSignalA2Name().isEmpty()) {
                references.add(getSignalA2Name());
            }
            if (!getSignalA3Name().isEmpty()) {
                references.add(getSignalA3Name());
            }
        }
        if (pointName.equals("B") || pointName.equals("All")) {  // NOI18N
            if (!getSignalBMastName().isEmpty()) {
                references.add(getSignalBMastName());
            }
            if (!getSensorBName().isEmpty()) {
                references.add(getSensorBName());
            }
            if (!getSignalB1Name().isEmpty()) {
                references.add(getSignalB1Name());
            }
            if (!getSignalB2Name().isEmpty()) {
                references.add(getSignalB2Name());
            }
        }
        if (pointName.equals("C") || pointName.equals("All")) {  // NOI18N
            if (!getSignalCMastName().isEmpty()) {
                references.add(getSignalCMastName());
            }
            if (!getSensorCName().isEmpty()) {
                references.add(getSensorCName());
            }
            if (!getSignalC1Name().isEmpty()) {
                references.add(getSignalC1Name());
            }
            if (!getSignalC2Name().isEmpty()) {
                references.add(getSignalC2Name());
            }
        }
        if (pointName.equals("D") || pointName.equals("All")) {  // NOI18N
            if (!getSignalDMastName().isEmpty()) {
                references.add(getSignalDMastName());
            }
            if (!getSensorDName().isEmpty()) {
                references.add(getSensorDName());
            }
            if (!getSignalD1Name().isEmpty()) {
                references.add(getSignalD1Name());
            }
            if (!getSignalD2Name().isEmpty()) {
                references.add(getSignalD2Name());
            }
        }
        return references;
    }

    @Nonnull
    public String getSignalAMastName() {
        if (signalAMastNamed != null) {
            return signalAMastNamed.getName();
        }
        return "";
    }

    // @CheckForNull temporary until we get central error check
    public SignalMast getSignalAMast() {
        if (signalAMastNamed != null) {
            return signalAMastNamed.getBean();
        }
        return null;
    }

    public void setSignalAMast(@CheckForNull String signalMast) {
        if (signalMast == null || signalMast.isEmpty()) {
            signalAMastNamed = null;
            return;
        }

        SignalMast mast = InstanceManager.getDefault(jmri.SignalMastManager.class).getSignalMast(signalMast);
        if (mast != null) {
            signalAMastNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalMast, mast);
        } else {
            signalAMastNamed = null;
            log.error("{}.setSignalAMast({}); not fournd for turnout {}", getName(), signalMast, getTurnoutName());
        }
    }

    @Nonnull
    public String getSignalBMastName() {
        if (signalBMastNamed != null) {
            return signalBMastNamed.getName();
        }
        return "";
    }

    // @CheckForNull temporary until we get central error check
    public SignalMast getSignalBMast() {
        if (signalBMastNamed != null) {
            return signalBMastNamed.getBean();
        }
        return null;
    }

    public void setSignalBMast(@CheckForNull String signalMast) {
        if (signalMast == null || signalMast.isEmpty()) {
            signalBMastNamed = null;
            return;
        }

        SignalMast mast = InstanceManager.getDefault(jmri.SignalMastManager.class).getSignalMast(signalMast);
        if (mast != null) {
            signalBMastNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalMast, mast);
        } else {
            signalBMastNamed = null;
            log.error("{}.setSignalBMast({}); not fournd for turnout {}", getName(), signalMast, getTurnoutName());
        }
    }

    @Nonnull
    public String getSignalCMastName() {
        if (signalCMastNamed != null) {
            return signalCMastNamed.getName();
        }
        return "";
    }

    // @CheckForNull temporary until we get central error check
    public SignalMast getSignalCMast() {
        if (signalCMastNamed != null) {
            return signalCMastNamed.getBean();
        }
        return null;
    }

    public void setSignalCMast(@CheckForNull String signalMast) {
        if (signalMast == null || signalMast.isEmpty()) {
            signalCMastNamed = null;
            return;
        }

        SignalMast mast = InstanceManager.getDefault(jmri.SignalMastManager.class).getSignalMast(signalMast);
        if (mast != null) {
            signalCMastNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalMast, mast);
        } else {
            log.error("{}.setSignalCMast({}); not fournd for turnout {}", getName(), signalMast, getTurnoutName());
            signalCMastNamed = null;
        }
    }

    @Nonnull
    public String getSignalDMastName() {
        if (signalDMastNamed != null) {
            return signalDMastNamed.getName();
        }
        return "";
    }

    // @CheckForNull temporary until we get central error check
    public SignalMast getSignalDMast() {
        if (signalDMastNamed != null) {
            return signalDMastNamed.getBean();
        }
        return null;
    }

    public void setSignalDMast(@CheckForNull String signalMast) {
        if (signalMast == null || signalMast.isEmpty()) {
            signalDMastNamed = null;
            return;
        }

        SignalMast mast = InstanceManager.getDefault(jmri.SignalMastManager.class).getSignalMast(signalMast);
        if (mast != null) {
            signalDMastNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalMast, mast);
        } else {
            log.error("{}.setSignalDMast({}); not fournd for turnout {}", getName(), signalMast, getTurnoutName());
            signalDMastNamed = null;
        }
    }

    @Nonnull
    public String getSensorAName() {
        if (sensorANamed != null) {
            return sensorANamed.getName();
        }
        return "";
    }

    @CheckForNull
    public Sensor getSensorA() {
        if (sensorANamed != null) {
            return sensorANamed.getBean();
        }
        return null;
    }

    public void setSensorA(@CheckForNull String sensorName) {
        if (sensorName == null || sensorName.isEmpty()) {
            sensorANamed = null;
            return;
        }

        try {
            Sensor sensor = InstanceManager.sensorManagerInstance().provideSensor(sensorName);
            sensorANamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(sensorName, sensor);
        } catch (IllegalArgumentException ex) {
            sensorANamed = null;
        }
    }

    @Nonnull
    public String getSensorBName() {
        if (sensorBNamed != null) {
            return sensorBNamed.getName();
        }
        return "";
    }

    @CheckForNull
    public Sensor getSensorB() {
        if (sensorBNamed != null) {
            return sensorBNamed.getBean();
        }
        return null;
    }

    public void setSensorB(@CheckForNull String sensorName) {
        if (sensorName == null || sensorName.isEmpty()) {
            sensorBNamed = null;
            return;
        }

        try {
            Sensor sensor = InstanceManager.sensorManagerInstance().provideSensor(sensorName);
            sensorBNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(sensorName, sensor);
        } catch (IllegalArgumentException ex) {
            sensorBNamed = null;
        }
    }

    @Nonnull
    public String getSensorCName() {
        if (sensorCNamed != null) {
            return sensorCNamed.getName();
        }
        return "";
    }

    @CheckForNull
    public Sensor getSensorC() {
        if (sensorCNamed != null) {
            return sensorCNamed.getBean();
        }
        return null;
    }

    public void setSensorC(@CheckForNull String sensorName) {
        if (sensorName == null || sensorName.isEmpty()) {
            sensorCNamed = null;
            return;
        }

        try {
            Sensor sensor = InstanceManager.sensorManagerInstance().provideSensor(sensorName);
            sensorCNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(sensorName, sensor);
        } catch (IllegalArgumentException ex) {
            sensorCNamed = null;
        }
    }

    @Nonnull
    public String getSensorDName() {
        if (sensorDNamed != null) {
            return sensorDNamed.getName();
        }
        return "";
    }

    @CheckForNull
    public Sensor getSensorD() {
        if (sensorDNamed != null) {
            return sensorDNamed.getBean();
        }
        return null;
    }

    public void setSensorD(@CheckForNull String sensorName) {
        if (sensorName == null || sensorName.isEmpty()) {
            sensorDNamed = null;
            return;
        }

        try {
            Sensor sensor = InstanceManager.sensorManagerInstance().provideSensor(sensorName);
            sensorDNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(sensorName, sensor);
        } catch (IllegalArgumentException ex) {
            sensorDNamed = null;
        }
    }

    @Nonnull
    public String getLinkedTurnoutName() {
        return linkedTurnoutName;
    }

    public void setLinkedTurnoutName(@Nonnull String s) {
        linkedTurnoutName = s;
    }  // Could be done with changing over to a NamedBeanHandle

    public LinkType getLinkType() {
        return linkType;
    }

    public void setLinkType(LinkType ltype) {
        linkType = ltype;
    }

    public TurnoutType getTurnoutType() {
        return type;
    }

    public LayoutTrack getConnectA() {
        return connectA;
    }

    public LayoutTrack getConnectB() {
        return connectB;
    }

    public LayoutTrack getConnectC() {
        return connectC;
    }

    public LayoutTrack getConnectD() {
        return connectD;
    }

    /**
     * Perhaps confusingly, this returns an actual Turnout reference
     * or null for the turnout associated with this is LayoutTurnout.
     * This is different from {@link #setTurnout(String)}, which
     * takes a name (system or user) or an empty string.
     * @return Null if no Turnout set
     */
    // @CheckForNull temporary - want to restore once better handled
    public Turnout getTurnout() {
        if (namedTurnout == null) {
            // set physical turnout if possible and needed
            setTurnout(turnoutName);
            if (namedTurnout == null) {
                return null;
            }
        }
        return namedTurnout.getBean();
    }

    public int getContinuingSense() {
        return continuingSense;
    }

    /**
     *
     * @return true is the continuingSense matches the known state
     */
    public boolean isInContinuingSenseState() {
        return getState() == continuingSense;
    }

    /**
     * Perhaps confusingly, this takes a Turnout name (system or user)
     * to locate and set the turnout associated with this is LayoutTurnout.
     * This is different from {@link #getTurnout()}, which returns an
     * actual Turnout reference or null.
     * @param tName provide empty string for none; never null
     */
    public void setTurnout(@Nonnull String tName) {
        assert tName != null;
        if (namedTurnout != null) {
            deactivateTurnout();
        }
        turnoutName = tName;
        Turnout turnout = null;
        if (!turnoutName.isEmpty()) {
            turnout = InstanceManager.turnoutManagerInstance().getTurnout(turnoutName);
        }
        if (turnout != null) {
            namedTurnout = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(turnoutName, turnout);
            activateTurnout();
        } else {
            turnoutName = "";
            namedTurnout = null;
            setDisableWhenOccupied(false);
        }
        Turnout secondTurnout = getSecondTurnout();
        if (secondTurnout != null && secondTurnout.getFeedbackMode() == Turnout.DIRECT) {
            secondTurnout.setLeadingTurnout(turnout, false);
        }
    }

    // @CheckForNull temporary until we have central paradigm for null
    public Turnout getSecondTurnout() {
        Turnout result = null;
        if (secondNamedTurnout == null) {
            // set physical turnout if possible and needed
            setSecondTurnout(secondTurnoutName);
        }
        if (secondNamedTurnout != null) {
            result = secondNamedTurnout.getBean();
        }
        return result;
    }

    /**
     * @param tName provide empty string for none (not null)
     */
    public void setSecondTurnout(@Nonnull String tName) {
        assert tName != null;
        if (tName.equals(secondTurnoutName)) { // haven't changed anything
            return;
        }

        if (secondNamedTurnout != null) {
            deactivateTurnout();
            Turnout turnout = secondNamedTurnout.getBean();
            if (turnout.getLeadingTurnout() == namedTurnout.getBean()) {
                turnout.setLeadingTurnout(null);
            }
        }
        String oldSecondTurnoutName = secondTurnoutName;
        secondTurnoutName = tName;
        Turnout turnout = null;
        if (! tName.isEmpty()) {
            turnout = InstanceManager.turnoutManagerInstance().getTurnout(secondTurnoutName);
        }
        if (turnout != null) {
            secondNamedTurnout = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(secondTurnoutName, turnout);
            if (turnout.getFeedbackMode() == Turnout.DIRECT) {
                turnout.setLeadingTurnout(getTurnout(), false);
            }
        } else {
            secondTurnoutName = "";
            secondNamedTurnout = null;
        }
        activateTurnout(); // Even if secondary is null, the primary Turnout may still need to be re-activated
        if (isTurnoutTypeTurnout()) {
            LayoutEditorFindItems lf = new LayoutEditorFindItems(models);
            if (oldSecondTurnoutName != null && !oldSecondTurnoutName.isEmpty()) {
                Turnout oldTurnout = InstanceManager.turnoutManagerInstance().getTurnout(oldSecondTurnoutName);
                String oldSystemName = (oldTurnout == null) ? null : oldTurnout.getSystemName();
                LayoutTurnout oldLinked = (oldSystemName == null) ? null
                        : lf.findLayoutTurnoutByTurnoutName(oldSystemName);
                if (oldLinked == null) {
                    String oldUserName = (oldTurnout == null) ? null : oldTurnout.getUserName();
                    oldLinked = (oldUserName == null) ? null
                            : lf.findLayoutTurnoutByTurnoutName(oldUserName);
                }
                if ((oldLinked != null) && oldLinked.getSecondTurnout() == getTurnout()) {
                    oldLinked.setSecondTurnout("");
                }
            }
            if (turnout != null) {
                LayoutTurnout newLinked = lf.findLayoutTurnoutByTurnoutName(turnout.getSystemName());
                if (newLinked == null) {
                    newLinked = lf.findLayoutTurnoutByTurnoutName(turnout.getUserName());
                }
                if (newLinked != null) {
                    newLinked.setSecondTurnout(turnoutName);
                }
            }
        }
    }

    public void setSecondTurnoutInverted(boolean inverted) {
        secondTurnoutInverted = inverted;
    }

    public void setContinuingSense(int sense) {
        continuingSense = sense;
    }

    public void setDisabled(boolean state) {
        if (disabled != state) {
            disabled = state;
            if (models != null) {
                models.redrawPanel();
            }
        }
    }

    public boolean isDisabled() {
        return disabled;
    }

    public void setDisableWhenOccupied(boolean state) {
        if (disableWhenOccupied != state) {
            disableWhenOccupied = state;
            if (models != null) {
                models.redrawPanel();
            }
        }
    }

    public boolean isDisabledWhenOccupied() {
        return disableWhenOccupied;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @CheckForNull
    public LayoutTrack getConnection(HitPointType connectionType) {
        LayoutTrack result = null;
        switch (connectionType) {
            case TURNOUT_A: {
                result = connectA;
                break;
            }
            case TURNOUT_B: {
                result = connectB;
                break;
            }
            case TURNOUT_C: {
                result = connectC;
                break;
            }
            case TURNOUT_D: {
                result = connectD;
                break;
            }
            default: {
                String errString = MessageFormat.format("{0}.getConnection({1}); Invalid Connection Type",
                        getName(), connectionType); // I18IN
                log.error("will throw {}", errString);
                throw new IllegalArgumentException(errString);
            }
        }
        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setConnection(HitPointType connectionType, @CheckForNull LayoutTrack o, HitPointType type) throws jmri.JmriException {
        if ((type != HitPointType.TRACK) && (type != HitPointType.NONE)) {
            String errString = MessageFormat.format("{0}.setConnection({1}, {2}, {3}); unexpected type",
                    getName(), connectionType, (o == null) ? "null" : o.getName(), type, new Exception("traceback")); // I18IN
            log.error("will throw {}", errString);
            throw new jmri.JmriException(errString);
        }
        switch (connectionType) {
            case TURNOUT_A:
                connectA = o;
                break;
            case TURNOUT_B:
                connectB = o;
                break;
            case TURNOUT_C:
                connectC = o;
                break;
            case TURNOUT_D:
                connectD = o;
                break;
            default:
                String errString = MessageFormat.format("{0}.setConnection({1}, {2}, {3}); Invalid Connection Type",
                        getName(), connectionType, (o == null) ? "null" : o.getName(), type); // I18IN
                log.error("will throw {}", errString);
                throw new jmri.JmriException(errString);
        }
    }

    public void setConnectA(@CheckForNull LayoutTrack o, HitPointType type) {
        connectA = o;
        if ((type != HitPointType.TRACK) && (type != HitPointType.NONE)) {
            log.error("{}.setConnectA({}, {}); unexpected type",
                    getName(), (o == null) ? "null" : o.getName(), type);
        }
    }

    public void setConnectB(@CheckForNull LayoutTrack o, HitPointType type) {
        connectB = o;
        if ((type != HitPointType.TRACK) && (type != HitPointType.NONE)) {
            log.error("{}.setConnectB({}, {}); unexpected type",
                    getName(), (o == null) ? "null" : o.getName(), type);
        }
    }

    public void setConnectC(@CheckForNull LayoutTrack o, HitPointType type) {
        connectC = o;
        if ((type != HitPointType.TRACK) && (type != HitPointType.NONE)) {
            log.error("{}.setConnectC({}, {}); unexpected type",
                    getName(), (o == null) ? "null" : o.getName(), type);
        }
    }

    public void setConnectD(@CheckForNull LayoutTrack o, HitPointType type) {
        connectD = o;
        if ((type != HitPointType.TRACK) && (type != HitPointType.NONE)) {
            log.error("{}.setConnectD({}, {}); unexpected type",
                    getName(), (o == null) ? "null" : o.getName(), type);
        }
    }

    // @CheckForNull - temporary, until we can centralize protection for this
    public LayoutBlock getLayoutBlock() {
        return (namedLayoutBlockA != null) ? namedLayoutBlockA.getBean() : null;
    }

    // @CheckForNull - temporary, until we can centralize protection for this
    public LayoutBlock getLayoutBlockB() {
        return (namedLayoutBlockB != null) ? namedLayoutBlockB.getBean() : getLayoutBlock();
    }

    // @CheckForNull - temporary, until we can centralize protection for this
    public LayoutBlock getLayoutBlockC() {
        return (namedLayoutBlockC != null) ? namedLayoutBlockC.getBean() : getLayoutBlock();
    }

    // @CheckForNull - temporary, until we can centralize protection for this
    public LayoutBlock getLayoutBlockD() {
        return (namedLayoutBlockD != null) ? namedLayoutBlockD.getBean() : getLayoutBlock();
    }

    // updates connectivity for blocks assigned to this turnout and connected track segments
    public void updateBlockInfo() {
        LayoutBlock bA = null;
        LayoutBlock bB = null;
        LayoutBlock bC = null;
        LayoutBlock bD = null;
        models.getLEAuxTools().setBlockConnectivityChanged();
        if (getLayoutBlock() != null) {
            getLayoutBlock().updatePaths();
        }
        if (connectA != null) {
            bA = ((TrackSegment) connectA).getLayoutBlock();
            if ((bA != null) && (bA != getLayoutBlock())) {
                bA.updatePaths();
            }
        }
        if ((getLayoutBlockB() != null)
                && (getLayoutBlockB() != getLayoutBlock())
                && (getLayoutBlockB() != bA)) {
            getLayoutBlockB().updatePaths();
        }
        if (connectB != null) {
            bB = ((TrackSegment) connectB).getLayoutBlock();
            if ((bB != null) && (bB != getLayoutBlock()) && (bB != bA)
                    && (bB != getLayoutBlockB())) {
                bB.updatePaths();
            }
        }
        if ((getLayoutBlockC() != null)
                && (getLayoutBlockC() != getLayoutBlock())
                && (getLayoutBlockC() != bA)
                && (getLayoutBlockC() != bB)
                && (getLayoutBlockC() != getLayoutBlockB())) {
            getLayoutBlockC().updatePaths();
        }
        if (connectC != null) {
            bC = ((TrackSegment) connectC).getLayoutBlock();
            if ((bC != null) && (bC != getLayoutBlock())
                    && (bC != bA) && (bC != getLayoutBlockB())
                    && (bC != bB)
                    && (bC != getLayoutBlockC())) {
                bC.updatePaths();
            }
        }
        if ((getLayoutBlockD() != null)
                && (getLayoutBlockD() != getLayoutBlock())
                && (getLayoutBlockD() != bA)
                && (getLayoutBlockD() != bB)
                && (getLayoutBlockD() != getLayoutBlockB())
                && (getLayoutBlockD() != bC)
                && (getLayoutBlockD() != getLayoutBlockC())) {
            getLayoutBlockD().updatePaths();
        }
        if (connectD != null) {
            bD = ((TrackSegment) connectD).getLayoutBlock();
            if ((bD != null) && (bD != getLayoutBlock())
                    && (bD != bA) && (bD != getLayoutBlockB())
                    && (bD != bB) && (bD != getLayoutBlockC())
                    && (bD != bC) && (bD != getLayoutBlockD())) {
                bD.updatePaths();
            }
        }
    }


    /**
     * Set up Layout Block(s) for this Turnout.
     * @param newLayoutBlock the new layout block.
     */
    protected void setLayoutBlock(LayoutBlock newLayoutBlock) {
        LayoutBlock blockA = getLayoutBlock();
        LayoutBlock blockB = getLayoutBlockB();
        LayoutBlock blockC = getLayoutBlockC();
        LayoutBlock blockD = getLayoutBlockD();
        if (blockA != newLayoutBlock) {
            // block has changed, if old block exists, decrement use
            if ((blockA != null)
                    && (blockA != blockB)
                    && (blockA != blockC)
                    && (blockA != blockD)) {
                blockA.decrementUse();
            }

            blockA = newLayoutBlock;
            if (newLayoutBlock != null) {
                String userName = newLayoutBlock.getUserName();
                if (userName != null) {
                    namedLayoutBlockA = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(userName, newLayoutBlock);
                }
            } else {
                namedLayoutBlockA = null;
                setDisableWhenOccupied(false);
            }
            // decrement use if block was already counted
            if ((blockA != null)
                    && ((blockA == blockB) || (blockA == blockC) || (blockA == blockD))) {
                blockA.decrementUse();
            }
        }
    }

    protected void setLayoutBlockB(LayoutBlock newLayoutBlock) {
        if (getLayoutBlock() == null) {
            setLayoutBlock(newLayoutBlock);
        }
        if (isTurnoutTypeXover() || isTurnoutTypeSlip()) {
            LayoutBlock blockA = getLayoutBlock();
            LayoutBlock blockB = getLayoutBlockB();
            LayoutBlock blockC = getLayoutBlockC();
            LayoutBlock blockD = getLayoutBlockD();
            if (blockB != newLayoutBlock) {
                // block has changed, if old block exists, decrement use
                if ((blockB != null)
                        && (blockB != blockA)
                        && (blockB != blockC)
                        && (blockB != blockD)) {
                    blockB.decrementUse();
                }
                blockB = newLayoutBlock;
                if (newLayoutBlock != null) {
                    String userName = newLayoutBlock.getUserName();
                    if (userName != null) {
                        namedLayoutBlockB = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(userName, newLayoutBlock);
                    }
                } else {
                    namedLayoutBlockB = null;
                }
                // decrement use if block was already counted
                if ((blockB != null)
                        && ((blockB == blockA) || (blockB == blockC) || (blockB == blockD))) {
                    blockB.decrementUse();
                }
            }
        } else {
            log.error("{}.setLayoutBlockB({}); not a crossover/slip", getName(), newLayoutBlock.getUserName());
        }
    }

    protected void setLayoutBlockC(@CheckForNull LayoutBlock newLayoutBlock) {
        if (getLayoutBlock() == null) {
            setLayoutBlock(newLayoutBlock);
        }
        if (isTurnoutTypeXover() || isTurnoutTypeSlip()) {
            LayoutBlock blockA = getLayoutBlock();
            LayoutBlock blockB = getLayoutBlockB();
            LayoutBlock blockC = getLayoutBlockC();
            LayoutBlock blockD = getLayoutBlockD();
            if (blockC != newLayoutBlock) {
                // block has changed, if old block exists, decrement use
                if ((blockC != null)
                        && (blockC != blockA)
                        && (blockC != blockB)
                        && (blockC != blockD)) {
                    blockC.decrementUse();
                }
                blockC = newLayoutBlock;
                if (newLayoutBlock != null) {
                    String userName = newLayoutBlock.getUserName();
                    if (userName != null) {
                        namedLayoutBlockC = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(userName, newLayoutBlock);
                    }
                } else {
                    namedLayoutBlockC = null;
                }
                // decrement use if block was already counted
                if ((blockC != null)
                        && ((blockC == blockA) || (blockC == blockB) || (blockC == blockD))) {
                    blockC.decrementUse();
                }
            }
        } else {
            log.error("{}.setLayoutBlockC({}); not a crossover/slip", getName(), newLayoutBlock.getUserName());
        }
    }

    protected void setLayoutBlockD(LayoutBlock newLayoutBlock) {
        if (getLayoutBlock() == null) {
            setLayoutBlock(newLayoutBlock);
        }
        if (isTurnoutTypeXover() || isTurnoutTypeSlip()) {
            LayoutBlock blockA = getLayoutBlock();
            LayoutBlock blockB = getLayoutBlockB();
            LayoutBlock blockC = getLayoutBlockC();
            LayoutBlock blockD = getLayoutBlockD();
            if (blockD != newLayoutBlock) {
                // block has changed, if old block exists, decrement use
                if ((blockD != null)
                        && (blockD != blockA)
                        && (blockD != blockB)
                        && (blockD != blockC)) {
                    blockD.decrementUse();
                }
                blockD = newLayoutBlock;
                if (newLayoutBlock != null) {
                    String userName = newLayoutBlock.getUserName();
                    if (userName != null) {
                        namedLayoutBlockD = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(userName, newLayoutBlock);
                    }
                } else {
                    namedLayoutBlockD = null;
                }
                // decrement use if block was already counted
                if ((blockD != null)
                        && ((blockD == blockA) || (blockD == blockB) || (blockD == blockC))) {
                    blockD.decrementUse();
                }
            }
        } else {
            log.error("{}.setLayoutBlockD({}); not a crossover/slip", getName(), newLayoutBlock.getUserName());
        }
    }

    public void setLayoutBlockByName(@Nonnull String name) {
        setLayoutBlock(models.provideLayoutBlock(name));
    }

    public void setLayoutBlockBByName(@Nonnull String name) {
        if (isTurnoutTypeXover() || isTurnoutTypeSlip()) {
            setLayoutBlockB(models.provideLayoutBlock(name));
        } else {
            log.error("{}.setLayoutBlockBByName({}); not a crossover/slip", getName(), name);
        }
    }

    public void setLayoutBlockCByName(@Nonnull String name) {
        if (isTurnoutTypeXover() || isTurnoutTypeSlip()) {
            setLayoutBlockC(models.provideLayoutBlock(name));
        } else {
            log.error("{}.setLayoutBlockCByName({}); not a crossover/slip", getName(), name);
        }
    }

    public void setLayoutBlockDByName(@Nonnull String name) {
        if (isTurnoutTypeXover() || isTurnoutTypeSlip()) {
            setLayoutBlockD(models.provideLayoutBlock(name));
        } else {
            log.error("{}.setLayoutBlockDByName({}); not a crossover/slip", getName(), name);
        }
    }

    /**
     * Test if turnout legs are mainline track or not.
     *
     * @return true if connecting track segment is mainline; Defaults to not
     *         mainline if connecting track segment is missing
     */
    public boolean isMainlineA() {
        if (connectA != null) {
            return ((TrackSegment) connectA).isMainline();
        } else {
            // if no connection, depends on type of turnout
            if (isTurnoutTypeXover()) {
                // All crossovers - straight continuing is B
                if (connectB != null) {
                    return ((TrackSegment) connectB).isMainline();
                }
            } else if (isTurnoutTypeSlip()) {
                if (connectD != null) {
                    return ((TrackSegment) connectD).isMainline();
                }
            } // must be RH, LH, or WYE turnout - A is the switch throat
            else if (((connectB != null)
                    && (((TrackSegment) connectB).isMainline()))
                    || ((connectC != null)
                    && (((TrackSegment) connectC).isMainline()))) {
                return true;
            }
        }
        return false;
    }

    public boolean isMainlineB() {
        if (connectB != null) {
            return ((TrackSegment) connectB).isMainline();
        } else {
            // if no connection, depends on type of turnout
            if (isTurnoutTypeXover()) {
                // All crossovers - straight continuing is A
                if (connectA != null) {
                    return ((TrackSegment) connectA).isMainline();
                }
            } else if (getTurnoutType() == TurnoutType.DOUBLE_SLIP) {
                if (connectD != null) {
                    return ((TrackSegment) connectD).isMainline();
                }
            } // must be RH, LH, or WYE turnout - A is the switch throat,
            //      B is normally the continuing straight
            else if (continuingSense == Turnout.CLOSED) {
                // user hasn't changed the continuing turnout state
                if (connectA != null) { // if throat is mainline, this leg must be also
                    return ((TrackSegment) connectA).isMainline();
                }
            }
        }
        return false;
    }

    public boolean isMainlineC() {
        if (connectC != null) {
            return ((TrackSegment) connectC).isMainline();
        } else {
            // if no connection, depends on type of turnout
            if (isTurnoutTypeXover()) {
                // All crossovers - straight continuing is D
                if (connectD != null) {
                    return ((TrackSegment) connectD).isMainline();
                }
            } else if (getTurnoutType() == TurnoutType.DOUBLE_SLIP) {
                if (connectB != null) {
                    return ((TrackSegment) connectB).isMainline();
                }
            } // must be RH, LH, or WYE turnout - A is the switch throat,
            //      B is normally the continuing straight
            else if (continuingSense == Turnout.THROWN) {
                // user has changed the continuing turnout state
                if (connectA != null) { // if throat is mainline, this leg must be also
                    return ((TrackSegment) connectA).isMainline();
                }
            }
        }
        return false;
    }

    public boolean isMainlineD() {
        // this is a crossover turnout
        if (connectD != null) {
            return ((TrackSegment) connectD).isMainline();
        } else if (isTurnoutTypeSlip()) {
            if (connectB != null) {
                return ((TrackSegment) connectB).isMainline();
            }
        } else if (connectC != null) {
            return ((TrackSegment) connectC).isMainline();
        }
        return false;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isMainline() {
        return (isMainlineA() || isMainlineB() || isMainlineC() || isMainlineD());
    }

    /**
     * Activate/Deactivate turnout to redraw when turnout state changes
     */
    private void activateTurnout() {
        deactivateTurnout();
        if (namedTurnout != null) {
            namedTurnout.getBean().addPropertyChangeListener(
                    mTurnoutListener = (java.beans.PropertyChangeEvent e) -> {
                        if (e.getNewValue() == null) {
                            return;
                        }
                        if (disableWhenOccupied && isOccupied()) {
                            return;
                        }
                        if (secondNamedTurnout != null) {
                            int t1state = namedTurnout.getBean().getCommandedState();
                            int t2state = secondNamedTurnout.getBean().getCommandedState();
                            if (e.getSource().equals(namedTurnout.getBean())
                            && e.getNewValue().equals(t1state)) {
                                if (secondTurnoutInverted) {
                                    t1state = Turnout.invertTurnoutState(t1state);
                                }
                                if (secondNamedTurnout.getBean().getCommandedState() != t1state) {
                                    secondNamedTurnout.getBean().setCommandedState(t1state);
                                }
                            } else if (e.getSource().equals(secondNamedTurnout.getBean())
                            && e.getNewValue().equals(t2state)) {
                                if (secondTurnoutInverted) {
                                    t2state = Turnout.invertTurnoutState(t2state);
                                }
                                if (namedTurnout.getBean().getCommandedState() != t2state) {
                                    namedTurnout.getBean().setCommandedState(t2state);
                                }
                            }
                        }
                        models.redrawPanel();
                    },
                    namedTurnout.getName(),
                    "Layout Editor Turnout"
            );
        }
        if (secondNamedTurnout != null) {
            secondNamedTurnout.getBean().addPropertyChangeListener(mTurnoutListener, secondNamedTurnout.getName(), "Layout Editor Turnout");
        }
    }

    private void deactivateTurnout() {
        if (mTurnoutListener != null) {
            if (namedTurnout != null) {
                namedTurnout.getBean().removePropertyChangeListener(mTurnoutListener);
            }
            if (secondNamedTurnout != null) {
                secondNamedTurnout.getBean().removePropertyChangeListener(mTurnoutListener);
            }
            mTurnoutListener = null;
        }
    }

    /**
     * Toggle turnout if clicked on, physical turnout exists, and not disabled.
     */
    public void toggleTurnout() {
        if (getTurnout() != null) {
            // toggle turnout
            if (getTurnout().getCommandedState() == Turnout.CLOSED) {
                setState(Turnout.THROWN);
            } else {
                setState(Turnout.CLOSED);
            }
        } else {
            log.debug("Turnout Icon not associated with a Turnout");
        }
    }

    /**
     * Set the LayoutTurnout state. Used for sending the toggle command Checks
     * not disabled, disable when occupied Also sets secondary Turnout commanded
     * state
     *
     * @param state New state to set, eg Turnout.CLOSED
     */
    public void setState(int state) {
        if ((getTurnout() != null) && !disabled) {
            if (disableWhenOccupied && isOccupied()) {
                log.debug("Turnout not changed as Block is Occupied");
            } else {
                getTurnout().setCommandedState(state);
                Turnout secondTurnout = getSecondTurnout();
                if (secondTurnout != null) {
                    if (secondTurnoutInverted) {
                        secondTurnout.setCommandedState(Turnout.invertTurnoutState(state));
                    } else {
                        secondTurnout.setCommandedState(state);
                    }
                }
            }
        }
    }

    /**
     * Get the LayoutTurnout state
     * <p>
     * Ensures the secondary Turnout state matches the primary
     *
     * @return the state, eg Turnout.CLOSED or Turnout.INCONSISTENT
     */
    public int getState() {
        int result = UNKNOWN;
        if (getTurnout() != null) {
            result = getTurnout().getKnownState();
        }
        if (getSecondTurnout() != null) {
            int t2state = getSecondTurnout().getKnownState();
            if (secondTurnoutInverted) {
                t2state = Turnout.invertTurnoutState(t2state);
            }
            if (result != t2state) {
                return INCONSISTENT;
            }
        }
        return result;
    }

    /**
     * Is this turnout occupied?
     *
     * @return true if occupied
     */
    boolean isOccupied() {
        if (isTurnoutTypeTurnout()) {
            if (getLayoutBlock().getOccupancy() == LayoutBlock.OCCUPIED) {
                log.debug("Block {} is Occupied", getBlockName());
                return true;
            }
        } else if (isTurnoutTypeXover()) {
            // If the turnout is set for straight over, we need to deal with the straight over connecting blocks
            if (getTurnout().getKnownState() == Turnout.CLOSED) {
                if ((getLayoutBlock().getOccupancy() == LayoutBlock.OCCUPIED)
                        && (getLayoutBlockB().getOccupancy() == LayoutBlock.OCCUPIED)) {
                    log.debug("Blocks {} & {} are Occupied", getBlockName(), getBlockBName());
                    return true;
                }
                if ((getLayoutBlockC().getOccupancy() == LayoutBlock.OCCUPIED)
                        && (getLayoutBlockD().getOccupancy() == LayoutBlock.OCCUPIED)) {
                    log.debug("Blocks {} & {} are Occupied", getBlockCName(), getBlockDName());
                    return true;
                }
            }

        }
        if ((getTurnoutType() == TurnoutType.DOUBLE_XOVER)
                || (getTurnoutType() == TurnoutType.LH_XOVER)) {
            if (getTurnout().getKnownState() == Turnout.THROWN) {
                if ((getLayoutBlockB().getOccupancy() == LayoutBlock.OCCUPIED)
                        && (getLayoutBlockD().getOccupancy() == LayoutBlock.OCCUPIED)) {
                    log.debug("Blocks {} & {} are Occupied", getBlockBName(), getBlockDName());
                    return true;
                }
            }
        }

        if ((getTurnoutType() == TurnoutType.DOUBLE_XOVER)
                || (getTurnoutType() == TurnoutType.RH_XOVER)) {
            if (getTurnout().getKnownState() == Turnout.THROWN) {
                if ((getLayoutBlock().getOccupancy() == LayoutBlock.OCCUPIED)
                        && (getLayoutBlockC().getOccupancy() == LayoutBlock.OCCUPIED)) {
                    log.debug("Blocks {} & {} are Occupied", getLayoutBlock(), getBlockCName());
                    return true;
                }
            }
        }
        return false;
    }

    // initialization instance variables (used when loading a LayoutEditor)
    public String connectAName = "";
    public String connectBName = "";
    public String connectCName = "";
    public String connectDName = "";

    public String tBlockAName = "";
    public String tBlockBName = "";
    public String tBlockCName = "";
    public String tBlockDName = "";

    /**
     * Initialization method. The above variables are initialized by
     * LayoutTurnoutXml, then the following method is called after the entire
     * LayoutEditor is loaded to set the specific TrackSegment objects.
     */
    @Override
    public void setObjects(@Nonnull LayoutEditor p) {
        connectA = p.getFinder().findTrackSegmentByName(connectAName);
        connectB = p.getFinder().findTrackSegmentByName(connectBName);
        connectC = p.getFinder().findTrackSegmentByName(connectCName);
        connectD = p.getFinder().findTrackSegmentByName(connectDName);

        LayoutBlock lb;
        if (!tBlockAName.isEmpty()) {
            lb = p.provideLayoutBlock(tBlockAName);
            if (lb != null) {
                String userName = lb.getUserName();
                if (userName != null) {
                    namedLayoutBlockA = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(userName, lb);
                    lb.incrementUse();
                }
            } else {
                log.error("{}.setObjects(...); bad blockname A '{}'", getName(), tBlockAName);
                namedLayoutBlockA = null;
            }
            tBlockAName = null; // release this memory
        }

        if (!tBlockBName.isEmpty()) {
            lb = p.provideLayoutBlock(tBlockBName);
            if (lb != null) {
                String userName = lb.getUserName();
                if (userName != null) {
                    namedLayoutBlockB = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(userName, lb);
                }
                if (namedLayoutBlockB != namedLayoutBlockA) {
                    lb.incrementUse();
                }
            } else {
                log.error("{}.setObjects(...); bad blockname B '{}'", getName(), tBlockBName);
                namedLayoutBlockB = null;
            }
            tBlockBName = null; // release this memory
        }

        if (!tBlockCName.isEmpty()) {
            lb = p.provideLayoutBlock(tBlockCName);
            if (lb != null) {
                String userName = lb.getUserName();
                if (userName != null) {
                    namedLayoutBlockC = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(userName, lb);
                }
                if ((namedLayoutBlockC != namedLayoutBlockA)
                        && (namedLayoutBlockC != namedLayoutBlockB)) {
                    lb.incrementUse();
                }
            } else {
                log.error("{}.setObjects(...); bad blockname C '{}'", getName(), tBlockCName);
                namedLayoutBlockC = null;
            }
            tBlockCName = null; // release this memory
        }

        if (!tBlockDName.isEmpty()) {
            lb = p.provideLayoutBlock(tBlockDName);
            if (lb != null) {
                String userName = lb.getUserName();
                if (userName != null) {
                    namedLayoutBlockD = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(userName, lb);
                }
                if ((namedLayoutBlockD != namedLayoutBlockA)
                        && (namedLayoutBlockD != namedLayoutBlockB)
                        && (namedLayoutBlockD != namedLayoutBlockC)) {
                    lb.incrementUse();
                }
            } else {
                log.error("{}.setObjects(...); bad blockname D '{}'", getName(), tBlockDName);
                namedLayoutBlockD = null;
            }
            tBlockDName = null; // release this memory
        }
        activateTurnout();
    } // setObjects

    public String[] getBlockBoundaries() {
        final String[] boundaryBetween = new String[4];
        if (isTurnoutTypeTurnout()) {
            // This should only be needed where we are looking at a single turnout.
            if (getLayoutBlock() != null) {
                LayoutBlock aLBlock = null;
                if (connectA instanceof TrackSegment) {
                    aLBlock = ((TrackSegment) connectA).getLayoutBlock();
                    if (aLBlock != getLayoutBlock()) {
                        try {
                            boundaryBetween[0] = (aLBlock.getDisplayName() + " - " + getLayoutBlock().getDisplayName());
                        } catch (java.lang.NullPointerException e) {
                            // Can be considered normal if tracksegement hasn't yet been allocated a block
                            log.debug("TrackSegement at connection A doesn't contain a layout block");
                        }
                    }
                }

                LayoutBlock bLBlock = null;
                if (connectB instanceof TrackSegment) {
                    bLBlock = ((TrackSegment) connectB).getLayoutBlock();
                    if (bLBlock != getLayoutBlock()) {
                        try {
                            boundaryBetween[1] = (bLBlock.getDisplayName() + " - " + getLayoutBlock().getDisplayName());
                        } catch (java.lang.NullPointerException e) {
                            // Can be considered normal if tracksegement hasn't yet been allocated a block
                            log.debug("TrackSegement at connection B doesn't contain a layout block");
                        }
                    }
                }

                LayoutBlock cLBlock = null;
                if ((connectC instanceof TrackSegment)
                        && (((TrackSegment) connectC).getLayoutBlock() != getLayoutBlock())) {
                    cLBlock = ((TrackSegment) connectC).getLayoutBlock();
                    if (cLBlock != getLayoutBlock()) {
                        try {
                            boundaryBetween[2] = (cLBlock.getDisplayName() + " - " + getLayoutBlock().getDisplayName());
                        } catch (java.lang.NullPointerException e) {
                            // Can be considered normal if tracksegement hasn't yet been allocated a block
                            log.debug("TrackSegement at connection C doesn't contain a layout block");
                        }
                    }
                }
            }
        } else {
            LayoutBlock aLBlock = null;
            LayoutBlock bLBlock = null;
            LayoutBlock cLBlock = null;
            LayoutBlock dLBlock = null;
            if (getLayoutBlock() != null) {
                if (connectA instanceof TrackSegment) {
                    aLBlock = ((TrackSegment) connectA).getLayoutBlock();
                    if (aLBlock != getLayoutBlock()) {
                        try {
                            boundaryBetween[0] = (aLBlock.getDisplayName() + " - " + getLayoutBlock().getDisplayName());
                        } catch (java.lang.NullPointerException e) {
                            // Can be considered normal if tracksegement hasn't yet been allocated a block
                            log.debug("TrackSegement at connection A doesn't contain a layout block");
                        }
                    } else if (getLayoutBlock() != getLayoutBlockB()) {
                        try {
                            boundaryBetween[0] = (getLayoutBlock().getDisplayName() + " - " + getLayoutBlockB().getDisplayName());
                        } catch (java.lang.NullPointerException e) {
                            // Can be considered normal if tracksegement hasn't yet been allocated a block
                            log.debug("TrackSegement at connection A doesn't contain a layout block");
                        }
                    }
                }

                if (connectB instanceof TrackSegment) {
                    bLBlock = ((TrackSegment) connectB).getLayoutBlock();

                    if (bLBlock != getLayoutBlock() && bLBlock != getLayoutBlockB()) {
                        try {
                            boundaryBetween[1] = (bLBlock.getDisplayName() + " - " + getLayoutBlockB().getDisplayName());
                        } catch (java.lang.NullPointerException e) {
                            // Can be considered normal if tracksegement hasn't yet been allocated a block
                            log.debug("TrackSegement at connection B doesn't contain a layout block");
                        }
                    } else if (getLayoutBlock() != getLayoutBlockB()) {
                        // This is an interal block on the turnout
                        try {
                            boundaryBetween[1] = (getLayoutBlockB().getDisplayName() + " - " + getLayoutBlock().getDisplayName());
                        } catch (java.lang.NullPointerException e) {
                            // Can be considered normal if tracksegement hasn't yet been allocated a block
                            log.debug("TrackSegement at connection A doesn't contain a layout block");
                        }
                    }
                }

                if (connectC instanceof TrackSegment) {
                    cLBlock = ((TrackSegment) connectC).getLayoutBlock();
                    if (cLBlock != getLayoutBlock() && cLBlock != getLayoutBlockB() && cLBlock != getLayoutBlockC()) {
                        try {
                            boundaryBetween[2] = (cLBlock.getDisplayName() + " - " + getLayoutBlockC().getDisplayName());
                        } catch (java.lang.NullPointerException e) {
                            // Can be considered normal if tracksegement hasn't yet been allocated a block
                            log.debug("TrackSegement at connection C doesn't contain a layout block");
                        }
                    } else if (getLayoutBlockC() != getLayoutBlockD()) {
                        // This is an interal block on the turnout
                        try {
                            boundaryBetween[2] = (getLayoutBlockC().getDisplayName() + " - " + getLayoutBlockD().getDisplayName());
                        } catch (java.lang.NullPointerException e) {
                            // Can be considered normal if tracksegement hasn't yet been allocated a block
                            log.debug("TrackSegement at connection A doesn't contain a layout block");
                        }
                    }
                }

                if (connectD instanceof TrackSegment) {
                    dLBlock = ((TrackSegment) connectD).getLayoutBlock();
                    if (dLBlock != getLayoutBlock() && dLBlock != getLayoutBlockB() && dLBlock != getLayoutBlockC() && dLBlock != getLayoutBlockD()) {
                        try {
                            boundaryBetween[3] = (dLBlock.getDisplayName() + " - " + getLayoutBlockD().getDisplayName());
                        } catch (java.lang.NullPointerException e) {
                            // Can be considered normal if tracksegement hasn't yet been allocated a block
                            log.debug("TrackSegement at connection C doesn't contain a layout block");
                        }
                    } else if (getLayoutBlockC() != getLayoutBlockD()) {
                        // This is an interal block on the turnout
                        try {
                            boundaryBetween[3] = (getLayoutBlockD().getDisplayName() + " - " + getLayoutBlockC().getDisplayName());
                        } catch (java.lang.NullPointerException e) {
                            // Can be considered normal if tracksegement hasn't yet been allocated a block
                            log.debug("TrackSegement at connection A doesn't contain a layout block");
                        }
                    }
                }
            }

        }
        return boundaryBetween;
    }   // getBlockBoundaries

    @Nonnull
    public ArrayList<LayoutBlock> getProtectedBlocks(jmri.NamedBean bean) {
        ArrayList<LayoutBlock> ret = new ArrayList<>(2);
        if (getLayoutBlock() == null) {
            return ret;
        }
        if (isTurnoutTypeXover()) {
            if ((getTurnoutType() == TurnoutType.DOUBLE_XOVER || getTurnoutType() == TurnoutType.RH_XOVER)
                    && (getSignalAMast() == bean || getSignalCMast() == bean || getSensorA() == bean || getSensorC() == bean)) {
                if (getSignalAMast() == bean || getSensorA() == bean) {
                    if (connectA != null) {
                        if (((TrackSegment) connectA).getLayoutBlock() == getLayoutBlock()) {
                            if (getLayoutBlockB() != null && getLayoutBlock() != getLayoutBlockB() && getLayoutBlockC() != null && getLayoutBlock() != getLayoutBlockC()) {
                                ret.add(getLayoutBlockB());
                                ret.add(getLayoutBlockC());
                            }
                        } else {
                            ret.add(getLayoutBlock());
                        }
                    }
                } else {
                    if (connectC != null && getLayoutBlockC() != null) {
                        if (((TrackSegment) connectC).getLayoutBlock() == getLayoutBlockC()) {
                            if (getLayoutBlockC() != getLayoutBlock() && getLayoutBlockD() != null && getLayoutBlockC() != getLayoutBlockD()) {
                                ret.add(getLayoutBlock());
                                ret.add(getLayoutBlockD());
                            }
                        } else {
                            ret.add(getLayoutBlockC());
                        }
                    }
                }
            }
            if ((getTurnoutType() == TurnoutType.DOUBLE_XOVER || getTurnoutType() == TurnoutType.LH_XOVER)
                    && (getSignalBMast() == bean || getSignalDMast() == bean || getSensorB() == bean || getSensorD() == bean)) {
                if (getSignalBMast() == bean || getSensorB() == bean) {
                    if (connectB != null && getLayoutBlockB() != null) {
                        if (((TrackSegment) connectB).getLayoutBlock() == getLayoutBlockB()) {
                            if (getLayoutBlock() != getLayoutBlockB() && getLayoutBlockD() != null && getLayoutBlockB() != getLayoutBlockD()) {
                                ret.add(getLayoutBlock());
                                ret.add(getLayoutBlockD());
                            }
                        } else {
                            ret.add(getLayoutBlockB());
                        }
                    }
                } else {
                    if (connectD != null && getLayoutBlockD() != null) {
                        if (((TrackSegment) connectD).getLayoutBlock() == getLayoutBlockD()) {
                            if (getLayoutBlockB() != null && getLayoutBlockB() != getLayoutBlockD() && getLayoutBlockC() != null && getLayoutBlockC() != getLayoutBlockD()) {
                                ret.add(getLayoutBlockB());
                                ret.add(getLayoutBlockC());
                            }
                        } else {
                            ret.add(getLayoutBlockD());
                        }
                    }
                }
            }
            if (getTurnoutType() == TurnoutType.RH_XOVER && (getSignalBMast() == bean
                    || getSignalDMast() == bean || getSensorB() == bean || getSensorD() == bean)) {
                if (getSignalBMast() == bean || getSensorB() == bean) {
                    if (connectB != null && ((TrackSegment) connectB).getLayoutBlock() == getLayoutBlockB()) {
                        if (getLayoutBlockB() != getLayoutBlock()) {
                            ret.add(getLayoutBlock());
                        }
                    } else {
                        ret.add(getLayoutBlockB());
                    }
                } else {
                    if (connectD != null && ((TrackSegment) connectD).getLayoutBlock() == getLayoutBlockD()) {
                        if (getLayoutBlockC() != getLayoutBlockD()) {
                            ret.add(getLayoutBlockC());
                        }
                    } else {
                        ret.add(getLayoutBlockD());
                    }
                }
            }
            if (getTurnoutType() == TurnoutType.LH_XOVER && (getSensorA() == bean
                    || getSensorC() == bean || getSignalAMast() == bean || getSignalCMast() == bean)) {
                if (getSignalAMast() == bean || getSensorA() == bean) {
                    if (connectA != null && ((TrackSegment) connectA).getLayoutBlock() == getLayoutBlock()) {
                        if (getLayoutBlockB() != getLayoutBlock()) {
                            ret.add(getLayoutBlockB());
                        }
                    } else {
                        ret.add(getLayoutBlock());
                    }
                } else {
                    if (connectC != null && ((TrackSegment) connectC).getLayoutBlock() == getLayoutBlockC()) {
                        if (getLayoutBlockC() != getLayoutBlockD()) {
                            ret.add(getLayoutBlockD());
                        }
                    } else {
                        ret.add(getLayoutBlockC());
                    }
                }
            }
        } else {
            if (connectA != null) {
                if (getSignalAMast() == bean || getSensorA() == bean) {
                    // Mast at throat
                    // if the turnout is in the same block as the segment connected at the throat, then we can be protecting two blocks
                    if (((TrackSegment) connectA).getLayoutBlock() == getLayoutBlock()) {
                        if (connectB != null && connectC != null) {
                            if (((TrackSegment) connectB).getLayoutBlock() != getLayoutBlock()
                                    && ((TrackSegment) connectC).getLayoutBlock() != getLayoutBlock()) {
                                ret.add(((TrackSegment) connectB).getLayoutBlock());
                                ret.add(((TrackSegment) connectC).getLayoutBlock());
                            }
                        }
                    } else {
                        ret.add(getLayoutBlock());
                    }
                } else if (getSignalBMast() == bean || getSensorB() == bean) {
                    // Mast at Continuing
                    if (connectB != null && ((TrackSegment) connectB).getLayoutBlock() == getLayoutBlock()) {
                        if (((TrackSegment) connectA).getLayoutBlock() != getLayoutBlock()) {
                            ret.add(((TrackSegment) connectA).getLayoutBlock());
                        }
                    } else {
                        ret.add(getLayoutBlock());
                    }
                } else if (getSignalCMast() == bean || getSensorC() == bean) {
                    // Mast at Diverging
                    if (connectC != null && ((TrackSegment) connectC).getLayoutBlock() == getLayoutBlock()) {
                        if (((TrackSegment) connectA).getLayoutBlock() != getLayoutBlock()) {
                            ret.add(((TrackSegment) connectA).getLayoutBlock());
                        }
                    } else {
                        ret.add(getLayoutBlock());
                    }
                }
            }
        }
        return ret;
    }   // getProtectedBlocks

    protected void removeSML(@CheckForNull SignalMast signalMast) {
        if (signalMast == null) {
            return;

        }
        if (jmri.InstanceManager.getDefault(LayoutBlockManager.class).isAdvancedRoutingEnabled()
                && InstanceManager.getDefault(jmri.SignalMastLogicManager.class).isSignalMastUsed(signalMast)) {

            SignallingGuiTools.removeSignalMastLogic(null, signalMast);
        }
    }

    /**
     * Remove this object from display and persistance.
     */
    public void remove() {
        // if a turnout has been activated, deactivate it
        deactivateTurnout();
        // remove from persistance by flagging inactive
        active = false;
    }

    boolean active = true;

    /**
     * "active" means that the object is still displayed, and should be stored.
     * @return true if active, else false.
     */
    public boolean isActive() {
        return active;
    }


    /*
    * Used by ConnectivityUtil to determine the turnout state necessary to get
    * from prevLayoutBlock ==> currLayoutBlock ==> nextLayoutBlock
     */
    protected int getConnectivityStateForLayoutBlocks(
            LayoutBlock currLayoutBlock,
            LayoutBlock prevLayoutBlock,
            LayoutBlock nextLayoutBlock,
            boolean suppress) {
        int result = UNKNOWN;

        LayoutBlock layoutBlockA = ((TrackSegment) getConnectA()).getLayoutBlock();
        LayoutBlock layoutBlockB = ((TrackSegment) getConnectB()).getLayoutBlock();
        LayoutBlock layoutBlockC = ((TrackSegment) getConnectC()).getLayoutBlock();
        // TODO: Determine if this should be being used
        // LayoutBlock layoutBlockD = ((TrackSegment) getConnectD()).getLayoutBlock();

        TurnoutType tTyp = getTurnoutType();
        switch (tTyp) {
            case RH_TURNOUT:
            case LH_TURNOUT:
            case WYE_TURNOUT: {
                if (layoutBlockA == currLayoutBlock) {
                    if ((layoutBlockC == nextLayoutBlock) || (layoutBlockC == prevLayoutBlock)) {
                        result = Turnout.THROWN;
                    } else if ((layoutBlockB == nextLayoutBlock) || (layoutBlockB == prevLayoutBlock)) {
                        result = Turnout.CLOSED;
                    } else if (layoutBlockB == currLayoutBlock) {
                        result = Turnout.CLOSED;
                    } else if (layoutBlockC == currLayoutBlock) {
                        result = Turnout.THROWN;
                    } else {
                        if (!suppress) {
                            log.error("{}.getConnectivityStateForLayoutBlocks(...); Cannot determine turnout setting for {}",
                                    getName(), getTurnoutName());
                        }
                        result = Turnout.CLOSED;
                    }
                } else if (layoutBlockB == currLayoutBlock) {
                    result = Turnout.CLOSED;
                } else if (layoutBlockC == currLayoutBlock) {
                    result = Turnout.THROWN;
                } else {
                    if (!suppress) {
                        log.debug("lb {} nlb {} connect B {} connect C {}", currLayoutBlock, nextLayoutBlock, layoutBlockB, layoutBlockC);
                        log.error("{}.getConnectivityStateForLayoutBlocks(...); Cannot determine turnout setting for {}",
                                getName(), getTurnoutName());
                    }
                    result = Turnout.CLOSED;
                }
                break;
            }
            case RH_XOVER:
            case LH_XOVER:
            case DOUBLE_XOVER: {
                if (getLayoutBlock() == currLayoutBlock) {
                    if ((tTyp != TurnoutType.LH_XOVER)
                            && ((getLayoutBlockC() == nextLayoutBlock)
                            || (getLayoutBlockC() == prevLayoutBlock))) {
                        result = Turnout.THROWN;
                    } else if ((getLayoutBlockB() == nextLayoutBlock) || (getLayoutBlockB() == prevLayoutBlock)) {
                        result = Turnout.CLOSED;
                    } else if (getLayoutBlockB() == currLayoutBlock) {
                        result = Turnout.CLOSED;
                    } else if ((tTyp != LayoutTurnout.TurnoutType.LH_XOVER)
                            && (getLayoutBlockC() == currLayoutBlock)) {
                        result = Turnout.THROWN;
                    } else {
                        if (!suppress) {
                            log.error("{}.getConnectivityStateForLayoutBlocks(...); Cannot determine turnout setting for {}",
                                    getName(), getTurnoutName());
                        }
                        result = Turnout.CLOSED;
                    }
                } else if (getLayoutBlockB() == currLayoutBlock) {
                    if ((getLayoutBlock() == nextLayoutBlock) || (getLayoutBlock() == prevLayoutBlock)) {
                        result = Turnout.CLOSED;
                    } else if ((tTyp != TurnoutType.RH_XOVER)
                            && ((getLayoutBlockD() == nextLayoutBlock)
                            || (getLayoutBlockD() == prevLayoutBlock) || (getLayoutBlockD() == currLayoutBlock))) {
                        result = Turnout.THROWN;
                    } else {
                        if (!suppress) {
                            log.error("{}.getConnectivityStateForLayoutBlocks(...); Cannot determine turnout setting for {}",
                                    getName(), getTurnoutName());
                        }
                        result = Turnout.CLOSED;
                    }
                } else if (getLayoutBlockC() == currLayoutBlock) {
                    if ((tTyp != TurnoutType.LH_XOVER)
                            && ((getLayoutBlock() == nextLayoutBlock) || (getLayoutBlock() == prevLayoutBlock))) {
                        result = Turnout.THROWN;
                    } else if ((getLayoutBlockD() == nextLayoutBlock) || (getLayoutBlockD() == prevLayoutBlock) || (getLayoutBlockD() == currLayoutBlock)) {
                        result = Turnout.CLOSED;
                    } else if ((tTyp != TurnoutType.LH_XOVER)
                            && (getLayoutBlockD() == currLayoutBlock)) {
                        result = Turnout.THROWN;
                    } else {
                        if (!suppress) {
                            log.error("{}.getConnectivityStateForLayoutBlocks(...); Cannot determine turnout setting for {}",
                                    getName(), getTurnoutName());
                        }
                        result = Turnout.CLOSED;
                    }
                } else if (getLayoutBlockD() == currLayoutBlock) {
                    if ((getLayoutBlockC() == nextLayoutBlock) || (getLayoutBlockC() == prevLayoutBlock)) {
                        result = Turnout.CLOSED;
                    } else if ((tTyp != TurnoutType.RH_XOVER)
                            && ((getLayoutBlockB() == nextLayoutBlock) || (getLayoutBlockB() == prevLayoutBlock))) {
                        result = Turnout.THROWN;
                    } else {
                        if (!suppress) {
                            log.error("{}.getConnectivityStateForLayoutBlocks(...); Cannot determine turnout setting for {}",
                                    getName(), getTurnoutName());
                        }
                        result = Turnout.CLOSED;
                    }
                }
                break;
            }
            default: {
                log.warn("{}.getConnectivityStateForLayoutBlocks(...) unknown getTurnoutType: {}", getName(), tTyp);
                break;
            }
        }   // switch (tTyp)

        return result;
    }   // getConnectivityStateForLayoutBlocks

    /**
     * {@inheritDoc}
     */
    // TODO: on the cross-overs, check the internal boundary details.
    @Override
    public void reCheckBlockBoundary() {
        if (connectA == null && connectB == null && connectC == null) {
            if (isTurnoutTypeTurnout()) {
                if (signalAMastNamed != null) {
                    removeSML(getSignalAMast());
                }
                if (signalBMastNamed != null) {
                    removeSML(getSignalBMast());
                }
                if (signalCMastNamed != null) {
                    removeSML(getSignalCMast());
                }
                signalAMastNamed = null;
                signalBMastNamed = null;
                signalCMastNamed = null;
                sensorANamed = null;
                sensorBNamed = null;
                sensorCNamed = null;
                return;
            } else if (isTurnoutTypeXover() && connectD == null) {
                if (signalAMastNamed != null) {
                    removeSML(getSignalAMast());
                }
                if (signalBMastNamed != null) {
                    removeSML(getSignalBMast());
                }
                if (signalCMastNamed != null) {
                    removeSML(getSignalCMast());
                }
                if (signalDMastNamed != null) {
                    removeSML(getSignalDMast());
                }
                signalAMastNamed = null;
                signalBMastNamed = null;
                signalCMastNamed = null;
                signalDMastNamed = null;
                sensorANamed = null;
                sensorBNamed = null;
                sensorCNamed = null;
                sensorDNamed = null;
                return;
            }
        }

        if (connectA == null || connectB == null || connectC == null) {
            // could still be in the process of rebuilding.
            return;
        } else if ((connectD == null) && isTurnoutTypeXover()) {
            // could still be in the process of rebuilding.
            return;
        }

        TrackSegment trkA;
        TrackSegment trkB;
        TrackSegment trkC;
        TrackSegment trkD;

        if (connectA instanceof TrackSegment) {
            trkA = (TrackSegment) connectA;
            if (trkA.getLayoutBlock() == getLayoutBlock()) {
                if (signalAMastNamed != null) {
                    removeSML(getSignalAMast());
                }
                signalAMastNamed = null;
                sensorANamed = null;
            }
        }
        if (connectB instanceof TrackSegment) {
            trkB = (TrackSegment) connectB;
            if (trkB.getLayoutBlock() == getLayoutBlock() || trkB.getLayoutBlock() == getLayoutBlockB()) {
                if (signalBMastNamed != null) {
                    removeSML(getSignalBMast());
                }
                signalBMastNamed = null;
                sensorBNamed = null;

            }
        }
        if (connectC instanceof TrackSegment) {
            trkC = (TrackSegment) connectC;
            if (trkC.getLayoutBlock() == getLayoutBlock()
                    || trkC.getLayoutBlock() == getLayoutBlockB()
                    || trkC.getLayoutBlock() == getLayoutBlockC()) {
                if (signalCMastNamed != null) {
                    removeSML(getSignalCMast());
                }
                signalCMastNamed = null;
                sensorCNamed = null;

            }
        }
        if (connectD != null && connectD instanceof TrackSegment
                && isTurnoutTypeXover()) {
            trkD = (TrackSegment) connectD;
            if (trkD.getLayoutBlock() == getLayoutBlock()
                    || trkD.getLayoutBlock() == getLayoutBlockB()
                    || trkD.getLayoutBlock() == getLayoutBlockC()
                    || trkD.getLayoutBlock() == getLayoutBlockD()) {
                if (signalDMastNamed != null) {
                    removeSML(getSignalDMast());
                }
                signalDMastNamed = null;
                sensorDNamed = null;
            }
        }
    }   // reCheckBlockBoundary

    /**
     * {@inheritDoc}
     */
    @Override
    @Nonnull
    protected List<LayoutConnectivity> getLayoutConnectivity() {
        List<LayoutConnectivity> results = new ArrayList<>();

        log.trace("Start in layoutTurnout.getLayoutConnectivity for {}", getName());

        LayoutConnectivity lc = null;

        LayoutBlock lbA = getLayoutBlock(), lbB = getLayoutBlockB(), lbC = getLayoutBlockC(), lbD = getLayoutBlockD();

        log.trace("    type: {}", type);
        log.trace("     lbA: {}", lbA);
        log.trace("     lbB: {}", lbB);
        log.trace("     lbC: {}", lbC);
        log.trace("     lbD: {}", lbD);

        if (hasEnteringDoubleTrack() && (lbA != null)) {
            // have a crossover turnout with at least one block, check for multiple blocks
            if ((lbA != lbB) || (lbA != lbC) || (lbA != lbD)) {
                // have multiple blocks and therefore internal block boundaries
                if (lbA != lbB) {
                    // have a AB block boundary, create a LayoutConnectivity
                    log.debug("Block boundary  ('{}'<->'{}') found at {}", lbA, lbB, this);
                    lc = new LayoutConnectivity(lbA, lbB);
                    lc.setXoverBoundary(this, LayoutConnectivity.XOVER_BOUNDARY_AB);

                    // The following line needed to change, because it uses location of
                    // the points on the TurnoutView itself. Change to
                    // direction from connections.
                    //lc.setDirection(Path.computeDirection(getCoordsA(), getCoordsB()));
                    lc.setDirection(models.computeDirectionAB(this));

                    log.trace("getLayoutConnectivity lbA != lbB");
                    log.trace("   Block boundary  ('{}'<->'{}') found at {}", lbA, lbB, this);

                    results.add(lc);
                }
                if ((getTurnoutType() != TurnoutType.LH_XOVER) && (lbA != lbC)) {
                    // have a AC block boundary, create a LayoutConnectivity
                    log.debug("Block boundary  ('{}'<->'{}') found at {}", lbA, lbC, this);
                    lc = new LayoutConnectivity(lbA, lbC);
                    lc.setXoverBoundary(this, LayoutConnectivity.XOVER_BOUNDARY_AC);

                    // The following line needed to change, because it uses location of
                    // the points on the TurnoutView itself. Change to
                    // direction from connections.
                    //lc.setDirection(Path.computeDirection(getCoordsA(), getCoordsC()));
                    lc.setDirection(models.computeDirectionAC(this));

                    log.trace("getLayoutConnectivity lbA != lbC");
                    log.trace("   Block boundary  ('{}'<->'{}') found at {}", lbA, lbC, this);

                    results.add(lc);
                }
                if (lbC != lbD) {
                    // have a CD block boundary, create a LayoutConnectivity
                    log.debug("Block boundary  ('{}'<->'{}') found at {}", lbC, lbD, this);
                    lc = new LayoutConnectivity(lbC, lbD);
                    lc.setXoverBoundary(this, LayoutConnectivity.XOVER_BOUNDARY_CD);

                    // The following line needed to change, because it uses location of
                    // the points on the TurnoutView itself. Change to
                    // direction from connections.
                    //lc.setDirection(Path.computeDirection(getCoordsC(), getCoordsD()));
                    lc.setDirection(models.computeDirectionCD(this));

                    log.trace("getLayoutConnectivity lbC != lbD");
                    log.trace("   Block boundary  ('{}'<->'{}') found at {}", lbC, lbD, this);

                    results.add(lc);
                }
                if ((getTurnoutType() != TurnoutType.RH_XOVER) && (lbB != lbD)) {
                    // have a BD block boundary, create a LayoutConnectivity
                    log.debug("Block boundary  ('{}'<->'{}') found at {}", lbB, lbD, this);
                    lc = new LayoutConnectivity(lbB, lbD);
                    lc.setXoverBoundary(this, LayoutConnectivity.XOVER_BOUNDARY_BD);

                    // The following line needed to change, because it uses location of
                    // the points on the TurnoutView itself. Change to
                    // direction from connections.
                    //lc.setDirection(Path.computeDirection(getCoordsB(), getCoordsD()));
                    lc.setDirection(models.computeDirectionBD(this));

                    log.trace("getLayoutConnectivity lbB != lbD");
                    log.trace("   Block boundary  ('{}'<->'{}') found at {}", lbB, lbD, this);

                    results.add(lc);
                }
            }
        }
        return results;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @Nonnull
    public List<HitPointType> checkForFreeConnections() {
        List<HitPointType> result = new ArrayList<>();

        // check the A connection point
        if (getConnectA() == null) {
            result.add(HitPointType.TURNOUT_A);
        }

        // check the B connection point
        if (getConnectB() == null) {
            result.add(HitPointType.TURNOUT_B);
        }

        // check the C connection point
        if (getConnectC() == null) {
            result.add(HitPointType.TURNOUT_C);
        }

        // check the D connection point
        if (isTurnoutTypeXover()) {
            if (getConnectD() == null) {
                result.add(HitPointType.TURNOUT_D);
            }
        }
        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean checkForUnAssignedBlocks() {
        // because getLayoutBlock[BCD] will return block [A] if they're null
        // we only need to test block [A]
        return (getLayoutBlock() != null);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void checkForNonContiguousBlocks(
            @Nonnull HashMap<String, List<Set<String>>> blockNamesToTrackNameSetsMap) {
        /*
                * For each (non-null) blocks of this track do:
                * #1) If it's got an entry in the blockNamesToTrackNameSetMap then
                * #2) If this track is already in the TrackNameSet for this block
                *     then return (done!)
                * #3) else add a new set (with this block/track) to
                *     blockNamesToTrackNameSetMap and check all the connections in this
                *     block (by calling the 2nd method below)
                * <p>
                *     Basically, we're maintaining contiguous track sets for each block found
                *     (in blockNamesToTrackNameSetMap)
         */

        // We're only using a map here because it's convient to
        // use it to pair up blocks and connections
        Map<LayoutTrack, String> blocksAndTracksMap = new HashMap<>();
        if (connectA != null) {
            blocksAndTracksMap.put(connectA, getBlockName());
        }
        if (connectB != null) {
            blocksAndTracksMap.put(connectB, getBlockBName());
        }
        if (connectC != null) {
            blocksAndTracksMap.put(connectC, getBlockCName());
        }
        if (isTurnoutTypeXover() || isTurnoutTypeSlip()) {
            if (connectD != null) {
                blocksAndTracksMap.put(connectD, getBlockDName());
            }
        }
        List<Set<String>> TrackNameSets = null;
        Set<String> TrackNameSet = null;
        for (Map.Entry<LayoutTrack, String> entry : blocksAndTracksMap.entrySet()) {
            LayoutTrack theConnect = entry.getKey();
            String theBlockName = entry.getValue();

            TrackNameSet = null;    // assume not found (pessimist!)
            TrackNameSets = blockNamesToTrackNameSetsMap.get(theBlockName);
            if (TrackNameSets != null) { // (#1)
                for (Set<String> checkTrackNameSet : TrackNameSets) {
                    if (checkTrackNameSet.contains(getName())) { // (#2)
                        TrackNameSet = checkTrackNameSet;
                        break;
                    }
                }
            } else {    // (#3)
                log.debug("*New block ('{}') trackNameSets", theBlockName);
                TrackNameSets = new ArrayList<>();
                blockNamesToTrackNameSetsMap.put(theBlockName, TrackNameSets);
            }
            if (TrackNameSet == null) {
                TrackNameSet = new LinkedHashSet<>();
                TrackNameSets.add(TrackNameSet);
            }
            if (TrackNameSet.add(getName())) {
                log.debug("*    Add track '{}' to trackNameSet for block '{}'", getName(), theBlockName);
            }
            theConnect.collectContiguousTracksNamesInBlockNamed(theBlockName, TrackNameSet);
        }
    }   // collectContiguousTracksNamesInBlockNamed

    /**
     * {@inheritDoc}
     */
    @Override
    public void collectContiguousTracksNamesInBlockNamed(
            @Nonnull String blockName,
            @Nonnull Set<String> TrackNameSet) {
        if (!TrackNameSet.contains(getName())) {

            // create list of our connects
            List<LayoutTrack> connects = new ArrayList<>();
            if (getBlockName().equals(blockName)
                    && (connectA != null)) {
                connects.add(connectA);
            }
            if (getBlockBName().equals(blockName)
                    && (connectB != null)) {
                connects.add(connectB);
            }
            if (getBlockCName().equals(blockName)
                    && (connectC != null)) {
                connects.add(connectC);
            }
            if (isTurnoutTypeXover() || isTurnoutTypeSlip()) {
                if (getBlockDName().equals(blockName)
                        && (connectD != null)) {
                    connects.add(connectD);
                }
            }

            for (LayoutTrack connect : connects) {
                // if we are added to the TrackNameSet
                if (TrackNameSet.add(getName())) {
                    log.debug("*    Add track '{}' for block '{}'", getName(), blockName);
                }
                // it's time to play... flood your neighbour!
                connect.collectContiguousTracksNamesInBlockNamed(blockName, TrackNameSet);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setAllLayoutBlocks(LayoutBlock layoutBlock) {
        setLayoutBlock(layoutBlock);
        if (isTurnoutTypeXover() || isTurnoutTypeSlip()) {
            setLayoutBlockB(layoutBlock);
            setLayoutBlockC(layoutBlock);
            setLayoutBlockD(layoutBlock);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getTypeName() {
        return Bundle.getMessage("TypeName_Turnout");
    }

    // Tooltip support
    private ToolTip _tooltip;
    private boolean _showTooltip;

    public void setShowToolTip(boolean set) {
        _showTooltip = set;
    }

    public boolean showToolTip() {
        return _showTooltip;
    }

    public void setToolTip(ToolTip tip) {
        _tooltip = tip;
    }

    public ToolTip getToolTip() {
        return _tooltip;
    }

    @Nonnull
    public  String getNameString() {
        var turnout = getTurnout();
        if (turnout != null) {
            return turnout.getDisplayName(jmri.NamedBean.DisplayOptions.USERNAME_SYSTEMNAME);
        }
        return getId();
    }

    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LayoutTurnout.class);
}