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

Summary

Maintainability
F
2 mos
Test Coverage
F
33%
package jmri.jmrit.display.layoutEditor;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javax.annotation.CheckForNull;
import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnull;

import jmri.Block;
import jmri.BlockManager;
import jmri.jmrit.display.EditorManager;
import jmri.InstanceManager;
import jmri.Memory;
import jmri.NamedBean;
import jmri.NamedBeanHandle;
import jmri.Sensor;
import jmri.SignalHead;
import jmri.SignalMast;
import jmri.Turnout;
import jmri.jmrit.roster.RosterEntry;
import jmri.jmrix.internal.InternalSystemConnectionMemo;
import jmri.managers.AbstractManager;
import jmri.util.swing.JmriJOptionPane;

/**
 * Implementation of a Manager to handle LayoutBlocks. Note: the same
 * LayoutBlocks may appear in multiple LayoutEditor panels.
 * <p>
 * This manager does not enforce any particular system naming convention.
 * <p>
 * LayoutBlocks are usually addressed by userName. The systemName is hidden from
 * the user for the most part.
 *
 * @author Dave Duchamp Copyright (C) 2007
 * @author George Warner Copyright (c) 2017-2018
 */
public class LayoutBlockManager extends AbstractManager<LayoutBlock> implements jmri.InstanceManagerAutoDefault {

    public LayoutBlockManager() {
        super(InstanceManager.getDefault(InternalSystemConnectionMemo.class));
        InstanceManager.sensorManagerInstance().addVetoableChangeListener(this);
        InstanceManager.memoryManagerInstance().addVetoableChangeListener(this);
    }

    @Override
    public int getXMLOrder() {
        return jmri.Manager.LAYOUTBLOCKS;
    }

    @Override
    public char typeLetter() {
        return 'B';
    }
    private int blkNum = 1;

    /**
     * Create a new LayoutBlock if the LayoutBlock does not exist.
     * <p>
     * Note that since the userName is used to address LayoutBlocks, the user
     * name must be present. If the user name is not present, the new
     * LayoutBlock is not created, and null is returned.
     *
     * @param systemName block system name.
     * @param userName block username, must be non-empty.
     * @return null if a LayoutBlock with the same systemName or userName
     *         already exists, or if there is trouble creating a new LayoutBlock
     */
    @CheckReturnValue
    @CheckForNull
    public LayoutBlock createNewLayoutBlock(
            @CheckForNull String systemName,
            String userName) {
        // Check that LayoutBlock does not already exist
        LayoutBlock result = null;

        if ((userName == null) || userName.isEmpty()) {
            log.error("Attempt to create a LayoutBlock with no user name");

            return null;
        }
        result = getByUserName(userName);

        if (result != null) {
            return null;
        }

        // here if not found under user name
        String sName = "";

        if (systemName == null) {
            //create a new unique system name
            boolean found = true;

            while (found) {
                sName = "ILB" + blkNum;
                blkNum++;
                result = getBySystemName(sName);

                if (result == null) {
                    found = false;
                }
            }
        } else {
            // try the supplied system name
            result = getBySystemName((systemName));

            if (result != null) {
                return null;
            }
            sName = systemName;
        }

        // LayoutBlock does not exist, create a new LayoutBlock
        result = new LayoutBlock(sName, userName);

        //save in the maps
        register(result);

        return result;
    }

    @CheckReturnValue
    @CheckForNull
    public LayoutBlock createNewLayoutBlock() {
        while (true) {
            String sName = "ILB" + blkNum;
            LayoutBlock block = getBySystemName(sName);

            if (block == null) {
                String uName = "AUTOBLK:" + blkNum;
                block = new LayoutBlock(sName, uName);
                register(block);

                return block;
            }
            blkNum++;
        }
    }

    /**
     * Remove an existing LayoutBlock.
     * @param block the block to remove.
     */
    public void deleteLayoutBlock(LayoutBlock block) {
        deregister(block);
    }

    /**
     * Get an existing LayoutBlock. First looks up assuming that name is a User
     * Name. If this fails, looks up assuming that name is a System Name.
     *
     * @param name ideally block username, can be system name.
     * @return LayoutBlock, or null if not found by either user name or system
     *         name
     */
    @CheckReturnValue
    @CheckForNull
    public LayoutBlock getLayoutBlock(@Nonnull String name) {
        LayoutBlock block = getByUserName(name);

        if (block != null) {
            return block;
        }
        return getBySystemName(name);
    }

    @CheckReturnValue
    @CheckForNull
    public LayoutBlock getLayoutBlock(@CheckForNull Block block) {
        for (LayoutBlock lb : getNamedBeanSet()) {
            if (lb.getBlock() == block) {
                return lb;
            }
        }
        return null;
    }

    /**
     * Find a LayoutBlock with a specified Sensor assigned as its occupancy
     * sensor.
     *
     * @param s the sensor to search for.
     * @return the block or null if no existing LayoutBlock has the Sensor
     *         assigned
     */
    @CheckReturnValue
    @CheckForNull
    public LayoutBlock getBlockWithSensorAssigned(@CheckForNull Sensor s) {
        for (LayoutBlock block : getNamedBeanSet()) {
            if (block.getOccupancySensor() == s) {
                return block;
            }
        }
        return null;
    }

    /**
     * Find a LayoutBlock with a specified Memory assigned as its value display.
     *
     * @param m the memory to search for.
     * @return the block or null if no existing LayoutBlock has the memory
     *         assigned.
     */
    @CheckReturnValue
    @CheckForNull
    public LayoutBlock getBlockWithMemoryAssigned(Memory m) {
        for (LayoutBlock block : getNamedBeanSet()) {
            if (block.getMemory() == m) {
                return block;
            }
        }
        return null;
    }

    /**
     * Initialize/check the Paths of all Blocks associated with LayoutBlocks.
     * <p>
     * This routine should be called when loading panels, after all Layout
     * Editor panels have been loaded.
     */
    public void initializeLayoutBlockPaths() {
        log.debug("start initializeLayoutBlockPaths");

        // cycle through all LayoutBlocks, completing initialization of associated jmri.Blocks
        for (LayoutBlock b : getNamedBeanSet()) {
            log.debug("Calling block '{}({})'.initializeLayoutBlock()", b.getSystemName(), b.getDisplayName());
            b.initializeLayoutBlock();
        }

        //cycle through all LayoutBlocks, updating Paths of associated jmri.Blocks
        badBeanErrors = 0; // perhaps incremented via addBadBeanError(), but that's never called?
        for (LayoutBlock b : getNamedBeanSet()) {
            log.debug("Calling block '{}({})'.updatePaths()", b.getSystemName(), b.getDisplayName());

            b.updatePaths();

            if (b.getBlock().getValue() != null) {
                b.getBlock().setValue(null);
            }
        }

        if (badBeanErrors > 0) { // perhaps incremented via addBadBeanError(), but that's never called?
            JmriJOptionPane.showMessageDialog(null, "" + badBeanErrors + " " + Bundle.getMessage("Warn2"),
                    Bundle.getMessage("WarningTitle"), JmriJOptionPane.ERROR_MESSAGE);
        }
        try {
            new BlockValueFile().readBlockValues();
        } catch (org.jdom2.JDOMException jde) {
            log.error("JDOM Exception when retreiving block values", jde);
        } catch (java.io.IOException ioe) {
            log.error("I/O Exception when retreiving block values", ioe);
        }

        //special tests for getFacingSignalHead method - comment out next three lines unless using LayoutEditorTests
        //LayoutEditorTests layoutEditorTests = new LayoutEditorTests();
        //layoutEditorTests.runClinicTests();
        //layoutEditorTests.runTestPanel3Tests();
        initialized = true;
        log.debug("start initializeLayoutBlockRouting");
        initializeLayoutBlockRouting();
        log.debug("end initializeLayoutBlockRouting and initializeLayoutBlockPaths");
    }

    private boolean initialized = false;

    // Is this ever called?
    public void addBadBeanError() {
        badBeanErrors++;
    }
    private int badBeanErrors = 0;

