001package jmri.jmrit.display.layoutEditor;
002
003import java.util.ArrayList;
004import java.util.List;
005import java.util.Set;
006import javax.annotation.CheckForNull;
007import javax.annotation.CheckReturnValue;
008import javax.annotation.Nonnull;
009import javax.swing.JOptionPane;
010import jmri.Block;
011import jmri.BlockManager;
012import jmri.jmrit.display.EditorManager;
013import jmri.InstanceManager;
014import jmri.Memory;
015import jmri.NamedBean;
016import jmri.NamedBeanHandle;
017import jmri.Sensor;
018import jmri.SignalHead;
019import jmri.SignalMast;
020import jmri.Turnout;
021import jmri.jmrit.roster.RosterEntry;
022import jmri.jmrix.internal.InternalSystemConnectionMemo;
023import jmri.managers.AbstractManager;
024
025/**
026 * Implementation of a Manager to handle LayoutBlocks. Note: the same
027 * LayoutBlocks may appear in multiple LayoutEditor panels.
028 * <p>
029 * This manager does not enforce any particular system naming convention.
030 * <p>
031 * LayoutBlocks are usually addressed by userName. The systemName is hidden from
032 * the user for the most part.
033 *
034 * @author Dave Duchamp Copyright (C) 2007
035 * @author George Warner Copyright (c) 2017-2018
036 */
037public class LayoutBlockManager extends AbstractManager<LayoutBlock> implements jmri.InstanceManagerAutoDefault {
038
039    public LayoutBlockManager() {
040        super(InstanceManager.getDefault(InternalSystemConnectionMemo.class));
041        InstanceManager.sensorManagerInstance().addVetoableChangeListener(this);
042        InstanceManager.memoryManagerInstance().addVetoableChangeListener(this);
043    }
044
045    @Override
046    public int getXMLOrder() {
047        return jmri.Manager.LAYOUTBLOCKS;
048    }
049
050    @Override
051    public char typeLetter() {
052        return 'B';
053    }
054    private int blkNum = 1;
055
056    /**
057     * Create a new LayoutBlock if the LayoutBlock does not exist.
058     * <p>
059     * Note that since the userName is used to address LayoutBlocks, the user
060     * name must be present. If the user name is not present, the new
061     * LayoutBlock is not created, and null is returned.
062     *
063     * @param systemName block system name.
064     * @param userName block username, must be non-empty.
065     * @return null if a LayoutBlock with the same systemName or userName
066     *         already exists, or if there is trouble creating a new LayoutBlock
067     */
068    @CheckReturnValue
069    @CheckForNull
070    public LayoutBlock createNewLayoutBlock(
071            @CheckForNull String systemName,
072            String userName) {
073        // Check that LayoutBlock does not already exist
074        LayoutBlock result = null;
075
076        if ((userName == null) || userName.isEmpty()) {
077            log.error("Attempt to create a LayoutBlock with no user name");
078
079            return null;
080        }
081        result = getByUserName(userName);
082
083        if (result != null) {
084            return null;
085        }
086
087        // here if not found under user name
088        String sName = "";
089
090        if (systemName == null) {
091            //create a new unique system name
092            boolean found = true;
093
094            while (found) {
095                sName = "ILB" + blkNum;
096                blkNum++;
097                result = getBySystemName(sName);
098
099                if (result == null) {
100                    found = false;
101                }
102            }
103        } else {
104            // try the supplied system name
105            result = getBySystemName((systemName));
106
107            if (result != null) {
108                return null;
109            }
110            sName = systemName;
111        }
112
113        // LayoutBlock does not exist, create a new LayoutBlock
114        result = new LayoutBlock(sName, userName);
115
116        //save in the maps
117        register(result);
118
119        return result;
120    }
121
122    @CheckReturnValue
123    @CheckForNull
124    public LayoutBlock createNewLayoutBlock() {
125        while (true) {
126            String sName = "ILB" + blkNum;
127            LayoutBlock block = getBySystemName(sName);
128
129            if (block == null) {
130                String uName = "AUTOBLK:" + blkNum;
131                block = new LayoutBlock(sName, uName);
132                register(block);
133
134                return block;
135            }
136            blkNum++;
137        }
138    }
139
140    /**
141     * Remove an existing LayoutBlock.
142     * @param block the block to remove.
143     */
144    public void deleteLayoutBlock(LayoutBlock block) {
145        deregister(block);
146    }
147
148    /**
149     * Get an existing LayoutBlock. First looks up assuming that name is a User
150     * Name. If this fails, looks up assuming that name is a System Name.
151     *
152     * @param name ideally block username, can be system name.
153     * @return LayoutBlock, or null if not found by either user name or system
154     *         name
155     */
156    @CheckReturnValue
157    @CheckForNull
158    public LayoutBlock getLayoutBlock(@Nonnull String name) {
159        LayoutBlock block = getByUserName(name);
160
161        if (block != null) {
162            return block;
163        }
164        return getBySystemName(name);
165    }
166
167    @CheckReturnValue
168    @CheckForNull
169    public LayoutBlock getLayoutBlock(@CheckForNull Block block) {
170        for (LayoutBlock lb : getNamedBeanSet()) {
171            if (lb.getBlock() == block) {
172                return lb;
173            }
174        }
175        return null;
176    }
177
178    /**
179     * Find a LayoutBlock with a specified Sensor assigned as its occupancy
180     * sensor.
181     *
182     * @param s the sensor to search for.
183     * @return the block or null if no existing LayoutBlock has the Sensor
184     *         assigned
185     */
186    @CheckReturnValue
187    @CheckForNull
188    public LayoutBlock getBlockWithSensorAssigned(@CheckForNull Sensor s) {
189        for (LayoutBlock block : getNamedBeanSet()) {
190            if (block.getOccupancySensor() == s) {
191                return block;
192            }
193        }
194        return null;
195    }
196
197    /**
198     * Find a LayoutBlock with a specified Memory assigned as its value display.
199     *
200     * @param m the memory to search for.
201     * @return the block or null if no existing LayoutBlock has the memory
202     *         assigned.
203     */
204    @CheckReturnValue
205    @CheckForNull
206    public LayoutBlock getBlockWithMemoryAssigned(Memory m) {
207        for (LayoutBlock block : getNamedBeanSet()) {
208            if (block.getMemory() == m) {
209                return block;
210            }
211        }
212        return null;
213    }
214
215    /**
216     * Initialize/check the Paths of all Blocks associated with LayoutBlocks.
217     * <p>
218     * This routine should be called when loading panels, after all Layout
219     * Editor panels have been loaded.
220     */
221    public void initializeLayoutBlockPaths() {
222        log.debug("start initializeLayoutBlockPaths");
223
224        // cycle through all LayoutBlocks, completing initialization of associated jmri.Blocks
225        for (LayoutBlock b : getNamedBeanSet()) {
226            log.debug("Calling block '{}({})'.initializeLayoutBlock()", b.getSystemName(), b.getDisplayName());
227            b.initializeLayoutBlock();
228        }
229
230        //cycle through all LayoutBlocks, updating Paths of associated jmri.Blocks
231        badBeanErrors = 0; // perhaps incremented via addBadBeanError(), but that's never called?
232        for (LayoutBlock b : getNamedBeanSet()) {
233            log.debug("Calling block '{}({})'.updatePaths()", b.getSystemName(), b.getDisplayName());
234
235            b.updatePaths();
236
237            if (b.getBlock().getValue() != null) {
238                b.getBlock().setValue(null);
239            }
240        }
241
242        if (badBeanErrors > 0) { // perhaps incremented via addBadBeanError(), but that's never called?
243            JOptionPane.showMessageDialog(null, "" + badBeanErrors + " " + Bundle.getMessage("Warn2"),
244                    Bundle.getMessage("WarningTitle"), JOptionPane.ERROR_MESSAGE);
245        }
246        try {
247            new BlockValueFile().readBlockValues();
248        } catch (org.jdom2.JDOMException jde) {
249            log.error("JDOM Exception when retreiving block values", jde);
250        } catch (java.io.IOException ioe) {
251            log.error("I/O Exception when retreiving block values", ioe);
252        }
253
254        //special tests for getFacingSignalHead method - comment out next three lines unless using LayoutEditorTests
255        //LayoutEditorTests layoutEditorTests = new LayoutEditorTests();
256        //layoutEditorTests.runClinicTests();
257        //layoutEditorTests.runTestPanel3Tests();
258        initialized = true;
259        log.debug("start initializeLayoutBlockRouting");
260        initializeLayoutBlockRouting();
261        log.debug("end initializeLayoutBlockRouting and initializeLayoutBlockPaths");
262    }
263
264    private boolean initialized = false;
265
266    // Is this ever called?
267    public void addBadBeanError() {
268        badBeanErrors++;
269    }
270    private int badBeanErrors = 0;
271
272    /**
273     * Get the Signal Head facing into a specified Block from a specified
274     * protected Block.
275     * <p>
276     * This method is primarily designed for use with scripts to get information
277     * initially residing in a Layout Editor panel. If either of the input
278     * Blocks is null, or if the two blocks do not join at a block boundary, or
279     * if either of the input Blocks are not Layout Editor panel blocks, an
280     * error message is logged, and "null" is returned. If the signal at the
281     * block boundary has two heads--is located at the facing point of a
282     * turnout-- the Signal Head that applies for the current setting of turnout
283     * (THROWN or CLOSED) is returned. If the turnout state is UNKNOWN or
284     * INCONSISTENT, an error message is logged, and "null" is returned. If the
285     * signal at the block boundary has three heads--the facing point of a 3-way
286     * turnout--the Signal Head that applies for the current settings of the two
287     * turnouts of the 3-way turnout is returned. If the turnout state of either
288     * turnout is UNKNOWN or INCONSISTENT, an error is logged and "null" is
289     * returned. "null" is returned if the block boundary is between the two
290     * turnouts of a THROAT_TO_THROAT turnout or a 3-way turnout. "null" is
291     * returned for block boundaries exiting a THROAT_TO_THROAT turnout block,
292     * since there are no signals that apply there.
293     * @param facingBlock the facing block.
294     * @param protectedBlock the protected block.
295     * @return the signal head, may be null.
296     */
297    @CheckReturnValue
298    @CheckForNull
299    public SignalHead getFacingSignalHead(
300            @CheckForNull Block facingBlock,
301            @CheckForNull Block protectedBlock) {
302        //check input
303        if ((facingBlock == null) || (protectedBlock == null)) {
304            log.error("null block in call to getFacingSignalHead");
305            return null;
306        }
307
308        //non-null - check if input corresponds to Blocks in a Layout Editor panel.
309        String facingBlockName = facingBlock.getUserName();
310        if ((facingBlockName == null) || facingBlockName.isEmpty()) {
311            log.error("facingBlockName has no user name");
312            return null;
313        }
314
315        String protectedBlockName = protectedBlock.getUserName();
316        if ((protectedBlockName == null) || protectedBlockName.isEmpty()) {
317            log.error("protectedBlockName has no user name");
318            return null;
319        }
320
321        LayoutBlock fLayoutBlock = getByUserName(facingBlockName);
322        LayoutBlock pLayoutBlock = getByUserName(protectedBlockName);
323        if ((fLayoutBlock == null) || (pLayoutBlock == null)) {
324            if (fLayoutBlock == null) {
325                log.error("Block {} is not on a Layout Editor panel.", facingBlock.getDisplayName());
326            }
327
328            if (pLayoutBlock == null) {
329                log.error("Block {} is not on a Layout Editor panel.", protectedBlock.getDisplayName());
330            }
331            return null;
332        }
333
334        //input has corresponding LayoutBlocks - does it correspond to a block boundary?
335        LayoutEditor panel = fLayoutBlock.getMaxConnectedPanel();
336        List<LayoutConnectivity> c = panel.getLEAuxTools().getConnectivityList(fLayoutBlock);
337        LayoutConnectivity lc = null;
338        int i = 0;
339        boolean facingIsBlock1 = true;
340
341        while ((i < c.size()) && (lc == null)) {
342            LayoutConnectivity tlc = c.get(i);
343
344            if ((tlc.getBlock1() == fLayoutBlock) && (tlc.getBlock2() == pLayoutBlock)) {
345                lc = tlc;
346            } else if ((tlc.getBlock1() == pLayoutBlock) && (tlc.getBlock2() == fLayoutBlock)) {
347                lc = tlc;
348                facingIsBlock1 = false;
349            }
350            i++;
351        }
352
353        if (lc == null) {
354            log.error("Block {} ({}) is not connected to Block {}", facingBlock.getDisplayName(),
355                    facingBlock.getDisplayName(), protectedBlock.getDisplayName());
356            return null;
357        }
358
359        //blocks are connected, get connection item types
360        LayoutTurnout lt = null;
361        TrackSegment tr = lc.getTrackSegment();
362        int boundaryType = 0;
363
364        if (tr == null) {
365            // this is an internal crossover block boundary
366            lt = lc.getXover();
367            boundaryType = lc.getXoverBoundaryType();
368
369            switch (boundaryType) {
370                case LayoutConnectivity.XOVER_BOUNDARY_AB: {
371                    if (facingIsBlock1) {
372                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
373                    } else {
374                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1);
375                    }
376                }
377
378                case LayoutConnectivity.XOVER_BOUNDARY_CD: {
379                    if (facingIsBlock1) {
380                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
381                    } else {
382                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTD1);
383                    }
384                }
385
386                case LayoutConnectivity.XOVER_BOUNDARY_AC: {
387                    if (facingIsBlock1) {
388                        if (lt.getSignalHead(LayoutTurnout.Geometry.POINTA2) == null) { //there is no signal head for diverging (crossed
389                            //over)
390                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
391                        } else { //there is a diverging (crossed over) signal head, return it
392                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTA2);
393                        }
394                    } else {
395                        if (lt.getSignalHead(LayoutTurnout.Geometry.POINTC2) == null) {
396                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
397                        } else {
398                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTC2);
399                        }
400                    }
401                }
402
403                case LayoutConnectivity.XOVER_BOUNDARY_BD: {
404                    if (facingIsBlock1) {
405                        if (lt.getSignalHead(LayoutTurnout.Geometry.POINTB2) == null) { //there is no signal head for diverging (crossed
406                            //over)
407                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1);
408                        } else { //there is a diverging (crossed over) signal head, return it
409                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTB2);
410                        }
411                    } else {
412                        if (lt.getSignalHead(LayoutTurnout.Geometry.POINTD2) == null) {
413                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTD1);
414                        } else {
415                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTD2);
416                        }
417                    }
418                }
419
420                default: {
421                    log.error("Unhandled crossover connection type: {}", boundaryType);
422                    break;
423                }
424            } //switch
425
426            //should never reach here, but ...
427            log.error("crossover turnout block boundary not found in getFacingSignal");
428
429            return null;
430        }
431
432        //not internal crossover block boundary
433        LayoutTrack connected = lc.getConnectedObject();
434        HitPointType cType = lc.getConnectedType();
435        if (connected == null) {
436            log.error("No connectivity object found between Blocks {}, {} {}", facingBlock.getDisplayName(),
437                    protectedBlock.getDisplayName(), cType);
438
439            return null;
440        }
441
442        if (cType == HitPointType.TRACK) {
443            // block boundary is at an Anchor Point
444            //    LayoutEditorTools tools = panel.getLETools(); //TODO: Dead-code strip this
445            PositionablePoint p = panel.getFinder().findPositionablePointAtTrackSegments(tr, (TrackSegment) connected);
446            boolean block1IsWestEnd = LayoutEditorTools.isAtWestEndOfAnchor(panel, tr, p);
447
448            if ((block1IsWestEnd && facingIsBlock1) || (!block1IsWestEnd && !facingIsBlock1)) {
449                //block1 is on the west (north) end of the block boundary
450                return p.getEastBoundSignalHead();
451            } else {
452                return p.getWestBoundSignalHead();
453            }
454        }
455
456        if (cType == HitPointType.TURNOUT_A) {
457            // block boundary is at the facing point of a turnout or A connection of a crossover turnout
458            lt = (LayoutTurnout) connected;
459
460            if (lt.getLinkType() == LayoutTurnout.LinkType.NO_LINK) {
461                //standard turnout or A connection of a crossover turnout
462                if (facingIsBlock1) {
463                    if (lt.getSignalHead(LayoutTurnout.Geometry.POINTA2) == null) { //there is no signal head for diverging
464                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
465                    } else {
466                        //check if track segments at B or C are in protected block (block 2)
467                        if (((TrackSegment) (lt.getConnectB())).getBlockName().equals(protectedBlock.getUserName())) {
468                            //track segment connected at B matches block 2, check C
469                            if (!(((TrackSegment) lt.getConnectC()).getBlockName().equals(protectedBlock.getUserName()))) {
470                                //track segment connected at C is not in block2, return continuing signal head at A
471                                if (lt.getContinuingSense() == Turnout.CLOSED) {
472                                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
473                                } else {
474                                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTA2);
475                                }
476                            } else {
477                                //B and C both in block2, check turnout position to decide which signal head to return
478                                int state = lt.getTurnout().getKnownState();
479
480                                if (((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.CLOSED))
481                                        || ((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.THROWN))) { //continuing
482                                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
483                                } else if (((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.CLOSED))
484                                        || ((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.THROWN))) { //diverging
485                                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTA2);
486                                } else {
487                                    //turnout state is UNKNOWN or INCONSISTENT
488                                    log.error("Cannot choose signal head because turnout {} is in an UNKNOWN or INCONSISTENT state.",
489                                            lt.getTurnout().getDisplayName());
490
491                                    return null;
492                                }
493                            }
494                        }
495
496                        //track segment connected at B is not in block 2
497                        if ((((TrackSegment) lt.getConnectC()).getBlockName().equals(protectedBlock.getUserName()))) {
498                            //track segment connected at C is in block 2, return diverging signal head
499                            if (lt.getContinuingSense() == Turnout.CLOSED) {
500                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTA2);
501                            } else {
502                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
503                            }
504                        } else {
505                            // neither track segment is in block 2 - will get here when layout turnout is the only item in block 2
506                            // Return signal head based on turnout position
507                            int state = lt.getTurnout().getKnownState();
508                            if (((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.CLOSED))
509                                    || ((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.THROWN))) { //continuing
510                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
511                            } else if (((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.CLOSED))
512                                    || ((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.THROWN))) { //diverging
513                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTA2);
514                            }
515
516                            // Turnout state is unknown or inconsistent
517                            return null;
518                        }
519                    }
520                } else {
521                    //check if track segments at B or C are in facing block (block 1)
522                    if (((TrackSegment) (lt.getConnectB())).getBlockName().equals(facingBlock.getUserName())) {
523                        //track segment connected at B matches block 1, check C
524                        if (!(((TrackSegment) lt.getConnectC()).getBlockName().equals(facingBlock.getDisplayName()))) {
525                            //track segment connected at C is not in block 2, return signal head at continuing end
526                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1);
527                        } else {
528                            //B and C both in block 1, check turnout position to decide which signal head to return
529                            int state = lt.getTurnout().getKnownState();
530
531                            if (((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.CLOSED))
532                                    || ((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.THROWN))) { //continuing
533                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1);
534                            } else if (((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.CLOSED))
535                                    || ((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.THROWN))) {
536                                //diverging, check for second head
537                                if (lt.getSignalHead(LayoutTurnout.Geometry.POINTC2) == null) {
538                                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
539                                } else {
540                                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTC2);
541                                }
542                            } else {
543                                //turnout state is UNKNOWN or INCONSISTENT
544                                log.error("Cannot choose signal head because turnout {} is in an UNKNOWN or INCONSISTENT state.",
545                                        lt.getTurnout().getDisplayName());
546
547                                return null;
548                            }
549                        }
550                    }
551
552                    //track segment connected at B is not in block 1
553                    if (((TrackSegment) lt.getConnectC()).getBlockName().equals(facingBlock.getUserName())) {
554                        //track segment connected at C is in block 1, return diverging signal head, check for second head
555                        if (lt.getSignalHead(LayoutTurnout.Geometry.POINTC2) == null) {
556                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
557                        } else {
558                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTC2);
559                        }
560                    } else {
561                        //neither track segment is in block 1 - should never get here unless layout turnout is
562                        //the only item in block 1
563                        if (!(lt.getBlockName().equals(facingBlock.getUserName()))) {
564                            log.error("no signal faces block {}, and turnout is not in block either",
565                                    facingBlock.getDisplayName());
566                        }
567                        return null;
568                    }
569                }
570            } else if (lt.getLinkType() == LayoutTurnout.LinkType.THROAT_TO_THROAT) {
571                //There are no signals at the throat of a THROAT_TO_THROAT
572
573                //There should not be a block boundary here
574                return null;
575            } else if (lt.getLinkType() == LayoutTurnout.LinkType.FIRST_3_WAY) {
576                //3-way turnout is in its own block - block boundary is at the throat of the 3-way turnout
577                if (!facingIsBlock1) {
578                    //facing block is within the three-way turnout's block - no signals for exit of the block
579                    return null;
580                } else {
581                    //select throat signal according to state of the 3-way turnout
582                    int state = lt.getTurnout().getKnownState();
583
584                    if (state == Turnout.THROWN) {
585                        if (lt.getSignalHead(LayoutTurnout.Geometry.POINTA2) == null) {
586                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
587                        } else {
588                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTA2);
589                        }
590                    } else if (state == Turnout.CLOSED) {
591                        LayoutTurnout tLinked = panel.getFinder().findLayoutTurnoutByTurnoutName(lt.getLinkedTurnoutName());
592                        state = tLinked.getTurnout().getKnownState();
593
594                        if (state == Turnout.CLOSED) {
595                            if (tLinked.getContinuingSense() == Turnout.CLOSED) {
596                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
597                            } else if (lt.getSignalHead(LayoutTurnout.Geometry.POINTA3) == null) {
598                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
599                            } else {
600                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTA3);
601                            }
602                        } else if (state == Turnout.THROWN) {
603                            if (tLinked.getContinuingSense() == Turnout.THROWN) {
604                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
605                            } else if (lt.getSignalHead(LayoutTurnout.Geometry.POINTA3) == null) {
606                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
607                            } else {
608                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTA3);
609                            }
610                        } else {
611                            //should never get here - linked turnout state is UNKNOWN or INCONSISTENT
612                            log.error("Cannot choose 3-way signal head to return because turnout {} is in an UNKNOWN or INCONSISTENT state.",
613                                    tLinked.getTurnout().getSystemName());
614                            return null;
615                        }
616                    } else {
617                        //should never get here - linked turnout state is UNKNOWN or INCONSISTENT
618                        log.error("Cannot choose 3-way signal head to return because turnout {} is in an UNKNOWN or INCONSISTENT state.",
619                                lt.getTurnout().getSystemName());
620                        return null;
621                    }
622                }
623            } else if (lt.getLinkType() == LayoutTurnout.LinkType.SECOND_3_WAY) {
624                //There are no signals at the throat of the SECOND_3_WAY turnout of a 3-way turnout
625
626                //There should not be a block boundary here
627                return null;
628            }
629        }
630
631        if (cType == HitPointType.TURNOUT_B) {
632            //block boundary is at the continuing track of a turnout or B connection of a crossover turnout
633            lt = (LayoutTurnout) connected;
634
635            //check for double crossover or LH crossover
636            if (((lt.getTurnoutType() == LayoutTurnout.TurnoutType.DOUBLE_XOVER)
637                    || (lt.getTurnoutType() == LayoutTurnout.TurnoutType.LH_XOVER))) {
638                if (facingIsBlock1) {
639                    if (lt.getSignalHead(LayoutTurnout.Geometry.POINTB2) == null) { //there is only one signal at B, return it
640                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1);
641                    }
642
643                    //check if track segments at A or D are in protected block (block 2)
644                    if (((TrackSegment) (lt.getConnectA())).getBlockName().equals(protectedBlock.getUserName())) {
645                        //track segment connected at A matches block 2, check D
646                        if (!(((TrackSegment) lt.getConnectD()).getBlockName().equals(protectedBlock.getUserName()))) {
647                            //track segment connected at D is not in block2, return continuing signal head at B
648                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1);
649                        } else {
650                            //A and D both in block 2, check turnout position to decide which signal head to return
651                            int state = lt.getTurnout().getKnownState();
652
653                            if (((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.CLOSED))
654                                    || ((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.THROWN))) { //continuing
655                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1);
656                            } else if (((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.CLOSED))
657                                    || ((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.THROWN))) { //diverging
658                                //(crossed
659
660                                //over)
661                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTB2);
662                            } else {
663                                //turnout state is UNKNOWN or INCONSISTENT
664                                log.error("LayoutTurnout {} cannot choose signal head because turnout {} is in an UNKNOWN or INCONSISTENT state.",
665                                        lt, lt.getTurnout());
666                                return null;
667                            }
668                        }
669                    }
670
671                    //track segment connected at A is not in block 2
672                    if ((((TrackSegment) lt.getConnectD()).getBlockName().equals(protectedBlock.getUserName()))) { //track segment
673                        //connected at D
674                        //is in block 2,
675                        //return
676                        //diverging
677
678                        //signal head
679                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTB2);
680                    } else {
681                        //neither track segment is in block 2 - should never get here unless layout turnout is
682                        //only item in block 2
683                        if (!(lt.getBlockName().equals(protectedBlock.getUserName()))) {
684                            log.error("neither signal at B protects block {}, and turnout is not in block either",
685                                    protectedBlock.getDisplayName());
686                        }
687                        return null;
688                    }
689                } else {
690                    //check if track segments at A or D are in facing block (block 1)
691                    if (((TrackSegment) (lt.getConnectA())).getBlockName().equals(facingBlock.getUserName())) {
692                        //track segment connected at A matches block 1, check D
693                        if (!(((TrackSegment) lt.getConnectD()).getBlockName().equals(facingBlock.getUserName()))) {
694                            //track segment connected at D is not in block 2, return signal head at continuing end
695                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
696                        } else {
697                            //A and D both in block 1, check turnout position to decide which signal head to return
698                            int state = lt.getTurnout().getKnownState();
699
700                            if (((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.CLOSED))
701                                    || ((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.THROWN))) { //continuing
702                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
703                            } else if (((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.CLOSED))
704                                    || ((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.THROWN))) {
705                                //diverging, check for second head
706                                if (lt.getSignalHead(LayoutTurnout.Geometry.POINTD2) == null) {
707                                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTD1);
708                                } else {
709                                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTD2);
710                                }
711                            } else {
712                                //turnout state is UNKNOWN or INCONSISTENT
713                                log.error("Cannot choose signal head because turnout {} is in an UNKNOWN or INCONSISTENT state.",
714                                        lt.getTurnout().getDisplayName());
715                                return null;
716                            }
717                        }
718                    }
719
720                    //track segment connected at A is not in block 1
721                    if (((TrackSegment) lt.getConnectD()).getBlockName().equals(facingBlock.getUserName())) {
722                        //track segment connected at D is in block 1, return diverging signal head, check for second head
723                        if (lt.getSignalHead(LayoutTurnout.Geometry.POINTD2) == null) {
724                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTD1);
725                        } else {
726                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTD2);
727                        }
728                    } else {
729                        //neither track segment is in block 1 - should never get here unless layout turnout is
730                        //the only item in block 1
731                        if (!(lt.getBlockName().equals(facingBlock.getUserName()))) {
732                            log.error("no signal faces block {}, and turnout is not in block either",
733                                    facingBlock.getDisplayName());
734                        }
735                        return null;
736                    }
737                }
738            }
739
740            //not double crossover or LH crossover
741            if ((lt.getLinkType() == LayoutTurnout.LinkType.NO_LINK) && (lt.getContinuingSense() == Turnout.CLOSED)) {
742                if (facingIsBlock1) {
743                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1);
744                } else {
745                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
746                }
747            } else if (lt.getLinkType() == LayoutTurnout.LinkType.NO_LINK) {
748                if (facingIsBlock1) {
749                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
750                } else {
751                    if (lt.getSignalHead(LayoutTurnout.Geometry.POINTA2) == null) {
752                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
753                    } else {
754                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTA2);
755                    }
756                }
757            } else if (lt.getLinkType() == LayoutTurnout.LinkType.THROAT_TO_THROAT) {
758                if (!facingIsBlock1) {
759                    //There are no signals at the throat of a THROAT_TO_THROAT
760                    return null;
761                }
762
763                //facing block is outside of the THROAT_TO_THROAT
764                if ((lt.getContinuingSense() == Turnout.CLOSED) && (lt.getSignalHead(LayoutTurnout.Geometry.POINTB2) == null)) {
765                    //there is only one signal head here - return it
766                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1);
767                } else if ((lt.getContinuingSense() == Turnout.THROWN) && (lt.getSignalHead(LayoutTurnout.Geometry.POINTC2) == null)) {
768                    //there is only one signal head here - return it
769                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
770                }
771
772                //There are two signals here get linked turnout and decide which to return from linked turnout state
773                LayoutTurnout tLinked = panel.getFinder().findLayoutTurnoutByTurnoutName(lt.getLinkedTurnoutName());
774                int state = tLinked.getTurnout().getKnownState();
775
776                if (state == Turnout.CLOSED) {
777                    if (lt.getContinuingSense() == Turnout.CLOSED) {
778                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1);
779                    } else {
780                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
781                    }
782                } else if (state == Turnout.THROWN) {
783                    if (lt.getContinuingSense() == Turnout.CLOSED) {
784                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTB2);
785                    } else {
786                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTC2);
787                    }
788                } else { //should never get here - linked turnout state is UNKNOWN or INCONSISTENT
789                    log.error("Cannot choose signal head to return because turnout {} is in an UNKNOWN or INCONSISTENT state.",
790                            tLinked.getTurnout().getDisplayName());
791                }
792                return null;
793            } else if (lt.getLinkType() == LayoutTurnout.LinkType.FIRST_3_WAY) {
794                //there is no signal at the FIRST_3_WAY turnout continuing track of a 3-way turnout
795                //there should not be a block boundary here
796                return null;
797            } else if (lt.getLinkType() == LayoutTurnout.LinkType.SECOND_3_WAY) {
798                if (facingIsBlock1) {
799                    if (lt.getContinuingSense() == Turnout.CLOSED) {
800                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1);
801                    } else {
802                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
803                    }
804                } else {
805                    //signal is at the linked turnout - the throat of the 3-way turnout
806                    LayoutTurnout tLinked = panel.getFinder().findLayoutTurnoutByTurnoutName(lt.getLinkedTurnoutName());
807
808                    if (lt.getContinuingSense() == Turnout.CLOSED) {
809                        return tLinked.getSignalHead(LayoutTurnout.Geometry.POINTA1);
810                    } else {
811                        if (tLinked.getSignalHead(LayoutTurnout.Geometry.POINTA3) == null) {
812                            return tLinked.getSignalHead(LayoutTurnout.Geometry.POINTA1);
813                        } else {
814                            return tLinked.getSignalHead(LayoutTurnout.Geometry.POINTA3);
815                        }
816                    }
817                }
818            }
819        }
820
821        if (cType == HitPointType.TURNOUT_C) {
822            //block boundary is at the diverging track of a turnout or C connection of a crossover turnout
823            lt = (LayoutTurnout) connected;
824
825            //check for double crossover or RH crossover
826            if ((lt.getTurnoutType() == LayoutTurnout.TurnoutType.DOUBLE_XOVER)
827                    || (lt.getTurnoutType() == LayoutTurnout.TurnoutType.RH_XOVER)) {
828                if (facingIsBlock1) {
829                    if (lt.getSignalHead(LayoutTurnout.Geometry.POINTC2) == null) { //there is only one head at C, return it
830                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
831                    }
832
833                    //check if track segments at A or D are in protected block (block 2)
834                    if (((TrackSegment) (lt.getConnectA())).getBlockName().equals(protectedBlock.getUserName())) {
835                        //track segment connected at A matches block 2, check D
836                        if (!(((TrackSegment) lt.getConnectD()).getBlockName().equals(protectedBlock.getUserName()))) {
837                            //track segment connected at D is not in block2, return diverging signal head at C
838                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTC2);
839                        } else {
840                            //A and D both in block 2, check turnout position to decide which signal head to return
841                            int state = lt.getTurnout().getKnownState();
842
843                            if (((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.CLOSED))
844                                    || ((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.THROWN))) { //continuing
845                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
846                            } else if (((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.CLOSED))
847                                    || ((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.THROWN))) { //diverging
848                                //(crossed
849
850                                //over)
851                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTC2);
852                            } else {
853                                //turnout state is UNKNOWN or INCONSISTENT
854                                log.error("Cannot choose signal head because turnout {} is in an UNKNOWN or INCONSISTENT state.",
855                                        lt.getTurnout().getDisplayName());
856                                return null;
857                            }
858                        }
859                    }
860
861                    //track segment connected at A is not in block 2
862                    if ((((TrackSegment) lt.getConnectD()).getBlockName().equals(protectedBlock.getUserName()))) {
863                        //track segment connected at D is in block 2, return continuing signal head
864                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
865                    } else {
866                        //neither track segment is in block 2 - should never get here unless layout turnout is
867                        //only item in block 2
868                        if (!(lt.getBlockName().equals(protectedBlock.getUserName()))) {
869                            log.error("neither signal at C protects block {}, and turnout is not in block either",
870                                    protectedBlock.getDisplayName());
871                        }
872                        return null;
873                    }
874                } else {
875                    //check if track segments at D or A are in facing block (block 1)
876                    if (((TrackSegment) (lt.getConnectD())).getBlockName().equals(facingBlock.getUserName())) {
877                        //track segment connected at D matches block 1, check A
878                        if (!(((TrackSegment) lt.getConnectA()).getBlockName().equals(facingBlock.getUserName()))) {
879                            //track segment connected at A is not in block 2, return signal head at continuing end
880                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTD1);
881                        } else {
882                            //A and D both in block 1, check turnout position to decide which signal head to return
883                            int state = lt.getTurnout().getKnownState();
884
885                            if (((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.CLOSED))
886                                    || ((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.THROWN))) { //continuing
887                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTD1);
888                            } else if (((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.CLOSED))
889                                    || ((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.THROWN))) {
890                                //diverging, check for second head
891                                if (lt.getSignalHead(LayoutTurnout.Geometry.POINTA2) == null) {
892                                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
893                                } else {
894                                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTA2);
895                                }
896                            } else {
897                                //turnout state is UNKNOWN or INCONSISTENT
898                                log.error("Cannot choose signal head because turnout {} is in an UNKNOWN or INCONSISTENT state.",
899                                        lt.getTurnout().getDisplayName());
900                                return null;
901                            }
902                        }
903                    }
904
905                    //track segment connected at D is not in block 1
906                    if (((TrackSegment) lt.getConnectA()).getBlockName().equals(facingBlock.getUserName())) {
907                        //track segment connected at A is in block 1, return diverging signal head, check for second head
908                        if (lt.getSignalHead(LayoutTurnout.Geometry.POINTA2) == null) {
909                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
910                        } else {
911                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTA2);
912                        }
913                    } else {
914                        //neither track segment is in block 1 - should never get here unless layout turnout is
915                        //the only item in block 1
916                        if (!(lt.getBlockName().equals(facingBlock.getUserName()))) {
917                            log.error("no signal faces block {}, and turnout is not in block either",
918                                    facingBlock.getDisplayName());
919                        }
920                        return null;
921                    }
922                }
923            }
924
925            //not double crossover or RH crossover
926            if ((lt.getLinkType() == LayoutTurnout.LinkType.NO_LINK) && (lt.getContinuingSense() == Turnout.CLOSED)) {
927                if (facingIsBlock1) {
928                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
929                } else if (lt.getTurnoutType() == LayoutTurnout.TurnoutType.LH_XOVER) { //LH turnout - this is continuing track for D connection
930                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTD1);
931                } else {
932                    //RH, LH or WYE turnout, this is diverging track for A connection
933                    if (lt.getSignalHead(LayoutTurnout.Geometry.POINTA2) == null) { //there is no signal head at the throat for diverging
934                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
935                    } else { //there is a diverging head at the throat, return it
936                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTA2);
937                    }
938                }
939            } else if (lt.getLinkType() == LayoutTurnout.LinkType.NO_LINK) {
940                if (facingIsBlock1) {
941                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1);
942                } else {
943                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
944                }
945            } else if (lt.getLinkType() == LayoutTurnout.LinkType.THROAT_TO_THROAT) {
946                if (!facingIsBlock1) {
947                    //There are no signals at the throat of a THROAT_TO_THROAT
948                    return null;
949                }
950
951                //facing block is outside of the THROAT_TO_THROAT
952                if ((lt.getContinuingSense() == Turnout.CLOSED) && (lt.getSignalHead(LayoutTurnout.Geometry.POINTC2) == null)) {
953                    //there is only one signal head here - return it
954                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
955                } else if ((lt.getContinuingSense() == Turnout.THROWN) && (lt.getSignalHead(LayoutTurnout.Geometry.POINTB2) == null)) {
956                    //there is only one signal head here - return it
957                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1);
958                }
959
960                //There are two signals here get linked turnout and decide which to return from linked turnout state
961                LayoutTurnout tLinked = panel.getFinder().findLayoutTurnoutByTurnoutName(lt.getLinkedTurnoutName());
962                int state = tLinked.getTurnout().getKnownState();
963
964                if (state == Turnout.CLOSED) {
965                    if (lt.getContinuingSense() == Turnout.CLOSED) {
966                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
967                    } else {
968                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1);
969                    }
970                } else if (state == Turnout.THROWN) {
971                    if (lt.getContinuingSense() == Turnout.CLOSED) {
972                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTC2);
973                    } else {
974                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTB2);
975                    }
976                } else {
977                    //should never get here - linked turnout state is UNKNOWN or INCONSISTENT
978                    log.error("Cannot choose signal head to return because turnout {} is in an UNKNOWN or INCONSISTENT state.",
979                            tLinked.getTurnout().getDisplayName());
980                    return null;
981                }
982            } else if (lt.getLinkType() == LayoutTurnout.LinkType.FIRST_3_WAY) {
983                if (facingIsBlock1) {
984                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
985                } else {
986                    if (lt.getSignalHead(LayoutTurnout.Geometry.POINTA2) == null) {
987                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTA1);
988                    } else {
989                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTA2);
990                    }
991                }
992            } else if (lt.getLinkType() == LayoutTurnout.LinkType.SECOND_3_WAY) {
993                if (facingIsBlock1) {
994                    if (lt.getContinuingSense() == Turnout.CLOSED) {
995                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
996                    } else {
997                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1);
998                    }
999                } else {
1000                    //signal is at the linked turnout - the throat of the 3-way turnout
1001                    LayoutTurnout tLinked = panel.getFinder().findLayoutTurnoutByTurnoutName(lt.getLinkedTurnoutName());
1002
1003                    if (lt.getContinuingSense() == Turnout.CLOSED) {
1004                        if (tLinked.getSignalHead(LayoutTurnout.Geometry.POINTA3) == null) {
1005                            return tLinked.getSignalHead(LayoutTurnout.Geometry.POINTA1);
1006                        } else {
1007                            return tLinked.getSignalHead(LayoutTurnout.Geometry.POINTA3);
1008                        }
1009                    } else {
1010                        if (tLinked.getSignalHead(LayoutTurnout.Geometry.POINTA2) == null) {
1011                            return tLinked.getSignalHead(LayoutTurnout.Geometry.POINTA1);
1012                        } else {
1013                            return tLinked.getSignalHead(LayoutTurnout.Geometry.POINTA2);
1014                        }
1015                    }
1016                }
1017            }
1018        }
1019
1020        if (cType == HitPointType.TURNOUT_D) {
1021            //block boundary is at D connectin of a crossover turnout
1022            lt = (LayoutTurnout) connected;
1023
1024            if (lt.getTurnoutType() == LayoutTurnout.TurnoutType.RH_XOVER) {
1025                //no diverging route possible, this is continuing track for C connection
1026                if (facingIsBlock1) {
1027                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTD1);
1028                } else {
1029                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
1030                }
1031            }
1032
1033            if (facingIsBlock1) {
1034                if (lt.getSignalHead(LayoutTurnout.Geometry.POINTD2) == null) { //there is no signal head for diverging
1035                    return lt.getSignalHead(LayoutTurnout.Geometry.POINTD1);
1036                } else {
1037                    //check if track segments at C or B are in protected block (block 2)
1038                    if (((TrackSegment) (lt.getConnectC())).getBlockName().equals(protectedBlock.getUserName())) {
1039                        //track segment connected at C matches block 2, check B
1040                        if (!(((TrackSegment) lt.getConnectB()).getBlockName().equals(protectedBlock.getUserName()))) {
1041                            //track segment connected at B is not in block2, return continuing signal head at D
1042                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTD1);
1043                        } else {
1044                            //C and B both in block2, check turnout position to decide which signal head to return
1045                            int state = lt.getTurnout().getKnownState();
1046
1047                            if (((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.CLOSED))
1048                                    || ((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.THROWN))) { //continuing
1049                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTD1);
1050                            } else if (((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.CLOSED))
1051                                    || ((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.THROWN))) { //diverging
1052                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTD2);
1053                            } else {
1054                                //turnout state is UNKNOWN or INCONSISTENT
1055                                log.error("Cannot choose signal head because turnout {} is in an UNKNOWN or INCONSISTENT state.",
1056                                        lt.getTurnout().getDisplayName());
1057                                return null;
1058                            }
1059                        }
1060                    }
1061
1062                    //track segment connected at C is not in block 2
1063                    if ((((TrackSegment) lt.getConnectB()).getBlockName().equals(protectedBlock.getUserName()))) {
1064                        //track segment connected at B is in block 2, return diverging signal head
1065                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTD2);
1066                    } else {
1067                        //neither track segment is in block 2 - should never get here unless layout turnout is
1068                        //the only item in block 2
1069                        if (!(lt.getBlockName().equals(protectedBlock.getUserName()))) {
1070                            log.error("neither signal at D protects block {}, and turnout is not in block either",
1071                                    protectedBlock.getDisplayName());
1072                        }
1073                        return null;
1074                    }
1075                }
1076            } else {
1077                //check if track segments at C or B are in facing block (block 1)
1078                if (((TrackSegment) (lt.getConnectC())).getBlockName().equals(facingBlock.getUserName())) {
1079                    //track segment connected at C matches block 1, check B
1080                    if (!(((TrackSegment) lt.getConnectB()).getBlockName().equals(facingBlock.getUserName()))) {
1081                        //track segment connected at B is not in block 2, return signal head at continuing end
1082                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
1083                    } else {
1084                        //C and B both in block 1, check turnout position to decide which signal head to return
1085                        int state = lt.getTurnout().getKnownState();
1086
1087                        if (((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.CLOSED))
1088                                || ((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.THROWN))) { //continuing
1089                            return lt.getSignalHead(LayoutTurnout.Geometry.POINTC1);
1090                        } else if (((state == Turnout.THROWN) && (lt.getContinuingSense() == Turnout.CLOSED))
1091                                || ((state == Turnout.CLOSED) && (lt.getContinuingSense() == Turnout.THROWN))) {
1092                            //diverging, check for second head
1093                            if (lt.getSignalHead(LayoutTurnout.Geometry.POINTB2) == null) {
1094                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1);
1095                            } else {
1096                                return lt.getSignalHead(LayoutTurnout.Geometry.POINTB2);
1097                            }
1098                        } else {
1099                            //turnout state is UNKNOWN or INCONSISTENT
1100                            log.error("Cannot choose signal head because turnout {} is in an UNKNOWN or INCONSISTENT state.",
1101                                    lt.getTurnout().getDisplayName());
1102                            return null;
1103                        }
1104                    }
1105                }
1106
1107                //track segment connected at C is not in block 1
1108                if (((TrackSegment) lt.getConnectB()).getBlockName().equals(facingBlock.getUserName())) {
1109                    //track segment connected at B is in block 1, return diverging signal head, check for second head
1110                    if (lt.getSignalHead(LayoutTurnout.Geometry.POINTB2) == null) {
1111                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTB1);
1112                    } else {
1113                        return lt.getSignalHead(LayoutTurnout.Geometry.POINTB2);
1114                    }
1115                } else {
1116                    //neither track segment is in block 1 - should never get here unless layout turnout is
1117                    //the only item in block 1
1118                    if (!(lt.getBlockName().equals(facingBlock.getUserName()))) {
1119                        log.error("no signal faces block {}, and turnout is not in block either",
1120                                facingBlock.getDisplayName());
1121                    }
1122                    return null;
1123                }
1124            }
1125        }
1126
1127        if (HitPointType.isSlipHitType(cType)) {
1128            if (!facingIsBlock1) {
1129                return null;
1130            }
1131
1132            LayoutSlip ls = (LayoutSlip) connected;
1133
1134            switch (cType) {
1135                case SLIP_A: {
1136                    if (ls.getSlipState() == LayoutSlip.STATE_AD) {
1137                        return ls.getSignalHead(LayoutTurnout.Geometry.POINTA2);
1138                    } else {
1139                        return ls.getSignalHead(LayoutTurnout.Geometry.POINTA1);
1140                    }
1141                }
1142
1143                case SLIP_B: {
1144                    if (ls.getTurnoutType() == LayoutSlip.TurnoutType.DOUBLE_SLIP) {
1145                        if (ls.getSlipState() == LayoutSlip.STATE_BC) {
1146                            return ls.getSignalHead(LayoutTurnout.Geometry.POINTB2);
1147                        } else {
1148                            return ls.getSignalHead(LayoutTurnout.Geometry.POINTB1);
1149                        }
1150                    } else {
1151                        return ls.getSignalHead(LayoutTurnout.Geometry.POINTB1);
1152                    }
1153                }
1154
1155                case SLIP_C: {
1156                    if (ls.getTurnoutType() == LayoutSlip.TurnoutType.DOUBLE_SLIP) {
1157                        if (ls.getSlipState() == LayoutSlip.STATE_BC) {
1158                            return ls.getSignalHead(LayoutTurnout.Geometry.POINTC2);
1159                        } else {
1160                            return ls.getSignalHead(LayoutTurnout.Geometry.POINTC1);
1161                        }
1162                    } else {
1163                        return ls.getSignalHead(LayoutTurnout.Geometry.POINTC1);
1164                    }
1165                }
1166
1167                case SLIP_D: {
1168                    if (ls.getSlipState() == LayoutSlip.STATE_AD) {
1169                        return ls.getSignalHead(LayoutTurnout.Geometry.POINTD2);
1170                    } else {
1171                        return ls.getSignalHead(LayoutTurnout.Geometry.POINTD1);
1172                    }
1173                }
1174
1175                default: {
1176                    break;
1177                }
1178            } //switch
1179        }
1180
1181        //block boundary must be at a level crossing
1182        if (!HitPointType.isLevelXingHitType(cType)) {
1183            log.error("{} {} Block Boundary not identified correctly - Blocks {}, {}",
1184                    cType, connected, facingBlock.getDisplayName(), protectedBlock.getDisplayName());
1185
1186            return null;
1187        }
1188        LevelXing xing = (LevelXing) connected;
1189
1190        switch (cType) {
1191            case LEVEL_XING_A: {
1192                //block boundary is at the A connection of a level crossing
1193                if (facingIsBlock1) {
1194                    return xing.getSignalHead(LevelXing.Geometry.POINTA);
1195                } else {
1196                    return xing.getSignalHead(LevelXing.Geometry.POINTC);
1197                }
1198            }
1199
1200            case LEVEL_XING_B: {
1201                //block boundary is at the B connection of a level crossing
1202                if (facingIsBlock1) {
1203                    return xing.getSignalHead(LevelXing.Geometry.POINTB);
1204                } else {
1205                    return xing.getSignalHead(LevelXing.Geometry.POINTD);
1206                }
1207            }
1208
1209            case LEVEL_XING_C: {
1210                //block boundary is at the C connection of a level crossing
1211                if (facingIsBlock1) {
1212                    return xing.getSignalHead(LevelXing.Geometry.POINTC);
1213                } else {
1214                    return xing.getSignalHead(LevelXing.Geometry.POINTA);
1215                }
1216            }
1217
1218            case LEVEL_XING_D: {
1219                //block boundary is at the D connection of a level crossing
1220                if (facingIsBlock1) {
1221                    return xing.getSignalHead(LevelXing.Geometry.POINTD);
1222                } else {
1223                    return xing.getSignalHead(LevelXing.Geometry.POINTB);
1224                }
1225            }
1226
1227            default: {
1228                break;
1229            }
1230        }
1231        return null;
1232    }
1233
1234    /**
1235     * Get the named bean of either a Sensor or signalmast facing into a
1236     * specified Block from a specified protected Block.
1237     * @param facingBlock the facing block.
1238     * @param panel the main layout editor.
1239     * @return The assigned sensor or signal mast as a named bean
1240     */
1241    @CheckReturnValue
1242    @CheckForNull
1243    public NamedBean getNamedBeanAtEndBumper(
1244            @CheckForNull Block facingBlock,
1245            @CheckForNull LayoutEditor panel) {
1246        NamedBean bean = getSignalMastAtEndBumper(facingBlock, panel);
1247
1248        if (bean != null) {
1249            return bean;
1250        } else {
1251            return getSensorAtEndBumper(facingBlock, panel);
1252        }
1253    }
1254
1255    /**
1256     * Get a Signal Mast that is assigned to a block which has an end bumper at
1257     * one end.
1258     * @param facingBlock the facing block.
1259     * @param panel the main layout editor.
1260     * @return the signal mast.
1261     */
1262    @CheckReturnValue
1263    @CheckForNull
1264    public SignalMast getSignalMastAtEndBumper(
1265            @CheckForNull Block facingBlock,
1266            @CheckForNull LayoutEditor panel) {
1267        if (facingBlock == null) {
1268            log.error("null block in call to getFacingSignalMast");
1269            return null;
1270        }
1271        String facingBlockName = facingBlock.getUserName();
1272        if ((facingBlockName == null) || facingBlockName.isEmpty()) {
1273            log.error("facing block has no user name");
1274            return null;
1275        }
1276
1277        LayoutBlock fLayoutBlock = getByUserName(facingBlockName);
1278        if (fLayoutBlock == null) {
1279            log.error("Block {} is not on a Layout Editor panel.", facingBlock.getDisplayName());
1280
1281            return null;
1282        }
1283
1284        if (panel == null) {
1285            panel = fLayoutBlock.getMaxConnectedPanel();
1286        }
1287
1288        for (TrackSegment t : panel.getTrackSegments()) {
1289            if (t.getLayoutBlock() == fLayoutBlock) {
1290                PositionablePoint p = null;
1291
1292                if (t.getType1() == HitPointType.POS_POINT) {
1293                    p = (PositionablePoint) t.getConnect1();
1294
1295                    if (p.getType() == PositionablePoint.PointType.END_BUMPER) {
1296                        if (p.getEastBoundSignalMast() != null) {
1297                            return p.getEastBoundSignalMast();
1298                        }
1299
1300                        if (p.getWestBoundSignalMast() != null) {
1301                            return p.getWestBoundSignalMast();
1302                        }
1303                    }
1304                }
1305
1306                if (t.getType2() == HitPointType.POS_POINT) {
1307                    p = (PositionablePoint) t.getConnect2();
1308
1309                    if (p.getType() == PositionablePoint.PointType.END_BUMPER) {
1310                        if (p.getEastBoundSignalMast() != null) {
1311                            return p.getEastBoundSignalMast();
1312                        }
1313
1314                        if (p.getWestBoundSignalMast() != null) {
1315                            return p.getWestBoundSignalMast();
1316                        }
1317                    }
1318                }
1319            }
1320        }
1321        return null;
1322    }
1323
1324    /**
1325     * Get a Sensor facing into a specific Block. This is used for Blocks that
1326     * have an end bumper at one end.
1327     * @param facingBlock the facing block.
1328     * @param panel the main layout editor.
1329     * @return the facing sensor.
1330     */
1331    @CheckReturnValue
1332    @CheckForNull
1333    public Sensor getSensorAtEndBumper(
1334            @CheckForNull Block facingBlock,
1335            @CheckForNull LayoutEditor panel) {
1336        if (facingBlock == null) {
1337            log.error("null block in call to getFacingSensor");
1338            return null;
1339        }
1340
1341        String facingBlockName = facingBlock.getUserName();
1342        if ((facingBlockName == null) || (facingBlockName.isEmpty())) {
1343            log.error("Block {} has no user name.", facingBlock.getDisplayName());
1344            return null;
1345        }
1346        LayoutBlock fLayoutBlock = getByUserName(facingBlockName);
1347        if (fLayoutBlock == null) {
1348            log.error("Block {} is not on a Layout Editor panel.", facingBlock.getDisplayName());
1349
1350            return null;
1351        }
1352
1353        if (panel == null) {
1354            panel = fLayoutBlock.getMaxConnectedPanel();
1355        }
1356
1357        for (TrackSegment t : panel.getTrackSegments()) {
1358            if (t.getLayoutBlock() == fLayoutBlock) {
1359                PositionablePoint p = null;
1360
1361                if (t.getType1() == HitPointType.POS_POINT) {
1362                    p = (PositionablePoint) t.getConnect1();
1363
1364                    if (p.getType() == PositionablePoint.PointType.END_BUMPER) {
1365                        if (p.getEastBoundSensor() != null) {
1366                            return p.getEastBoundSensor();
1367                        }
1368
1369                        if (p.getWestBoundSensor() != null) {
1370                            return p.getWestBoundSensor();
1371                        }
1372                    }
1373                }
1374
1375                if (t.getType2() == HitPointType.POS_POINT) {
1376                    p = (PositionablePoint) t.getConnect2();
1377
1378                    if (p.getType() == PositionablePoint.PointType.END_BUMPER) {
1379                        if (p.getEastBoundSensor() != null) {
1380                            return p.getEastBoundSensor();
1381                        }
1382
1383                        if (p.getWestBoundSensor() != null) {
1384                            return p.getWestBoundSensor();
1385                        }
1386                    }
1387                }
1388            }
1389        }
1390        return null;
1391    }
1392
1393    /**
1394     * Get the named bean of either a Sensor or signalmast facing into a
1395     * specified Block from a specified protected Block.
1396     * @param facingBlock the facing block.
1397     * @param protectedBlock the protected block.
1398     * @param panel the main layout editor.
1399     * @return The assigned sensor or signal mast as a named bean
1400     */
1401    @CheckReturnValue
1402    @CheckForNull
1403    public NamedBean getFacingNamedBean(@CheckForNull Block facingBlock,
1404            @CheckForNull Block protectedBlock,
1405            @CheckForNull LayoutEditor panel) {
1406        NamedBean bean = getFacingBean(facingBlock, protectedBlock, panel, SignalMast.class);
1407
1408        if (bean != null) {
1409            return bean;
1410        }
1411        bean = getFacingBean(facingBlock, protectedBlock, panel, Sensor.class);
1412
1413        if (bean != null) {
1414            return bean;
1415        }
1416        return getFacingSignalHead(facingBlock, protectedBlock);
1417    }
1418
1419    @CheckReturnValue
1420    @CheckForNull
1421    public SignalMast getFacingSignalMast(
1422            @Nonnull Block facingBlock,
1423            @CheckForNull Block protectedBlock) {
1424        return getFacingSignalMast(facingBlock, protectedBlock, null);
1425    }
1426
1427    /**
1428     * Get the Signal Mast facing into a specified Block from a specified
1429     * protected Block.
1430     *
1431     * @param facingBlock the facing block.
1432     * @param protectedBlock the protected block.
1433     * @param panel the main layout editor.
1434     * @return The assigned signalMast.
1435     */
1436    @CheckReturnValue
1437    @CheckForNull
1438    public SignalMast getFacingSignalMast(
1439            @Nonnull Block facingBlock,
1440            @CheckForNull Block protectedBlock,
1441            @CheckForNull LayoutEditor panel) {
1442        log.debug("calling getFacingMast on block '{}'", facingBlock.getDisplayName());
1443        return (SignalMast) getFacingBean(facingBlock, protectedBlock, panel, SignalMast.class);
1444    }
1445
1446    /**
1447     * Get the Sensor facing into a specified Block from a specified protected
1448     * Block.
1449     * @param facingBlock the facing block.
1450     * @param protectedBlock the protected block.
1451     * @param panel the main layout editor.
1452     * @return The assigned sensor
1453     */
1454    @CheckReturnValue
1455    @CheckForNull
1456    public Sensor getFacingSensor(@CheckForNull Block facingBlock,
1457            @CheckForNull Block protectedBlock,
1458            @CheckForNull LayoutEditor panel) {
1459        return (Sensor) getFacingBean(facingBlock, protectedBlock, panel, Sensor.class);
1460    }
1461
1462    /**
1463     * Get a facing bean into a specified Block from a specified protected
1464     * Block.
1465     *
1466     * @param facingBlock the facing block.
1467     * @param protectedBlock the protected block.
1468     * @param panel the layout editor panel the block is assigned, if null then
1469     *              the maximum connected panel of the facing block is used
1470     * @param T     The class of the item that we are looking for, either
1471     *              SignalMast or Sensor
1472     * @return The assigned sensor.
1473     */
1474    @CheckReturnValue
1475    @CheckForNull
1476    public NamedBean getFacingBean(@CheckForNull Block facingBlock,
1477            @CheckForNull Block protectedBlock,
1478            @CheckForNull LayoutEditor panel, Class< ?> T) {
1479        //check input
1480        if ((facingBlock == null) || (protectedBlock == null)) {
1481            log.error("null block in call to getFacingSignalMast");
1482            return null;
1483        }
1484
1485        if (!T.equals(SignalMast.class) && !T.equals(Sensor.class)) {
1486            log.error("Incorrect class type called, must be either SignalMast or Sensor");
1487
1488            return null;
1489        }
1490
1491        if (log.isDebugEnabled()) {
1492            log.debug("find signal mast between facing {} ({}) - protected {} ({})",
1493                    facingBlock.getDisplayName(), facingBlock.getDisplayName(),
1494                    protectedBlock.getDisplayName(), protectedBlock.getDisplayName());
1495        }
1496
1497        //non-null - check if input corresponds to Blocks in a Layout Editor panel.
1498        String facingBlockName = facingBlock.getUserName();
1499        if ((facingBlockName == null) || facingBlockName.isEmpty()) {
1500            log.error("facing block has no user name");
1501            return null;
1502        }
1503        LayoutBlock fLayoutBlock = getByUserName(facingBlockName);
1504        String protectedBlockName = protectedBlock.getUserName();
1505        LayoutBlock pLayoutBlock = (protectedBlockName == null) ? null : getByUserName(protectedBlockName);
1506        if ((fLayoutBlock == null) || (pLayoutBlock == null)) {
1507            if (fLayoutBlock == null) {
1508                log.error("Block {} is not on a Layout Editor panel.", facingBlock.getDisplayName());
1509            }
1510
1511            if (pLayoutBlock == null) {
1512                log.error("Block {} is not on a Layout Editor panel.", protectedBlock.getDisplayName());
1513            }
1514            return null;
1515        }
1516
1517        //input has corresponding LayoutBlocks - does it correspond to a block boundary?
1518        if (panel == null) {
1519            panel = fLayoutBlock.getMaxConnectedPanel();
1520        }
1521        List<LayoutConnectivity> c = panel.getLEAuxTools().getConnectivityList(fLayoutBlock);
1522        LayoutConnectivity lc = null;
1523        int i = 0;
1524        boolean facingIsBlock1 = true;
1525
1526        while ((i < c.size()) && (lc == null)) {
1527            LayoutConnectivity tlc = c.get(i);
1528
1529            if ((tlc.getBlock1() == fLayoutBlock) && (tlc.getBlock2() == pLayoutBlock)) {
1530                lc = tlc;
1531            } else if ((tlc.getBlock1() == pLayoutBlock) && (tlc.getBlock2() == fLayoutBlock)) {
1532                lc = tlc;
1533                facingIsBlock1 = false;
1534            }
1535            i++;
1536        }
1537
1538        if (lc == null) {
1539            PositionablePoint p = panel.getFinder().findPositionableLinkPoint(fLayoutBlock);
1540
1541            if (p == null) {
1542                p = panel.getFinder().findPositionableLinkPoint(pLayoutBlock);
1543            }
1544
1545            if ((p != null) && (p.getLinkedEditor() != null)) {
1546                return getFacingBean(facingBlock, protectedBlock, p.getLinkedEditor(), T);
1547            }
1548            log.debug("Block {} is not connected to Block {} on panel {}", facingBlock.getDisplayName(),
1549                    protectedBlock.getDisplayName(), panel.getLayoutName());
1550
1551            return null;
1552        }
1553        LayoutTurnout lt = null;
1554        LayoutTrack connected = lc.getConnectedObject();
1555
1556        TrackSegment tr = lc.getTrackSegment();
1557        HitPointType cType = lc.getConnectedType();
1558
1559        if (connected == null) {
1560            if (lc.getXover() != null) {
1561                if (lc.getXoverBoundaryType() == LayoutConnectivity.XOVER_BOUNDARY_AB) {
1562                    if (fLayoutBlock == lc.getXover().getLayoutBlock()) {
1563                        cType = HitPointType.TURNOUT_A;
1564                    } else {
1565                        cType = HitPointType.TURNOUT_B;
1566                    }
1567                    connected = lc.getXover();
1568                } else if (lc.getXoverBoundaryType() == LayoutConnectivity.XOVER_BOUNDARY_CD) {
1569                    if (fLayoutBlock == lc.getXover().getLayoutBlockC()) {
1570                        cType = HitPointType.TURNOUT_C;
1571                    } else {
1572                        cType = HitPointType.TURNOUT_D;
1573                    }
1574                    connected = lc.getXover();
1575                } else if (lc.getXoverBoundaryType() == LayoutConnectivity.XOVER_BOUNDARY_AC) {
1576                    if (fLayoutBlock == lc.getXover().getLayoutBlock()) {
1577                        cType = HitPointType.TURNOUT_A;
1578                    } else {
1579                        cType = HitPointType.TURNOUT_C;
1580                    }
1581                    connected = lc.getXover();
1582                } else if (lc.getXoverBoundaryType() == LayoutConnectivity.XOVER_BOUNDARY_BD) {
1583                    if (fLayoutBlock == lc.getXover().getLayoutBlockB()) {
1584                        cType = HitPointType.TURNOUT_B;
1585                    } else {
1586                        cType = HitPointType.TURNOUT_D;
1587                    }
1588                    connected = lc.getXover();
1589                }
1590            }
1591        }
1592
1593        if (connected == null) {
1594            log.error("No connectivity object found between Blocks {}, {} {}", facingBlock.getDisplayName(),
1595                    protectedBlock.getDisplayName(), cType);
1596
1597            return null;
1598        }
1599
1600        if (cType == HitPointType.TRACK) {
1601            //block boundary is at an Anchor Point
1602            PositionablePoint p = panel.getFinder().findPositionablePointAtTrackSegments(tr, (TrackSegment) connected);
1603
1604            boolean block1IsWestEnd = LayoutEditorTools.isAtWestEndOfAnchor(panel, tr, p);
1605            log.debug("Track is west end? {}", block1IsWestEnd);
1606            if ((block1IsWestEnd && facingIsBlock1) || (!block1IsWestEnd && !facingIsBlock1)) {
1607                //block1 is on the west (north) end of the block boundary
1608                if (T.equals(SignalMast.class)) {
1609                    return p.getEastBoundSignalMast();
1610                } else if (T.equals(Sensor.class)) {
1611                    return p.getEastBoundSensor();
1612                }
1613            } else {
1614                if (T.equals(SignalMast.class)) {
1615                    return p.getWestBoundSignalMast();
1616                } else if (T.equals(Sensor.class)) {
1617                    return p.getWestBoundSensor();
1618                }
1619            }
1620        }
1621
1622        if (cType == HitPointType.TURNOUT_A) {
1623            lt = (LayoutTurnout) connected;
1624
1625            if ((lt.getLinkType() == LayoutTurnout.LinkType.NO_LINK) || (lt.getLinkType() == LayoutTurnout.LinkType.FIRST_3_WAY)) {
1626                if ((T.equals(SignalMast.class) && (lt.getSignalAMast() != null))
1627                        || (T.equals(Sensor.class) && (lt.getSensorA() != null))) {
1628                    if (tr == null) {
1629                        if (lt.getConnectA() instanceof TrackSegment) {
1630                            TrackSegment t = (TrackSegment) lt.getConnectA();
1631
1632                            if ((t.getLayoutBlock() != null) && (t.getLayoutBlock() == lt.getLayoutBlock())) {
1633                                if (T.equals(SignalMast.class)) {
1634                                    return lt.getSignalAMast();
1635                                } else if (T.equals(Sensor.class)) {
1636                                    return lt.getSensorA();
1637                                }
1638                            }
1639                        }
1640                    } else if (tr.getLayoutBlock().getBlock() == facingBlock) {
1641                        if (T.equals(SignalMast.class)) {
1642                            return lt.getSignalAMast();
1643                        } else if (T.equals(Sensor.class)) {
1644                            return lt.getSensorA();
1645                        }
1646                    }
1647                }
1648            }
1649            return null;
1650        }
1651
1652        if (cType == HitPointType.TURNOUT_B) {
1653            lt = (LayoutTurnout) connected;
1654
1655            if ((T.equals(SignalMast.class) && (lt.getSignalBMast() != null))
1656                    || (T.equals(Sensor.class) && (lt.getSensorB() != null))) {
1657                if (tr == null) {
1658                    if (lt.getConnectB() instanceof TrackSegment) {
1659                        TrackSegment t = (TrackSegment) lt.getConnectB();
1660
1661                        if ((t.getLayoutBlock() != null) && (t.getLayoutBlock() == lt.getLayoutBlockB())) {
1662                            if (T.equals(SignalMast.class)) {
1663                                return lt.getSignalBMast();
1664                            } else if (T.equals(Sensor.class)) {
1665                                return lt.getSensorB();
1666                            }
1667                        }
1668                    }
1669                } else if (tr.getLayoutBlock().getBlock() == facingBlock) {
1670                    if (T.equals(SignalMast.class)) {
1671                        return lt.getSignalBMast();
1672                    } else if (T.equals(Sensor.class)) {
1673                        return lt.getSensorB();
1674                    }
1675                }
1676            }
1677            return null;
1678        }
1679
1680        if (cType == HitPointType.TURNOUT_C) {
1681            lt = (LayoutTurnout) connected;
1682
1683            if ((T.equals(SignalMast.class) && (lt.getSignalCMast() != null))
1684                    || (T.equals(Sensor.class) && (lt.getSensorC() != null))) {
1685                if (tr == null) {
1686                    if (lt.getConnectC() instanceof TrackSegment) {
1687                        TrackSegment t = (TrackSegment) lt.getConnectC();
1688
1689                        if ((t.getLayoutBlock() != null) && (t.getLayoutBlock() == lt.getLayoutBlockC())) {
1690                            if (T.equals(SignalMast.class)) {
1691                                return lt.getSignalCMast();
1692                            } else if (T.equals(Sensor.class)) {
1693                                return lt.getSensorC();
1694                            }
1695                        }
1696                    }
1697                } else if (tr.getLayoutBlock().getBlock() == facingBlock) {
1698                    if (T.equals(SignalMast.class)) {
1699                        return lt.getSignalCMast();
1700                    } else if (T.equals(Sensor.class)) {
1701                        return lt.getSensorC();
1702                    }
1703                }
1704            }
1705            return null;
1706        }
1707
1708        if (cType == HitPointType.TURNOUT_D) {
1709            lt = (LayoutTurnout) connected;
1710
1711            if ((T.equals(SignalMast.class) && (lt.getSignalDMast() != null))
1712                    || (T.equals(Sensor.class) && (lt.getSensorD() != null))) {
1713                if (tr == null) {
1714                    if (lt.getConnectD() instanceof TrackSegment) {
1715                        TrackSegment t = (TrackSegment) lt.getConnectD();
1716
1717                        if ((t.getLayoutBlock() != null) && (t.getLayoutBlock() == lt.getLayoutBlockD())) {
1718                            if (T.equals(SignalMast.class)) {
1719                                return lt.getSignalDMast();
1720                            } else if (T.equals(Sensor.class)) {
1721                                return lt.getSensorD();
1722                            }
1723                        }
1724                    }
1725                } else if (tr.getLayoutBlock().getBlock() == facingBlock) {
1726                    if (T.equals(SignalMast.class)) {
1727                        return lt.getSignalDMast();
1728                    } else if (T.equals(Sensor.class)) {
1729                        return lt.getSensorD();
1730                    }
1731                }
1732            }
1733            return null;
1734        }
1735
1736        if ((tr == null) || (tr.getLayoutBlock().getBlock() != facingBlock)) {
1737            return null;
1738        }
1739
1740        if (HitPointType.isSlipHitType(cType)) {
1741            LayoutSlip ls = (LayoutSlip) connected;
1742
1743            if (cType == HitPointType.SLIP_A) {
1744                if (T.equals(SignalMast.class)) {
1745                    return ls.getSignalAMast();
1746                } else if (T.equals(Sensor.class)) {
1747                    return ls.getSensorA();
1748                }
1749            }
1750
1751            if (cType == HitPointType.SLIP_B) {
1752                if (T.equals(SignalMast.class)) {
1753                    return ls.getSignalBMast();
1754                } else if (T.equals(Sensor.class)) {
1755                    return ls.getSensorB();
1756                }
1757            }
1758
1759            if (cType == HitPointType.SLIP_C) {
1760                if (T.equals(SignalMast.class)) {
1761                    return ls.getSignalCMast();
1762                } else if (T.equals(Sensor.class)) {
1763                    return ls.getSensorC();
1764                }
1765            }
1766
1767            if (cType == HitPointType.SLIP_D) {
1768                if (T.equals(SignalMast.class)) {
1769                    return ls.getSignalDMast();
1770                } else if (T.equals(Sensor.class)) {
1771                    return ls.getSensorD();
1772                }
1773            }
1774        }
1775
1776        if (!HitPointType.isLevelXingHitType(cType)) {
1777            log.error("Block Boundary not identified correctly - Blocks {}, {}", facingBlock.getDisplayName(),
1778                    protectedBlock.getDisplayName());
1779
1780            return null;
1781        }
1782
1783        /* We don't allow signal masts on the block outward facing from the level
1784        xing, nor do we consider the signal mast, that is protecting the in block on the xing */
1785        LevelXing xing = (LevelXing) connected;
1786
1787        if (cType == HitPointType.LEVEL_XING_A) {
1788            //block boundary is at the A connection of a level crossing
1789            if (T.equals(SignalMast.class)) {
1790                return xing.getSignalAMast();
1791            } else if (T.equals(Sensor.class)) {
1792                return xing.getSensorA();
1793            }
1794        }
1795
1796        if (cType == HitPointType.LEVEL_XING_B) {
1797            //block boundary is at the B connection of a level crossing
1798            if (T.equals(SignalMast.class)) {
1799                return xing.getSignalBMast();
1800            } else if (T.equals(Sensor.class)) {
1801                return xing.getSensorB();
1802            }
1803        }
1804
1805        if (cType == HitPointType.LEVEL_XING_C) {
1806            //block boundary is at the C connection of a level crossing
1807            if (T.equals(SignalMast.class)) {
1808                return xing.getSignalCMast();
1809            } else if (T.equals(Sensor.class)) {
1810                return xing.getSensorC();
1811            }
1812        }
1813
1814        if (cType == HitPointType.LEVEL_XING_D) {
1815            if (T.equals(SignalMast.class)) {
1816                return xing.getSignalDMast();
1817            } else if (T.equals(Sensor.class)) {
1818                return xing.getSensorD();
1819            }
1820        }
1821        return null;
1822    } //getFacingBean
1823
1824    /**
1825     * In the first instance get a Signal Mast or if none exists a Signal Head
1826     * for a given facing block and protected block combination. See
1827     * #getFacingSignalMast() and #getFacingSignalHead() as to how they deal
1828     * with what each returns.
1829     * @param facingBlock the facing block to search for.
1830     * @param protectedBlock the protected block to search for.
1831     *
1832     * @return either a signalMast or signalHead
1833     */
1834    @CheckReturnValue
1835    @CheckForNull
1836    public Object getFacingSignalObject(
1837            @Nonnull Block facingBlock,
1838            @CheckForNull Block protectedBlock) {
1839        Object sig = getFacingSignalMast(facingBlock, protectedBlock, null);
1840
1841        if (sig != null) {
1842            return sig;
1843        }
1844        sig = getFacingSignalHead(facingBlock, protectedBlock);
1845        return sig;
1846    }
1847
1848    /**
1849     * Get the block that a given bean object (Sensor, SignalMast or SignalHead)
1850     * is protecting.
1851     *
1852     * @param nb    NamedBean
1853     * @param panel panel that this bean is on
1854     * @return The block that the bean object is facing
1855     */
1856    @CheckReturnValue
1857    @CheckForNull
1858    public LayoutBlock getProtectedBlockByNamedBean(
1859            @CheckForNull NamedBean nb,
1860            @CheckForNull LayoutEditor panel) {
1861        if (nb instanceof SignalHead) {
1862            return getProtectedBlock((SignalHead) nb, panel);
1863        }
1864        List<LayoutBlock> proBlocks = getProtectingBlocksByBean(nb, panel);
1865
1866        if (proBlocks.isEmpty()) {
1867            return null;
1868        }
1869        return proBlocks.get(0);
1870    } //getProtectedBlockByNamedBean
1871
1872    @CheckReturnValue
1873    @Nonnull
1874    public List<LayoutBlock> getProtectingBlocksByNamedBean(
1875            @CheckForNull NamedBean nb,
1876            @CheckForNull LayoutEditor panel) {
1877        ArrayList<LayoutBlock> ret = new ArrayList<>();
1878
1879        if (nb instanceof SignalHead) {
1880            ret.add(getProtectedBlock((SignalHead) nb, panel));
1881            return ret;
1882        }
1883        return getProtectingBlocksByBean(nb, panel);
1884    }
1885
1886    /**
1887     * If the panel variable is null, search all LE panels. This was added to
1888     * support multi panel entry/exit.
1889     *
1890     * @param bean  The sensor, mast or head to be located.
1891     * @param panel The panel to search. If null, search all LE panels.
1892     * @return a list of protected layout blocks.
1893     */
1894    @Nonnull
1895    private List<LayoutBlock> getProtectingBlocksByBean(
1896            @CheckForNull NamedBean bean,
1897            @CheckForNull LayoutEditor panel) {
1898        if (panel == null) {
1899            Set<LayoutEditor> panels = InstanceManager.getDefault(EditorManager.class).getAll(LayoutEditor.class);
1900            List<LayoutBlock> protectingBlocks = new ArrayList<>();
1901            for (LayoutEditor p : panels) {
1902                protectingBlocks = getProtectingBlocksByBeanByPanel(bean, p);
1903                if (!protectingBlocks.isEmpty()) {
1904                    break;
1905                }
1906            }
1907            return protectingBlocks;
1908        } else {
1909            return getProtectingBlocksByBeanByPanel(bean, panel);
1910        }
1911    }
1912
1913    @Nonnull
1914    private List<LayoutBlock> getProtectingBlocksByBeanByPanel(
1915            @CheckForNull NamedBean bean,
1916            @CheckForNull LayoutEditor panel) {
1917        List<LayoutBlock> protectingBlocks = new ArrayList<>();
1918
1919        if (!(bean instanceof SignalMast) && !(bean instanceof Sensor)) {
1920            log.error("Incorrect class type called, must be either SignalMast or Sensor");
1921
1922            return protectingBlocks;
1923        }
1924
1925        PositionablePoint pp = panel.getFinder().findPositionablePointByEastBoundBean(bean);
1926        TrackSegment tr = null;
1927        boolean east = true;
1928
1929        if (pp == null) {
1930            pp = panel.getFinder().findPositionablePointByWestBoundBean(bean);
1931            east = false;
1932        }
1933
1934        if (pp != null) {
1935            //   LayoutEditorTools tools = panel.getLETools(); //TODO: Dead-code strip this
1936
1937            if (east) {
1938                if (LayoutEditorTools.isAtWestEndOfAnchor(panel, pp.getConnect1(), pp)) {
1939                    tr = pp.getConnect2();
1940                } else {
1941                    tr = pp.getConnect1();
1942                }
1943            } else {
1944                if (LayoutEditorTools.isAtWestEndOfAnchor(panel, pp.getConnect1(), pp)) {
1945                    tr = pp.getConnect1();
1946                } else {
1947                    tr = pp.getConnect2();
1948                }
1949            }
1950
1951            if (tr != null) {
1952                protectingBlocks.add(tr.getLayoutBlock());
1953
1954                return protectingBlocks;
1955            }
1956        }
1957
1958        LevelXing l = panel.getFinder().findLevelXingByBean(bean);
1959
1960        if (l != null) {
1961            if (bean instanceof SignalMast) {
1962                if (l.getSignalAMast() == bean) {
1963                    protectingBlocks.add(l.getLayoutBlockAC());
1964                } else if (l.getSignalBMast() == bean) {
1965                    protectingBlocks.add(l.getLayoutBlockBD());
1966                } else if (l.getSignalCMast() == bean) {
1967                    protectingBlocks.add(l.getLayoutBlockAC());
1968                } else {
1969                    protectingBlocks.add(l.getLayoutBlockBD());
1970                }
1971            } else if (bean instanceof Sensor) {
1972                if (l.getSensorA() == bean) {
1973                    protectingBlocks.add(l.getLayoutBlockAC());
1974                } else if (l.getSensorB() == bean) {
1975                    protectingBlocks.add(l.getLayoutBlockBD());
1976                } else if (l.getSensorC() == bean) {
1977                    protectingBlocks.add(l.getLayoutBlockAC());
1978                } else {
1979                    protectingBlocks.add(l.getLayoutBlockBD());
1980                }
1981            }
1982            return protectingBlocks;
1983        }
1984
1985        LayoutSlip ls = panel.getFinder().findLayoutSlipByBean(bean);
1986
1987        if (ls != null) {
1988            protectingBlocks.add(ls.getLayoutBlock());
1989
1990            return protectingBlocks;
1991        }
1992
1993        LayoutTurnout t = panel.getFinder().findLayoutTurnoutByBean(bean);
1994
1995        if (t != null) {
1996            return t.getProtectedBlocks(bean);
1997        }
1998        return protectingBlocks;
1999    } //getProtectingBlocksByBean
2000
2001    @CheckReturnValue
2002    @CheckForNull
2003    public LayoutBlock getProtectedBlockByMast(
2004            @CheckForNull SignalMast signalMast,
2005            @CheckForNull LayoutEditor panel) {
2006        List<LayoutBlock> proBlocks = getProtectingBlocksByBean(signalMast, panel);
2007
2008        if (proBlocks.isEmpty()) {
2009            return null;
2010        }
2011        return proBlocks.get(0);
2012    }
2013
2014    /**
2015     * Get the LayoutBlock that a given sensor is protecting.
2016     * @param sensorName the sensor name to search for.
2017     * @param panel the layout editor panel.
2018     * @return the layout block, may be null.
2019     */
2020    @CheckReturnValue
2021    @CheckForNull
2022    public LayoutBlock getProtectedBlockBySensor(
2023            @Nonnull String sensorName,
2024            @CheckForNull LayoutEditor panel) {
2025        Sensor sensor = InstanceManager.sensorManagerInstance().getSensor(sensorName);
2026
2027        return getProtectedBlockBySensor(sensor, panel);
2028    }
2029
2030    @Nonnull
2031    public List<LayoutBlock> getProtectingBlocksBySensor(
2032            @CheckForNull Sensor sensor, @CheckForNull LayoutEditor panel) {
2033        return getProtectingBlocksByBean(sensor, panel);
2034    }
2035
2036    @Nonnull
2037    public List<LayoutBlock> getProtectingBlocksBySensorOld(
2038            @CheckForNull Sensor sensor, @Nonnull LayoutEditor panel) {
2039        List<LayoutBlock> result = new ArrayList<>();
2040        PositionablePoint pp = panel.getFinder().findPositionablePointByEastBoundBean(sensor);
2041        TrackSegment tr;
2042        boolean east = true;
2043
2044        if (pp == null) {
2045            pp = panel.getFinder().findPositionablePointByWestBoundBean(sensor);
2046            east = false;
2047        }
2048
2049        if (pp != null) {
2050            //            LayoutEditorTools tools = panel.getLETools(); //TODO: Dead-code strip this
2051
2052            if (east) {
2053                if (LayoutEditorTools.isAtWestEndOfAnchor(panel, pp.getConnect1(), pp)) {
2054                    tr = pp.getConnect2();
2055                } else {
2056                    tr = pp.getConnect1();
2057                }
2058            } else {
2059                if (LayoutEditorTools.isAtWestEndOfAnchor(panel, pp.getConnect1(), pp)) {
2060                    tr = pp.getConnect1();
2061                } else {
2062                    tr = pp.getConnect2();
2063                }
2064            }
2065
2066            if (tr != null) {
2067                result.add(tr.getLayoutBlock());
2068
2069                return result;
2070            }
2071        }
2072
2073        LevelXing l = panel.getFinder().findLevelXingByBean(sensor);
2074
2075        if (l != null) {
2076            if (l.getSensorA() == sensor) {
2077                result.add(l.getLayoutBlockAC());
2078            } else if (l.getSensorB() == sensor) {
2079                result.add(l.getLayoutBlockBD());
2080            } else if (l.getSensorC() == sensor) {
2081                result.add(l.getLayoutBlockAC());
2082            } else {
2083                result.add(l.getLayoutBlockBD());
2084            }
2085            return result;
2086        }
2087        LayoutSlip ls = panel.getFinder().findLayoutSlipByBean(sensor);
2088
2089        if (ls != null) {
2090            result.add(ls.getLayoutBlock());
2091
2092            return result;
2093        }
2094        LayoutTurnout t = panel.getFinder().findLayoutTurnoutByBean(sensor);
2095
2096        if (t != null) {
2097            return t.getProtectedBlocks(sensor);
2098        }
2099        return result;
2100    } //getProtectingBlocksBySensorOld
2101
2102    /**
2103     * Get the LayoutBlock that a given sensor is protecting.
2104     * @param sensor sensor to search for.
2105     * @param panel layout editor panel to search.
2106     * @return the layout block, may be null.
2107     */
2108    @CheckReturnValue
2109    @CheckForNull
2110    public LayoutBlock getProtectedBlockBySensor(
2111            @CheckForNull Sensor sensor, @CheckForNull LayoutEditor panel) {
2112        List<LayoutBlock> proBlocks = getProtectingBlocksByBean(sensor, panel);
2113
2114        if (proBlocks.isEmpty()) {
2115            return null;
2116        }
2117        return proBlocks.get(0);
2118    }
2119
2120    /**
2121     * Get the block facing a given bean object (Sensor, SignalMast or
2122     * SignalHead).
2123     *
2124     * @param nb    NamedBean
2125     * @param panel panel that this bean is on
2126     * @return The block that the bean object is facing
2127     */
2128    @CheckReturnValue
2129    @CheckForNull
2130    public LayoutBlock getFacingBlockByNamedBean(
2131            @Nonnull NamedBean nb, @CheckForNull LayoutEditor panel) {
2132        if (nb instanceof SignalHead) {
2133            return getFacingBlock((SignalHead) nb, panel);
2134        }
2135        return getFacingBlockByBean(nb, panel);
2136    }
2137
2138    /**
2139     * Get the LayoutBlock that a given sensor is facing.
2140     * @param sensorName the sensor name.
2141     * @param panel the layout editor panel.
2142     * @return the facing layout block, may be null.
2143     */
2144    @CheckReturnValue
2145    @CheckForNull
2146    public LayoutBlock getFacingBlockBySensor(@Nonnull String sensorName,
2147            @CheckForNull LayoutEditor panel) {
2148        LayoutBlock result = null;  //assume failure (pessimist!)
2149        if (panel != null) {
2150            Sensor sensor = InstanceManager.sensorManagerInstance().getSensor(sensorName);
2151            result = (sensor == null) ? null : getFacingBlockBySensor(sensor, panel);
2152        }
2153        return result;
2154    }
2155
2156    /**
2157     * Get the LayoutBlock that a given signal is facing.
2158     * @param signalMast the signal mast to search for.
2159     * @param panel the layout editor panel.
2160     * @return the layout block, may be null.
2161     */
2162    @CheckReturnValue
2163    @CheckForNull
2164    public LayoutBlock getFacingBlockByMast(
2165            @Nonnull SignalMast signalMast,
2166            @Nonnull LayoutEditor panel) {
2167        return getFacingBlockByBean(signalMast, panel);
2168    }
2169
2170    /**
2171     * If the panel variable is null, search all LE panels. This was added to
2172     * support multi panel entry/exit.
2173     *
2174     * @param bean  The sensor, mast or head to be located.
2175     * @param panel The panel to search. Search all LE panels if null.
2176     * @return the facing layout block.
2177     */
2178    @CheckReturnValue
2179    @CheckForNull
2180    private LayoutBlock getFacingBlockByBean(
2181            @Nonnull NamedBean bean,
2182            LayoutEditor panel) {
2183        if (panel == null) {
2184            Set<LayoutEditor> panels = InstanceManager.getDefault(EditorManager.class).getAll(LayoutEditor.class);
2185            LayoutBlock returnBlock = null;
2186            for (LayoutEditor p : panels) {
2187                returnBlock = getFacingBlockByBeanByPanel(bean, p);
2188                if (returnBlock != null) {
2189                    break;
2190                }
2191            }
2192            return returnBlock;
2193        } else {
2194            return getFacingBlockByBeanByPanel(bean, panel);
2195        }
2196    }
2197
2198    @CheckReturnValue
2199    @CheckForNull
2200    private LayoutBlock getFacingBlockByBeanByPanel(
2201            @Nonnull NamedBean bean,
2202            @Nonnull LayoutEditor panel) {
2203        PositionablePoint pp = panel.getFinder().findPositionablePointByEastBoundBean(bean);
2204        TrackSegment tr = null;
2205        boolean east = true;
2206
2207        //Don't think that the logic for this is the right way round
2208        if (pp == null) {
2209            pp = panel.getFinder().findPositionablePointByWestBoundBean(bean);
2210            east = false;
2211        }
2212
2213        if (pp != null) {
2214            // LayoutEditorTools tools = panel.getLETools(); //TODO: Dead-code strip this
2215
2216            if (east) {
2217                if (LayoutEditorTools.isAtWestEndOfAnchor(panel, pp.getConnect1(), pp)) {
2218                    tr = pp.getConnect1();
2219                } else {
2220                    tr = pp.getConnect2();
2221                }
2222            } else {
2223                if (LayoutEditorTools.isAtWestEndOfAnchor(panel, pp.getConnect1(), pp)) {
2224                    tr = pp.getConnect2();
2225                } else {
2226                    tr = pp.getConnect1();
2227                }
2228            }
2229
2230            if (tr != null) {
2231                log.debug("found facing block by positionable point");
2232
2233                return tr.getLayoutBlock();
2234            }
2235        }
2236        LayoutTurnout t = panel.getFinder().findLayoutTurnoutByBean(bean);
2237
2238        if (t != null) {
2239            log.debug("found signalmast at turnout {}", t.getTurnout().getDisplayName());
2240            Object connect = null;
2241
2242            if (bean instanceof SignalMast) {
2243                if (t.getSignalAMast() == bean) {
2244                    connect = t.getConnectA();
2245                } else if (t.getSignalBMast() == bean) {
2246                    connect = t.getConnectB();
2247                } else if (t.getSignalCMast() == bean) {
2248                    connect = t.getConnectC();
2249                } else {
2250                    connect = t.getConnectD();
2251                }
2252            } else if (bean instanceof Sensor) {
2253                if (t.getSensorA() == bean) {
2254                    connect = t.getConnectA();
2255                } else if (t.getSensorB() == bean) {
2256                    connect = t.getConnectB();
2257                } else if (t.getSensorC() == bean) {
2258                    connect = t.getConnectC();
2259                } else {
2260                    connect = t.getConnectD();
2261                }
2262            }
2263
2264            if (connect instanceof TrackSegment) {
2265                tr = (TrackSegment) connect;
2266                log.debug("return block {}", tr.getLayoutBlock().getDisplayName());
2267
2268                return tr.getLayoutBlock();
2269            }
2270        }
2271
2272        LevelXing l = panel.getFinder().findLevelXingByBean(bean);
2273
2274        if (l != null) {
2275            Object connect = null;
2276
2277            if (bean instanceof SignalMast) {
2278                if (l.getSignalAMast() == bean) {
2279                    connect = l.getConnectA();
2280                } else if (l.getSignalBMast() == bean) {
2281                    connect = l.getConnectB();
2282                } else if (l.getSignalCMast() == bean) {
2283                    connect = l.getConnectC();
2284                } else {
2285                    connect = l.getConnectD();
2286                }
2287            } else if (bean instanceof Sensor) {
2288                if (l.getSensorA() == bean) {
2289                    connect = l.getConnectA();
2290                } else if (l.getSensorB() == bean) {
2291                    connect = l.getConnectB();
2292                } else if (l.getSensorC() == bean) {
2293                    connect = l.getConnectC();
2294                } else {
2295                    connect = l.getConnectD();
2296                }
2297            }
2298
2299            if (connect instanceof TrackSegment) {
2300                tr = (TrackSegment) connect;
2301                log.debug("return block {}", tr.getLayoutBlock().getDisplayName());
2302
2303                return tr.getLayoutBlock();
2304            }
2305        }
2306
2307        LayoutSlip ls = panel.getFinder().findLayoutSlipByBean(bean);
2308
2309        if (ls != null) {
2310            Object connect = null;
2311
2312            if (bean instanceof SignalMast) {
2313                if (ls.getSignalAMast() == bean) {
2314                    connect = ls.getConnectA();
2315                } else if (ls.getSignalBMast() == bean) {
2316                    connect = ls.getConnectB();
2317                } else if (ls.getSignalCMast() == bean) {
2318                    connect = ls.getConnectC();
2319                } else {
2320                    connect = ls.getConnectD();
2321                }
2322            } else if (bean instanceof Sensor) {
2323                if (ls.getSensorA() == bean) {
2324                    connect = ls.getConnectA();
2325                } else if (ls.getSensorB() == bean) {
2326                    connect = ls.getConnectB();
2327                } else if (ls.getSensorC() == bean) {
2328                    connect = ls.getConnectC();
2329                } else {
2330                    connect = ls.getConnectD();
2331                }
2332            }
2333
2334            if (connect instanceof TrackSegment) {
2335                tr = (TrackSegment) connect;
2336                log.debug("return block {}", tr.getLayoutBlock().getDisplayName());
2337
2338                return tr.getLayoutBlock();
2339            }
2340        }
2341        return null;
2342    } //getFacingBlockByBean
2343
2344    /**
2345     * Get the LayoutBlock that a given sensor is facing.
2346     * @param sensor the sensor to search for.
2347     * @param panel the layout editor panel to search.
2348     * @return the layout block, may be null.
2349     */
2350    @CheckReturnValue
2351    @CheckForNull
2352    public LayoutBlock getFacingBlockBySensor(
2353            @Nonnull Sensor sensor,
2354            @Nonnull LayoutEditor panel) {
2355        return getFacingBlockByBean(sensor, panel);
2356    }
2357
2358    @CheckReturnValue
2359    @CheckForNull
2360    public LayoutBlock getProtectedBlock(
2361            @Nonnull SignalHead signalHead, @CheckForNull LayoutEditor panel) {
2362        LayoutBlock result = null;  //assume failure (pessimist!)
2363        if (panel != null) {
2364            String userName = signalHead.getUserName();
2365            result = (userName == null) ? null : getProtectedBlock(userName, panel);
2366
2367            if (result == null) {
2368                result = getProtectedBlock(signalHead.getSystemName(), panel);
2369            }
2370        }
2371        return result;
2372    }
2373
2374    /**
2375     * Get the LayoutBlock that a given signal is protecting.
2376     * @param signalName the signal name to search for.
2377     * @param panel the main layout editor panel.
2378     * @return the layout block, may be null.
2379     */
2380    /* @TODO This needs to be expanded to cover turnouts and level crossings. */
2381    @CheckReturnValue
2382    @CheckForNull
2383    public LayoutBlock getProtectedBlock(
2384            @Nonnull String signalName, @Nonnull LayoutEditor panel) {
2385        PositionablePoint pp = panel.getFinder().findPositionablePointByEastBoundSignal(signalName);
2386        TrackSegment tr;
2387
2388        if (pp == null) {
2389            pp = panel.getFinder().findPositionablePointByWestBoundSignal(signalName);
2390
2391            if (pp == null) {
2392                return null;
2393            }
2394            tr = pp.getConnect1();
2395        } else {
2396            tr = pp.getConnect2();
2397        }
2398
2399        //tr = pp.getConnect2();
2400        if (tr == null) {
2401            return null;
2402        }
2403        return tr.getLayoutBlock();
2404    }
2405
2406    @CheckReturnValue
2407    @CheckForNull
2408    public LayoutBlock getFacingBlock(
2409            @Nonnull SignalHead signalHead, @CheckForNull LayoutEditor panel) {
2410        LayoutBlock result = null;  //assume failure (pessimist!)
2411        if (panel != null) {
2412            String userName = signalHead.getUserName();
2413            result = (userName == null) ? null : getFacingBlock(userName, panel);
2414            if (result == null) {
2415                result = getFacingBlock(signalHead.getSystemName(), panel);
2416            }
2417        }
2418        return result;
2419    }
2420
2421    /**
2422     * Get the LayoutBlock that a given signal is facing.
2423     * @param signalName signal name.
2424     * @param panel layout editor panel.
2425     * @return the facing layout block.
2426     */
2427    /* @TODO This needs to be expanded to cover turnouts and level crossings. */
2428    @CheckReturnValue
2429    @CheckForNull
2430    public LayoutBlock getFacingBlock(
2431            @Nonnull String signalName, @Nonnull LayoutEditor panel) {
2432        PositionablePoint pp = panel.getFinder().findPositionablePointByWestBoundSignal(signalName);
2433        TrackSegment tr;
2434
2435        if (pp == null) {
2436            pp = panel.getFinder().findPositionablePointByWestBoundSignal(signalName);
2437
2438            if (pp == null) {
2439                return null;
2440            }
2441            tr = pp.getConnect1();
2442        } else {
2443            tr = pp.getConnect2();
2444        }
2445
2446        if (tr == null) {
2447            return null;
2448        }
2449        return tr.getLayoutBlock();
2450    }
2451
2452    private boolean warnConnectivity = true;
2453
2454    /**
2455     * Controls switching off incompatible block connectivity messages.
2456     * <p>
2457     * Warnings are always on when program starts up. Once stopped by the user,
2458     * these messages may not be switched on again until program restarts.
2459     * @return true if connectivity warning flag set, else false.
2460     */
2461    public boolean warn() {
2462        return warnConnectivity;
2463    }
2464
2465    public void turnOffWarning() {
2466        warnConnectivity = false;
2467    }
2468
2469    protected boolean enableAdvancedRouting = false;
2470
2471    /**
2472     * @return true if advanced layout block routing has been enabled
2473     */
2474    public boolean isAdvancedRoutingEnabled() {
2475        return enableAdvancedRouting;
2476    }
2477
2478    /**
2479     * Enable the advanced layout block routing protocol
2480     * <p>
2481     * The block routing protocol enables each layout block to build up a list
2482     * of all reachable blocks, along with how far away they are, which
2483     * direction they are in and which of the connected blocks they are
2484     * reachable from.
2485     */
2486    private long firstRoutingChange;
2487
2488    public void enableAdvancedRouting(boolean boo) {
2489        if (boo == enableAdvancedRouting) {
2490            return;
2491        }
2492        enableAdvancedRouting = boo;
2493
2494        if (boo && initialized) {
2495            initializeLayoutBlockRouting();
2496        }
2497        firePropertyChange("advancedRoutingEnabled", !enableAdvancedRouting, enableAdvancedRouting);
2498    }
2499
2500    private void initializeLayoutBlockRouting() {
2501        if (!enableAdvancedRouting || !initialized) {
2502            log.debug("initializeLayoutBlockRouting immediate return due to {} {}", enableAdvancedRouting, initialized);
2503
2504            return;
2505        }
2506        firstRoutingChange = System.nanoTime();
2507
2508        //cycle through all LayoutBlocks, completing initialization of the layout block routing
2509        java.util.Enumeration<LayoutBlock> en = _tsys.elements();
2510
2511        while (en.hasMoreElements()) {
2512            en.nextElement().initializeLayoutBlockRouting();
2513        }
2514    }
2515
2516    @Nonnull
2517    public LayoutBlockConnectivityTools getLayoutBlockConnectivityTools() {
2518        return lbct;
2519    }
2520
2521    LayoutBlockConnectivityTools lbct = new LayoutBlockConnectivityTools();
2522
2523    private long lastRoutingChange;
2524
2525    void setLastRoutingChange() {
2526        log.debug("setLastRoutingChange");
2527        lastRoutingChange = System.nanoTime();
2528        stabilised = false;
2529        setRoutingStabilised();
2530    }
2531
2532    boolean checking = false;
2533    boolean stabilised = false;
2534
2535    private void setRoutingStabilised() {
2536        if (checking) {
2537            return;
2538        }
2539        log.debug("routing table change has been initiated");
2540        checking = true;
2541
2542        if (namedStabilisedIndicator != null) {
2543            try {
2544                namedStabilisedIndicator.getBean().setState(Sensor.INACTIVE);
2545            } catch (jmri.JmriException ex) {
2546                log.debug("Error setting stability indicator sensor");
2547            }
2548        }
2549        Runnable r = () -> {
2550            try {
2551                firePropertyChange("topology", true, false);
2552                long oldvalue = lastRoutingChange;
2553
2554                while (!stabilised) {
2555                    Thread.sleep(2000L); //two seconds
2556
2557                    if (oldvalue == lastRoutingChange) {
2558                        log.debug("routing table has now been stable for 2 seconds");
2559                        checking = false;
2560                        stabilised = true;
2561                        jmri.util.ThreadingUtil.runOnLayoutEventually(() -> firePropertyChange("topology", false, true));
2562
2563                        if (namedStabilisedIndicator != null) {
2564                            jmri.util.ThreadingUtil.runOnLayoutEventually(() -> {
2565                                log.debug("Setting StabilisedIndicator Sensor {} ACTIVE",
2566                                        namedStabilisedIndicator.getBean().getDisplayName());
2567                                try {
2568                                    namedStabilisedIndicator.getBean().setState(Sensor.ACTIVE);
2569                                } catch (jmri.JmriException ex) {
2570                                    log.debug("Error setting stability indicator sensor");
2571                                }
2572                            });
2573                        } else {
2574                            log.debug("Stable, no sensor to set");
2575                        }
2576                    } else {
2577                        long seconds = (long) ((lastRoutingChange - firstRoutingChange) / 1e9);
2578                        log.debug("routing table not stable after {} in {}",
2579                                String.format("%d:%02d:%02d", seconds / 3600, (seconds / 60) % 60, seconds % 60),
2580                                Thread.currentThread().getName());
2581                    }
2582                    oldvalue = lastRoutingChange;
2583                }
2584            } catch (InterruptedException ex) {
2585                Thread.currentThread().interrupt();
2586                checking = false;
2587
2588                //} catch (jmri.JmriException ex) {
2589                //log.debug("Error setting stability indicator sensor");
2590            }
2591        };
2592        thr = jmri.util.ThreadingUtil.newThread(r, "Routing stabilising timer");
2593        thr.start();
2594    } //setRoutingStabilised
2595
2596    private Thread thr = null;
2597
2598    private NamedBeanHandle<Sensor> namedStabilisedIndicator;
2599
2600    /**
2601     * Assign a sensor to the routing protocol, that changes state dependant
2602     * upon if the routing protocol has stabilised or is under going a change.
2603     * @param pName sensor name, will be provided if not existing.
2604     * @throws jmri.JmriException if no sensor manager.
2605     *
2606     */
2607    public void setStabilisedSensor(@Nonnull String pName) throws jmri.JmriException {
2608        if (InstanceManager.getNullableDefault(jmri.SensorManager.class) != null) {
2609            try {
2610                Sensor sensor = InstanceManager.sensorManagerInstance().provideSensor(pName);
2611                namedStabilisedIndicator = jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(
2612                        pName,
2613                        sensor);
2614                try {
2615                    if (stabilised) {
2616                        sensor.setState(Sensor.ACTIVE);
2617                    } else {
2618                        sensor.setState(Sensor.INACTIVE);
2619                    }
2620                } catch (jmri.JmriException ex) {
2621                    log.error("Error setting stablilty indicator sensor");
2622                }
2623            } catch (IllegalArgumentException ex) {
2624                log.error("Sensor '{}' not available", pName);
2625                throw new jmri.JmriException("Sensor '" + pName + "' not available");
2626            }
2627        } else {
2628            log.error("No SensorManager for this protocol");
2629            throw new jmri.JmriException("No Sensor Manager Found");
2630        }
2631    }
2632
2633    /**
2634     * Get the sensor used to indicate if the routing protocol has stabilised or
2635     * not.
2636     * @return routing stability sensor, may be null.
2637     */
2638    public Sensor getStabilisedSensor() {
2639        if (namedStabilisedIndicator == null) {
2640            return null;
2641        }
2642        return namedStabilisedIndicator.getBean();
2643    }
2644
2645    /**
2646     * Get the sensor used for the stability indication.
2647     * @return stability sensor, may be null.
2648     */
2649    @CheckReturnValue
2650    @CheckForNull
2651    public NamedBeanHandle<Sensor> getNamedStabilisedSensor() {
2652        return namedStabilisedIndicator;
2653    }
2654
2655    /**
2656     * @return true if the layout block routing protocol has stabilised
2657     */
2658    public boolean routingStablised() {
2659        return stabilised;
2660    }
2661
2662    /**
2663     * @return the time when the last routing change was made, recorded as
2664     *         System.nanoTime()
2665     */
2666    public long getLastRoutingChange() {
2667        return lastRoutingChange;
2668    }
2669
2670    @Override
2671    @Nonnull
2672    public String getBeanTypeHandled(boolean plural) {
2673        return Bundle.getMessage(plural ? "BeanNameLayoutBlocks" : "BeanNameLayoutBlock");
2674    }
2675
2676    /**
2677     * {@inheritDoc}
2678     */
2679    @Override
2680    public Class<LayoutBlock> getNamedBeanClass() {
2681        return LayoutBlock.class;
2682    }
2683
2684    /**
2685     * Get a list of layout blocks which this roster entry appears to be
2686     * occupying. A layout block is assumed to contain this roster entry if the
2687     * value of the underlying block is the RosterEntry itself, or a string with
2688     * the entry's id or dcc address.
2689     *
2690     * @param re the roster entry
2691     * @return list of layout block user names
2692     */
2693    @Nonnull
2694    public List<LayoutBlock> getLayoutBlocksOccupiedByRosterEntry(
2695            @Nonnull RosterEntry re) {
2696        List<LayoutBlock> result = new ArrayList<>();
2697
2698        BlockManager bm = jmri.InstanceManager.getDefault(jmri.BlockManager.class);
2699        List<Block> blockList = bm.getBlocksOccupiedByRosterEntry(re);
2700        for (Block block : blockList) {
2701            String uname = block.getUserName();
2702            if (uname != null) {
2703                LayoutBlock lb = getByUserName(uname);
2704                if (lb != null) {
2705                    result.add(lb);
2706                }
2707            }
2708        }
2709        return result;
2710    }
2711
2712    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LayoutBlockManager.class);
2713
2714}