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