    /**
     * Get the Signal Head facing into a specified Block from a specified
     * protected Block.
     * <p>
     * This method is primarily designed for use with scripts to get information
     * initially residing in a Layout Editor panel. If either of the input
     * Blocks is null, or if the two blocks do not join at a block boundary, or
     * if either of the input Blocks are not Layout Editor panel blocks, an
     * error message is logged, and "null" is returned. If the signal at the
     * block boundary has two heads--is located at the facing point of a
     * turnout-- the Signal Head that applies for the current setting of turnout
     * (THROWN or CLOSED) is returned. If the turnout state is UNKNOWN or
     * INCONSISTENT, an error message is logged, and "null" is returned. If the
     * signal at the block boundary has three heads--the facing point of a 3-way
     * turnout--the Signal Head that applies for the current settings of the two
     * turnouts of the 3-way turnout is returned. If the turnout state of either
     * turnout is UNKNOWN or INCONSISTENT, an error is logged and "null" is
     * returned. "null" is returned if the block boundary is between the two
     * turnouts of a THROAT_TO_THROAT turnout or a 3-way turnout. "null" is
     * returned for block boundaries exiting a THROAT_TO_THROAT turnout block,
     * since there are no signals that apply there.
     * @param facingBlock the facing block.
     * @param protectedBlock the protected block.
     * @return the signal head, may be null.
     */
    @CheckReturnValue
    @CheckForNull
    public SignalHead getFacingSignalHead(
            @CheckForNull Block facingBlock,
            @CheckForNull Block protectedBlock) {
        //check input
        if ((facingBlock == null) || (protectedBlock == null)) {
            log.error("null block in call to getFacingSignalHead");
            return null;
        }

        //non-null - check if input corresponds to Blocks in a Layout Editor panel.
        String facingBlockName = facingBlock.getUserName();
        if ((facingBlockName == null) || facingBlockName.isEmpty()) {
            log.error("facingBlockName has no user name");
            return null;
        }

        String protectedBlockName = protectedBlock.getUserName();
        if ((protectedBlockName == null) || protectedBlockName.isEmpty()) {
            log.error("protectedBlockName has no user name");
            return null;
        }

        LayoutBlock fLayoutBlock = getByUserName(facingBlockName);
        LayoutBlock pLayoutBlock = getByUserName(protectedBlockName);
        if ((fLayoutBlock == null) || (pLayoutBlock == null)) {
            if (fLayoutBlock == null) {
                log.error("Block {} is not on a Layout Editor panel.", facingBlock.getDisplayName());
            }

            if (pLayoutBlock == null) {
                log.error("Block {} is not on a Layout Editor panel.", protectedBlock.getDisplayName());
            }
            return null;
        }

        //input has corresponding LayoutBlocks - does it correspond to a block boundary?
        LayoutEditor panel = fLayoutBlock.getMaxConnectedPanel();
        List<LayoutConnectivity> c = panel.getLEAuxTools().getConnectivityList(fLayoutBlock);
        LayoutConnectivity lc = null;
        int i = 0;
        boolean facingIsBlock1 = true;

        while ((i < c.size()) && (lc == null)) {
            LayoutConnectivity tlc = c.get(i);

            if ((tlc.getBlock1() == fLayoutBlock) && (tlc.getBlock2() == pLayoutBlock)) {
                lc = tlc;
            } else if ((tlc.getBlock1() == pLayoutBlock) && (tlc.getBlock2() == fLayoutBlock)) {
                lc = tlc;
                facingIsBlock1 = false;
            }
            i++;
        }

        if (lc == null) {
            log.error("Block {} ({}) is not connected to Block {}", facingBlock.getDisplayName(),
                    facingBlock.getDisplayName(), protectedBlock.getDisplayName());
            return null;
        }

        //blocks are connected, get connection item types
        LayoutTurnout lt = null;
        TrackSegment tr = lc.getTrackSegment();
        int boundaryType = 0;

        if (tr == null) {
            // this is an internal crossover block boundary
            lt = lc.getXover();
            boundaryType = lc.getXoverBoundaryType();

            switch (boundaryType) {
                case LayoutConnectivity.XOVER_BOUNDARY_AB: {
                    if (facingIsBlock1) {
                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
                    } else {
                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1);
                    }
                }

                case LayoutConnectivity.XOVER_BOUNDARY_CD: {
                    if (facingIsBlock1) {
                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
                    } else {
                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTD1);
                    }
                }

                case LayoutConnectivity.XOVER_BOUNDARY_AC: {
                    if (facingIsBlock1) {
                        if (lt.getSignalHead(LayoutTurnout.Geometry.POINTA2) == null) { //there is no signal head for diverging (crossed
                            //over)
                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
                        } else { //there is a diverging (crossed over) signal head, return it
                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTA2);
                        }
                    } else {
                        if (lt.getSignalHead(LayoutTurnout.Geometry.POINTC2) == null) {
                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
                        } else {
                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTC2);
                        }
                    }
                }

                case LayoutConnectivity.XOVER_BOUNDARY_BD: {
                    if (facingIsBlock1) {
                        if (lt.getSignalHead(LayoutTurnout.Geometry.POINTB2) == null) { //there is no signal head for diverging (crossed
                            //over)
                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1);
                        } else { //there is a diverging (crossed over) signal head, return it
                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTB2);
                        }
                    } else {
                        if (lt.getSignalHead(LayoutTurnout.Geometry.POINTD2) == null) {
                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTD1);
                        } else {
                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTD2);
                        }
                    }
                }

                default: {
                    log.error("Unhandled crossover connection type: {}", boundaryType);
                    break;
                }
            } //switch

            //should never reach here, but ...
            log.error("crossover turnout block boundary not found in getFacingSignal");

            return null;
        }

        //not internal crossover block boundary
        LayoutTrack connected = lc.getConnectedObject();
        HitPointType cType = lc.getConnectedType();
        if (connected == null) {
            log.error("No connectivity object found between Blocks {}, {} {}", facingBlock.getDisplayName(),
                    protectedBlock.getDisplayName(), cType);

            return null;
        }

        if (cType == HitPointType.TRACK) {
            // block boundary is at an Anchor Point
            //    LayoutEditorTools tools = panel.getLETools(); //TODO: Dead-code strip this
            PositionablePoint p = panel.getFinder().findPositionablePointAtTrackSegments(tr, (TrackSegment) connected);
            boolean block1IsWestEnd = LayoutEditorTools.isAtWestEndOfAnchor(panel, tr, p);

            if ((block1IsWestEnd && facingIsBlock1) || (!block1IsWestEnd && !facingIsBlock1)) {
                //block1 is on the west (north) end of the block boundary
                return p.getEastBoundSignalHead();
            } else {
                return p.getWestBoundSignalHead();
            }
        }

        if (cType == HitPointType.TURNOUT_A) {
            // block boundary is at the facing point of a turnout or A connection of a crossover turnout
            lt = (LayoutTurnout) connected;

            if (lt.getLinkType() == LayoutTurnout.LinkType.NO_LINK) {
                //standard turnout or A connection of a crossover turnout
                if (facingIsBlock1) {
                    if (lt.getSignalHead(LayoutTurnout.Geometry.POINTA2) == null) { //there is no signal head for diverging
                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
                    } else {
                        //check if track segments at B or C are in protected block (block 2)
                        if (((TrackSegment) (lt.getConnectB())).getBlockName().equals(protectedBlock.getUserName())) {
                            //track segment connected at B matches block 2, check C
                            if (!(((TrackSegment) lt.getConnectC()).getBlockName().equals(protectedBlock.getUserName()))) {
                                //track segment connected at C is not in block2, return continuing signal head at A
                                if (lt.getContinuingSense() == Turnout.CLOSED) {
                                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
                                } else {
                                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTA2);
                                }
                            } else {
                                //B and C both in block2, check turnout position to decide which signal head to return
                                int state = lt.getTurnout().getKnownState();

                                if (((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.CLOSED))
                                        || ((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.THROWN))) { //continuing
                                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
                                } else if (((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.CLOSED))
                                        || ((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.THROWN))) { //diverging
                                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTA2);
                                } else {
                                    //turnout state is UNKNOWN or INCONSISTENT
                                    log.error("Cannot choose signal head because turnout {} is in an UNKNOWN or INCONSISTENT state.",
                                            lt.getTurnout().getDisplayName());

                                    return null;
                                }
                            }
                        }

                        //track segment connected at B is not in block 2
                        if ((((TrackSegment) lt.getConnectC()).getBlockName().equals(protectedBlock.getUserName()))) {
                            //track segment connected at C is in block 2, return diverging signal head
                            if (lt.getContinuingSense() == Turnout.CLOSED) {
                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTA2);
                            } else {
                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
                            }
                        } else {
                            // neither track segment is in block 2 - will get here when layout turnout is the only item in block 2
                            // Return signal head based on turnout position
                            int state = lt.getTurnout().getKnownState();
                            if (((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.CLOSED))
                                    || ((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.THROWN))) { //continuing
                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
                            } else if (((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.CLOSED))
                                    || ((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.THROWN))) { //diverging
                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTA2);
                            }

                            // Turnout state is unknown or inconsistent
                            return null;
                        }
                    }
                } else {
                    //check if track segments at B or C are in facing block (block 1)
                    if (((TrackSegment) (lt.getConnectB())).getBlockName().equals(facingBlock.getUserName())) {
                        //track segment connected at B matches block 1, check C
                        if (!(((TrackSegment) lt.getConnectC()).getBlockName().equals(facingBlock.getDisplayName()))) {
                            //track segment connected at C is not in block 2, return signal head at continuing end
                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1);
                        } else {
                            //B and C both in block 1, check turnout position to decide which signal head to return
                            int state = lt.getTurnout().getKnownState();

                            if (((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.CLOSED))
                                    || ((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.THROWN))) { //continuing
                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1);
                            } else if (((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.CLOSED))
                                    || ((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.THROWN))) {
                                //diverging, check for second head
                                if (lt.getSignalHead(LayoutTurnout.Geometry.POINTC2) == null) {
                                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
                                } else {
                                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTC2);
                                }
                            } else {
                                //turnout state is UNKNOWN or INCONSISTENT
                                log.error("Cannot choose signal head because turnout {} is in an UNKNOWN or INCONSISTENT state.",
                                        lt.getTurnout().getDisplayName());

                                return null;
                            }
                        }
                    }

                    //track segment connected at B is not in block 1
                    if (((TrackSegment) lt.getConnectC()).getBlockName().equals(facingBlock.getUserName())) {
                        //track segment connected at C is in block 1, return diverging signal head, check for second head
                        if (lt.getSignalHead(LayoutTurnout.Geometry.POINTC2) == null) {
                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
                        } else {
                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTC2);
                        }
                    } else {
                        //neither track segment is in block 1 - should never get here unless layout turnout is
                        //the only item in block 1
                        if (!(lt.getBlockName().equals(facingBlock.getUserName()))) {
                            log.error("no signal faces block {}, and turnout is not in block either",
                                    facingBlock.getDisplayName());
                        }
                        return null;
                    }
                }
            } else if (lt.getLinkType() == LayoutTurnout.LinkType.THROAT_TO_THROAT) {
                //There are no signals at the throat of a THROAT_TO_THROAT

                //There should not be a block boundary here
                return null;
            } else if (lt.getLinkType() == LayoutTurnout.LinkType.FIRST_3_WAY) {
                //3-way turnout is in its own block - block boundary is at the throat of the 3-way turnout
                if (!facingIsBlock1) {
                    //facing block is within the three-way turnout's block - no signals for exit of the block
                    return null;
                } else {
                    //select throat signal according to state of the 3-way turnout
                    int state = lt.getTurnout().getKnownState();

                    if (state == Turnout.THROWN) {
                        if (lt.getSignalHead(LayoutTurnout.Geometry.POINTA2) == null) {
                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
                        } else {
                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTA2);
                        }
                    } else if (state == Turnout.CLOSED) {
                        LayoutTurnout tLinked = panel.getFinder().findLayoutTurnoutByTurnoutName(lt.getLinkedTurnoutName());
                        state = tLinked.getTurnout().getKnownState();

                        if (state == Turnout.CLOSED) {
                            if (tLinked.getContinuingSense() == Turnout.CLOSED) {
                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
                            } else if (lt.getSignalHead(LayoutTurnout.Geometry.POINTA3) == null) {
                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
                            } else {
                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTA3);
                            }
                        } else if (state == Turnout.THROWN) {
                            if (tLinked.getContinuingSense() == Turnout.THROWN) {
                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
                            } else if (lt.getSignalHead(LayoutTurnout.Geometry.POINTA3) == null) {
                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
                            } else {
                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTA3);
                            }
                        } else {
                            //should never get here - linked turnout state is UNKNOWN or INCONSISTENT
                            log.error("Cannot choose 3-way signal head to return because turnout {} is in an UNKNOWN or INCONSISTENT state.",
                                    tLinked.getTurnout().getSystemName());
                            return null;
                        }
                    } else {
                        //should never get here - linked turnout state is UNKNOWN or INCONSISTENT
                        log.error("Cannot choose 3-way signal head to return because turnout {} is in an UNKNOWN or INCONSISTENT state.",
                                lt.getTurnout().getSystemName());
                        return null;
                    }
                }
            } else if (lt.getLinkType() == LayoutTurnout.LinkType.SECOND_3_WAY) {
                //There are no signals at the throat of the SECOND_3_WAY turnout of a 3-way turnout

                //There should not be a block boundary here
                return null;
            }
        }

        if (cType == HitPointType.TURNOUT_B) {
            //block boundary is at the continuing track of a turnout or B connection of a crossover turnout
            lt = (LayoutTurnout) connected;

            //check for double crossover or LH crossover
            if (((lt.getTurnoutType() == LayoutTurnout.TurnoutType.DOUBLE_XOVER)
                    || (lt.getTurnoutType() == LayoutTurnout.TurnoutType.LH_XOVER))) {
                if (facingIsBlock1) {
                    if (lt.getSignalHead(LayoutTurnout.Geometry.POINTB2) == null) { //there is only one signal at B, return it
                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1);
                    }

                    //check if track segments at A or D are in protected block (block 2)
                    if (((TrackSegment) (lt.getConnectA())).getBlockName().equals(protectedBlock.getUserName())) {
                        //track segment connected at A matches block 2, check D
                        if (!(((TrackSegment) lt.getConnectD()).getBlockName().equals(protectedBlock.getUserName()))) {
                            //track segment connected at D is not in block2, return continuing signal head at B
                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1);
                        } else {
                            //A and D both in block 2, check turnout position to decide which signal head to return
                            int state = lt.getTurnout().getKnownState();

                            if (((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.CLOSED))
                                    || ((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.THROWN))) { //continuing
                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1);
                            } else if (((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.CLOSED))
                                    || ((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.THROWN))) { //diverging
                                //(crossed

                                //over)
                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTB2);
                            } else {
                                //turnout state is UNKNOWN or INCONSISTENT
                                log.error("LayoutTurnout {} cannot choose signal head because turnout {} is in an UNKNOWN or INCONSISTENT state.",
                                        lt, lt.getTurnout());
                                return null;
                            }
                        }
                    }

                    //track segment connected at A is not in block 2
                    if ((((TrackSegment) lt.getConnectD()).getBlockName().equals(protectedBlock.getUserName()))) { //track segment
                        //connected at D
                        //is in block 2,
                        //return
                        //diverging

                        //signal head
                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTB2);
                    } else {
                        //neither track segment is in block 2 - should never get here unless layout turnout is
                        //only item in block 2
                        if (!(lt.getBlockName().equals(protectedBlock.getUserName()))) {
                            log.error("neither signal at B protects block {}, and turnout is not in block either",
                                    protectedBlock.getDisplayName());
                        }
                        return null;
                    }
                } else {
                    //check if track segments at A or D are in facing block (block 1)
                    if (((TrackSegment) (lt.getConnectA())).getBlockName().equals(facingBlock.getUserName())) {
                        //track segment connected at A matches block 1, check D
                        if (!(((TrackSegment) lt.getConnectD()).getBlockName().equals(facingBlock.getUserName()))) {
                            //track segment connected at D is not in block 2, return signal head at continuing end
                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
                        } else {
                            //A and D both in block 1, check turnout position to decide which signal head to return
                            int state = lt.getTurnout().getKnownState();

                            if (((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.CLOSED))
                                    || ((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.THROWN))) { //continuing
                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
                            } else if (((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.CLOSED))
                                    || ((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.THROWN))) {
                                //diverging, check for second head
                                if (lt.getSignalHead(LayoutTurnout.Geometry.POINTD2) == null) {
                                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTD1);
                                } else {
                                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTD2);
                                }
                            } else {
                                //turnout state is UNKNOWN or INCONSISTENT
                                log.error("Cannot choose signal head because turnout {} is in an UNKNOWN or INCONSISTENT state.",
                                        lt.getTurnout().getDisplayName());
                                return null;
                            }
                        }
                    }

                    //track segment connected at A is not in block 1
                    if (((TrackSegment) lt.getConnectD()).getBlockName().equals(facingBlock.getUserName())) {
                        //track segment connected at D is in block 1, return diverging signal head, check for second head
                        if (lt.getSignalHead(LayoutTurnout.Geometry.POINTD2) == null) {
                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTD1);
                        } else {
                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTD2);
                        }
                    } else {
                        //neither track segment is in block 1 - should never get here unless layout turnout is
                        //the only item in block 1
                        if (!(lt.getBlockName().equals(facingBlock.getUserName()))) {
                            log.error("no signal faces block {}, and turnout is not in block either",
                                    facingBlock.getDisplayName());
                        }
                        return null;
                    }
                }
            }

            //not double crossover or LH crossover
            if ((lt.getLinkType() == LayoutTurnout.LinkType.NO_LINK) && (lt.getContinuingSense() == Turnout.CLOSED)) {
                if (facingIsBlock1) {
                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1);
                } else {
                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
                }
            } else if (lt.getLinkType() == LayoutTurnout.LinkType.NO_LINK) {
                if (facingIsBlock1) {
                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
                } else {
                    if (lt.getSignalHead(LayoutTurnout.Geometry.POINTA2) == null) {
                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
                    } else {
                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTA2);
                    }
                }
            } else if (lt.getLinkType() == LayoutTurnout.LinkType.THROAT_TO_THROAT) {
                if (!facingIsBlock1) {
                    //There are no signals at the throat of a THROAT_TO_THROAT
                    return null;
                }

                //facing block is outside of the THROAT_TO_THROAT
                if ((lt.getContinuingSense() == Turnout.CLOSED) && (lt.getSignalHead(LayoutTurnout.Geometry.POINTB2) == null)) {
                    //there is only one signal head here - return it
                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1);
                } else if ((lt.getContinuingSense() == Turnout.THROWN) && (lt.getSignalHead(LayoutTurnout.Geometry.POINTC2) == null)) {
                    //there is only one signal head here - return it
                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
                }

                //There are two signals here get linked turnout and decide which to return from linked turnout state
                LayoutTurnout tLinked = panel.getFinder().findLayoutTurnoutByTurnoutName(lt.getLinkedTurnoutName());
                int state = tLinked.getTurnout().getKnownState();

                if (state == Turnout.CLOSED) {
                    if (lt.getContinuingSense() == Turnout.CLOSED) {
                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1);
                    } else {
                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
                    }
                } else if (state == Turnout.THROWN) {
                    if (lt.getContinuingSense() == Turnout.CLOSED) {
                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTB2);
                    } else {
                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTC2);
                    }
                } else { //should never get here - linked turnout state is UNKNOWN or INCONSISTENT
                    log.error("Cannot choose signal head to return because turnout {} is in an UNKNOWN or INCONSISTENT state.",
                            tLinked.getTurnout().getDisplayName());
                }
                return null;
            } else if (lt.getLinkType() == LayoutTurnout.LinkType.FIRST_3_WAY) {
                //there is no signal at the FIRST_3_WAY turnout continuing track of a 3-way turnout
                //there should not be a block boundary here
                return null;
            } else if (lt.getLinkType() == LayoutTurnout.LinkType.SECOND_3_WAY) {
                if (facingIsBlock1) {
                    if (lt.getContinuingSense() == Turnout.CLOSED) {
                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1);
                    } else {
                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
                    }
                } else {
                    //signal is at the linked turnout - the throat of the 3-way turnout
                    LayoutTurnout tLinked = panel.getFinder().findLayoutTurnoutByTurnoutName(lt.getLinkedTurnoutName());

                    if (lt.getContinuingSense() == Turnout.CLOSED) {
                        return tLinked.getSignalHead(LayoutTurnout.Geometry.POINTA1);
                    } else {
                        if (tLinked.getSignalHead(LayoutTurnout.Geometry.POINTA3) == null) {
                            return tLinked.getSignalHead(LayoutTurnout.Geometry.POINTA1);
                        } else {
                            return tLinked.getSignalHead(LayoutTurnout.Geometry.POINTA3);
                        }
                    }
                }
            }
        }

        if (cType == HitPointType.TURNOUT_C) {
            //block boundary is at the diverging track of a turnout or C connection of a crossover turnout
            lt = (LayoutTurnout) connected;

            //check for double crossover or RH crossover
            if ((lt.getTurnoutType() == LayoutTurnout.TurnoutType.DOUBLE_XOVER)
                    || (lt.getTurnoutType() == LayoutTurnout.TurnoutType.RH_XOVER)) {
                if (facingIsBlock1) {
                    if (lt.getSignalHead(LayoutTurnout.Geometry.POINTC2) == null) { //there is only one head at C, return it
                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
                    }

                    //check if track segments at A or D are in protected block (block 2)
                    if (((TrackSegment) (lt.getConnectA())).getBlockName().equals(protectedBlock.getUserName())) {
                        //track segment connected at A matches block 2, check D
                        if (!(((TrackSegment) lt.getConnectD()).getBlockName().equals(protectedBlock.getUserName()))) {
                            //track segment connected at D is not in block2, return diverging signal head at C
                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTC2);
                        } else {
                            //A and D both in block 2, check turnout position to decide which signal head to return
                            int state = lt.getTurnout().getKnownState();

                            if (((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.CLOSED))
                                    || ((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.THROWN))) { //continuing
                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
                            } else if (((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.CLOSED))
                                    || ((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.THROWN))) { //diverging
                                //(crossed

                                //over)
                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTC2);
                            } else {
                                //turnout state is UNKNOWN or INCONSISTENT
                                log.error("Cannot choose signal head because turnout {} is in an UNKNOWN or INCONSISTENT state.",
                                        lt.getTurnout().getDisplayName());
                                return null;
                            }
                        }
                    }

                    //track segment connected at A is not in block 2
                    if ((((TrackSegment) lt.getConnectD()).getBlockName().equals(protectedBlock.getUserName()))) {
                        //track segment connected at D is in block 2, return continuing signal head
                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
                    } else {
                        //neither track segment is in block 2 - should never get here unless layout turnout is
                        //only item in block 2
                        if (!(lt.getBlockName().equals(protectedBlock.getUserName()))) {
                            log.error("neither signal at C protects block {}, and turnout is not in block either",
                                    protectedBlock.getDisplayName());
                        }
                        return null;
                    }
                } else {
                    //check if track segments at D or A are in facing block (block 1)
                    if (((TrackSegment) (lt.getConnectD())).getBlockName().equals(facingBlock.getUserName())) {
                        //track segment connected at D matches block 1, check A
                        if (!(((TrackSegment) lt.getConnectA()).getBlockName().equals(facingBlock.getUserName()))) {
                            //track segment connected at A is not in block 2, return signal head at continuing end
                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTD1);
                        } else {
                            //A and D both in block 1, check turnout position to decide which signal head to return
                            int state = lt.getTurnout().getKnownState();

                            if (((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.CLOSED))
                                    || ((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.THROWN))) { //continuing
                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTD1);
                            } else if (((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.CLOSED))
                                    || ((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.THROWN))) {
                                //diverging, check for second head
                                if (lt.getSignalHead(LayoutTurnout.Geometry.POINTA2) == null) {
                                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
                                } else {
                                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTA2);
                                }
                            } else {
                                //turnout state is UNKNOWN or INCONSISTENT
                                log.error("Cannot choose signal head because turnout {} is in an UNKNOWN or INCONSISTENT state.",
                                        lt.getTurnout().getDisplayName());
                                return null;
                            }
                        }
                    }

                    //track segment connected at D is not in block 1
                    if (((TrackSegment) lt.getConnectA()).getBlockName().equals(facingBlock.getUserName())) {
                        //track segment connected at A is in block 1, return diverging signal head, check for second head
                        if (lt.getSignalHead(LayoutTurnout.Geometry.POINTA2) == null) {
                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
                        } else {
                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTA2);
                        }
                    } else {
                        //neither track segment is in block 1 - should never get here unless layout turnout is
                        //the only item in block 1
                        if (!(lt.getBlockName().equals(facingBlock.getUserName()))) {
                            log.error("no signal faces block {}, and turnout is not in block either",
                                    facingBlock.getDisplayName());
                        }
                        return null;
                    }
                }
            }

            //not double crossover or RH crossover
            if ((lt.getLinkType() == LayoutTurnout.LinkType.NO_LINK) && (lt.getContinuingSense() == Turnout.CLOSED)) {
                if (facingIsBlock1) {
                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
                } else if (lt.getTurnoutType() == LayoutTurnout.TurnoutType.LH_XOVER) { //LH turnout - this is continuing track for D connection
                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTD1);
                } else {
                    //RH, LH or WYE turnout, this is diverging track for A connection
                    if (lt.getSignalHead(LayoutTurnout.Geometry.POINTA2) == null) { //there is no signal head at the throat for diverging
                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
                    } else { //there is a diverging head at the throat, return it
                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTA2);
                    }
                }
            } else if (lt.getLinkType() == LayoutTurnout.LinkType.NO_LINK) {
                if (facingIsBlock1) {
                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1);
                } else {
                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
                }
            } else if (lt.getLinkType() == LayoutTurnout.LinkType.THROAT_TO_THROAT) {
                if (!facingIsBlock1) {
                    //There are no signals at the throat of a THROAT_TO_THROAT
                    return null;
                }

                //facing block is outside of the THROAT_TO_THROAT
                if ((lt.getContinuingSense() == Turnout.CLOSED) && (lt.getSignalHead(LayoutTurnout.Geometry.POINTC2) == null)) {
                    //there is only one signal head here - return it
                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
                } else if ((lt.getContinuingSense() == Turnout.THROWN) && (lt.getSignalHead(LayoutTurnout.Geometry.POINTB2) == null)) {
                    //there is only one signal head here - return it
                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1);
                }

                //There are two signals here get linked turnout and decide which to return from linked turnout state
                LayoutTurnout tLinked = panel.getFinder().findLayoutTurnoutByTurnoutName(lt.getLinkedTurnoutName());
                int state = tLinked.getTurnout().getKnownState();

                if (state == Turnout.CLOSED) {
                    if (lt.getContinuingSense() == Turnout.CLOSED) {
                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
                    } else {
                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1);
                    }
                } else if (state == Turnout.THROWN) {
                    if (lt.getContinuingSense() == Turnout.CLOSED) {
                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTC2);
                    } else {
                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTB2);
                    }
                } else {
                    //should never get here - linked turnout state is UNKNOWN or INCONSISTENT
                    log.error("Cannot choose signal head to return because turnout {} is in an UNKNOWN or INCONSISTENT state.",
                            tLinked.getTurnout().getDisplayName());
                    return null;
                }
            } else if (lt.getLinkType() == LayoutTurnout.LinkType.FIRST_3_WAY) {
                if (facingIsBlock1) {
                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
                } else {
                    if (lt.getSignalHead(LayoutTurnout.Geometry.POINTA2) == null) {
                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
                    } else {
                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTA2);
                    }
                }
            } else if (lt.getLinkType() == LayoutTurnout.LinkType.SECOND_3_WAY) {
                if (facingIsBlock1) {
                    if (lt.getContinuingSense() == Turnout.CLOSED) {
                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
                    } else {
                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1);
                    }
                } else {
                    //signal is at the linked turnout - the throat of the 3-way turnout
                    LayoutTurnout tLinked = panel.getFinder().findLayoutTurnoutByTurnoutName(lt.getLinkedTurnoutName());

                    if (lt.getContinuingSense() == Turnout.CLOSED) {
                        if (tLinked.getSignalHead(LayoutTurnout.Geometry.POINTA3) == null) {
                            return tLinked.getSignalHead(LayoutTurnout.Geometry.POINTA1);
                        } else {
                            return tLinked.getSignalHead(LayoutTurnout.Geometry.POINTA3);
                        }
                    } else {
                        if (tLinked.getSignalHead(LayoutTurnout.Geometry.POINTA2) == null) {
                            return tLinked.getSignalHead(LayoutTurnout.Geometry.POINTA1);
                        } else {
                            return tLinked.getSignalHead(LayoutTurnout.Geometry.POINTA2);
                        }
                    }
                }
            }
        }

        if (cType == HitPointType.TURNOUT_D) {
            //block boundary is at D connectin of a crossover turnout
            lt = (LayoutTurnout) connected;

            if (lt.getTurnoutType() == LayoutTurnout.TurnoutType.RH_XOVER) {
                //no diverging route possible, this is continuing track for C connection
                if (facingIsBlock1) {
                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTD1);
                } else {
                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
                }
            }

            if (facingIsBlock1) {
                if (lt.getSignalHead(LayoutTurnout.Geometry.POINTD2) == null) { //there is no signal head for diverging
                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTD1);
                } else {
                    //check if track segments at C or B are in protected block (block 2)
                    if (((TrackSegment) (lt.getConnectC())).getBlockName().equals(protectedBlock.getUserName())) {
                        //track segment connected at C matches block 2, check B
                        if (!(((TrackSegment) lt.getConnectB()).getBlockName().equals(protectedBlock.getUserName()))) {
                            //track segment connected at B is not in block2, return continuing signal head at D
                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTD1);
                        } else {
                            //C and B both in block2, check turnout position to decide which signal head to return
                            int state = lt.getTurnout().getKnownState();

                            if (((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.CLOSED))
                                    || ((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.THROWN))) { //continuing
                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTD1);
                            } else if (((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.CLOSED))
                                    || ((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.THROWN))) { //diverging
                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTD2);
                            } else {
                                //turnout state is UNKNOWN or INCONSISTENT
                                log.error("Cannot choose signal head because turnout {} is in an UNKNOWN or INCONSISTENT state.",
                                        lt.getTurnout().getDisplayName());
                                return null;
                            }
                        }
                    }

                    //track segment connected at C is not in block 2
                    if ((((TrackSegment) lt.getConnectB()).getBlockName().equals(protectedBlock.getUserName()))) {
                        //track segment connected at B is in block 2, return diverging signal head
                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTD2);
                    } else {
                        //neither track segment is in block 2 - should never get here unless layout turnout is
                        //the only item in block 2
                        if (!(lt.getBlockName().equals(protectedBlock.getUserName()))) {
                            log.error("neither signal at D protects block {}, and turnout is not in block either",
                                    protectedBlock.getDisplayName());
                        }
                        return null;
                    }
                }
            } else {
                //check if track segments at C or B are in facing block (block 1)
                if (((TrackSegment) (lt.getConnectC())).getBlockName().equals(facingBlock.getUserName())) {
                    //track segment connected at C matches block 1, check B
                    if (!(((TrackSegment) lt.getConnectB()).getBlockName().equals(facingBlock.getUserName()))) {
                        //track segment connected at B is not in block 2, return signal head at continuing end
                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
                    } else {
                        //C and B both in block 1, check turnout position to decide which signal head to return
                        int state = lt.getTurnout().getKnownState();

                        if (((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.CLOSED))
                                || ((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.THROWN))) { //continuing
                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
                        } else if (((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.CLOSED))
                                || ((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.THROWN))) {
                            //diverging, check for second head
                            if (lt.getSignalHead(LayoutTurnout.Geometry.POINTB2) == null) {
                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1);
                            } else {
                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTB2);
                            }
                        } else {
                            //turnout state is UNKNOWN or INCONSISTENT
                            log.error("Cannot choose signal head because turnout {} is in an UNKNOWN or INCONSISTENT state.",
                                    lt.getTurnout().getDisplayName());
                            return null;
                        }
                    }
                }

                //track segment connected at C is not in block 1
                if (((TrackSegment) lt.getConnectB()).getBlockName().equals(facingBlock.getUserName())) {
                    //track segment connected at B is in block 1, return diverging signal head, check for second head
                    if (lt.getSignalHead(LayoutTurnout.Geometry.POINTB2) == null) {
                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1);
                    } else {
                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTB2);
                    }
                } else {
                    //neither track segment is in block 1 - should never get here unless layout turnout is
                    //the only item in block 1
                    if (!(lt.getBlockName().equals(facingBlock.getUserName()))) {
                        log.error("no signal faces block {}, and turnout is not in block either",
                                facingBlock.getDisplayName());
                    }
                    return null;
                }
            }
        }

        if (HitPointType.isSlipHitType(cType)) {
            if (!facingIsBlock1) {
                return null;
            }

            LayoutSlip ls = (LayoutSlip) connected;

            switch (cType) {
                case SLIP_A: {
                    if (ls.getSlipState() == LayoutSlip.STATE_AD) {
                        return ls.getSignalHead(LayoutTurnout.Geometry.POINTA2);
                    } else {
                        return ls.getSignalHead(LayoutTurnout.Geometry.POINTA1);
                    }
                }

                case SLIP_B: {
                    if (ls.getTurnoutType() == LayoutSlip.TurnoutType.DOUBLE_SLIP) {
                        if (ls.getSlipState() == LayoutSlip.STATE_BC) {
                            return ls.getSignalHead(LayoutTurnout.Geometry.POINTB2);
                        } else {
                            return ls.getSignalHead(LayoutTurnout.Geometry.POINTB1);
                        }
                    } else {
                        return ls.getSignalHead(LayoutTurnout.Geometry.POINTB1);
                    }
                }

                case SLIP_C: {
                    if (ls.getTurnoutType() == LayoutSlip.TurnoutType.DOUBLE_SLIP) {
                        if (ls.getSlipState() == LayoutSlip.STATE_BC) {
                            return ls.getSignalHead(LayoutTurnout.Geometry.POINTC2);
                        } else {
                            return ls.getSignalHead(LayoutTurnout.Geometry.POINTC1);
                        }
                    } else {
                        return ls.getSignalHead(LayoutTurnout.Geometry.POINTC1);
                    }
                }

                case SLIP_D: {
                    if (ls.getSlipState() == LayoutSlip.STATE_AD) {
                        return ls.getSignalHead(LayoutTurnout.Geometry.POINTD2);
                    } else {
                        return ls.getSignalHead(LayoutTurnout.Geometry.POINTD1);
                    }
                }

                default: {
                    break;
                }
            } //switch
        }

        //block boundary must be at a level crossing
        if (!HitPointType.isLevelXingHitType(cType)) {
            log.error("{} {} Block Boundary not identified correctly - Blocks {}, {}",
                    cType, connected, facingBlock.getDisplayName(), protectedBlock.getDisplayName());

            return null;
        }
        LevelXing xing = (LevelXing) connected;

        switch (cType) {
            case LEVEL_XING_A: {
                //block boundary is at the A connection of a level crossing
                if (facingIsBlock1) {
                    return xing.getSignalHead(LevelXing.Geometry.POINTA);
                } else {
                    return xing.getSignalHead(LevelXing.Geometry.POINTC);
                }
            }

            case LEVEL_XING_B: {
                //block boundary is at the B connection of a level crossing
                if (facingIsBlock1) {
                    return xing.getSignalHead(LevelXing.Geometry.POINTB);
                } else {
                    return xing.getSignalHead(LevelXing.Geometry.POINTD);
                }
            }

            case LEVEL_XING_C: {
                //block boundary is at the C connection of a level crossing
                if (facingIsBlock1) {
                    return xing.getSignalHead(LevelXing.Geometry.POINTC);
                } else {
                    return xing.getSignalHead(LevelXing.Geometry.POINTA);
                }
            }

            case LEVEL_XING_D: {
                //block boundary is at the D connection of a level crossing
                if (facingIsBlock1) {
                    return xing.getSignalHead(LevelXing.Geometry.POINTD);
                } else {
                    return xing.getSignalHead(LevelXing.Geometry.POINTB);
                }
            }

            default: {
                break;
            }
        }
        return null;
    }

    /**
     * Get the named bean of either a Sensor or signalmast facing into a
     * specified Block from a specified protected Block.
     * @param facingBlock the facing block.
     * @param panel the main layout editor.
     * @return The assigned sensor or signal mast as a named bean
     */
    @CheckReturnValue
    @CheckForNull
    public NamedBean getNamedBeanAtEndBumper(
            @CheckForNull Block facingBlock,
            @CheckForNull LayoutEditor panel) {
        NamedBean bean = getSignalMastAtEndBumper(facingBlock, panel);

        if (bean != null) {
            return bean;
        } else {
            return getSensorAtEndBumper(facingBlock, panel);
        }
    }

    /**
     * Get a Signal Mast that is assigned to a block which has an end bumper at
     * one end.
     * @param facingBlock the facing block.
     * @param panel the main layout editor.
     * @return the signal mast.
     */
    @CheckReturnValue
    @CheckForNull
    public SignalMast getSignalMastAtEndBumper(
            @CheckForNull Block facingBlock,
            @CheckForNull LayoutEditor panel) {
        if (facingBlock == null) {
            log.error("null block in call to getFacingSignalMast");
            return null;
        }
        String facingBlockName = facingBlock.getUserName();
        if ((facingBlockName == null) || facingBlockName.isEmpty()) {
            log.error("facing block has no user name");
            return null;
        }

        LayoutBlock fLayoutBlock = getByUserName(facingBlockName);
        if (fLayoutBlock == null) {
            log.error("Block {} is not on a Layout Editor panel.", facingBlock.getDisplayName());

            return null;
        }

        if (panel == null) {
            panel = fLayoutBlock.getMaxConnectedPanel();
        }

        for (TrackSegment t : panel.getTrackSegments()) {
            if (t.getLayoutBlock() == fLayoutBlock) {
                PositionablePoint p = null;

                if (t.getType1() == HitPointType.POS_POINT) {
                    p = (PositionablePoint) t.getConnect1();

                    if (p.getType() == PositionablePoint.PointType.END_BUMPER) {
                        if (p.getEastBoundSignalMast() != null) {
                            return p.getEastBoundSignalMast();
                        }

                        if (p.getWestBoundSignalMast() != null) {
                            return p.getWestBoundSignalMast();
                        }
                    }
                }

                if (t.getType2() == HitPointType.POS_POINT) {
                    p = (PositionablePoint) t.getConnect2();

                    if (p.getType() == PositionablePoint.PointType.END_BUMPER) {
                        if (p.getEastBoundSignalMast() != null) {
                            return p.getEastBoundSignalMast();
                        }

                        if (p.getWestBoundSignalMast() != null) {
                            return p.getWestBoundSignalMast();
                        }
                    }
                }
            }
        }
        return null;
    }

    /**
     * Get a Sensor facing into a specific Block. This is used for Blocks that
     * have an end bumper at one end.
     * @param facingBlock the facing block.
     * @param panel the main layout editor.
     * @return the facing sensor.
     */
    @CheckReturnValue
    @CheckForNull
    public Sensor getSensorAtEndBumper(
            @CheckForNull Block facingBlock,
            @CheckForNull LayoutEditor panel) {
        if (facingBlock == null) {
            log.error("null block in call to getFacingSensor");
            return null;
        }

        String facingBlockName = facingBlock.getUserName();
        if ((facingBlockName == null) || (facingBlockName.isEmpty())) {
            log.error("Block {} has no user name.", facingBlock.getDisplayName());
            return null;
        }
        LayoutBlock fLayoutBlock = getByUserName(facingBlockName);
        if (fLayoutBlock == null) {
            log.error("Block {} is not on a Layout Editor panel.", facingBlock.getDisplayName());

            return null;
        }

        if (panel == null) {
            panel = fLayoutBlock.getMaxConnectedPanel();
        }

        for (TrackSegment t : panel.getTrackSegments()) {
            if (t.getLayoutBlock() == fLayoutBlock) {
                PositionablePoint p = null;

                if (t.getType1() == HitPointType.POS_POINT) {
                    p = (PositionablePoint) t.getConnect1();

                    if (p.getType() == PositionablePoint.PointType.END_BUMPER) {
                        if (p.getEastBoundSensor() != null) {
                            return p.getEastBoundSensor();
                        }

                        if (p.getWestBoundSensor() != null) {
                            return p.getWestBoundSensor();
                        }
                    }
                }

                if (t.getType2() == HitPointType.POS_POINT) {
                    p = (PositionablePoint) t.getConnect2();

                    if (p.getType() == PositionablePoint.PointType.END_BUMPER) {
                        if (p.getEastBoundSensor() != null) {
                            return p.getEastBoundSensor();
                        }

                        if (p.getWestBoundSensor() != null) {
                            return p.getWestBoundSensor();
                        }
                    }
                }
            }
        }
        return null;
    }

    /**
     * Get the named bean of either a Sensor or signalmast facing into a
     * specified Block from a specified protected Block.
     * @param facingBlock the facing block.
     * @param protectedBlock the protected block.
     * @param panel the main layout editor.
     * @return The assigned sensor or signal mast as a named bean
     */
    @CheckReturnValue
    @CheckForNull
    public NamedBean getFacingNamedBean(@CheckForNull Block facingBlock,
            @CheckForNull Block protectedBlock,
            @CheckForNull LayoutEditor panel) {
        NamedBean bean = getFacingBean(facingBlock, protectedBlock, panel, SignalMast.class);

        if (bean != null) {
            return bean;
        }
        bean = getFacingBean(facingBlock, protectedBlock, panel, Sensor.class);

        if (bean != null) {
            return bean;
        }
        return getFacingSignalHead(facingBlock, protectedBlock);
    }

    @CheckReturnValue
    @CheckForNull
    public SignalMast getFacingSignalMast(
            @Nonnull Block facingBlock,
            @CheckForNull Block protectedBlock) {
        return getFacingSignalMast(facingBlock, protectedBlock, null);
    }

    /**
     * Get the Signal Mast facing into a specified Block from a specified
     * protected Block.
     *
     * @param facingBlock the facing block.
     * @param protectedBlock the protected block.
     * @param panel the main layout editor.
     * @return The assigned signalMast.
     */
    @CheckReturnValue
    @CheckForNull
    public SignalMast getFacingSignalMast(
            @Nonnull Block facingBlock,
            @CheckForNull Block protectedBlock,
            @CheckForNull LayoutEditor panel) {
        log.debug("calling getFacingMast on block '{}'", facingBlock.getDisplayName());
        return (SignalMast) getFacingBean(facingBlock, protectedBlock, panel, SignalMast.class);
    }

    /**
     * Get the Sensor facing into a specified Block from a specified protected
     * Block.
     * @param facingBlock the facing block.
     * @param protectedBlock the protected block.
     * @param panel the main layout editor.
     * @return The assigned sensor
     */
    @CheckReturnValue
    @CheckForNull
    public Sensor getFacingSensor(@CheckForNull Block facingBlock,
            @CheckForNull Block protectedBlock,
            @CheckForNull LayoutEditor panel) {
        return (Sensor) getFacingBean(facingBlock, protectedBlock, panel, Sensor.class);
    }

    /**
     * Get a facing bean into a specified Block from a specified protected
     * Block.
     *
     * @param facingBlock the facing block.
     * @param protectedBlock the protected block.
     * @param panel the layout editor panel the block is assigned, if null then
     *              the maximum connected panel of the facing block is used
     * @param T     The class of the item that we are looking for, either
     *              SignalMast or Sensor
     * @return The assigned sensor.
     */
    @CheckReturnValue
    @CheckForNull
    public NamedBean getFacingBean(@CheckForNull Block facingBlock,
            @CheckForNull Block protectedBlock,
            @CheckForNull LayoutEditor panel, Class< ?> T) {
        //check input
        if ((facingBlock == null) || (protectedBlock == null)) {
            log.error("null block in call to getFacingSignalMast");
            return null;
        }

        if (!T.equals(SignalMast.class) && !T.equals(Sensor.class)) {
            log.error("Incorrect class type called, must be either SignalMast or Sensor");

            return null;
        }

        if (log.isDebugEnabled()) {
            log.debug("find signal mast between facing {} ({}) - protected {} ({})",
                    facingBlock.getDisplayName(), facingBlock.getDisplayName(),
                    protectedBlock.getDisplayName(), protectedBlock.getDisplayName());
        }

        //non-null - check if input corresponds to Blocks in a Layout Editor panel.
        String facingBlockName = facingBlock.getUserName();
        if ((facingBlockName == null) || facingBlockName.isEmpty()) {
            log.error("facing block has no user name");
            return null;
        }
        LayoutBlock fLayoutBlock = getByUserName(facingBlockName);
        String protectedBlockName = protectedBlock.getUserName();
        LayoutBlock pLayoutBlock = (protectedBlockName == null) ? null : getByUserName(protectedBlockName);
        if ((fLayoutBlock == null) || (pLayoutBlock == null)) {
            if (fLayoutBlock == null) {
                log.error("Block {} is not on a Layout Editor panel.", facingBlock.getDisplayName());
            }

            if (pLayoutBlock == null) {
                log.error("Block {} is not on a Layout Editor panel.", protectedBlock.getDisplayName());
            }
            return null;
        }

        //input has corresponding LayoutBlocks - does it correspond to a block boundary?
        if (panel == null) {
            panel = fLayoutBlock.getMaxConnectedPanel();
        }
        List<LayoutConnectivity> c = panel.getLEAuxTools().getConnectivityList(fLayoutBlock);
        LayoutConnectivity lc = null;
        int i = 0;
        boolean facingIsBlock1 = true;

        while ((i < c.size()) && (lc == null)) {
            LayoutConnectivity tlc = c.get(i);

            if ((tlc.getBlock1() == fLayoutBlock) && (tlc.getBlock2() == pLayoutBlock)) {
                lc = tlc;
            } else if ((tlc.getBlock1() == pLayoutBlock) && (tlc.getBlock2() == fLayoutBlock)) {
                lc = tlc;
                facingIsBlock1 = false;
            }
            i++;
        }

        if (lc == null) {
            PositionablePoint p = panel.getFinder().findPositionableLinkPoint(fLayoutBlock);

            if (p == null) {
                p = panel.getFinder().findPositionableLinkPoint(pLayoutBlock);
            }

            if ((p != null) && (p.getLinkedEditor() != null)) {
                return getFacingBean(facingBlock, protectedBlock, p.getLinkedEditor(), T);
            }
            log.debug("Block {} is not connected to Block {} on panel {}", facingBlock.getDisplayName(),
                    protectedBlock.getDisplayName(), panel.getLayoutName());

            return null;
        }
        LayoutTurnout lt = null;
        LayoutTrack connected = lc.getConnectedObject();

        TrackSegment tr = lc.getTrackSegment();
        HitPointType cType = lc.getConnectedType();

        if (connected == null) {
            if (lc.getXover() != null) {
                if (lc.getXoverBoundaryType() == LayoutConnectivity.XOVER_BOUNDARY_AB) {
                    if (fLayoutBlock == lc.getXover().getLayoutBlock()) {
                        cType = HitPointType.TURNOUT_A;
                    } else {
                        cType = HitPointType.TURNOUT_B;
                    }
                    connected = lc.getXover();
                } else if (lc.getXoverBoundaryType() == LayoutConnectivity.XOVER_BOUNDARY_CD) {
                    if (fLayoutBlock == lc.getXover().getLayoutBlockC()) {
                        cType = HitPointType.TURNOUT_C;
                    } else {
                        cType = HitPointType.TURNOUT_D;
                    }
                    connected = lc.getXover();
                } else if (lc.getXoverBoundaryType() == LayoutConnectivity.XOVER_BOUNDARY_AC) {
                    if (fLayoutBlock == lc.getXover().getLayoutBlock()) {
                        cType = HitPointType.TURNOUT_A;
                    } else {
                        cType = HitPointType.TURNOUT_C;
                    }
                    connected = lc.getXover();
                } else if (lc.getXoverBoundaryType() == LayoutConnectivity.XOVER_BOUNDARY_BD) {
                    if (fLayoutBlock == lc.getXover().getLayoutBlockB()) {
                        cType = HitPointType.TURNOUT_B;
                    } else {
                        cType = HitPointType.TURNOUT_D;
                    }
                    connected = lc.getXover();
                }
            }
        }

        if (connected == null) {
            log.error("No connectivity object found between Blocks {}, {} {}", facingBlock.getDisplayName(),
                    protectedBlock.getDisplayName(), cType);

            return null;
        }

        if (cType == HitPointType.TRACK) {
            //block boundary is at an Anchor Point
            PositionablePoint p = panel.getFinder().findPositionablePointAtTrackSegments(tr, (TrackSegment) connected);

            boolean block1IsWestEnd = LayoutEditorTools.isAtWestEndOfAnchor(panel, tr, p);
            log.debug("Track is west end? {}", block1IsWestEnd);
            if ((block1IsWestEnd && facingIsBlock1) || (!block1IsWestEnd && !facingIsBlock1)) {
                //block1 is on the west (north) end of the block boundary
                if (T.equals(SignalMast.class)) {
                    return p.getEastBoundSignalMast();
                } else if (T.equals(Sensor.class)) {
                    return p.getEastBoundSensor();
                }
            } else {
                if (T.equals(SignalMast.class)) {
                    return p.getWestBoundSignalMast();
                } else if (T.equals(Sensor.class)) {
                    return p.getWestBoundSensor();
                }
            }
        }

        if (cType == HitPointType.TURNOUT_A) {
            lt = (LayoutTurnout) connected;

            if ((lt.getLinkType() == LayoutTurnout.LinkType.NO_LINK) || (lt.getLinkType() == LayoutTurnout.LinkType.FIRST_3_WAY)) {
                if ((T.equals(SignalMast.class) && (lt.getSignalAMast() != null))
                        || (T.equals(Sensor.class) && (lt.getSensorA() != null))) {
                    if (tr == null) {
                        if (lt.getConnectA() instanceof TrackSegment) {
                            TrackSegment t = (TrackSegment) lt.getConnectA();

                            if ((t.getLayoutBlock() != null) && (t.getLayoutBlock() == lt.getLayoutBlock())) {
                                if (T.equals(SignalMast.class)) {
                                    return lt.getSignalAMast();
                                } else if (T.equals(Sensor.class)) {
                                    return lt.getSensorA();
                                }
                            }
                        }
                    } else if (tr.getLayoutBlock().getBlock() == facingBlock) {
                        if (T.equals(SignalMast.class)) {
                            return lt.getSignalAMast();
                        } else if (T.equals(Sensor.class)) {
                            return lt.getSensorA();
                        }
                    }
                }
            }
            return null;
        }

        if (cType == HitPointType.TURNOUT_B) {
            lt = (LayoutTurnout) connected;

            if ((T.equals(SignalMast.class) && (lt.getSignalBMast() != null))
                    || (T.equals(Sensor.class) && (lt.getSensorB() != null))) {
                if (tr == null) {
                    if (lt.getConnectB() instanceof TrackSegment) {
                        TrackSegment t = (TrackSegment) lt.getConnectB();

                        if ((t.getLayoutBlock() != null) && (t.getLayoutBlock() == lt.getLayoutBlockB())) {
                            if (T.equals(SignalMast.class)) {
                                return lt.getSignalBMast();
                            } else if (T.equals(Sensor.class)) {
                                return lt.getSensorB();
                            }
                        }
                    }
                } else if (tr.getLayoutBlock().getBlock() == facingBlock) {
                    if (T.equals(SignalMast.class)) {
                        return lt.getSignalBMast();
                    } else if (T.equals(Sensor.class)) {
                        return lt.getSensorB();
                    }
                }
            }
            return null;
        }

        if (cType == HitPointType.TURNOUT_C) {
            lt = (LayoutTurnout) connected;

            if ((T.equals(SignalMast.class) && (lt.getSignalCMast() != null))
                    || (T.equals(Sensor.class) && (lt.getSensorC() != null))) {
                if (tr == null) {
                    if (lt.getConnectC() instanceof TrackSegment) {
                        TrackSegment t = (TrackSegment) lt.getConnectC();

                        if ((t.getLayoutBlock() != null) && (t.getLayoutBlock() == lt.getLayoutBlockC())) {
                            if (T.equals(SignalMast.class)) {
                                return lt.getSignalCMast();
                            } else if (T.equals(Sensor.class)) {
                                return lt.getSensorC();
                            }
                        }
                    }
                } else if (tr.getLayoutBlock().getBlock() == facingBlock) {
                    if (T.equals(SignalMast.class)) {
                        return lt.getSignalCMast();
                    } else if (T.equals(Sensor.class)) {
                        return lt.getSensorC();
                    }
                }
            }
            return null;
        }

        if (cType == HitPointType.TURNOUT_D) {
            lt = (LayoutTurnout) connected;

            if ((T.equals(SignalMast.class) && (lt.getSignalDMast() != null))
                    || (T.equals(Sensor.class) && (lt.getSensorD() != null))) {
                if (tr == null) {
                    if (lt.getConnectD() instanceof TrackSegment) {
                        TrackSegment t = (TrackSegment) lt.getConnectD();

                        if ((t.getLayoutBlock() != null) && (t.getLayoutBlock() == lt.getLayoutBlockD())) {
                            if (T.equals(SignalMast.class)) {
                                return lt.getSignalDMast();
                            } else if (T.equals(Sensor.class)) {
                                return lt.getSensorD();
                            }
                        }
                    }
                } else if (tr.getLayoutBlock().getBlock() == facingBlock) {
                    if (T.equals(SignalMast.class)) {
                        return lt.getSignalDMast();
                    } else if (T.equals(Sensor.class)) {
                        return lt.getSensorD();
                    }
                }
            }
            return null;
        }

        if ((tr == null) || (tr.getLayoutBlock().getBlock() != facingBlock)) {
            return null;
        }

        if (HitPointType.isSlipHitType(cType)) {
            LayoutSlip ls = (LayoutSlip) connected;

            if (cType == HitPointType.SLIP_A) {
                if (T.equals(SignalMast.class)) {
                    return ls.getSignalAMast();
                } else if (T.equals(Sensor.class)) {
                    return ls.getSensorA();
                }
            }

            if (cType == HitPointType.SLIP_B) {
                if (T.equals(SignalMast.class)) {
                    return ls.getSignalBMast();
                } else if (T.equals(Sensor.class)) {
                    return ls.getSensorB();
                }
            }

            if (cType == HitPointType.SLIP_C) {
                if (T.equals(SignalMast.class)) {
                    return ls.getSignalCMast();
                } else if (T.equals(Sensor.class)) {
                    return ls.getSensorC();
                }
            }

            if (cType == HitPointType.SLIP_D) {
                if (T.equals(SignalMast.class)) {
                    return ls.getSignalDMast();
                } else if (T.equals(Sensor.class)) {
                    return ls.getSensorD();
                }
            }
        }

        if (!HitPointType.isLevelXingHitType(cType)) {
            log.error("Block Boundary not identified correctly - Blocks {}, {}", facingBlock.getDisplayName(),
                    protectedBlock.getDisplayName());

            return null;
        }

        /* We don't allow signal masts on the block outward facing from the level
        xing, nor do we consider the signal mast, that is protecting the in block on the xing */
        LevelXing xing = (LevelXing) connected;

        if (cType == HitPointType.LEVEL_XING_A) {
            //block boundary is at the A connection of a level crossing
            if (T.equals(SignalMast.class)) {
                return xing.getSignalAMast();
            } else if (T.equals(Sensor.class)) {
                return xing.getSensorA();
            }
        }

        if (cType == HitPointType.LEVEL_XING_B) {
            //block boundary is at the B connection of a level crossing
            if (T.equals(SignalMast.class)) {
                return xing.getSignalBMast();
            } else if (T.equals(Sensor.class)) {
                return xing.getSensorB();
            }
        }

        if (cType == HitPointType.LEVEL_XING_C) {
            //block boundary is at the C connection of a level crossing
            if (T.equals(SignalMast.class)) {
                return xing.getSignalCMast();
            } else if (T.equals(Sensor.class)) {
                return xing.getSensorC();
            }
        }

        if (cType == HitPointType.LEVEL_XING_D) {
            if (T.equals(SignalMast.class)) {
                return xing.getSignalDMast();
            } else if (T.equals(Sensor.class)) {
                return xing.getSensorD();
            }
        }
        return null;
    } //getFacingBean

    /**
     * In the first instance get a Signal Mast or if none exists a Signal Head
     * for a given facing block and protected block combination. See
     * #getFacingSignalMast() and #getFacingSignalHead() as to how they deal
     * with what each returns.
     * @param facingBlock the facing block to search for.
     * @param protectedBlock the protected block to search for.
     *
     * @return either a signalMast or signalHead
     */
    @CheckReturnValue
    @CheckForNull
    public Object getFacingSignalObject(
            @Nonnull Block facingBlock,
            @CheckForNull Block protectedBlock) {
        Object sig = getFacingSignalMast(facingBlock, protectedBlock, null);

        if (sig != null) {
            return sig;
        }
        sig = getFacingSignalHead(facingBlock, protectedBlock);
        return sig;
    }

    /**
     * Get the block that a given bean object (Sensor, SignalMast or SignalHead)
     * is protecting.
     *
     * @param nb    NamedBean
     * @param panel panel that this bean is on
     * @return The block that the bean object is facing
     */
    @CheckReturnValue
    @CheckForNull
    public LayoutBlock getProtectedBlockByNamedBean(
            @CheckForNull NamedBean nb,
            @CheckForNull LayoutEditor panel) {
        if (nb instanceof SignalHead) {
            return getProtectedBlock((SignalHead) nb, panel);
        }
        List<LayoutBlock> proBlocks = getProtectingBlocksByBean(nb, panel);

        if (proBlocks.isEmpty()) {
            return null;
        }
        return proBlocks.get(0);
    } //getProtectedBlockByNamedBean

    @CheckReturnValue
    @Nonnull
    public List<LayoutBlock> getProtectingBlocksByNamedBean(
            @CheckForNull NamedBean nb,
            @CheckForNull LayoutEditor panel) {
        ArrayList<LayoutBlock> ret = new ArrayList<>();

        if (nb instanceof SignalHead) {
            ret.add(getProtectedBlock((SignalHead) nb, panel));
            return ret;
        }
        return getProtectingBlocksByBean(nb, panel);
    }

    /**
     * If the panel variable is null, search all LE panels. This was added to
     * support multi panel entry/exit.
     *
     * @param bean  The sensor, mast or head to be located.
     * @param panel The panel to search. If null, search all LE panels.
     * @return a list of protected layout blocks.
     */
    @Nonnull
    private List<LayoutBlock> getProtectingBlocksByBean(
            @CheckForNull NamedBean bean,
            @CheckForNull LayoutEditor panel) {
        if (panel == null) {
            Set<LayoutEditor> panels = InstanceManager.getDefault(EditorManager.class).getAll(LayoutEditor.class);
            List<LayoutBlock> protectingBlocks = new ArrayList<>();
            for (LayoutEditor p : panels) {
                protectingBlocks = getProtectingBlocksByBeanByPanel(bean, p);
                if (!protectingBlocks.isEmpty()) {
                    break;
                }
            }
            return protectingBlocks;
        } else {
            return getProtectingBlocksByBeanByPanel(bean, panel);
        }
    }

    @Nonnull
    private List<LayoutBlock> getProtectingBlocksByBeanByPanel(
            @CheckForNull NamedBean bean,
            @CheckForNull LayoutEditor panel) {
        List<LayoutBlock> protectingBlocks = new ArrayList<>();

        if (!(bean instanceof SignalMast) && !(bean instanceof Sensor)) {
            log.error("Incorrect class type called, must be either SignalMast or Sensor");

            return protectingBlocks;
        }

        PositionablePoint pp = panel.getFinder().findPositionablePointByEastBoundBean(bean);
        TrackSegment tr = null;
        boolean east = true;

        if (pp == null) {
            pp = panel.getFinder().findPositionablePointByWestBoundBean(bean);
            east = false;
        }

        if (pp != null) {
            //   LayoutEditorTools tools = panel.getLETools(); //TODO: Dead-code strip this

            if (east) {
                if (LayoutEditorTools.isAtWestEndOfAnchor(panel, pp.getConnect1(), pp)) {
                    tr = pp.getConnect2();
                } else {
                    tr = pp.getConnect1();
                }
            } else {
                if (LayoutEditorTools.isAtWestEndOfAnchor(panel, pp.getConnect1(), pp)) {
                    tr = pp.getConnect1();
                } else {
                    tr = pp.getConnect2();
                }
            }

            if (tr != null) {
                protectingBlocks.add(tr.getLayoutBlock());

                return protectingBlocks;
            }
        }

        LevelXing l = panel.getFinder().findLevelXingByBean(bean);

        if (l != null) {
            if (bean instanceof SignalMast) {
                if (l.getSignalAMast() == bean) {
                    protectingBlocks.add(l.getLayoutBlockAC());
                } else if (l.getSignalBMast() == bean) {
                    protectingBlocks.add(l.getLayoutBlockBD());
                } else if (l.getSignalCMast() == bean) {
                    protectingBlocks.add(l.getLayoutBlockAC());
                } else {
                    protectingBlocks.add(l.getLayoutBlockBD());
                }
            } else if (bean instanceof Sensor) {
                if (l.getSensorA() == bean) {
                    protectingBlocks.add(l.getLayoutBlockAC());
                } else if (l.getSensorB() == bean) {
                    protectingBlocks.add(l.getLayoutBlockBD());
                } else if (l.getSensorC() == bean) {
                    protectingBlocks.add(l.getLayoutBlockAC());
                } else {
                    protectingBlocks.add(l.getLayoutBlockBD());
                }
            }
            return protectingBlocks;
        }

        LayoutSlip ls = panel.getFinder().findLayoutSlipByBean(bean);

        if (ls != null) {
            protectingBlocks.add(ls.getLayoutBlock());

            return protectingBlocks;
        }

        LayoutTurnout t = panel.getFinder().findLayoutTurnoutByBean(bean);

        if (t != null) {
            return t.getProtectedBlocks(bean);
        }
        return protectingBlocks;
    } //getProtectingBlocksByBean

    @CheckReturnValue
    @CheckForNull
    public LayoutBlock getProtectedBlockByMast(
            @CheckForNull SignalMast signalMast,
            @CheckForNull LayoutEditor panel) {
        List<LayoutBlock> proBlocks = getProtectingBlocksByBean(signalMast, panel);

        if (proBlocks.isEmpty()) {
            return null;
        }
        return proBlocks.get(0);
    }

    /**
     * Get the LayoutBlock that a given sensor is protecting.
     * @param sensorName the sensor name to search for.
     * @param panel the layout editor panel.
     * @return the layout block, may be null.
     */
    @CheckReturnValue
    @CheckForNull
    public LayoutBlock getProtectedBlockBySensor(
            @Nonnull String sensorName,
            @CheckForNull LayoutEditor panel) {
        Sensor sensor = InstanceManager.sensorManagerInstance().getSensor(sensorName);

        return getProtectedBlockBySensor(sensor, panel);
    }

    @Nonnull
    public List<LayoutBlock> getProtectingBlocksBySensor(
            @CheckForNull Sensor sensor, @CheckForNull LayoutEditor panel) {
        return getProtectingBlocksByBean(sensor, panel);
    }

    @Nonnull
    public List<LayoutBlock> getProtectingBlocksBySensorOld(
            @CheckForNull Sensor sensor, @Nonnull LayoutEditor panel) {
        List<LayoutBlock> result = new ArrayList<>();
        PositionablePoint pp = panel.getFinder().findPositionablePointByEastBoundBean(sensor);
        TrackSegment tr;
        boolean east = true;

        if (pp == null) {
            pp = panel.getFinder().findPositionablePointByWestBoundBean(sensor);
            east = false;
        }

        if (pp != null) {
            //            LayoutEditorTools tools = panel.getLETools(); //TODO: Dead-code strip this

            if (east) {
                if (LayoutEditorTools.isAtWestEndOfAnchor(panel, pp.getConnect1(), pp)) {
                    tr = pp.getConnect2();
                } else {
                    tr = pp.getConnect1();
                }
            } else {
                if (LayoutEditorTools.isAtWestEndOfAnchor(panel, pp.getConnect1(), pp)) {
                    tr = pp.getConnect1();
                } else {
                    tr = pp.getConnect2();
                }
            }

            if (tr != null) {
                result.add(tr.getLayoutBlock());

                return result;
            }
        }

        LevelXing l = panel.getFinder().findLevelXingByBean(sensor);

        if (l != null) {
            if (l.getSensorA() == sensor) {
                result.add(l.getLayoutBlockAC());
            } else if (l.getSensorB() == sensor) {
                result.add(l.getLayoutBlockBD());
            } else if (l.getSensorC() == sensor) {
                result.add(l.getLayoutBlockAC());
            } else {
                result.add(l.getLayoutBlockBD());
            }
            return result;
        }
        LayoutSlip ls = panel.getFinder().findLayoutSlipByBean(sensor);

        if (ls != null) {
            result.add(ls.getLayoutBlock());

            return result;
        }
        LayoutTurnout t = panel.getFinder().findLayoutTurnoutByBean(sensor);

        if (t != null) {
            return t.getProtectedBlocks(sensor);
        }
        return result;
    } //getProtectingBlocksBySensorOld

    /**
     * Get the LayoutBlock that a given sensor is protecting.
     * @param sensor sensor to search for.
     * @param panel layout editor panel to search.
     * @return the layout block, may be null.
     */
    @CheckReturnValue
    @CheckForNull
    public LayoutBlock getProtectedBlockBySensor(
            @CheckForNull Sensor sensor, @CheckForNull LayoutEditor panel) {
        List<LayoutBlock> proBlocks = getProtectingBlocksByBean(sensor, panel);

        if (proBlocks.isEmpty()) {
            return null;
        }
        return proBlocks.get(0);
    }

    /**
     * Get the block facing a given bean object (Sensor, SignalMast or
     * SignalHead).
     *
     * @param nb    NamedBean
     * @param panel panel that this bean is on
     * @return The block that the bean object is facing
     */
    @CheckReturnValue
    @CheckForNull
    public LayoutBlock getFacingBlockByNamedBean(
            @Nonnull NamedBean nb, @CheckForNull LayoutEditor panel) {
        if (nb instanceof SignalHead) {
            return getFacingBlock((SignalHead) nb, panel);
        }
        return getFacingBlockByBean(nb, panel);
    }

    /**
     * Get the LayoutBlock that a given sensor is facing.
     * @param sensorName the sensor name.
     * @param panel the layout editor panel.
     * @return the facing layout block, may be null.
     */
    @CheckReturnValue
    @CheckForNull
    public LayoutBlock getFacingBlockBySensor(@Nonnull String sensorName,
            @CheckForNull LayoutEditor panel) {
        LayoutBlock result = null;  //assume failure (pessimist!)
        if (panel != null) {
            Sensor sensor = InstanceManager.sensorManagerInstance().getSensor(sensorName);
            result = (sensor == null) ? null : getFacingBlockBySensor(sensor, panel);
        }
        return result;
    }

    /**
     * Get the LayoutBlock that a given signal is facing.
     * @param signalMast the signal mast to search for.
     * @param panel the layout editor panel.
     * @return the layout block, may be null.
     */
    @CheckReturnValue
    @CheckForNull
    public LayoutBlock getFacingBlockByMast(
            @Nonnull SignalMast signalMast,
            @Nonnull LayoutEditor panel) {
        return getFacingBlockByBean(signalMast, panel);
    }

    /**
     * If the panel variable is null, search all LE panels. This was added to
     * support multi panel entry/exit.
     *
     * @param bean  The sensor, mast or head to be located.
     * @param panel The panel to search. Search all LE panels if null.
     * @return the facing layout block.
     */
    @CheckReturnValue
    @CheckForNull
    private LayoutBlock getFacingBlockByBean(
            @Nonnull NamedBean bean,
            LayoutEditor panel) {
        if (panel == null) {
            Set<LayoutEditor> panels = InstanceManager.getDefault(EditorManager.class).getAll(LayoutEditor.class);
            LayoutBlock returnBlock = null;
            for (LayoutEditor p : panels) {
                returnBlock = getFacingBlockByBeanByPanel(bean, p);
                if (returnBlock != null) {
                    break;
                }
            }
            return returnBlock;
        } else {
            return getFacingBlockByBeanByPanel(bean, panel);
        }
    }

    @CheckReturnValue
    @CheckForNull
    private LayoutBlock getFacingBlockByBeanByPanel(
            @Nonnull NamedBean bean,
            @Nonnull LayoutEditor panel) {
        PositionablePoint pp = panel.getFinder().findPositionablePointByEastBoundBean(bean);
        TrackSegment tr = null;
        boolean east = true;

        //Don't think that the logic for this is the right way round
        if (pp == null) {
            pp = panel.getFinder().findPositionablePointByWestBoundBean(bean);
            east = false;
        }

        if (pp != null) {
            // LayoutEditorTools tools = panel.getLETools(); //TODO: Dead-code strip this

            if (east) {
                if (LayoutEditorTools.isAtWestEndOfAnchor(panel, pp.getConnect1(), pp)) {
                    tr = pp.getConnect1();
                } else {
                    tr = pp.getConnect2();
                }
            } else {
                if (LayoutEditorTools.isAtWestEndOfAnchor(panel, pp.getConnect1(), pp)) {
                    tr = pp.getConnect2();
                } else {
                    tr = pp.getConnect1();
                }
            }

            if (tr != null) {
                log.debug("found facing block by positionable point");

                return tr.getLayoutBlock();
            }
        }
        LayoutTurnout t = panel.getFinder().findLayoutTurnoutByBean(bean);

        if (t != null) {
            log.debug("found signalmast at turnout {}", t.getTurnout().getDisplayName());
            Object connect = null;

            if (bean instanceof SignalMast) {
                if (t.getSignalAMast() == bean) {
                    connect = t.getConnectA();
                } else if (t.getSignalBMast() == bean) {
                    connect = t.getConnectB();
                } else if (t.getSignalCMast() == bean) {
                    connect = t.getConnectC();
                } else {
                    connect = t.getConnectD();
                }
            } else if (bean instanceof Sensor) {
                if (t.getSensorA() == bean) {
                    connect = t.getConnectA();
                } else if (t.getSensorB() == bean) {
                    connect = t.getConnectB();
                } else if (t.getSensorC() == bean) {
                    connect = t.getConnectC();
                } else {
                    connect = t.getConnectD();
                }
            }

            if (connect instanceof TrackSegment) {
                tr = (TrackSegment) connect;
                log.debug("return block {}", tr.getLayoutBlock().getDisplayName());

                return tr.getLayoutBlock();
            }
        }

        LevelXing l = panel.getFinder().findLevelXingByBean(bean);

        if (l != null) {
            Object connect = null;

            if (bean instanceof SignalMast) {
                if (l.getSignalAMast() == bean) {
                    connect = l.getConnectA();
                } else if (l.getSignalBMast() == bean) {
                    connect = l.getConnectB();
                } else if (l.getSignalCMast() == bean) {
                    connect = l.getConnectC();
                } else {
                    connect = l.getConnectD();
                }
            } else if (bean instanceof Sensor) {
                if (l.getSensorA() == bean) {
                    connect = l.getConnectA();
                } else if (l.getSensorB() == bean) {
                    connect = l.getConnectB();
                } else if (l.getSensorC() == bean) {
                    connect = l.getConnectC();
                } else {
                    connect = l.getConnectD();
                }
            }

            if (connect instanceof TrackSegment) {
                tr = (TrackSegment) connect;
                log.debug("return block {}", tr.getLayoutBlock().getDisplayName());

                return tr.getLayoutBlock();
            }
        }

        LayoutSlip ls = panel.getFinder().findLayoutSlipByBean(bean);

        if (ls != null) {
            Object connect = null;

            if (bean instanceof SignalMast) {
                if (ls.getSignalAMast() == bean) {
                    connect = ls.getConnectA();
                } else if (ls.getSignalBMast() == bean) {
                    connect = ls.getConnectB();
                } else if (ls.getSignalCMast() == bean) {
                    connect = ls.getConnectC();
                } else {
                    connect = ls.getConnectD();
                }
            } else if (bean instanceof Sensor) {
                if (ls.getSensorA() == bean) {
                    connect = ls.getConnectA();
                } else if (ls.getSensorB() == bean) {
                    connect = ls.getConnectB();
                } else if (ls.getSensorC() == bean) {
                    connect = ls.getConnectC();
                } else {
                    connect = ls.getConnectD();
                }
            }

            if (connect instanceof TrackSegment) {
                tr = (TrackSegment) connect;
                log.debug("return block {}", tr.getLayoutBlock().getDisplayName());

                return tr.getLayoutBlock();
            }
        }
        return null;
    } //getFacingBlockByBean

    /**
     * Get the LayoutBlock that a given sensor is facing.
     * @param sensor the sensor to search for.
     * @param panel the layout editor panel to search.
     * @return the layout block, may be null.
     */
    @CheckReturnValue
    @CheckForNull
    public LayoutBlock getFacingBlockBySensor(
            @Nonnull Sensor sensor,
            @Nonnull LayoutEditor panel) {
        return getFacingBlockByBean(sensor, panel);
    }

    @CheckReturnValue
    @CheckForNull
    public LayoutBlock getProtectedBlock(
            @Nonnull SignalHead signalHead, @CheckForNull LayoutEditor panel) {
        LayoutBlock result = null;  //assume failure (pessimist!)
        if (panel != null) {
            String userName = signalHead.getUserName();
            result = (userName == null) ? null : getProtectedBlock(userName, panel);

            if (result == null) {
                result = getProtectedBlock(signalHead.getSystemName(), panel);
            }
        }
        return result;
    }

    /**
     * Get the LayoutBlock that a given signal is protecting.
     * @param signalName the signal name to search for.
     * @param panel the main layout editor panel.
     * @return the layout block, may be null.
     */
    /* @TODO This needs to be expanded to cover turnouts and level crossings. */
    @CheckReturnValue
    @CheckForNull
    public LayoutBlock getProtectedBlock(
            @Nonnull String signalName, @Nonnull LayoutEditor panel) {
        PositionablePoint pp = panel.getFinder().findPositionablePointByEastBoundSignal(signalName);
        TrackSegment tr;

        if (pp == null) {
            pp = panel.getFinder().findPositionablePointByWestBoundSignal(signalName);

            if (pp == null) {
                return null;
            }
            tr = pp.getConnect1();
        } else {
            tr = pp.getConnect2();
        }

        //tr = pp.getConnect2();
        if (tr == null) {
            return null;
        }
        return tr.getLayoutBlock();
    }

    @CheckReturnValue
    @CheckForNull
    public LayoutBlock getFacingBlock(
            @Nonnull SignalHead signalHead, @CheckForNull LayoutEditor panel) {
        LayoutBlock result = null;  //assume failure (pessimist!)
        if (panel != null) {
            String userName = signalHead.getUserName();
            result = (userName == null) ? null : getFacingBlock(userName, panel);
            if (result == null) {
                result = getFacingBlock(signalHead.getSystemName(), panel);
            }
        }
        return result;
    }

    /**
     * Get the LayoutBlock that a given signal is facing.
     * @param signalName signal name.
     * @param panel layout editor panel.
     * @return the facing layout block.
     */
    /* @TODO This needs to be expanded to cover turnouts and level crossings. */
    @CheckReturnValue
    @CheckForNull
    public LayoutBlock getFacingBlock(
            @Nonnull String signalName, @Nonnull LayoutEditor panel) {
        PositionablePoint pp = panel.getFinder().findPositionablePointByWestBoundSignal(signalName);
        TrackSegment tr;

        if (pp == null) {
            pp = panel.getFinder().findPositionablePointByWestBoundSignal(signalName);

            if (pp == null) {
                return null;
            }
            tr = pp.getConnect1();
        } else {
            tr = pp.getConnect2();
        }

        if (tr == null) {
            return null;
        }
        return tr.getLayoutBlock();
    }

    private boolean warnConnectivity = true;

    /**
     * Controls switching off incompatible block connectivity messages.
     * <p>
     * Warnings are always on when program starts up. Once stopped by the user,
     * these messages may not be switched on again until program restarts.
     * @return true if connectivity warning flag set, else false.
     */
    public boolean warn() {
        return warnConnectivity;
    }

    public void turnOffWarning() {
        warnConnectivity = false;
    }

    protected boolean enableAdvancedRouting = false;

    /**
     * @return true if advanced layout block routing has been enabled
     */
    public boolean isAdvancedRoutingEnabled() {
        return enableAdvancedRouting;
    }

    /**
     * Enable the advanced layout block routing protocol
     * <p>
     * The block routing protocol enables each layout block to build up a list
     * of all reachable blocks, along with how far away they are, which
     * direction they are in and which of the connected blocks they are
     * reachable from.
     */
    private long firstRoutingChange;

    public void enableAdvancedRouting(boolean boo) {
        if (boo == enableAdvancedRouting) {
            return;
        }
        enableAdvancedRouting = boo;

        if (boo && initialized) {
            initializeLayoutBlockRouting();
        }
        firePropertyChange("advancedRoutingEnabled", !enableAdvancedRouting, enableAdvancedRouting);
    }

    private void initializeLayoutBlockRouting() {
        if (!enableAdvancedRouting || !initialized) {
            log.debug("initializeLayoutBlockRouting immediate return due to {} {}", enableAdvancedRouting, initialized);

            return;
        }
        firstRoutingChange = System.nanoTime();

        //cycle through all LayoutBlocks, completing initialization of the layout block routing
        java.util.Enumeration<LayoutBlock> en = _tsys.elements();

        while (en.hasMoreElements()) {
            en.nextElement().initializeLayoutBlockRouting();
        }
    }

    @Nonnull
    public LayoutBlockConnectivityTools getLayoutBlockConnectivityTools() {
        return lbct;
    }

    LayoutBlockConnectivityTools lbct = new LayoutBlockConnectivityTools();

    private long lastRoutingChange;

    void setLastRoutingChange() {
        log.debug("setLastRoutingChange");
        lastRoutingChange = System.nanoTime();
        stabilised = false;
        setRoutingStabilised();
    }

    boolean checking = false;
    boolean stabilised = false;

    private void setRoutingStabilised() {
        if (checking) {
            return;
        }
        log.debug("routing table change has been initiated");
        checking = true;

        if (namedStabilisedIndicator != null) {
            try {
                namedStabilisedIndicator.getBean().setState(Sensor.INACTIVE);
            } catch (jmri.JmriException ex) {
                log.debug("Error setting stability indicator sensor");
            }
        }
        Runnable r = () -> {
            try {
                firePropertyChange("topology", true, false);
                long oldvalue = lastRoutingChange;

                while (!stabilised) {
                    Thread.sleep(2000L); //two seconds

                    if (oldvalue == lastRoutingChange) {
                        log.debug("routing table has now been stable for 2 seconds");
                        checking = false;
                        stabilised = true;
                        jmri.util.ThreadingUtil.runOnLayoutEventually(() -> firePropertyChange("topology", false, true));

                        if (namedStabilisedIndicator != null) {
                            jmri.util.ThreadingUtil.runOnLayoutEventually(() -> {
                                log.debug("Setting StabilisedIndicator Sensor {} ACTIVE",
                                        namedStabilisedIndicator.getBean().getDisplayName());
                                try {
                                    namedStabilisedIndicator.getBean().setState(Sensor.ACTIVE);
                                } catch (jmri.JmriException ex) {
                                    log.debug("Error setting stability indicator sensor");
                                }
                            });
                        } else {
                            log.debug("Stable, no sensor to set");
                        }
                    } else {
                        long seconds = (long) ((lastRoutingChange - firstRoutingChange) / 1e9);
                        log.debug("routing table not stable after {} in {}",
                                String.format("%d:%02d:%02d", seconds / 3600, (seconds / 60) % 60, seconds % 60),
                                Thread.currentThread().getName());
                    }
                    oldvalue = lastRoutingChange;
                }
            } catch (InterruptedException ex) {
                Thread.currentThread().interrupt();
                checking = false;

                //} catch (jmri.JmriException ex) {
                //log.debug("Error setting stability indicator sensor");
            }
        };
        thr = jmri.util.ThreadingUtil.newThread(r, "Routing stabilising timer");
        thr.start();
    } //setRoutingStabilised

    private Thread thr = null;

    private NamedBeanHandle<Sensor> namedStabilisedIndicator;

    /**
     * Assign a sensor to the routing protocol, that changes state dependant
     * upon if the routing protocol has stabilised or is under going a change.
     * @param pName sensor name, will be provided if not existing.
     * @throws jmri.JmriException if no sensor manager.
     *
     */
    public void setStabilisedSensor(@Nonnull String pName) throws jmri.JmriException {
        if (InstanceManager.getNullableDefault(jmri.SensorManager.class) != null) {
            try {
                Sensor sensor = InstanceManager.sensorManagerInstance().provideSensor(pName);
                namedStabilisedIndicator = jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(
                        pName,
                        sensor);
                try {
                    if (stabilised) {
                        sensor.setState(Sensor.ACTIVE);
                    } else {
                        sensor.setState(Sensor.INACTIVE);
                    }
                } catch (jmri.JmriException ex) {
                    log.error("Error setting stablilty indicator sensor");
                }
            } catch (IllegalArgumentException ex) {
                log.error("Sensor '{}' not available", pName);
                throw new jmri.JmriException("Sensor '" + pName + "' not available");
            }
        } else {
            log.error("No SensorManager for this protocol");
            throw new jmri.JmriException("No Sensor Manager Found");
        }
    }

    /**
     * Get the sensor used to indicate if the routing protocol has stabilised or
     * not.
     * @return routing stability sensor, may be null.
     */
    public Sensor getStabilisedSensor() {
        if (namedStabilisedIndicator == null) {
            return null;
        }
        return namedStabilisedIndicator.getBean();
    }

    /**
     * Get the sensor used for the stability indication.
     * @return stability sensor, may be null.
     */
    @CheckReturnValue
    @CheckForNull
    public NamedBeanHandle<Sensor> getNamedStabilisedSensor() {
        return namedStabilisedIndicator;
    }

    /**
     * @return true if the layout block routing protocol has stabilised
     */
    public boolean routingStablised() {
        return stabilised;
    }

    /**
     * @return the time when the last routing change was made, recorded as
     *         System.nanoTime()
     */
    public long getLastRoutingChange() {
        return lastRoutingChange;
    }

    @Override
    @Nonnull
    public String getBeanTypeHandled(boolean plural) {
        return Bundle.getMessage(plural ? "BeanNameLayoutBlocks" : "BeanNameLayoutBlock");
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Class<LayoutBlock> getNamedBeanClass() {
        return LayoutBlock.class;
    }

    /**
     * Get a list of layout blocks which this roster entry appears to be
     * occupying. A layout block is assumed to contain this roster entry if the
     * value of the underlying block is the RosterEntry itself, or a string with
     * the entry's id or dcc address.
     *
     * @param re the roster entry
     * @return list of layout block user names
     */
    @Nonnull
    public List<LayoutBlock> getLayoutBlocksOccupiedByRosterEntry(
            @Nonnull RosterEntry re) {
        List<LayoutBlock> result = new ArrayList<>();

        BlockManager bm = jmri.InstanceManager.getDefault(jmri.BlockManager.class);
        List<Block> blockList = bm.getBlocksOccupiedByRosterEntry(re);
        for (Block block : blockList) {
            String uname = block.getUserName();
            if (uname != null) {
                LayoutBlock lb = getByUserName(uname);
                if (lb != null) {
                    result.add(lb);
                }
            }
        }
        return result;
    }

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

}