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