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}