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("check valid des 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                    throw e;
118                }
119            } else {
120                log.debug("blocks not found");
121            }
122        }
123        if (log.isDebugEnabled()) {
124            log.debug("No valid route from {} to {}", sourceBean.getDisplayName(), destBean.getDisplayName());
125        }
126        throw new jmri.JmriException("Blocks Not Found");
127    }
128
129    /**
130     * The is used in conjunction with the layout block routing protocol, to
131     * discover a clear path from a source layout block through to a destination
132     * layout block. By specifying the sourceLayoutBlock and
133     * protectingLayoutBlock or sourceLayoutBlock+1, a direction of travel can
134     * then be determined, eg east to west, south to north etc.
135     * @param sourceBean    The source bean (SignalHead, SignalMast or Sensor)
136     *                     assigned to a block boundary that we are starting
137     *                     from.
138     * @param destBean      The destination bean.
139     * @param validateOnly  When set false, the system will not use layout
140     *                     blocks that are set as either reserved(useExtraColor
141     *                     set) or occupied, if it finds any then it will try to
142     *                     find an alternative path When set false, no block
143     *                     state checking is performed.
144     * @param pathMethod    Performs a check to see if any signal heads/masts
145     *                     are in the path, if there are then the system will
146     *                     try to find an alternative path. If set to NONE, then
147     *                     no checking is performed.
148     * @return an List of all the layoutblocks in the path.
149     * @throws jmri.JmriException if it can not find a valid path or the routing
150     *                            has not been enabled.
151     */
152    public List<LayoutBlock> getLayoutBlocks(NamedBean sourceBean, NamedBean destBean, boolean validateOnly, Routing pathMethod) throws jmri.JmriException {
153        Set<LayoutEditor> layout = InstanceManager.getDefault(EditorManager.class).getAll(LayoutEditor.class);
154        LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class);
155        LayoutBlock facingBlock = null;
156        LayoutBlock protectingBlock = null;
157        LayoutBlock destFacingBlock = null;
158        for (LayoutEditor layoutEditor : layout) {
159            if (log.isDebugEnabled()) {
160                log.debug("Layout name {}", layoutEditor.getLayoutName());
161            }
162            if (facingBlock == null) {
163                facingBlock = lbm.getFacingBlockByNamedBean(sourceBean, layoutEditor);
164            }
165            if (protectingBlock == null) {
166                protectingBlock = lbm.getProtectedBlockByNamedBean(sourceBean, layoutEditor);
167            }
168            if (destFacingBlock == null) {
169                destFacingBlock = lbm.getFacingBlockByNamedBean(destBean, layoutEditor);
170            }
171            if ((destFacingBlock != null) && (facingBlock != null) && (protectingBlock != null)) {
172                try {
173                    return getLayoutBlocks(facingBlock, destFacingBlock, protectingBlock, validateOnly, pathMethod);
174                } catch (JmriException e) {
175                    throw e;
176                }
177            } else {
178                log.debug("blocks not found");
179            }
180        }
181        if (log.isDebugEnabled()) {
182            log.debug("No valid route from {} to {}", sourceBean.getDisplayName(), destBean.getDisplayName());
183        }
184        throw new jmri.JmriException("Blocks Not Found");
185    }
186
187    /**
188     * Returns a list of NamedBeans (Signalhead, Signalmast or Sensor) that are
189     * assigned to block boundaries in a given list.
190     *
191     * @param blocklist The list of block in order that need to be checked.
192     * @param panel     (Optional) panel that the blocks need to be checked
193     *                  against
194     * @param T         (Optional) the class that we want to check against,
195     *                  either Sensor, SignalMast or SignalHead, set null will
196     *                  return any.
197     * @return the list of NamedBeans
198     */
199    public List<NamedBean> getBeansInPath(List<LayoutBlock> blocklist, LayoutEditor panel, Class<?> T) {
200        List<NamedBean> beansInPath = new ArrayList<>();
201        if (blocklist.size() >= 2) {
202            LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class);
203            for (int x = 1; x < blocklist.size(); x++) {
204                LayoutBlock facingBlock = blocklist.get(x - 1);
205                LayoutBlock protectingBlock = blocklist.get(x);
206                NamedBean nb = null;
207                if (T == null) {
208                    nb = lbm.getFacingNamedBean(facingBlock.getBlock(), protectingBlock.getBlock(), panel);
209                } else if (T.equals(jmri.SignalMast.class)) {
210                    nb = lbm.getFacingSignalMast(facingBlock.getBlock(), protectingBlock.getBlock(), panel);
211                } else if (T.equals(jmri.Sensor.class)) {
212                    nb = lbm.getFacingSensor(facingBlock.getBlock(), protectingBlock.getBlock(), panel);
213                } else if (T.equals(jmri.SignalHead.class)) {
214                    nb = lbm.getFacingSignalHead(facingBlock.getBlock(), protectingBlock.getBlock());
215                }
216                if (nb != null) {
217                    beansInPath.add(nb);
218                }
219            }
220        }
221        return beansInPath;
222    }
223
224    /**
225     * Determines if one set of blocks is reachable from another set of blocks
226     * based upon the directions of the set of blocks.
227     * <ul>
228     * <li>Called by {@link jmri.implementation.DefaultSignalMastLogic} using MASTTOMAST.</li>
229     * <li>Called by {@link jmri.jmrit.entryexit.DestinationPoints} using SENSORTOSENSOR.</li>
230     * <li>Called by {@link jmri.jmrit.entryexit.EntryExitPairs} using SENSORTOSENSOR.</li>
231     * </ul>
232     * Convert the destination protected block to an array list.
233     * Call the 3 block+list version of checkValidDest() to finish the checks.
234     * @param currentBlock The facing layout block for the source signal or sensor.
235     * @param nextBlock    The protected layout block for the source signal or sensor.
236     * @param destBlock    The facing layout block for the destination signal mast or sensor.
237     * @param destProBlock The protected destination block.
238     * @param pathMethod   Indicates the type of path:  Signal head, signal mast or sensor.
239     * @return true if a path to the destination is valid.
240     * @throws jmri.JmriException if any Block is null;
241     */
242    public boolean checkValidDest(LayoutBlock currentBlock, LayoutBlock nextBlock, LayoutBlock destBlock, LayoutBlock destProBlock, Routing pathMethod) throws jmri.JmriException {
243
244        List<LayoutBlock> destList = new ArrayList<>();
245        if (destProBlock != null) {
246            destList.add(destProBlock);
247        }
248        try {
249            return checkValidDest(currentBlock, nextBlock, destBlock, destList, pathMethod);
250        } catch (jmri.JmriException e) {
251            throw e;
252        }
253
254    }
255
256    /**
257     * Determines if one set of blocks is reachable from another set of blocks
258     * based upon the directions of the set of blocks.
259     * <p>
260     * This is used to help with identifying items such as signalmasts located
261     * at positionable points or turnouts are facing in the same direction as
262     * other given signalmasts.
263     * <p>
264     * Given the current block and the next block we can work out the direction
265     * of travel. Given the destBlock and the next block on, we can determine
266     * the whether the destBlock comes before the destBlock+1.
267     * <p>
268     * Note: This version is internally called by other versions that
269     * pre-process external calls.
270     * @param currentBlock The facing layout block for the source signal or
271     *                     sensor.
272     * @param nextBlock    The protected layout block for the source signal or
273     *                     sensor.
274     * @param destBlock    The facing layout block for the destination signal
275     *                     mast or sensor.
276     * @param destBlockn1  A list of protected destination blocks. Can be empty
277     *                     if the destination is at an end bumper.
278     * @param pathMethod   Indicates the type of path: Signal head, signal mast
279     *                     or sensor.
280     * @return true if a path to the destination is valid.
281     * @throws jmri.JmriException if any layout block is null or advanced
282     *                            routing is not enabled.
283     */
284    public boolean checkValidDest(LayoutBlock currentBlock, LayoutBlock nextBlock, LayoutBlock destBlock, List<LayoutBlock> destBlockn1, Routing pathMethod) throws jmri.JmriException {
285        LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class);
286        if (!lbm.isAdvancedRoutingEnabled()) {
287            log.debug("Advanced routing has not been enabled therefore we cannot use this function");
288            throw new jmri.JmriException("Advanced routing has not been enabled therefore we cannot use this function");
289        }
290
291        if (log.isDebugEnabled()) {
292            try {
293                log.debug("faci {}", currentBlock.getDisplayName());
294                log.debug("next {}", nextBlock.getDisplayName());
295                log.debug("dest {}", destBlock.getDisplayName());
296                for (LayoutBlock dp : destBlockn1) {
297                    log.debug("dest + 1 {}", dp.getDisplayName());
298                }
299            } catch (java.lang.NullPointerException e) {
300
301            }
302        }
303        if ((destBlock != null) && (currentBlock != null) && (nextBlock != null)) {
304            if (!currentBlock.isRouteToDestValid(nextBlock.getBlock(), destBlock.getBlock())) {
305                log.debug("Route to dest not valid");
306                return false;
307            }
308            if (log.isDebugEnabled()) {
309                log.debug("dest {}", destBlock.getDisplayName());
310                /*if(destBlockn1!=null)
311                 log.debug("remote prot " + destBlockn1.getDisplayName());*/
312            }
313            if (!destBlockn1.isEmpty() && currentBlock == destBlockn1.get(0) && nextBlock == destBlock) {
314                log.debug("Our dest protecting block is our current block and our protecting block is the same as our destination block");
315                return false;
316            }
317            // Do a simple test to see if one is reachable from the other.
318            int proCount = 0;
319            int desCount = 0;
320            if (!destBlockn1.isEmpty()) {
321                desCount = currentBlock.getBlockHopCount(destBlock.getBlock(), nextBlock.getBlock());
322                proCount = currentBlock.getBlockHopCount(destBlockn1.get(0).getBlock(), nextBlock.getBlock());
323                if (log.isDebugEnabled()) {
324                    log.debug("dest {} protecting {}", desCount, proCount);
325                }
326            }
327
328            if ((proCount == -1) && (desCount == -1)) {
329                // The destination block and destBlock+1 are both directly connected
330                log.debug("Dest and dest+1 are directly connected");
331                return false;
332            }
333
334            if (proCount > desCount && (proCount - 1) == desCount) {
335                // The block that we are protecting should be one hop greater than the destination count.
336                log.debug("Protecting is one hop away from destination and therefore valid.");
337                return true;
338            }
339
340            /*Need to do a more advanced check in this case as the destBlockn1
341             could be reached via a different route and therefore have a smaller
342             hop count we need to therefore step through each block until we reach
343             the end.
344             The advanced check also covers cases where the route table is inconsistent.
345             We also need to perform a more advanced check if the destBlockn1
346             is null as this indicates that the destination signal mast is assigned
347             on an end bumper*/
348
349            if (pathMethod == Routing.SENSORTOSENSOR && destBlockn1.size() == 0) {
350                // Change the pathMethod to accept the NX sensor at the end bumper.
351                pathMethod = Routing.NONE;
352            }
353
354            List<LayoutBlock> blockList = getLayoutBlocks(currentBlock, destBlock, nextBlock, true, pathMethod); // Was MASTTOMAST
355            if (log.isDebugEnabled()) {
356                log.debug("checkValidDest blockList for {}", destBlock.getDisplayName());
357                blockList.forEach(blk -> log.debug("  block = {}", blk.getDisplayName()));
358            }
359            for (LayoutBlock dp : destBlockn1) {
360                log.debug("dp = {}", dp.getDisplayName());
361                if (blockList.contains(dp) && currentBlock != dp) {
362                    log.debug("Signal mast in the wrong direction");
363                    return false;
364                }
365            }
366                /*Work on the basis that if you get the blocks from source to dest
367                 then the dest+1 block should not be included*/
368            log.debug("Signal mast in the correct direction");
369            return true;
370
371        } else if (destBlock == null) {
372            throw new jmri.JmriException("Block in Destination Field returns as invalid");
373        } else if (currentBlock == null) {
374            throw new jmri.JmriException("Block in Facing Field returns as invalid");
375        } else if (nextBlock == null) {
376            throw new jmri.JmriException("Block in Protecting Field returns as invalid");
377        }
378        throw new jmri.JmriException("BlockIsNull");
379    }
380
381    /**
382     * This uses the layout editor to check if the destination location is
383     * reachable from the source location.<br>
384     * Only used internally to the class.
385     * <p>
386     * @param facing     Layout Block that is considered our first block
387     * @param protecting Layout Block that is considered first block +1
388     * @param dest       Layout Block that we want to get to
389     * @param pathMethod the path method
390     * @return true if valid
391     * @throws JmriException during nested getProtectingBlocks operation
392     */
393    private boolean checkValidDest(LayoutBlock facing, LayoutBlock protecting, FacingProtecting dest, Routing pathMethod) throws JmriException {
394        if (facing == null || protecting == null || dest == null) {
395            return false;
396        }
397        if (log.isDebugEnabled()) {
398            log.debug("facing : {} protecting : {} dest {}", protecting.getDisplayName(), dest.getBean().getDisplayName(), facing.getDisplayName());
399        }
400
401        // In this instance it doesn't matter what the destination protecting block is so we get the first
402        /*LayoutBlock destProt = null;
403         if(!dest.getProtectingBlocks().isEmpty()){
404         destProt = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlock(dest.getProtectingBlocks().get(0));
405         // log.info(dest.getProtectingBlocks());
406         }*/
407         
408        List<LayoutBlock> destList = new ArrayList<>();
409
410         // may throw JmriException here
411        dest.getProtectingBlocks().forEach((b) -> {
412            destList.add(InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlock(b));
413        });
414        return checkValidDest(facing, protecting, InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlock(dest.getFacing()), destList, pathMethod);
415    }
416
417    /**
418     * This used in conjunction with the layout block routing protocol, to
419     * discover a clear path from a source layout block through to a destination
420     * layout block. By specifying the sourceLayoutBlock and
421     * protectingLayoutBlock or sourceLayoutBlock+1, a direction of travel can
422     * then be determined, eg east to west, south to north etc.
423     *
424     * @param sourceLayoutBlock      The layout block that we are starting from,
425     *                               can also be considered as the block facing
426     *                               a signal.
427     * @param destinationLayoutBlock The layout block that we want to get to
428     * @param protectingLayoutBlock  The next layout block connected to the
429     *                               source block, this can also be considered
430     *                               as the block being protected by a signal
431     * @param validateOnly           When set false, the system will not use
432     *                               layout blocks that are set as either
433     *                               reserved(useExtraColor set) or occupied, if
434     *                               it finds any then it will try to find an
435     *                               alternative path When set true, no block
436     *                               state checking is performed.
437     * @param pathMethod             Performs a check to see if any signal
438     *                               heads/masts are in the path, if there are
439     *                               then the system will try to find an
440     *                               alternative path. If set to NONE, then no
441     *                               checking is performed.
442     * @return an List of all the layoutblocks in the path.
443     * @throws jmri.JmriException if it can not find a valid path or the routing
444     *                            has not been enabled.
445     */
446    public List<LayoutBlock> getLayoutBlocks(LayoutBlock sourceLayoutBlock, LayoutBlock destinationLayoutBlock, LayoutBlock protectingLayoutBlock, boolean validateOnly, Routing pathMethod) throws jmri.JmriException {
447        lastErrorMessage = "Unknown Error Occured";
448        LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class);
449        
450        if (!lbm.isAdvancedRoutingEnabled()) {
451            log.debug("Advanced routing has not been enabled therefore we cannot use this function");
452            throw new jmri.JmriException("Advanced routing has not been enabled therefore we cannot use this function");
453        }
454
455        int directionOfTravel = sourceLayoutBlock.getNeighbourDirection(protectingLayoutBlock);
456        Block currentBlock = sourceLayoutBlock.getBlock();
457
458        Block destBlock = destinationLayoutBlock.getBlock();
459        log.debug("Destination Block {} {}", destinationLayoutBlock.getDisplayName(), destBlock);
460
461        Block nextBlock = protectingLayoutBlock.getBlock();
462        if (log.isDebugEnabled()) {
463            log.debug("s:{} p:{} d:{}", sourceLayoutBlock.getDisplayName(), protectingLayoutBlock.getDisplayName(), destinationLayoutBlock.getDisplayName());
464        }
465        List<BlocksTested> blocksInRoute = new ArrayList<>();
466        blocksInRoute.add(new BlocksTested(sourceLayoutBlock));
467
468        if (!validateOnly) {
469            if (canLBlockBeUsed(protectingLayoutBlock)) {
470                blocksInRoute.add(new BlocksTested(protectingLayoutBlock));
471            } else {
472                lastErrorMessage = "Block we are protecting is already occupied or reserved";
473                log.debug("will throw {}", lastErrorMessage);
474                throw new jmri.JmriException(lastErrorMessage);
475            }
476            if (!canLBlockBeUsed(destinationLayoutBlock)) {
477                lastErrorMessage = "Destination Block is already occupied or reserved";
478                log.debug("will throw {}", lastErrorMessage);
479                throw new jmri.JmriException(lastErrorMessage);
480            }
481        } else {
482            blocksInRoute.add(new BlocksTested(protectingLayoutBlock));
483        }
484        if (destinationLayoutBlock == protectingLayoutBlock) {
485            List<LayoutBlock> returnBlocks = new ArrayList<>();
486            blocksInRoute.forEach((blocksTested) -> {
487                returnBlocks.add(blocksTested.getBlock());
488            });
489            return returnBlocks;
490        }
491
492        BlocksTested bt = blocksInRoute.get(blocksInRoute.size() - 1);
493
494        int ttl = 1;
495        List<Integer> offSet = new ArrayList<>();
496        while (ttl < ttlSize) { // value should be higher but low for test!
497            log.debug("===== Ttl value = {} ======", ttl);
498            log.debug("Looking for next block");
499            int nextBlockIndex = findBestHop(currentBlock, nextBlock, destBlock, directionOfTravel, offSet, validateOnly, pathMethod);
500            if (nextBlockIndex != -1) {
501                bt.addIndex(nextBlockIndex);
502                if (log.isDebugEnabled()) {
503                    log.debug("block index returned {} Blocks in route size {}", nextBlockIndex, blocksInRoute.size());
504                }
505                // Sets the old next block to be our current block.
506                LayoutBlock currentLBlock = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlock(nextBlock);
507                if (currentLBlock == null) {
508                    log.error("Unable to get block :{}: from instancemanager", nextBlock);
509                    continue;
510                }
511                offSet.clear();
512
513                directionOfTravel = currentLBlock.getRouteDirectionAtIndex(nextBlockIndex);
514
515                //allow for routes that contain more than one occurrence of a block in a route to allow change of direction.
516                Block prevBlock = currentBlock;
517                LayoutBlock prevLBlock = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlock(prevBlock);
518                currentBlock = nextBlock;
519                nextBlock = currentLBlock.getRouteNextBlockAtIndex(nextBlockIndex);
520                LayoutBlock nextLBlock = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlock(nextBlock);
521                if (log.isDebugEnabled()) {
522                    log.debug("Blocks in route size {}", blocksInRoute.size());
523                    log.debug("next: {} dest: {}", nextBlock.getDisplayName(), destBlock.getDisplayName());
524                }
525                if (nextBlock == currentBlock) {
526                    nextBlock = currentLBlock.getRouteDestBlockAtIndex(nextBlockIndex);
527                    log.debug("the next block to our destination we are looking for is directly connected to this one");
528                } else if (!((protectingLayoutBlock == prevLBlock)&&(protectingLayoutBlock == nextLBlock))) {
529                    if (nextLBlock != null) {
530                        log.debug("Add block {}", nextLBlock.getDisplayName());
531                    }
532                    bt = new BlocksTested(nextLBlock);
533                    blocksInRoute.add(bt);
534                }
535                if (nextBlock == destBlock) {
536                    if (!validateOnly && !checkForLevelCrossing(destinationLayoutBlock)) {
537                        throw new jmri.JmriException("Destination block is in conflict on a crossover");
538                    }
539                    List<LayoutBlock> returnBlocks = new ArrayList<>();
540                    blocksInRoute.forEach((blocksTested) -> {
541                        returnBlocks.add(blocksTested.getBlock());
542                    });
543                    returnBlocks.add(destinationLayoutBlock);
544                    if (log.isDebugEnabled()) {
545                        log.debug("Adding destination Block {}", destinationLayoutBlock.getDisplayName());
546                        log.debug("arrived at destination block");
547                        log.debug("{} Return as Long", sourceLayoutBlock.getDisplayName());
548                        returnBlocks.forEach((returnBlock) -> {
549                            log.debug("  return block {}", returnBlock.getDisplayName());
550                        });
551                        log.debug("Finished List");
552                    }
553                    return returnBlocks;
554                }
555            } else {
556                //-1 is returned when there are no more valid besthop valids found
557                // Block index is -1, so we need to go back a block and find another way.
558
559                // So we have gone back as far as our starting block so we better return.
560                int birSize = blocksInRoute.size();
561                log.debug("block in route size {}", birSize);
562                if (birSize <= 2) {
563                    log.debug("drop out here with ttl");
564                    ttl = ttlSize + 1;
565                } else {
566                    if (log.isDebugEnabled()) {
567                        for (int t = 0; t < blocksInRoute.size(); t++) {
568                            log.debug("index {} block {}", t, blocksInRoute.get(t).getBlock().getDisplayName());
569                        }
570                        log.debug("To remove last block {}", blocksInRoute.get(birSize - 1).getBlock().getDisplayName());
571                    }
572
573                    currentBlock = blocksInRoute.get(birSize - 3).getBlock().getBlock();
574                    nextBlock = blocksInRoute.get(birSize - 2).getBlock().getBlock();
575                    offSet = blocksInRoute.get(birSize - 2).getTestedIndexes();
576                    bt = blocksInRoute.get(birSize - 2);
577                    blocksInRoute.remove(birSize - 1);
578                    ttl--;
579                }
580            }
581            ttl++;
582        }
583        if (ttl == ttlSize) {
584            lastErrorMessage = "ttlExpired";
585        }
586        // we exited the loop without either finding the destination or we had error.
587        throw new jmri.JmriException(lastErrorMessage);
588    }
589
590    static class BlocksTested {
591
592        LayoutBlock block;
593        List<Integer> indexNumber = new ArrayList<>();
594
595        BlocksTested(LayoutBlock block) {
596            this.block = block;
597        }
598
599        void addIndex(int x) {
600            indexNumber.add(x);
601        }
602
603        int getLastIndex() {
604            return indexNumber.get(indexNumber.size() - 1); // get the last one in the list
605        }
606
607        List<Integer> getTestedIndexes() {
608            return indexNumber;
609        }
610
611        LayoutBlock getBlock() {
612            return block;
613        }
614    }
615
616    private boolean canLBlockBeUsed(LayoutBlock lBlock) {
617        if (lBlock == null) {
618            return true;
619        }
620        if (lBlock.getUseExtraColor()) {
621            return false;
622        }
623        if (lBlock.getBlock().getPermissiveWorking()) {
624            return true;
625        }
626        return (lBlock.getState() != Block.OCCUPIED);
627    }
628
629    String lastErrorMessage = "Unknown Error Occured";
630
631    // We need to take into account if the returned block has a signalmast attached.
632    int findBestHop(final Block preBlock, final Block currentBlock, Block destBlock, int direction, List<Integer> offSet, boolean validateOnly, Routing pathMethod) {
633        int result = 0;
634
635        LayoutBlock currentLBlock = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlock(currentBlock);
636        if (currentLBlock == null) {
637            return -1;
638        }
639        List<Integer> blkIndexTested = new ArrayList<>(5);
640        if (log.isDebugEnabled()) {
641            log.debug("In find best hop current {} previous {}", currentLBlock.getDisplayName(), preBlock.getDisplayName());
642        }
643        Block block;
644        while (result != -1) {
645            if (currentBlock == preBlock) {
646                // Basically looking for the connected block, which there should only be one of!
647                log.debug("At get ConnectedBlockRoute");
648                result = currentLBlock.getConnectedBlockRouteIndex(destBlock, direction);
649            } else {
650                if (log.isDebugEnabled()) {
651                    log.debug("Off Set {}", offSet);
652                }
653                result = currentLBlock.getNextBestBlock(preBlock, destBlock, offSet, Metric.METRIC);
654            }
655            if (result != -1) {
656                block = currentLBlock.getRouteNextBlockAtIndex(result);
657                LayoutBlock lBlock = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlock(block);
658
659                Block blocktoCheck = block;
660                if (block == currentBlock) {
661                    log.debug("current block matches returned block therefore the next block is directly connected");
662                    blocktoCheck = destBlock;
663                }
664
665                if ((block == currentBlock) && (currentLBlock.getThroughPathIndex(preBlock, destBlock) == -1)) {
666                    lastErrorMessage = "block " + block.getDisplayName() + " is directly attached, however the route to the destination block " + destBlock.getDisplayName() + " can not be directly used";
667                    log.debug("continue after {}", lastErrorMessage);
668                } else if ((validateOnly) || ((checkForDoubleCrossover(preBlock, currentLBlock, blocktoCheck) && checkForLevelCrossing(currentLBlock)) && canLBlockBeUsed(lBlock))) {
669                    if (log.isDebugEnabled()) {
670                        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());
671                        log.debug("  current {} {}", currentBlock.getDisplayName(), block.getDisplayName());
672                    }
673
674                    jmri.NamedBean foundBean = null;
675                    /* We change the logging level to fatal in the layout block manager as we are testing to make sure that no signalhead/mast exists
676                     this would generate an error message that is expected.*/
677                    MDC.put("loggingDisabled", LayoutBlockManager.class.getName());
678                    switch (pathMethod) {
679                        case MASTTOMAST:
680                            foundBean = InstanceManager.getDefault(LayoutBlockManager.class).getFacingSignalMast(currentBlock, blocktoCheck);
681                            break;
682                        case HEADTOHEAD:
683                            foundBean = InstanceManager.getDefault(LayoutBlockManager.class).getFacingSignalHead(currentBlock, blocktoCheck);
684                            break;
685                        case SENSORTOSENSOR:
686                            foundBean = InstanceManager.getDefault(LayoutBlockManager.class).getFacingSensor(currentBlock, blocktoCheck, null);
687                            break;
688                        case NONE:
689                            break;
690                        default:
691                            foundBean = InstanceManager.getDefault(LayoutBlockManager.class).getFacingNamedBean(currentBlock, blocktoCheck, null);
692                            break;
693                    }
694                    MDC.remove("loggingDisabled");
695                    if (foundBean == null) {
696                        log.debug("No object found so okay to return");
697                        return result;
698                    } else {
699                        lastErrorMessage = "Signal " + foundBean.getDisplayName() + " already exists between blocks " + currentBlock.getDisplayName() + " and " + blocktoCheck.getDisplayName() + " in the same direction on this path";
700                        log.debug("continue after {}", lastErrorMessage);
701                    }
702                } else {
703                    lastErrorMessage = "block " + block.getDisplayName() + " found not to be not usable";
704                    log.debug("continue after {}", lastErrorMessage);
705                }
706                if (blkIndexTested.contains(result)) {
707                    lastErrorMessage = ("No valid free path found");
708                    return -1;
709                }
710                blkIndexTested.add(result);
711                offSet.add(result);
712            } else {
713                log.debug("At this point the getNextBextBlock() has returned a -1");
714            }
715        }
716        return -1;
717    }
718
719    private boolean checkForDoubleCrossover(Block prevBlock, LayoutBlock curBlock, Block nextBlock) {
720        LayoutEditor le = curBlock.getMaxConnectedPanel();
721        ConnectivityUtil ct = le.getConnectivityUtil();
722        List<LayoutTrackExpectedState<LayoutTurnout>> turnoutList = ct.getTurnoutList(curBlock.getBlock(), prevBlock, nextBlock);
723        for (LayoutTrackExpectedState<LayoutTurnout> layoutTurnoutLayoutTrackExpectedState : turnoutList) {
724            LayoutTurnout lt = layoutTurnoutLayoutTrackExpectedState.getObject();
725            if (lt.getTurnoutType() == LayoutTurnout.TurnoutType.DOUBLE_XOVER) {
726                if (layoutTurnoutLayoutTrackExpectedState.getExpectedState() == jmri.Turnout.THROWN) {
727                    jmri.Turnout t = lt.getTurnout();
728                    if (t.getKnownState() == jmri.Turnout.THROWN) {
729                        if (lt.getLayoutBlock() == curBlock || lt.getLayoutBlockC() == curBlock) {
730                            if (!canLBlockBeUsed(lt.getLayoutBlockB()) && !canLBlockBeUsed(lt.getLayoutBlockD())) {
731                                return false;
732                            }
733                        } else if (lt.getLayoutBlockB() == curBlock || lt.getLayoutBlockD() == curBlock) {
734                            if (!canLBlockBeUsed(lt.getLayoutBlock()) && !canLBlockBeUsed(lt.getLayoutBlockC())) {
735                                return false;
736                            }
737                        }
738                    }
739                }
740            }
741        }
742        return true;
743    }
744
745    private boolean checkForLevelCrossing(LayoutBlock curBlock) {
746        LayoutEditor lay = curBlock.getMaxConnectedPanel();
747        for (LevelXing lx : lay.getLevelXings()) {
748            if (lx.getLayoutBlockAC() == curBlock
749                    || lx.getLayoutBlockBD() == curBlock) {
750                if ((lx.getLayoutBlockAC() != null)
751                        && (lx.getLayoutBlockBD() != null)
752                        && (lx.getLayoutBlockAC() != lx.getLayoutBlockBD())) {
753                    if (lx.getLayoutBlockAC() == curBlock) {
754                        if (!canLBlockBeUsed(lx.getLayoutBlockBD())) {
755                            return false;
756                        }
757                    } else if (lx.getLayoutBlockBD() == curBlock) {
758                        if (!canLBlockBeUsed(lx.getLayoutBlockAC())) {
759                            return false;
760                        }
761                    }
762                }
763            }
764        }
765        return true;
766    }
767
768    /**
769     * Discovers valid pairs of beans type T assigned to a layout editor. If no
770     * bean type is provided, then either SignalMasts or Sensors are discovered
771     * If no editor is provided, then all editors are considered
772     *
773     * @param editor     the layout editor panel
774     * @param T          the type
775     * @param pathMethod Determine whether or not we should reject pairs if
776     *                   there are other beans in the way. Constant values of
777     *                   NONE, ANY, MASTTOMAST, HEADTOHEAD
778     * @return the valid pairs
779     */
780    public HashMap<NamedBean, List<NamedBean>> discoverValidBeanPairs(LayoutEditor editor, Class<?> T, Routing pathMethod) {
781        LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class);
782        HashMap<NamedBean, List<NamedBean>> retPairs = new HashMap<>();
783        List<FacingProtecting> beanList = generateBlocksWithBeans(editor, T);
784        beanList.forEach((fp) -> {
785            fp.getProtectingBlocks().stream().map((block) -> {
786                if (log.isDebugEnabled()) {
787                    try {
788                        log.debug("\nSource {}", fp.getBean().getDisplayName());
789                        log.debug("facing {}", fp.getFacing().getDisplayName());
790                        log.debug("protecting {}", block.getDisplayName());
791                    } catch (java.lang.NullPointerException e) {
792                        // Can be considered normal if the signalmast is assigned to an end bumper.
793                    }
794                }
795                return block;
796            }).forEachOrdered((block) -> {
797                LayoutBlock lFacing = lbm.getLayoutBlock(fp.getFacing());
798                LayoutBlock lProtecting = lbm.getLayoutBlock(block);
799                NamedBean source = fp.getBean();
800                try {
801                    retPairs.put(source, discoverPairDest(source, lProtecting, lFacing, beanList, pathMethod));
802                } catch (JmriException ex) {
803                    log.error("exception in retPairs.put", ex);
804                }
805            });
806        });
807        return retPairs;
808    }
809
810    /**
811     * Returns a list of valid destination beans reachable from a given source
812     * bean.
813     *
814     * @param source     Either a SignalMast or Sensor
815     * @param editor     The layout editor that the source is located on, if
816     *                   null, then all editors are considered
817     * @param T          The class of the remote destination, if null, then both
818     *                   SignalMasts and Sensors are considered
819     * @param pathMethod Determine whether or not we should reject pairs if
820     *                   there are other beans in the way. Constant values of
821     *                   NONE, ANY, MASTTOMAST, HEADTOHEAD
822     * @return A list of all reachable NamedBeans
823     * @throws jmri.JmriException occurring during nested readAll operation
824     */
825    public List<NamedBean> discoverPairDest(NamedBean source, LayoutEditor editor, Class<?> T, Routing pathMethod) throws JmriException {
826        if (log.isDebugEnabled()) {
827            log.debug("discover pairs from source {}", source.getDisplayName());
828        }
829        LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class);
830        LayoutBlock lFacing = lbm.getFacingBlockByNamedBean(source, editor);
831        List<LayoutBlock> lProtecting = lbm.getProtectingBlocksByNamedBean(source, editor);
832        List<NamedBean> ret = new ArrayList<>();
833        List<FacingProtecting> beanList = generateBlocksWithBeans(editor, T);
834        
835        // may throw JmriException here
836        for (LayoutBlock lb : lProtecting) {
837            ret.addAll(discoverPairDest(source, lb, lFacing, beanList, pathMethod));
838        }
839        return ret;
840    }
841
842    List<NamedBean> discoverPairDest(NamedBean source, LayoutBlock lProtecting, LayoutBlock lFacing, List<FacingProtecting> blockList, Routing pathMethod) throws JmriException {
843        LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class);
844        if (!lbm.isAdvancedRoutingEnabled()) {
845            throw new JmriException("advanced routing not enabled");
846        }
847        if (!lbm.routingStablised()) {
848            throw new JmriException("routing not stabilised");
849        }
850        List<NamedBean> validDestBean = new ArrayList<>();
851        for (FacingProtecting facingProtecting : blockList) {
852            if (facingProtecting.getBean() != source) {
853                NamedBean destObj = facingProtecting.getBean();
854                if (log.isDebugEnabled()) {
855                    log.debug("looking for pair {} {}", source.getDisplayName(), destObj.getDisplayName());
856                }
857                try {
858                    if (checkValidDest(lFacing, lProtecting, facingProtecting, pathMethod)) {
859                        if (log.isDebugEnabled()) {
860                            log.debug("Valid pair {} {}", source.getDisplayName(), destObj.getDisplayName());
861                        }
862                        LayoutBlock ldstBlock = lbm.getLayoutBlock(facingProtecting.getFacing());
863                        try {
864                            List<LayoutBlock> lblks = getLayoutBlocks(lFacing, ldstBlock, lProtecting, true, pathMethod);
865                            if (log.isDebugEnabled()) {
866                                log.debug("Adding block {} to paths, current size {}", destObj.getDisplayName(), lblks.size());
867                            }
868                            validDestBean.add(destObj);
869                        } catch (JmriException e) {  // Considered normal if route not found.
870                            log.debug("not a valid route through {} - {}", source.getDisplayName(), destObj.getDisplayName());
871                        }
872                    }
873                } catch (JmriException ex) {
874                    log.debug("caught exception in discoverPairsDest", ex);
875                }
876            }
877        }
878        return validDestBean;
879    }
880
881    List<FacingProtecting> generateBlocksWithBeans(LayoutEditor editor, Class<?> T) {
882        LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class);
883        List<FacingProtecting> beanList = new ArrayList<>();
884
885        for (LayoutBlock curLblk : lbm.getNamedBeanSet()) {
886            Block curBlk = curLblk.getBlock();
887            LayoutEditor useEdit = editor;
888            if (editor == null) {
889                useEdit = curLblk.getMaxConnectedPanel();
890            }
891            if (curBlk != null) {
892                int noNeigh = curLblk.getNumberOfNeighbours();
893                for (int x = 0; x < noNeigh; x++) {
894                    Block blk = curLblk.getNeighbourAtIndex(x);
895                    List<Block> proBlk = new ArrayList<>();
896                    NamedBean bean = null;
897                    if (T == null) {
898                        proBlk.add(blk);
899                        bean = lbm.getFacingNamedBean(curBlk, blk, useEdit);
900                    } else if (T.equals(SignalMast.class)) {
901                        bean = lbm.getFacingSignalMast(curBlk, blk, useEdit);
902                        if (bean != null) {
903                            if (log.isDebugEnabled()) {
904                                log.debug("Get list of protecting blocks for {} facing {}", bean.getDisplayName(), curBlk.getDisplayName());
905                            }
906                            List<LayoutBlock> lProBlk = lbm.getProtectingBlocksByNamedBean(bean, useEdit);
907                            for (LayoutBlock lb : lProBlk) {
908                                if (lb != null) {
909                                    proBlk.add(lb.getBlock());
910                                }
911                            }
912                        }
913                    } else if (T.equals(Sensor.class)) {
914                        bean = lbm.getFacingSensor(curBlk, blk, useEdit);
915                        if (bean != null) {
916                            if (log.isDebugEnabled()) {
917                                log.debug("Get list of protecting blocks for {}", bean.getDisplayName());
918                            }
919                            List<LayoutBlock> lProBlk = lbm.getProtectingBlocksByNamedBean(bean, useEdit);
920                            for (LayoutBlock lb : lProBlk) {
921                                if (lb != null) {
922                                    proBlk.add(lb.getBlock());
923                                }
924                            }
925                        }
926                    } else {
927                        log.error("Past bean type is unknown {}", T);
928                    }
929                    if (bean != null) {
930                        FacingProtecting toadd = new FacingProtecting(curBlk, proBlk, bean);
931                        boolean found = false;
932                        for (FacingProtecting fp : beanList) {
933                            if (fp.equals(toadd)) {
934                                found = true;
935                                break;
936                            }
937                        }
938                        if (!found) {
939                            beanList.add(toadd);
940                        }
941                    }
942                }
943                if (noNeigh == 1) {
944                    NamedBean bean = null;
945                    if (log.isDebugEnabled()) {
946                        log.debug("We have a dead end {}", curBlk.getDisplayName());
947                    }
948                    if (T == null) {
949                        bean = lbm.getNamedBeanAtEndBumper(curBlk, useEdit);
950                    } else if (T.equals(SignalMast.class)) {
951                        bean = lbm.getSignalMastAtEndBumper(curBlk, useEdit);
952                    } else if (T.equals(Sensor.class)) {
953                        bean = lbm.getSensorAtEndBumper(curBlk, useEdit);
954                    } else {
955                        log.error("Past bean type is unknown {}", T);
956                    }
957                    if (bean != null) {
958                        FacingProtecting toadd = new FacingProtecting(curBlk, null, bean);
959                        boolean found = false;
960                        for (FacingProtecting fp : beanList) {
961                            if (fp.equals(toadd)) {
962                                found = true;
963                                break;
964                            }
965                        }
966                        if (!found) {
967                            beanList.add(toadd);
968                        }
969                    }
970                }
971            }
972        }
973        return beanList;
974    }
975
976    static class FacingProtecting {
977
978        Block facing;
979        List<Block> protectingBlocks;
980        NamedBean bean;
981
982        FacingProtecting(Block facing, List<Block> protecting, NamedBean bean) {
983            this.facing = facing;
984            if (protecting == null) {
985                this.protectingBlocks = new ArrayList<>(0);
986            } else {
987                this.protectingBlocks = protecting;
988            }
989            this.bean = bean;
990        }
991
992        Block getFacing() {
993            return facing;
994        }
995
996        List<Block> getProtectingBlocks() {
997            return protectingBlocks;
998        }
999
1000        NamedBean getBean() {
1001            return bean;
1002        }
1003
1004        @Override
1005        public boolean equals(Object obj) {
1006
1007            if (obj == this) {
1008                return true;
1009            }
1010            if (obj == null) {
1011                return false;
1012            }
1013
1014            if (!(getClass() == obj.getClass())) {
1015                return false;
1016            } else {
1017                FacingProtecting tmp = (FacingProtecting) obj;
1018                if (tmp.getBean() != this.bean) {
1019                    return false;
1020                }
1021                if (tmp.getFacing() != this.facing) {
1022                    return false;
1023                }
1024                if (!tmp.getProtectingBlocks().equals(this.protectingBlocks)) {
1025                    return false;
1026                }
1027            }
1028            return true;
1029        }
1030
1031        @Override
1032        public int hashCode() {
1033            int hash = 7;
1034            hash = 37 * hash + (this.bean != null ? this.bean.hashCode() : 0);
1035            hash = 37 * hash + (this.facing != null ? this.facing.hashCode() : 0);
1036            hash = 37 * hash + (this.protectingBlocks != null ? this.protectingBlocks.hashCode() : 0);
1037            return hash;
1038        }
1039    }
1040
1041    private final static Logger log
1042            = LoggerFactory.getLogger(LayoutBlockConnectivityTools.class);
1043}