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