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