001package jmri.jmrit.display.layoutEditor;
002
003import java.util.ArrayList;
004import java.util.HashMap;
005import java.util.List;
006import java.util.Set;
007import jmri.*;
008import jmri.jmrit.display.EditorManager;
009import org.slf4j.Logger;
010import org.slf4j.LoggerFactory;
011import org.slf4j.MDC;
012
013/**
014 * These are a series of layout block connectivity tools that can be used when
015 * the advanced layout block routing has been enabled. These tools can determine
016 * if a path from a source to destination bean is valid. If a route between two
017 * layout blocks is usable and free.
018 *
019 * @author Kevin Dickerson Copyright (C) 2011
020 * @author George Warner Copyright (c) 2017-2018
021 */
022final public class LayoutBlockConnectivityTools {
023
024    public LayoutBlockConnectivityTools() {
025    }
026
027    public enum Routing {
028        /**
029         * Constant used in the getLayoutBlocks to represent a path from one Signal
030         * Mast to another and that no mast should be in the path.
031         */
032        MASTTOMAST,
033
034        /**
035         * Constant used in the getLayoutBlocks to represent a path from one Signal
036         * Head to another and that no head should be in the path.
037         */
038        HEADTOHEAD,
039
040        /**
041         * Constant used in the getLayoutBlocks to represent a path from one Sensor
042         * to another and that no sensor should be in the path.
043         */
044        SENSORTOSENSOR,
045
046        /**
047         * Constant used in the getLayoutBlocks to represent a path from either a
048         * Signal Mast or Head to another Signal Mast or Head and that no mast of
049         * head should be in the path.
050         */
051        ANY,
052
053        /**
054         * Constant used in the getLayoutBlocks to indicate that the system
055         * should not check for signal masts or heads on the path.
056         */
057        NONE
058    }
059
060    public enum Metric {
061        HOPCOUNT,
062        METRIC,
063        DISTANCE
064    }
065    
066    private static final int ttlSize = 50;
067    
068
069    /**
070     * Determines if a pair of NamedBeans (Signalhead, Signalmast or Sensor)
071     * assigned to a block boundary are reachable.<br>
072     * Called by {@link jmri.jmrit.signalling.SignallingPanel} using MASTTOMAST.
073     * <p>
074     * Search all of the layout editor panels to find the facing and protecting
075     * layout blocks for each bean.  Call the 3 block+list version of checkValidDest() to finish the checks.
076     *
077     * @param sourceBean The source bean.
078     * @param destBean   The destination bean.
079     * @param pathMethod Indicates the type of path:  Signal head, signal mast or sensor.
080     * @return true if source and destination beans are reachable.
081     * @throws jmri.JmriException if no blocks can be found that related to the
082     *                            named beans.
083     */
084    public boolean checkValidDest(NamedBean sourceBean, NamedBean destBean, Routing pathMethod) throws jmri.JmriException {
085        if (log.isDebugEnabled()) {
086            log.debug("checkValidDest with source/dest bean {} {}", sourceBean.getDisplayName(), destBean.getDisplayName());
087        }
088        LayoutBlock facingBlock = null;
089        LayoutBlock protectingBlock = null;
090        LayoutBlock destFacingBlock = null;
091        List<LayoutBlock> destProtectBlock = null;
092        Set<LayoutEditor> layout = InstanceManager.getDefault(EditorManager.class).getAll(LayoutEditor.class);
093        LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class);
094        for (LayoutEditor layoutEditor : layout) {
095            if (log.isDebugEnabled()) {
096                log.debug("Layout name {}", layoutEditor.getLayoutName());
097            }
098            if (facingBlock == null) {
099                facingBlock = lbm.getFacingBlockByNamedBean(sourceBean, layoutEditor);
100            }
101            if (protectingBlock == null) {
102                protectingBlock = lbm.getProtectedBlockByNamedBean(sourceBean, layoutEditor);
103            }
104            if (destFacingBlock == null) {
105                destFacingBlock = lbm.getFacingBlockByNamedBean(destBean, layoutEditor);
106            }
107            if (destProtectBlock == null) {
108                destProtectBlock = lbm.getProtectingBlocksByNamedBean(destBean, layoutEditor);
109            }
110            if ((destFacingBlock != null) && (facingBlock != null) && (protectingBlock != null)) {
111                /*Destination protecting block list is allowed to be empty, as the destination signalmast
112                 could be assigned to an end bumper */
113                // A simple to check to see if the remote signal/sensor is in the correct direction to ours.
114                try {
115                    return checkValidDest(facingBlock, protectingBlock, destFacingBlock, destProtectBlock, pathMethod);
116                } catch (JmriException e) {
117                    log.debug("Rethrowing exception from checkValidDest(..)");
118                    throw e;
119                }
120            } else {
121                log.debug("blocks not found");
122            }
123        }
124        if (log.isDebugEnabled()) {
125            log.debug("No valid route from {} to {}", sourceBean.getDisplayName(), destBean.getDisplayName());
126        }
127        throw new jmri.JmriException("Blocks Not Found");
128    }
129
130    /**
131     * The is used in conjunction with the layout block routing protocol, to
132     * discover a clear path from a source layout block through to a destination
133     * layout block. By specifying the sourceLayoutBlock and
134     * protectingLayoutBlock or sourceLayoutBlock+1, a direction of travel can
135     * then be determined, eg east to west, south to north etc.
136     * @param sourceBean    The source bean (SignalHead, SignalMast or Sensor)
137     *                     assigned to a block boundary that we are starting
138     *                     from.
139     * @param destBean      The destination bean.
140     * @param validateOnly  When set false, the system will not use layout
141     *                     blocks that are set as either reserved(useExtraColor
142     *                     set) or occupied, if it finds any then it will try to
143     *                     find an alternative path When set false, no block
144     *                     state checking is performed.
145     * @param pathMethod    Performs a check to see if any signal heads/masts
146     *                     are in the path, if there are then the system will
147     *                     try to find an alternative path. If set to NONE, then
148     *                     no checking is performed.
149     * @return an List of all the layoutblocks in the path.
150     * @throws jmri.JmriException if it can not find a valid path or the routing
151     *                            has not been enabled.
152     */
153    public List<LayoutBlock> getLayoutBlocks(NamedBean sourceBean, NamedBean destBean, boolean validateOnly, Routing pathMethod) throws jmri.JmriException {
154        Set<LayoutEditor> layout = InstanceManager.getDefault(EditorManager.class).getAll(LayoutEditor.class);
155        LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class);
156        LayoutBlock facingBlock = null;
157        LayoutBlock protectingBlock = null;
158        LayoutBlock destFacingBlock = null;
159        for (LayoutEditor layoutEditor : layout) {
160            if (log.isDebugEnabled()) {
161                log.debug("Layout name {}", layoutEditor.getLayoutName());
162            }
163            if (facingBlock == null) {
164                facingBlock = lbm.getFacingBlockByNamedBean(sourceBean, layoutEditor);
165            }
166            if (protectingBlock == null) {
167                protectingBlock = lbm.getProtectedBlockByNamedBean(sourceBean, layoutEditor);
168            }
169            if (destFacingBlock == null) {
170                destFacingBlock = lbm.getFacingBlockByNamedBean(destBean, layoutEditor);
171            }
172            if ((destFacingBlock != null) && (facingBlock != null) && (protectingBlock != null)) {
173                try {
174                    return getLayoutBlocks(facingBlock, destFacingBlock, protectingBlock, validateOnly, pathMethod);
175                } catch (JmriException e) {
176                    log.debug("Rethrowing exception from getLayoutBlocks()");
177                    throw e;
178                }
179            } else {
180                log.debug("blocks not found");
181            }
182        }
183        if (log.isDebugEnabled()) {
184            log.debug("No valid route from {} to {}", sourceBean.getDisplayName(), destBean.getDisplayName());
185        }
186        throw new jmri.JmriException("Blocks Not Found");
187    }
188
189    /**
190     * Returns a list of NamedBeans (Signalhead, Signalmast or Sensor) that are
191     * assigned to block boundaries in a given list.
192     *
193     * @param blocklist The list of block in order that need to be checked.
194     * @param panel     (Optional) panel that the blocks need to be checked
195     *                  against
196     * @param T         (Optional) the class that we want to check against,
197     *                  either Sensor, SignalMast or SignalHead, set null will
198     *                  return any.
199     * @return the list of NamedBeans
200     */
201    public List<NamedBean> getBeansInPath(List<LayoutBlock> blocklist, LayoutEditor panel, Class<?> T) {
202        List<NamedBean> beansInPath = new ArrayList<>();
203        if (blocklist.size() >= 2) {
204            LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class);
205            for (int x = 1; x < blocklist.size(); x++) {
206                LayoutBlock facingBlock = blocklist.get(x - 1);
207                LayoutBlock protectingBlock = blocklist.get(x);
208                NamedBean nb = null;
209                if (T == null) {
210                    nb = lbm.getFacingNamedBean(facingBlock.getBlock(), protectingBlock.getBlock(), panel);
211                } else if (T.equals(jmri.SignalMast.class)) {
212                    nb = lbm.getFacingSignalMast(facingBlock.getBlock(), protectingBlock.getBlock(), panel);
213                } else if (T.equals(jmri.Sensor.class)) {
214                    nb = lbm.getFacingSensor(facingBlock.getBlock(), protectingBlock.getBlock(), panel);
215                } else if (T.equals(jmri.SignalHead.class)) {
216                    nb = lbm.getFacingSignalHead(facingBlock.getBlock(), protectingBlock.getBlock());
217                }
218                if (nb != null) {
219                    beansInPath.add(nb);
220                }
221            }
222        }
223        return beansInPath;
224    }
225
226    /**
227     * Determines if one set of blocks is reachable from another set of blocks
228     * based upon the directions of the set of blocks.
229     * <ul>
230     * <li>Called by {@link jmri.implementation.DefaultSignalMastLogic} using MASTTOMAST.</li>
231     * <li>Called by {@link jmri.jmrit.entryexit.DestinationPoints} using SENSORTOSENSOR.</li>
232     * <li>Called by {@link jmri.jmrit.entryexit.EntryExitPairs} using SENSORTOSENSOR.</li>
233     * </ul>
234     * Convert the destination protected block to an array list.
235     * Call the 3 block+list version of checkValidDest() to finish the checks.
236     * @param currentBlock The facing layout block for the source signal or sensor.
237     * @param nextBlock    The protected layout block for the source signal or sensor.
238     * @param destBlock    The facing layout block for the destination signal mast or sensor.
239     * @param destProBlock The protected destination block.
240     * @param pathMethod   Indicates the type of path:  Signal head, signal mast or sensor.
241     * @return true if a path to the destination is valid.
242     * @throws jmri.JmriException if any Block is null;
243     */
244    public boolean checkValidDest(LayoutBlock currentBlock, LayoutBlock nextBlock, LayoutBlock destBlock, LayoutBlock destProBlock, Routing pathMethod) throws jmri.JmriException {
245
246        List<LayoutBlock> destList = new ArrayList<>();
247        if (destProBlock != null) {
248            destList.add(destProBlock);
249        }
250        try {
251            return checkValidDest(currentBlock, nextBlock, destBlock, destList, pathMethod);
252        } catch (jmri.JmriException e) {
253            throw e;
254        }
255
256    }
257
258    /**
259     * Determines if one set of blocks is reachable from another set of blocks
260     * based upon the directions of the set of blocks.
261     * <p>
262     * This is used to help with identifying items such as signalmasts located
263     * at positionable points or turnouts are facing in the same direction as
264     * other given signalmasts.
265     * <p>
266     * Given the current block and the next block we can work out the direction
267     * of travel. Given the destBlock and the next block on, we can determine
268     * the whether the destBlock comes before the destBlock+1.
269     * <p>
270     * Note: This version is internally called by other versions that
271     * pre-process external calls.
272     * @param currentBlock The facing layout block for the source signal or
273     *                     sensor.
274     * @param nextBlock    The protected layout block for the source signal or
275     *                     sensor.
276     * @param destBlock    The facing layout block for the destination signal
277     *                     mast or sensor.
278     * @param destBlockn1  A list of protected destination blocks. Can be empty
279     *                     if the destination is at an end bumper.
280     * @param pathMethod   Indicates the type of path: Signal head, signal mast
281     *                     or sensor.
282     * @return true if a path to the destination is valid.
283     * @throws jmri.JmriException if any layout block is null or advanced
284     *                            routing is not enabled.
285     */
286    public boolean checkValidDest(LayoutBlock currentBlock, LayoutBlock nextBlock, LayoutBlock destBlock, List<LayoutBlock> destBlockn1, Routing pathMethod) throws jmri.JmriException {
287        // ----- Begin Turntable Exit Path Check -----
288        // This handles the special case for a path exiting a turntable (Path 1), where the protecting block (a ray)
289        // can be the same as the destination's facing block.
290        for (LayoutEditor panel : InstanceManager.getDefault(EditorManager.class).getAll(LayoutEditor.class)) {
291            for (LayoutTurntable turntable : panel.getLayoutTurntables()) {
292                // Check if the path starts from this turntable's block
293                if (turntable.getLayoutBlock() == currentBlock) {
294                    // A path from a turntable is valid if the destination is a ray block or a neighbor of a ray block.
295                    for (LayoutTurntable.RayTrack ray : turntable.getRayTrackList()) {
296                        TrackSegment track = ray.getConnect();
297                        if (track != null && track.getLayoutBlock() != null) {
298                            LayoutBlock rayBlock = track.getLayoutBlock();
299                            // First, check if the ray block itself is the destination.
300                            if (rayBlock == destBlock) {
301                                return true;
302                            }
303                            // Next, check if the destination block is a valid neighbor of this ray block.
304                            for (int i = 0; i < rayBlock.getNumberOfNeighbours(); i++) {
305                                Block neighbor = rayBlock.getNeighbourAtIndex(i);
306                                if (neighbor != null && neighbor == destBlock.getBlock()) {
307                                    return true;
308                                }
309                            }
310                        }
311                    }
312                }
313            }
314        }
315        // ----- Begin Turntable Siding Path Check (Path 3) -----
316        // This handles the special case for a path entering a turntable to a buffer stop,
317        // where the destination block is the turntable itself.
318        for (LayoutEditor panel : InstanceManager.getDefault(EditorManager.class).getAll(LayoutEditor.class)) {
319            for (LayoutTurntable turntable : panel.getLayoutTurntables()) {
320                if (turntable.getLayoutBlock() == destBlock) {
321                    return true;
322                }
323            }
324        }
325        // ----- End Turntable Exit Path Check -----
326        LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class);
327        if (!lbm.isAdvancedRoutingEnabled()) {
328            log.debug("Advanced routing has not been enabled therefore we cannot use this function");
329            throw new jmri.JmriException("Advanced routing has not been enabled therefore we cannot use this function");
330        }
331
332        if (log.isDebugEnabled()) {
333            try {
334                log.debug("faci {}", currentBlock.getDisplayName());
335                log.debug("next {}", nextBlock.getDisplayName());
336                log.debug("dest {}", destBlock.getDisplayName());
337                for (LayoutBlock dp : destBlockn1) {
338                    log.debug("dest + 1 {}", dp.getDisplayName());
339                }
340            } catch (java.lang.NullPointerException e) {
341
342            }
343        }
344        if ((destBlock != null) && (currentBlock != null) && (nextBlock != null)) {
345            if (!currentBlock.isRouteToDestValid(nextBlock.getBlock(), destBlock.getBlock())) {
346                log.debug("Route to dest not valid");
347                return false;
348            }
349            if (log.isDebugEnabled()) {
350                log.debug("dest {}", destBlock.getDisplayName());
351                /*if(destBlockn1!=null)
352                 log.debug("remote prot " + destBlockn1.getDisplayName());*/
353            }
354            if (!destBlockn1.isEmpty() && currentBlock == destBlockn1.get(0) && nextBlock == destBlock) {
355                log.debug("Our dest protecting block is our current block and our protecting block is the same as our destination block");
356                return false;
357            }
358            // Do a simple test to see if one is reachable from the other.
359            int proCount = 0;
360            int desCount = 0;
361            if (!destBlockn1.isEmpty()) {
362                desCount = currentBlock.getBlockHopCount(destBlock.getBlock(), nextBlock.getBlock());
363                proCount = currentBlock.getBlockHopCount(destBlockn1.get(0).getBlock(), nextBlock.getBlock());
364                if (log.isDebugEnabled()) {
365                    log.debug("dest {} protecting {}", desCount, proCount);
366                }
367            }
368
369            if ((proCount == -1) && (desCount == -1)) {
370                // The destination block and destBlock+1 are both directly connected
371                log.debug("Dest and dest+1 are directly connected");
372                return false;
373            }
374
375            if (proCount > desCount && (proCount - 1) == desCount) {
376                // The block that we are protecting should be one hop greater than the destination count.
377                log.debug("Protecting is one hop away from destination and therefore valid.");
378                return true;
379            }
380
381            /*Need to do a more advanced check in this case as the destBlockn1
382             could be reached via a different route and therefore have a smaller
383             hop count we need to therefore step through each block until we reach
384             the end.
385             The advanced check also covers cases where the route table is inconsistent.
386             We also need to perform a more advanced check if the destBlockn1
387             is null as this indicates that the destination signal mast is assigned
388             on an end bumper*/
389
390            if (pathMethod == Routing.SENSORTOSENSOR && destBlockn1.size() == 0) {
391                // Change the pathMethod to accept the NX sensor at the end bumper.
392                pathMethod = Routing.NONE;
393            }
394
395            List<LayoutBlock> blockList = getLayoutBlocks(currentBlock, destBlock, nextBlock, true, pathMethod); // Was MASTTOMAST
396            if (log.isDebugEnabled()) {
397                log.debug("checkValidDest blockList for {}", destBlock.getDisplayName());
398                blockList.forEach(blk -> log.debug("  block = {}", blk.getDisplayName()));
399            }
400            for (LayoutBlock dp : destBlockn1) {
401                log.debug("dp = {}", dp.getDisplayName());
402                if (blockList.contains(dp) && currentBlock != dp) {
403                    log.debug("Signal mast in the wrong direction");
404                    return false;
405                }
406            }
407                /*Work on the basis that if you get the blocks from source to dest
408                 then the dest+1 block should not be included*/
409            log.debug("Signal mast in the correct direction");
410            return true;
411
412        } else if (destBlock == null) {
413            throw new jmri.JmriException("Block in Destination Field returns as invalid");
414        } else if (currentBlock == null) {
415            throw new jmri.JmriException("Block in Facing Field returns as invalid");
416        } else if (nextBlock == null) {
417            throw new jmri.JmriException("Block in Protecting Field returns as invalid");
418        }
419        throw new jmri.JmriException("BlockIsNull");
420    }
421
422    /**
423     * This uses the layout editor to check if the destination location is
424     * reachable from the source location.<br>
425     * Only used internally to the class.
426     * <p>
427     * @param facing     Layout Block that is considered our first block
428     * @param protecting Layout Block that is considered first block +1
429     * @param dest       Layout Block that we want to get to
430     * @param pathMethod the path method
431     * @return true if valid
432     * @throws JmriException during nested getProtectingBlocks operation
433     */
434    private boolean checkValidDest(LayoutBlock facing, LayoutBlock protecting, FacingProtecting dest, Routing pathMethod) throws JmriException {
435        if (facing == null || protecting == null || dest == null) {
436            return false;
437        }
438        if (log.isDebugEnabled()) {
439            log.debug("facing : {} protecting : {} dest {}", protecting.getDisplayName(), dest.getBean().getDisplayName(), facing.getDisplayName());
440        }
441
442        // In this instance it doesn't matter what the destination protecting block is so we get the first
443        /*LayoutBlock destProt = null;
444         if(!dest.getProtectingBlocks().isEmpty()){
445         destProt = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlock(dest.getProtectingBlocks().get(0));
446         }*/
447         
448        List<LayoutBlock> destList = new ArrayList<>();
449
450         // may throw JmriException here
451        dest.getProtectingBlocks().forEach((b) -> {
452            destList.add(InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlock(b));
453        });
454        return checkValidDest(facing, protecting, InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlock(dest.getFacing()), destList, pathMethod);
455    }
456
457    /**
458     * This used in conjunction with the layout block routing protocol, to
459     * discover a clear path from a source layout block through to a destination
460     * layout block. By specifying the sourceLayoutBlock and
461     * protectingLayoutBlock or sourceLayoutBlock+1, a direction of travel can
462     * then be determined, eg east to west, south to north etc.
463     *
464     * @param sourceLayoutBlock      The layout block that we are starting from,
465     *                               can also be considered as the block facing
466     *                               a signal.
467     * @param destinationLayoutBlock The layout block that we want to get to
468     * @param protectingLayoutBlock  The next layout block connected to the
469     *                               source block, this can also be considered
470     *                               as the block being protected by a signal
471     * @param validateOnly           When set false, the system will not use
472     *                               layout blocks that are set as either
473     *                               reserved(useExtraColor set) or occupied, if
474     *                               it finds any then it will try to find an
475     *                               alternative path When set true, no block
476     *                               state checking is performed.
477     * @param pathMethod             Performs a check to see if any signal
478     *                               heads/masts are in the path, if there are
479     *                               then the system will try to find an
480     *                               alternative path. If set to NONE, then no
481     *                               checking is performed.
482     * @return an List of all the layoutblocks in the path.
483     * @throws jmri.JmriException if it can not find a valid path or the routing
484     *                            has not been enabled.
485     */
486    public List<LayoutBlock> getLayoutBlocks(LayoutBlock sourceLayoutBlock, LayoutBlock destinationLayoutBlock, LayoutBlock protectingLayoutBlock, boolean validateOnly, Routing pathMethod) throws jmri.JmriException {
487        lastErrorMessage = "Unknown Error Occured";
488        LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class);
489        
490        if (!lbm.isAdvancedRoutingEnabled()) {
491            log.debug("Advanced routing has not been enabled therefore we cannot use this function");
492            throw new jmri.JmriException("Advanced routing has not been enabled therefore we cannot use this function");
493        }
494
495        int directionOfTravel = sourceLayoutBlock.getNeighbourDirection(protectingLayoutBlock);
496        Block currentBlock = sourceLayoutBlock.getBlock();
497
498        Block destBlock = destinationLayoutBlock.getBlock();
499        log.debug("Destination Block {} {}", destinationLayoutBlock.getDisplayName(), destBlock);
500
501        Block nextBlock = protectingLayoutBlock.getBlock();
502        if (log.isDebugEnabled()) {
503            log.debug("s:{} p:{} d:{}", sourceLayoutBlock.getDisplayName(), protectingLayoutBlock.getDisplayName(), destinationLayoutBlock.getDisplayName());
504        }
505        List<BlocksTested> blocksInRoute = new ArrayList<>();
506        blocksInRoute.add(new BlocksTested(sourceLayoutBlock));
507
508        if (!validateOnly) {
509            if (canLBlockBeUsed(protectingLayoutBlock)) {
510                blocksInRoute.add(new BlocksTested(protectingLayoutBlock));
511            } else {
512                lastErrorMessage = "Block we are protecting is already occupied or reserved";
513                log.debug("will throw {}", lastErrorMessage);
514                throw new jmri.JmriException(lastErrorMessage);
515            }
516            if (!canLBlockBeUsed(destinationLayoutBlock)) {
517                lastErrorMessage = "Destination Block is already occupied or reserved";
518                log.debug("will throw {}", lastErrorMessage);
519                throw new jmri.JmriException(lastErrorMessage);
520            }
521        } else {
522            blocksInRoute.add(new BlocksTested(protectingLayoutBlock));
523        }
524        if (destinationLayoutBlock == protectingLayoutBlock) {
525            List<LayoutBlock> returnBlocks = new ArrayList<>();
526            blocksInRoute.forEach((blocksTested) -> {
527                returnBlocks.add(blocksTested.getBlock());
528            });
529            return returnBlocks;
530        }
531
532        BlocksTested bt = blocksInRoute.get(blocksInRoute.size() - 1);
533
534        int ttl = 1;
535        List<Integer> offSet = new ArrayList<>();
536        while (ttl < ttlSize) { // value should be higher but low for test!
537            log.debug("===== Ttl value = {} ======", ttl);
538            log.debug("Looking for next block");
539            int nextBlockIndex = findBestHop(currentBlock, nextBlock, destBlock, directionOfTravel, offSet, validateOnly, pathMethod);
540            if (nextBlockIndex != -1) {
541                bt.addIndex(nextBlockIndex);
542                if (log.isDebugEnabled()) {
543                    log.debug("block index returned {} Blocks in route size {}", nextBlockIndex, blocksInRoute.size());
544                }
545                // Sets the old next block to be our current block.
546                LayoutBlock currentLBlock = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlock(nextBlock);
547                if (currentLBlock == null) {
548                    log.error("Unable to get block :{}: from instancemanager", nextBlock);
549                    continue;
550                }
551                offSet.clear();
552
553                directionOfTravel = currentLBlock.getRouteDirectionAtIndex(nextBlockIndex);
554
555                //allow for routes that contain more than one occurrence of a block in a route to allow change of direction.
556                Block prevBlock = currentBlock;
557                LayoutBlock prevLBlock = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlock(prevBlock);
558                currentBlock = nextBlock;
559                nextBlock = currentLBlock.getRouteNextBlockAtIndex(nextBlockIndex);
560                LayoutBlock nextLBlock = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlock(nextBlock);
561                if (log.isDebugEnabled()) {
562                    log.debug("Blocks in route size {}", blocksInRoute.size());
563                    log.debug("next: {} dest: {}", nextBlock.getDisplayName(), destBlock.getDisplayName());
564                }
565                if (nextBlock == currentBlock) {
566                    nextBlock = currentLBlock.getRouteDestBlockAtIndex(nextBlockIndex);
567                    log.debug("the next block to our destination we are looking for is directly connected to this one");
568                } else if (!((protectingLayoutBlock == prevLBlock)&&(protectingLayoutBlock == nextLBlock))) {
569                    if (nextLBlock != null) {
570                        log.debug("Add block {}", nextLBlock.getDisplayName());
571                    }
572                    bt = new BlocksTested(nextLBlock);
573                    blocksInRoute.add(bt);
574                }
575                if (nextBlock == destBlock) {
576                    if (!validateOnly && !checkForLevelCrossing(destinationLayoutBlock)) {
577                        throw new jmri.JmriException("Destination block is in conflict on a crossover");
578                    }
579                    List<LayoutBlock> returnBlocks = new ArrayList<>();
580                    blocksInRoute.forEach((blocksTested) -> {
581                        returnBlocks.add(blocksTested.getBlock());
582                    });
583                    returnBlocks.add(destinationLayoutBlock);
584                    if (log.isDebugEnabled()) {
585                        log.debug("Adding destination Block {}", destinationLayoutBlock.getDisplayName());
586                        log.debug("arrived at destination block");
587                        log.debug("{} Return as Long", sourceLayoutBlock.getDisplayName());
588                        returnBlocks.forEach((returnBlock) -> {
589                            log.debug("  return block {}", returnBlock.getDisplayName());
590                        });
591                        log.debug("Finished List");
592                    }
593                    return returnBlocks;
594                }
595            } else {
596                //-1 is returned when there are no more valid besthop valids found
597                // Block index is -1, so we need to go back a block and find another way.
598
599                // So we have gone back as far as our starting block so we better return.
600                int birSize = blocksInRoute.size();
601                log.debug("block in route size {}", birSize);
602                if (birSize <= 2) {
603                    log.debug("drop out here with ttl");
604                    ttl = ttlSize + 1;
605                } else {
606                    if (log.isDebugEnabled()) {
607                        for (int t = 0; t < blocksInRoute.size(); t++) {
608                            log.debug("index {} block {}", t, blocksInRoute.get(t).getBlock().getDisplayName());
609                        }
610                        log.debug("To remove last block {}", blocksInRoute.get(birSize - 1).getBlock().getDisplayName());
611                    }
612
613                    currentBlock = blocksInRoute.get(birSize - 3).getBlock().getBlock();
614                    nextBlock = blocksInRoute.get(birSize - 2).getBlock().getBlock();
615                    offSet = blocksInRoute.get(birSize - 2).getTestedIndexes();
616                    bt = blocksInRoute.get(birSize - 2);
617                    blocksInRoute.remove(birSize - 1);
618                    ttl--;
619                }
620            }
621            ttl++;
622        }
623        if (ttl == ttlSize) {
624            lastErrorMessage = "ttlExpired";
625        }
626        // we exited the loop without either finding the destination or we had error.
627        throw new jmri.JmriException(lastErrorMessage);
628    }
629
630    static class BlocksTested {
631
632        LayoutBlock block;
633        List<Integer> indexNumber = new ArrayList<>();
634
635        BlocksTested(LayoutBlock block) {
636            this.block = block;
637        }
638
639        void addIndex(int x) {
640            indexNumber.add(x);
641        }
642
643        int getLastIndex() {
644            return indexNumber.get(indexNumber.size() - 1); // get the last one in the list
645        }
646
647        List<Integer> getTestedIndexes() {
648            return indexNumber;
649        }
650
651        LayoutBlock getBlock() {
652            return block;
653        }
654    }
655
656    private boolean canLBlockBeUsed(LayoutBlock lBlock) {
657        if (lBlock == null) {
658            return true;
659        }
660        if (lBlock.getUseExtraColor()) {
661            return false;
662        }
663        if (lBlock.getBlock().getPermissiveWorking()) {
664            return true;
665        }
666        return (lBlock.getState() != Block.OCCUPIED);
667    }
668
669    String lastErrorMessage = "Unknown Error Occured";
670
671    // We need to take into account if the returned block has a signalmast attached.
672    int findBestHop(final Block preBlock, final Block currentBlock, Block destBlock, int direction, List<Integer> offSet, boolean validateOnly, Routing pathMethod) {
673        int result = 0;
674
675        LayoutBlock currentLBlock = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlock(currentBlock);
676        if (currentLBlock == null) {
677            return -1;
678        }
679        List<Integer> blkIndexTested = new ArrayList<>(5);
680        if (log.isDebugEnabled()) {
681            log.debug("In find best hop current {} previous {}", currentLBlock.getDisplayName(), preBlock.getDisplayName());
682        }
683        Block block;
684        while (result != -1) {
685            if (currentBlock == preBlock) {
686                // Basically looking for the connected block, which there should only be one of!
687                log.debug("At get ConnectedBlockRoute");
688                result = currentLBlock.getConnectedBlockRouteIndex(destBlock, direction);
689                log.trace("getConnectedBlockRouteIndex returns result {} with destBlock {}, direction {}", result, destBlock, direction);
690            } else {
691                if (log.isDebugEnabled()) {
692                    log.debug("Off Set {}", offSet);
693                }
694                result = currentLBlock.getNextBestBlock(preBlock, destBlock, offSet, Metric.METRIC);
695                log.trace("getNextBestBlock returns result {} with preBlock {}, destBlock {}", result, preBlock, destBlock);
696            }
697            if (result != -1) {
698                block = currentLBlock.getRouteNextBlockAtIndex(result);
699                LayoutBlock lBlock = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlock(block);
700
701                Block blocktoCheck = block;
702                if (block == currentBlock) {
703                    log.debug("current block matches returned block therefore the next block is directly connected");
704                    blocktoCheck = destBlock;
705                }
706
707                if ((block == currentBlock) && (currentLBlock.getThroughPathIndex(preBlock, destBlock) == -1)) {
708                    lastErrorMessage = "block " + block.getDisplayName() + " is directly attached, however the route to the destination block " + destBlock.getDisplayName() + " can not be directly used";
709                    log.debug("continue after {}", lastErrorMessage);
710                } else if ((validateOnly) || ((checkForDoubleCrossover(preBlock, currentLBlock, blocktoCheck) && checkForLevelCrossing(currentLBlock)) && canLBlockBeUsed(lBlock))) {
711                    if (log.isDebugEnabled()) {
712                        log.debug("{} not occupied & not reserved but we need to check if the anchor point between the two contains a signal or not", block.getDisplayName());
713                        log.debug("  current {} {}", currentBlock.getDisplayName(), block.getDisplayName());
714                    }
715
716                    jmri.NamedBean foundBean = null;
717                    /* We change the logging level to fatal in the layout block manager as we are testing to make sure that no signalhead/mast exists
718                     this would generate an error message that is expected.*/
719                    MDC.put("loggingDisabled", LayoutBlockManager.class.getName());
720                    log.trace(" current pathMethod is {}", pathMethod, new Exception("traceback"));
721                    switch (pathMethod) {
722                        case MASTTOMAST:
723                            foundBean = InstanceManager.getDefault(LayoutBlockManager.class).getFacingSignalMast(currentBlock, blocktoCheck);
724                            break;
725                        case HEADTOHEAD:
726                            foundBean = InstanceManager.getDefault(LayoutBlockManager.class).getFacingSignalHead(currentBlock, blocktoCheck);
727                            break;
728                        case SENSORTOSENSOR:
729                            foundBean = InstanceManager.getDefault(LayoutBlockManager.class).getFacingSensor(currentBlock, blocktoCheck, null);
730                            break;
731                        case NONE:
732                            break;
733                        default:
734                            foundBean = InstanceManager.getDefault(LayoutBlockManager.class).getFacingNamedBean(currentBlock, blocktoCheck, null);
735                            break;
736                    }
737                    MDC.remove("loggingDisabled");
738                    if (foundBean == null) {
739                        log.debug("No object found so okay to return");
740                        return result;
741                    } else {
742                        lastErrorMessage = "Signal " + foundBean.getDisplayName() + " already exists between blocks " + currentBlock.getDisplayName() + " and " + blocktoCheck.getDisplayName() + " in the same direction on this path";
743                        log.debug("continue after {}", lastErrorMessage);
744                    }
745                } else {
746                    lastErrorMessage = "block " + block.getDisplayName() + " found not to be not usable";
747                    log.debug("continue after {}", lastErrorMessage);
748                }
749                if (blkIndexTested.contains(result)) {
750                    lastErrorMessage = ("No valid free path found");
751                    return -1;
752                }
753                blkIndexTested.add(result);
754                offSet.add(result);
755            } else {
756                log.debug("At this point the getNextBextBlock() has returned a -1");
757            }
758        }
759        return -1;
760    }
761
762    private boolean checkForDoubleCrossover(Block prevBlock, LayoutBlock curBlock, Block nextBlock) {
763        LayoutEditor le = curBlock.getMaxConnectedPanel();
764        ConnectivityUtil ct = le.getConnectivityUtil();
765        List<LayoutTrackExpectedState<LayoutTurnout>> turnoutList = ct.getTurnoutList(curBlock.getBlock(), prevBlock, nextBlock);
766        for (LayoutTrackExpectedState<LayoutTurnout> layoutTurnoutLayoutTrackExpectedState : turnoutList) {
767            LayoutTurnout lt = layoutTurnoutLayoutTrackExpectedState.getObject();
768            if (lt.getTurnoutType() == LayoutTurnout.TurnoutType.DOUBLE_XOVER) {
769                if (layoutTurnoutLayoutTrackExpectedState.getExpectedState() == jmri.Turnout.THROWN) {
770                    jmri.Turnout t = lt.getTurnout();
771                    if (t.getKnownState() == jmri.Turnout.THROWN) {
772                        if (lt.getLayoutBlock() == curBlock || lt.getLayoutBlockC() == curBlock) {
773                            if (!canLBlockBeUsed(lt.getLayoutBlockB()) && !canLBlockBeUsed(lt.getLayoutBlockD())) {
774                                return false;
775                            }
776                        } else if (lt.getLayoutBlockB() == curBlock || lt.getLayoutBlockD() == curBlock) {
777                            if (!canLBlockBeUsed(lt.getLayoutBlock()) && !canLBlockBeUsed(lt.getLayoutBlockC())) {
778                                return false;
779                            }
780                        }
781                    }
782                }
783            }
784        }
785        return true;
786    }
787
788    private boolean checkForLevelCrossing(LayoutBlock curBlock) {
789        LayoutEditor lay = curBlock.getMaxConnectedPanel();
790        for (LevelXing lx : lay.getLevelXings()) {
791            if (lx.getLayoutBlockAC() == curBlock
792                    || lx.getLayoutBlockBD() == curBlock) {
793                if ((lx.getLayoutBlockAC() != null)
794                        && (lx.getLayoutBlockBD() != null)
795                        && (lx.getLayoutBlockAC() != lx.getLayoutBlockBD())) {
796                    if (lx.getLayoutBlockAC() == curBlock) {
797                        if (!canLBlockBeUsed(lx.getLayoutBlockBD())) {
798                            return false;
799                        }
800                    } else if (lx.getLayoutBlockBD() == curBlock) {
801                        if (!canLBlockBeUsed(lx.getLayoutBlockAC())) {
802                            return false;
803                        }
804                    }
805                }
806            }
807        }
808        return true;
809    }
810
811    /**
812     * Discovers valid pairs of beans type T assigned to a layout editor. If no
813     * bean type is provided, then either SignalMasts or Sensors are discovered
814     * If no editor is provided, then all editors are considered
815     *
816     * @param editor     the layout editor panel
817     * @param T          the type
818     * @param pathMethod Determine whether or not we should reject pairs if
819     *                   there are other beans in the way. Constant values of
820     *                   NONE, ANY, MASTTOMAST, HEADTOHEAD
821     * @return the valid pairs
822     */
823    public HashMap<NamedBean, List<NamedBean>> discoverValidBeanPairs(LayoutEditor editor, Class<?> T, Routing pathMethod) {
824        LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class);
825
826        // ----- Begin Turntable Path Discovery -----
827        HashMap<NamedBean, List<NamedBean>> retPairs = new HashMap<>();
828        List<SignalMast> turntableMasts = new ArrayList<>();
829        if (T == SignalMast.class) {
830            for (LayoutEditor panel : InstanceManager.getDefault(EditorManager.class).getAll(LayoutEditor.class)) {
831                for (LayoutTurntable turntable : panel.getLayoutTurntables()) {
832                    if (!turntable.isDispatcherManaged()) {
833                        continue;
834                    }
835                    LayoutBlock turntableBlock = turntable.getLayoutBlock();
836
837                    // Add all turntable-related masts to a list for exclusion from general discovery
838                    if (turntable.getExitSignalMast() != null) turntableMasts.add(turntable.getExitSignalMast());
839                    if (turntable.getBufferMast() != null) turntableMasts.add(turntable.getBufferMast());
840
841                    // Path 1: From turntable's Exit Mast to the next mast on a ray's track
842                    SignalMast exitMast = turntable.getExitSignalMast();
843                    if (exitMast != null) {
844                        for (LayoutTurntable.RayTrack ray : turntable.getRayTrackList()) {
845                            TrackSegment track = ray.getConnect();
846                            if (track != null && track.getLayoutBlock() != null) {
847                                LayoutBlock rayBlock = track.getLayoutBlock();
848                                // Find the block connected to the ray that is NOT the turntable, then find the mast protecting it.
849                                for (int i = 0; i < rayBlock.getNumberOfNeighbours(); i++) {
850                                    Block neighbor = rayBlock.getNeighbourAtIndex(i);
851                                    if (neighbor != turntableBlock.getBlock()) {
852                                        SignalMast nextMast = lbm.getFacingSignalMast(rayBlock.getBlock(), neighbor, panel);
853                                        if (nextMast != null) {
854                                            retPairs.computeIfAbsent(exitMast, k -> new ArrayList<>()).add(nextMast);
855                                        }
856                                        break; // Assume only one exit from the ray block
857                                    }
858                                }
859                                // Also check for a buffer mast at the end of this ray's block
860                                if (rayBlock.getNumberOfNeighbours() == 1) { // Only connected to the turntable block
861                                    SignalMast bufferMast = lbm.getSignalMastAtEndBumper(rayBlock.getBlock(), panel);
862                                    if (bufferMast != null) {
863                                        if (log.isDebugEnabled()) {
864                                            log.debug("Found turntable exit to buffer mast path: {} -> {}", exitMast.getDisplayName(), bufferMast.getDisplayName());
865                                        }
866                                        retPairs.computeIfAbsent(exitMast, k -> new ArrayList<>()).add(bufferMast);
867                                    }
868                                }
869                            }
870                        }
871                    }
872
873
874                    // Path 2 & 3: Paths involving Approach and Buffer masts
875                    for (LayoutTurntable.RayTrack ray : turntable.getRayTrackList()) {
876                        SignalMast approachMast = ray.getApproachMast();
877                        if (approachMast == null) { // this is logged elsewhere
878                            continue;
879                        }
880                        turntableMasts.add(approachMast);
881
882                        // Path 2: From a remote mast on the layout to this ray's Approach Mast
883                        TrackSegment track = ray.getConnect();
884                        if (track != null && track.getLayoutBlock() != null && turntableBlock != null) {
885                            LayoutBlock rayBlock = track.getLayoutBlock();
886                            // Find the block connected to the ray that is NOT the turntable, then find the mast protecting the ray from it.
887                            for (int i = 0; i < rayBlock.getNumberOfNeighbours(); i++) {
888                                Block neighbor = rayBlock.getNeighbourAtIndex(i);
889                                if (neighbor != turntableBlock.getBlock()) {
890                                    SignalMast remoteMast = lbm.getFacingSignalMast(neighbor, rayBlock.getBlock(), panel);
891                                    if (remoteMast != null) {
892                                        retPairs.computeIfAbsent(remoteMast, k -> new ArrayList<>()).add(approachMast);
893                                    }
894                                    // Assume only one entry to the ray block
895                                    break;
896                                }
897                            }
898                        }
899
900                        // Path 3: From this ray's Approach Mast to the turntable's Buffer Mast (a siding path)
901                        SignalMast bufferMast = turntable.getBufferMast();
902                        if (bufferMast != null) {
903                            retPairs.computeIfAbsent(approachMast, k -> new ArrayList<>()).add(bufferMast);
904                        }
905                    }
906                }
907            }
908        }
909        // ----- End Turntable Path Discovery -----
910
911        // ----- Begin General Path Discovery (excluding turntable masts) -----
912        List<FacingProtecting> beanList = generateBlocksWithBeans(editor, T);
913        beanList.forEach((fp) -> {
914            // Skip any mast that has already been handled by the turntable-specific logic above
915            if (turntableMasts.contains(fp.getBean())) {
916                return; // continue to next fp in forEach
917            }
918            fp.getProtectingBlocks().stream().map((block) -> {
919                if (log.isDebugEnabled()) {
920                    try {
921                        log.debug("\nSource {}", fp.getBean().getDisplayName());
922                        log.debug("facing {}", fp.getFacing().getDisplayName());
923                        log.debug("protecting {}", block.getDisplayName());
924                    } catch (java.lang.NullPointerException e) {
925                        // Can be considered normal if the signalmast is assigned to an end bumper.
926                    }
927                }
928                return block;
929            }).forEachOrdered((block) -> {
930                LayoutBlock lFacing = lbm.getLayoutBlock(fp.getFacing());
931                LayoutBlock lProtecting = lbm.getLayoutBlock(block);
932                NamedBean source = fp.getBean();
933                try {
934                    retPairs.computeIfAbsent(source, k -> new ArrayList<>()).addAll(discoverPairDest(source, lProtecting, lFacing, beanList, pathMethod));
935                } catch (JmriException ex) {
936                    log.error("exception in retPairs.put", ex);
937                }
938            });
939        });
940        return retPairs;
941    }
942
943    /**
944     * Returns a list of valid destination beans reachable from a given source
945     * bean.
946     *
947     * @param source     Either a SignalMast or Sensor
948     * @param editor     The layout editor that the source is located on, if
949     *                   null, then all editors are considered
950     * @param T          The class of the remote destination, if null, then both
951     *                   SignalMasts and Sensors are considered
952     * @param pathMethod Determine whether or not we should reject pairs if
953     *                   there are other beans in the way. Constant values of
954     *                   NONE, ANY, MASTTOMAST, HEADTOHEAD
955     * @return A list of all reachable NamedBeans
956     * @throws jmri.JmriException occurring during nested readAll operation
957     */
958    public List<NamedBean> discoverPairDest(NamedBean source, LayoutEditor editor, Class<?> T, Routing pathMethod) throws JmriException {
959        if (log.isDebugEnabled()) {
960            log.debug("discover pairs from source {}", source.getDisplayName());
961        }
962
963        LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class);
964
965        // First, check if the source is a turntable mast. If so, handle it specially.
966        if (T == SignalMast.class) {
967            if (! (source instanceof SignalMast)) {
968                throw new IllegalArgumentException("source is not a SignalMast: " + (source != null ? source.getClass().getName() : "null"));
969            }
970            SignalMast sourceMast = (SignalMast) source;
971            for (LayoutEditor panel : InstanceManager.getDefault(EditorManager.class).getAll(LayoutEditor.class)) {
972                for (LayoutTurntable turntable : panel.getLayoutTurntables()) {
973                    if (!turntable.isDispatcherManaged()) {
974                        continue;
975                    }
976
977                    // Case 1: Source is the turntable's Exit Mast
978                    if (sourceMast.equals(turntable.getExitSignalMast())) {
979                        log.debug("Source is an exit mast for turntable {}", turntable.getName());
980                        List<NamedBean> destinations = new ArrayList<>();
981                        LayoutBlock turntableBlock = turntable.getLayoutBlock();
982                        for (LayoutTurntable.RayTrack ray : turntable.getRayTrackList()) {
983                            TrackSegment track = ray.getConnect();
984                            if (track != null && track.getLayoutBlock() != null) {
985                                LayoutBlock rayBlock = track.getLayoutBlock();
986                                for (int i = 0; i < rayBlock.getNumberOfNeighbours(); i++) {
987                                    Block neighbor = rayBlock.getNeighbourAtIndex(i);
988                                    if (neighbor != turntableBlock.getBlock()) {
989                                        SignalMast nextMast = lbm.getFacingSignalMast(rayBlock.getBlock(), neighbor, panel);
990                                        if (nextMast != null) destinations.add(nextMast);
991                                    }
992                                }
993                                if (rayBlock.getNumberOfNeighbours() == 1) { // End of line buffer
994                                    SignalMast bufferMast = lbm.getSignalMastAtEndBumper(rayBlock.getBlock(), panel);
995                                    if (bufferMast != null) destinations.add(bufferMast);
996                                }
997                            }
998                        }
999                        return destinations;
1000                    }
1001
1002                    // Case 2: Source is an Approach Mast for one of the rays
1003                    for (LayoutTurntable.RayTrack ray : turntable.getRayTrackList()) {
1004                        if (sourceMast.equals(ray.getApproachMast())) {
1005                            log.info("Source is an approach mast for turntable {}", turntable.getName());
1006                            List<NamedBean> destinations = new ArrayList<>();
1007                            if (turntable.getBufferMast() != null) destinations.add(turntable.getBufferMast());
1008                            return destinations;
1009                        }
1010                    }
1011                }
1012            }
1013        }
1014
1015        LayoutBlock lFacing = lbm.getFacingBlockByNamedBean(source, editor);
1016        List<LayoutBlock> lProtecting = lbm.getProtectingBlocksByNamedBean(source, editor);
1017        List<NamedBean> ret = new ArrayList<>();
1018        List<FacingProtecting> beanList = generateBlocksWithBeans(editor, T);
1019        
1020        // may throw JmriException here
1021        for (LayoutBlock lb : lProtecting) {
1022            ret.addAll(discoverPairDest(source, lb, lFacing, beanList, pathMethod));
1023        }
1024        return ret;
1025    }
1026
1027    List<NamedBean> discoverPairDest(NamedBean source, LayoutBlock lProtecting, LayoutBlock lFacing, List<FacingProtecting> blockList, Routing pathMethod) throws JmriException {
1028        LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class);
1029        if (!lbm.isAdvancedRoutingEnabled()) {
1030            throw new JmriException("advanced routing not enabled");
1031        }
1032        if (!lbm.routingStablised()) {
1033            throw new JmriException("routing not stabilised");
1034        }
1035        List<NamedBean> validDestBean = new ArrayList<>();
1036        for (FacingProtecting facingProtecting : blockList) {
1037            if (facingProtecting.getBean() != source) {
1038                NamedBean destObj = facingProtecting.getBean();
1039                if (log.isDebugEnabled()) {
1040                    log.debug("looking for pair {} {}", source.getDisplayName(), destObj.getDisplayName());
1041                }
1042                try {
1043                    if (checkValidDest(lFacing, lProtecting, facingProtecting, pathMethod)) {
1044                        if (log.isDebugEnabled()) {
1045                            log.debug("Valid pair {} {}", source.getDisplayName(), destObj.getDisplayName());
1046                        }
1047                        LayoutBlock ldstBlock = lbm.getLayoutBlock(facingProtecting.getFacing());
1048                        try {
1049                            List<LayoutBlock> lblks = getLayoutBlocks(lFacing, ldstBlock, lProtecting, true, pathMethod);
1050                            if (log.isDebugEnabled()) {
1051                                log.debug("Adding block {} to paths, current size {}", destObj.getDisplayName(), lblks.size());
1052                            }
1053                            validDestBean.add(destObj);
1054                        } catch (JmriException e) {  // Considered normal if route not found.
1055                            log.debug("not a valid route through {} - {}", source.getDisplayName(), destObj.getDisplayName());
1056                        }
1057                    }
1058                } catch (JmriException ex) {
1059                    log.debug("caught exception in discoverPairsDest", ex);
1060                }
1061            }
1062        }
1063        return validDestBean;
1064    }
1065
1066    List<FacingProtecting> generateBlocksWithBeans(LayoutEditor editor, Class<?> T) {
1067        LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class);
1068        List<FacingProtecting> beanList = new ArrayList<>();
1069
1070        for (LayoutBlock curLblk : lbm.getNamedBeanSet()) {
1071            Block curBlk = curLblk.getBlock();
1072            LayoutEditor useEdit = editor;
1073            if (editor == null) {
1074                useEdit = curLblk.getMaxConnectedPanel();
1075            }
1076            if (curBlk != null) {
1077                int noNeigh = curLblk.getNumberOfNeighbours();
1078                for (int x = 0; x < noNeigh; x++) {
1079                    Block blk = curLblk.getNeighbourAtIndex(x);
1080                    List<Block> proBlk = new ArrayList<>();
1081                    NamedBean bean = null;
1082                    if (T == null) {
1083                        proBlk.add(blk);
1084                        bean = lbm.getFacingNamedBean(curBlk, blk, useEdit);
1085                    } else if (T.equals(SignalMast.class)) {
1086                        bean = lbm.getFacingSignalMast(curBlk, blk, useEdit);
1087                        if (bean != null) {
1088                            if (log.isDebugEnabled()) {
1089                                log.debug("Get list of protecting blocks for {} facing {}", bean.getDisplayName(), curBlk.getDisplayName());
1090                            }
1091                            List<LayoutBlock> lProBlk = lbm.getProtectingBlocksByNamedBean(bean, useEdit);
1092                            for (LayoutBlock lb : lProBlk) {
1093                                if (lb != null) {
1094                                    proBlk.add(lb.getBlock());
1095                                }
1096                            }
1097                        }
1098                    } else if (T.equals(Sensor.class)) {
1099                        bean = lbm.getFacingSensor(curBlk, blk, useEdit);
1100                        if (bean != null) {
1101                            if (log.isDebugEnabled()) {
1102                                log.debug("Get list of protecting blocks for {}", bean.getDisplayName());
1103                            }
1104                            List<LayoutBlock> lProBlk = lbm.getProtectingBlocksByNamedBean(bean, useEdit);
1105                            for (LayoutBlock lb : lProBlk) {
1106                                if (lb != null) {
1107                                    proBlk.add(lb.getBlock());
1108                                }
1109                            }
1110                        }
1111                    } else {
1112                        log.error("Past bean type is unknown {}", T);
1113                    }
1114                    if (bean != null) {
1115                        FacingProtecting toadd = new FacingProtecting(curBlk, proBlk, bean);
1116                        boolean found = false;
1117                        for (FacingProtecting fp : beanList) {
1118                            if (fp.equals(toadd)) {
1119                                found = true;
1120                                break;
1121                            }
1122                        }
1123                        if (!found) {
1124                            beanList.add(toadd);
1125                        }
1126                    }
1127                }
1128                if (noNeigh == 1) {
1129                    NamedBean bean = null;
1130                    if (log.isDebugEnabled()) {
1131                        log.debug("We have a dead end {}", curBlk.getDisplayName());
1132                    }
1133                    if (T == null) {
1134                        bean = lbm.getNamedBeanAtEndBumper(curBlk, useEdit);
1135                    } else if (T.equals(SignalMast.class)) {
1136                        bean = lbm.getSignalMastAtEndBumper(curBlk, useEdit);
1137                    } else if (T.equals(Sensor.class)) {
1138                        bean = lbm.getSensorAtEndBumper(curBlk, useEdit);
1139                    } else {
1140                        log.error("Past bean type is unknown {}", T);
1141                    }
1142                    if (bean != null) {
1143                        FacingProtecting toadd = new FacingProtecting(curBlk, null, bean);
1144                        boolean found = false;
1145                        for (FacingProtecting fp : beanList) {
1146                            if (fp.equals(toadd)) {
1147                                found = true;
1148                                break;
1149                            }
1150                        }
1151                        if (!found) {
1152                            beanList.add(toadd);
1153                        }
1154                    }
1155                }
1156            }
1157        }
1158        return beanList;
1159    }
1160
1161    static class FacingProtecting {
1162
1163        Block facing;
1164        List<Block> protectingBlocks;
1165        NamedBean bean;
1166
1167        FacingProtecting(Block facing, List<Block> protecting, NamedBean bean) {
1168            this.facing = facing;
1169            if (protecting == null) {
1170                this.protectingBlocks = new ArrayList<>(0);
1171            } else {
1172                this.protectingBlocks = protecting;
1173            }
1174            this.bean = bean;
1175        }
1176
1177        Block getFacing() {
1178            return facing;
1179        }
1180
1181        List<Block> getProtectingBlocks() {
1182            return protectingBlocks;
1183        }
1184
1185        NamedBean getBean() {
1186            return bean;
1187        }
1188
1189        @Override
1190        public boolean equals(Object obj) {
1191
1192            if (obj == this) {
1193                return true;
1194            }
1195            if (obj == null) {
1196                return false;
1197            }
1198
1199            if (!(getClass() == obj.getClass())) {
1200                return false;
1201            } else {
1202                FacingProtecting tmp = (FacingProtecting) obj;
1203                if (tmp.getBean() != this.bean) {
1204                    return false;
1205                }
1206                if (tmp.getFacing() != this.facing) {
1207                    return false;
1208                }
1209                if (!tmp.getProtectingBlocks().equals(this.protectingBlocks)) {
1210                    return false;
1211                }
1212            }
1213            return true;
1214        }
1215
1216        @Override
1217        public int hashCode() {
1218            int hash = 7;
1219            hash = 37 * hash + (this.bean != null ? this.bean.hashCode() : 0);
1220            hash = 37 * hash + (this.facing != null ? this.facing.hashCode() : 0);
1221            hash = 37 * hash + (this.protectingBlocks != null ? this.protectingBlocks.hashCode() : 0);
1222            return hash;
1223        }
1224    }
1225
1226    private final static Logger log
1227            = LoggerFactory.getLogger(LayoutBlockConnectivityTools.class);
1228}