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