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