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 offSet = blocksInRoute.get(birSize - 2).getTestedIndexes(); 656 bt = blocksInRoute.get(birSize - 2); 657 blocksInRoute.remove(birSize - 1); 658 ttl--; 659 } 660 } 661 ttl++; 662 } 663 if (ttl == ttlSize) { 664 lastErrorMessage = "ttlExpired"; 665 } 666 // we exited the loop without either finding the destination or we had error. 667 throw new jmri.JmriException(lastErrorMessage); 668 } 669 670 static class BlocksTested { 671 672 LayoutBlock block; 673 List<Integer> indexNumber = new ArrayList<>(); 674 675 BlocksTested(LayoutBlock block) { 676 this.block = block; 677 } 678 679 void addIndex(int x) { 680 indexNumber.add(x); 681 } 682 683 int getLastIndex() { 684 return indexNumber.get(indexNumber.size() - 1); // get the last one in the list 685 } 686 687 List<Integer> getTestedIndexes() { 688 return indexNumber; 689 } 690 691 LayoutBlock getBlock() { 692 return block; 693 } 694 } 695 696 private boolean canLBlockBeUsed(LayoutBlock lBlock) { 697 if (lBlock == null) { 698 return true; 699 } 700 if (lBlock.getUseExtraColor()) { 701 return false; 702 } 703 if (lBlock.getBlock().getPermissiveWorking()) { 704 return true; 705 } 706 return (lBlock.getState() != Block.OCCUPIED); 707 } 708 709 String lastErrorMessage = "Unknown Error Occured"; 710 711 // We need to take into account if the returned block has a signalmast attached. 712 int findBestHop(final Block preBlock, final Block currentBlock, Block destBlock, int direction, List<Integer> offSet, boolean validateOnly, Routing pathMethod) { 713 int result = 0; 714 715 LayoutBlock currentLBlock = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlock(currentBlock); 716 if (currentLBlock == null) { 717 return -1; 718 } 719 List<Integer> blkIndexTested = new ArrayList<>(5); 720 if (log.isDebugEnabled()) { 721 log.debug("In find best hop current {} previous {}", currentLBlock.getDisplayName(), preBlock.getDisplayName()); 722 } 723 Block block; 724 while (result != -1) { 725 if (currentBlock == preBlock) { 726 // Basically looking for the connected block, which there should only be one of! 727 log.debug("At get ConnectedBlockRoute"); 728 result = currentLBlock.getConnectedBlockRouteIndex(destBlock, direction); 729 log.trace("getConnectedBlockRouteIndex returns result {} with destBlock {}, direction {}", result, destBlock, direction); 730 } else { 731 if (log.isDebugEnabled()) { 732 log.debug("Off Set {}", offSet); 733 } 734 result = currentLBlock.getNextBestBlock(preBlock, destBlock, offSet, Metric.METRIC); 735 log.trace("getNextBestBlock returns result {} with preBlock {}, destBlock {}", result, preBlock, destBlock); 736 } 737 if (result != -1) { 738 block = currentLBlock.getRouteNextBlockAtIndex(result); 739 LayoutBlock lBlock = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlock(block); 740 741 Block blocktoCheck = block; 742 if (block == currentBlock) { 743 log.debug("current block matches returned block therefore the next block is directly connected"); 744 blocktoCheck = destBlock; 745 } 746 747 if ((block == currentBlock) && (currentLBlock.getThroughPathIndex(preBlock, destBlock) == -1)) { 748 lastErrorMessage = "block " + block.getDisplayName() + " is directly attached, however the route to the destination block " + destBlock.getDisplayName() + " can not be directly used"; 749 log.debug("continue after {}", lastErrorMessage); 750 } else if ((validateOnly) || ((checkForDoubleCrossover(preBlock, currentLBlock, blocktoCheck) && checkForLevelCrossing(currentLBlock)) && canLBlockBeUsed(lBlock))) { 751 if (log.isDebugEnabled()) { 752 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()); 753 log.debug(" current {} {}", currentBlock.getDisplayName(), block.getDisplayName()); 754 } 755 756 jmri.NamedBean foundBean = null; 757 /* We change the logging level to fatal in the layout block manager as we are testing to make sure that no signalhead/mast exists 758 this would generate an error message that is expected.*/ 759 MDC.put("loggingDisabled", LayoutBlockManager.class.getName()); 760 log.trace(" current pathMethod is {}", pathMethod, new Exception("traceback")); 761 switch (pathMethod) { 762 case MASTTOMAST: 763 foundBean = InstanceManager.getDefault(LayoutBlockManager.class).getFacingSignalMast(currentBlock, blocktoCheck); 764 break; 765 case HEADTOHEAD: 766 foundBean = InstanceManager.getDefault(LayoutBlockManager.class).getFacingSignalHead(currentBlock, blocktoCheck); 767 break; 768 case SENSORTOSENSOR: 769 foundBean = InstanceManager.getDefault(LayoutBlockManager.class).getFacingSensor(currentBlock, blocktoCheck, null); 770 break; 771 case NONE: 772 break; 773 default: 774 foundBean = InstanceManager.getDefault(LayoutBlockManager.class).getFacingNamedBean(currentBlock, blocktoCheck, null); 775 break; 776 } 777 MDC.remove("loggingDisabled"); 778 if (foundBean == null) { 779 log.debug("No object found so okay to return"); 780 return result; 781 } else { 782 lastErrorMessage = "Signal " + foundBean.getDisplayName() + " already exists between blocks " + currentBlock.getDisplayName() + " and " + blocktoCheck.getDisplayName() + " in the same direction on this path"; 783 log.debug("continue after {}", lastErrorMessage); 784 } 785 } else { 786 lastErrorMessage = "block " + block.getDisplayName() + " found not to be not usable"; 787 log.debug("continue after {}", lastErrorMessage); 788 } 789 if (blkIndexTested.contains(result)) { 790 lastErrorMessage = ("No valid free path found"); 791 return -1; 792 } 793 blkIndexTested.add(result); 794 offSet.add(result); 795 } else { 796 log.debug("At this point the getNextBextBlock() has returned a -1"); 797 } 798 } 799 return -1; 800 } 801 802 private boolean checkForDoubleCrossover(Block prevBlock, LayoutBlock curBlock, Block nextBlock) { 803 LayoutEditor le = curBlock.getMaxConnectedPanel(); 804 ConnectivityUtil ct = le.getConnectivityUtil(); 805 List<LayoutTrackExpectedState<LayoutTurnout>> turnoutList = ct.getTurnoutList(curBlock.getBlock(), prevBlock, nextBlock); 806 for (LayoutTrackExpectedState<LayoutTurnout> layoutTurnoutLayoutTrackExpectedState : turnoutList) { 807 LayoutTurnout lt = layoutTurnoutLayoutTrackExpectedState.getObject(); 808 if (lt.getTurnoutType() == LayoutTurnout.TurnoutType.DOUBLE_XOVER) { 809 if (layoutTurnoutLayoutTrackExpectedState.getExpectedState() == jmri.Turnout.THROWN) { 810 jmri.Turnout t = lt.getTurnout(); 811 if (t.getKnownState() == jmri.Turnout.THROWN) { 812 if (lt.getLayoutBlock() == curBlock || lt.getLayoutBlockC() == curBlock) { 813 if (!canLBlockBeUsed(lt.getLayoutBlockB()) && !canLBlockBeUsed(lt.getLayoutBlockD())) { 814 return false; 815 } 816 } else if (lt.getLayoutBlockB() == curBlock || lt.getLayoutBlockD() == curBlock) { 817 if (!canLBlockBeUsed(lt.getLayoutBlock()) && !canLBlockBeUsed(lt.getLayoutBlockC())) { 818 return false; 819 } 820 } 821 } 822 } 823 } 824 } 825 return true; 826 } 827 828 private boolean checkForLevelCrossing(LayoutBlock curBlock) { 829 LayoutEditor lay = curBlock.getMaxConnectedPanel(); 830 for (LevelXing lx : lay.getLevelXings()) { 831 if (lx.getLayoutBlockAC() == curBlock 832 || lx.getLayoutBlockBD() == curBlock) { 833 if ((lx.getLayoutBlockAC() != null) 834 && (lx.getLayoutBlockBD() != null) 835 && (lx.getLayoutBlockAC() != lx.getLayoutBlockBD())) { 836 if (lx.getLayoutBlockAC() == curBlock) { 837 if (!canLBlockBeUsed(lx.getLayoutBlockBD())) { 838 return false; 839 } 840 } else if (lx.getLayoutBlockBD() == curBlock) { 841 if (!canLBlockBeUsed(lx.getLayoutBlockAC())) { 842 return false; 843 } 844 } 845 } 846 } 847 } 848 return true; 849 } 850 851 /** 852 * Discovers valid pairs of beans type T assigned to a layout editor. If no 853 * bean type is provided, then either SignalMasts or Sensors are discovered 854 * If no editor is provided, then all editors are considered 855 * 856 * @param editor the layout editor panel 857 * @param T the type 858 * @param pathMethod Determine whether or not we should reject pairs if 859 * there are other beans in the way. Constant values of 860 * NONE, ANY, MASTTOMAST, HEADTOHEAD 861 * @return the valid pairs 862 */ 863 public HashMap<NamedBean, List<NamedBean>> discoverValidBeanPairs(LayoutEditor editor, Class<?> T, Routing pathMethod) { 864 LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class); 865 866 // ----- Begin Turntable Path Discovery ----- 867 HashMap<NamedBean, List<NamedBean>> retPairs = new HashMap<>(); 868 List<SignalMast> turntableMasts = new ArrayList<>(); 869 if (T == SignalMast.class) { 870 for (LayoutEditor panel : InstanceManager.getDefault(EditorManager.class).getAll(LayoutEditor.class)) { 871 for (LayoutTurntable turntable : panel.getLayoutTurntables()) { 872 if (!turntable.isDispatcherManaged()) { 873 continue; 874 } 875 LayoutBlock turntableBlock = turntable.getLayoutBlock(); 876 877 // Add all turntable-related masts to a list for exclusion from general discovery 878 if (turntable.getExitSignalMast() != null) turntableMasts.add(turntable.getExitSignalMast()); 879 if (turntable.getBufferMast() != null) turntableMasts.add(turntable.getBufferMast()); 880 881 // Path 1: From turntable's Exit Mast to the next mast on a ray's track 882 SignalMast exitMast = turntable.getExitSignalMast(); 883 if (exitMast != null) { 884 for (LayoutTurntable.RayTrack ray : turntable.getRayTrackList()) { 885 TrackSegment track = ray.getConnect(); 886 if (track != null && track.getLayoutBlock() != null) { 887 LayoutBlock rayBlock = track.getLayoutBlock(); 888 // Find the block connected to the ray that is NOT the turntable, then find the mast protecting it. 889 for (int i = 0; i < rayBlock.getNumberOfNeighbours(); i++) { 890 Block neighbor = rayBlock.getNeighbourAtIndex(i); 891 if (neighbor != turntableBlock.getBlock()) { 892 SignalMast nextMast = lbm.getFacingSignalMast(rayBlock.getBlock(), neighbor, panel); 893 if (nextMast != null) { 894 retPairs.computeIfAbsent(exitMast, k -> new ArrayList<>()).add(nextMast); 895 } 896 break; // Assume only one exit from the ray block 897 } 898 } 899 // Also check for a buffer mast at the end of this ray's block 900 if (rayBlock.getNumberOfNeighbours() == 1) { // Only connected to the turntable block 901 SignalMast bufferMast = lbm.getSignalMastAtEndBumper(rayBlock.getBlock(), panel); 902 if (bufferMast != null) { 903 if (log.isDebugEnabled()) { 904 log.debug("Found turntable exit to buffer mast path: {} -> {}", exitMast.getDisplayName(), bufferMast.getDisplayName()); 905 } 906 retPairs.computeIfAbsent(exitMast, k -> new ArrayList<>()).add(bufferMast); 907 } 908 } 909 } 910 } 911 } 912 913 914 // Path 2 & 3: Paths involving Approach and Buffer masts 915 for (LayoutTurntable.RayTrack ray : turntable.getRayTrackList()) { 916 SignalMast approachMast = ray.getApproachMast(); 917 if (approachMast == null) { // this is logged elsewhere 918 continue; 919 } 920 turntableMasts.add(approachMast); 921 922 // Path 2: From a remote mast on the layout to this ray's Approach Mast 923 TrackSegment track = ray.getConnect(); 924 if (track != null && track.getLayoutBlock() != null && turntableBlock != null) { 925 LayoutBlock rayBlock = track.getLayoutBlock(); 926 // Find the block connected to the ray that is NOT the turntable, then find the mast protecting the ray from it. 927 for (int i = 0; i < rayBlock.getNumberOfNeighbours(); i++) { 928 Block neighbor = rayBlock.getNeighbourAtIndex(i); 929 if (neighbor != turntableBlock.getBlock()) { 930 SignalMast remoteMast = lbm.getFacingSignalMast(neighbor, rayBlock.getBlock(), panel); 931 if (remoteMast != null) { 932 retPairs.computeIfAbsent(remoteMast, k -> new ArrayList<>()).add(approachMast); 933 } 934 // Assume only one entry to the ray block 935 break; 936 } 937 } 938 } 939 940 // Path 3: From this ray's Approach Mast to the turntable's Buffer Mast (a siding path) 941 SignalMast bufferMast = turntable.getBufferMast(); 942 if (bufferMast != null) { 943 retPairs.computeIfAbsent(approachMast, k -> new ArrayList<>()).add(bufferMast); 944 } 945 } 946 } 947 } 948 } 949 // ----- Begin Traverser Path Discovery ----- 950// HashMap<NamedBean, List<NamedBean>> retPairs = new HashMap<>(); 951 List<SignalMast> traverserMasts = new ArrayList<>(); 952 if (T == SignalMast.class) { 953 for (LayoutEditor panel : InstanceManager.getDefault(EditorManager.class).getAll(LayoutEditor.class)) { 954 for (LayoutTraverser traverser : panel.getLayoutTraversers()) { 955 if (!traverser.isDispatcherManaged()) { 956 continue; 957 } 958 LayoutBlock traverserBlock = traverser.getLayoutBlock(); 959 960 // Add all traverser-related masts to a list for exclusion from general discovery 961 if (traverser.getExitSignalMast() != null) traverserMasts.add(traverser.getExitSignalMast()); 962 if (traverser.getBufferMast() != null) traverserMasts.add(traverser.getBufferMast()); 963 964 // Path 1: From traverser's Exit Mast to the next mast on a ray's track 965 SignalMast exitMast = traverser.getExitSignalMast(); 966 if (exitMast != null) { 967 for (LayoutTraverser.SlotTrack slot : traverser.getSlotList()) { 968 TrackSegment track = slot.getConnect(); 969 if (track != null && track.getLayoutBlock() != null) { 970 LayoutBlock slotBlock = track.getLayoutBlock(); 971 // Find the block connected to the slot that is NOT the traverser, then find the mast protecting it. 972 for (int i = 0; i < slotBlock.getNumberOfNeighbours(); i++) { 973 Block neighbor = slotBlock.getNeighbourAtIndex(i); 974 if (neighbor != traverserBlock.getBlock()) { 975 SignalMast nextMast = lbm.getFacingSignalMast(slotBlock.getBlock(), neighbor, panel); 976 if (nextMast != null) { 977 retPairs.computeIfAbsent(exitMast, k -> new ArrayList<>()).add(nextMast); 978 } 979 break; // Assume only one exit from the slot block 980 } 981 } 982 // Also check for a buffer mast at the end of this slot's block 983 if (slotBlock.getNumberOfNeighbours() == 1) { // Only connected to the traverser block 984 SignalMast bufferMast = lbm.getSignalMastAtEndBumper(slotBlock.getBlock(), panel); 985 if (bufferMast != null) { 986 if (log.isDebugEnabled()) { 987 log.debug("Found traverser exit to buffer mast path: {} -> {}", exitMast.getDisplayName(), bufferMast.getDisplayName()); 988 } 989 retPairs.computeIfAbsent(exitMast, k -> new ArrayList<>()).add(bufferMast); 990 } 991 } 992 } 993 } 994 } 995 996 997 // Path 2 & 3: Paths involving Approach and Buffer masts 998 for (LayoutTraverser.SlotTrack slot : traverser.getSlotList()) { 999 SignalMast approachMast = slot.getApproachMast(); 1000 if (approachMast == null) { // this is logged elsewhere 1001 continue; 1002 } 1003 traverserMasts.add(approachMast); 1004 1005 // Path 2: From a remote mast on the layout to this slot's Approach Mast 1006 TrackSegment track = slot.getConnect(); 1007 if (track != null && track.getLayoutBlock() != null && traverserBlock != null) { 1008 LayoutBlock slotBlock = track.getLayoutBlock(); 1009 // Find the block connected to the slot that is NOT the traverser, then find the mast protecting the slot from it. 1010 for (int i = 0; i < slotBlock.getNumberOfNeighbours(); i++) { 1011 Block neighbor = slotBlock.getNeighbourAtIndex(i); 1012 if (neighbor != traverserBlock.getBlock()) { 1013 SignalMast remoteMast = lbm.getFacingSignalMast(neighbor, slotBlock.getBlock(), panel); 1014 if (remoteMast != null) { 1015 retPairs.computeIfAbsent(remoteMast, k -> new ArrayList<>()).add(approachMast); 1016 } 1017 // Assume only one entry to the slot block 1018 break; 1019 } 1020 } 1021 } 1022 1023 // Path 3: From this ray's Approach Mast to the traverser's Buffer Mast (a siding path) 1024 SignalMast bufferMast = traverser.getBufferMast(); 1025 if (bufferMast != null) { 1026 retPairs.computeIfAbsent(approachMast, k -> new ArrayList<>()).add(bufferMast); 1027 } 1028 } 1029 } 1030 } 1031 } 1032 // ----- End Turntable/Traverser Path Discovery ----- 1033 1034 // ----- Begin General Path Discovery (excluding turntable masts) ----- 1035 List<FacingProtecting> beanList = generateBlocksWithBeans(editor, T); 1036 beanList.forEach((fp) -> { 1037 // Skip any mast that has already been handled by the turntable-specific logic above 1038 if (turntableMasts.contains(fp.getBean())) { 1039 return; // continue to next fp in forEach 1040 } 1041 // Skip any mast that has already been handled by the traverser-specific logic above 1042 if (traverserMasts.contains(fp.getBean())) { 1043 return; // continue to next fp in forEach 1044 } 1045 fp.getProtectingBlocks().stream().map((block) -> { 1046 if (log.isDebugEnabled()) { 1047 try { 1048 log.debug("\nSource {}", fp.getBean().getDisplayName()); 1049 log.debug("facing {}", fp.getFacing().getDisplayName()); 1050 log.debug("protecting {}", block.getDisplayName()); 1051 } catch (java.lang.NullPointerException e) { 1052 // Can be considered normal if the signalmast is assigned to an end bumper. 1053 } 1054 } 1055 return block; 1056 }).forEachOrdered((block) -> { 1057 LayoutBlock lFacing = lbm.getLayoutBlock(fp.getFacing()); 1058 LayoutBlock lProtecting = lbm.getLayoutBlock(block); 1059 NamedBean source = fp.getBean(); 1060 try { 1061 retPairs.computeIfAbsent(source, k -> new ArrayList<>()).addAll(discoverPairDest(source, lProtecting, lFacing, beanList, pathMethod)); 1062 } catch (JmriException ex) { 1063 log.error("exception in retPairs.put", ex); 1064 } 1065 }); 1066 }); 1067 return retPairs; 1068 } 1069 1070 /** 1071 * Returns a list of valid destination beans reachable from a given source 1072 * bean. 1073 * 1074 * @param source Either a SignalMast or Sensor 1075 * @param editor The layout editor that the source is located on, if 1076 * null, then all editors are considered 1077 * @param T The class of the remote destination, if null, then both 1078 * SignalMasts and Sensors are considered 1079 * @param pathMethod Determine whether or not we should reject pairs if 1080 * there are other beans in the way. Constant values of 1081 * NONE, ANY, MASTTOMAST, HEADTOHEAD 1082 * @return A list of all reachable NamedBeans 1083 * @throws jmri.JmriException occurring during nested readAll operation 1084 */ 1085 public List<NamedBean> discoverPairDest(NamedBean source, LayoutEditor editor, Class<?> T, Routing pathMethod) throws JmriException { 1086 if (log.isDebugEnabled()) { 1087 log.debug("discover pairs from source {}", source.getDisplayName()); 1088 } 1089 1090 LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class); 1091 1092 // First, check if the source is a turntable mast. If so, handle it specially. 1093 if (T == SignalMast.class) { 1094 if (! (source instanceof SignalMast)) { 1095 throw new IllegalArgumentException("source is not a SignalMast: " + (source != null ? source.getClass().getName() : "null")); 1096 } 1097 SignalMast sourceMast = (SignalMast) source; 1098 for (LayoutEditor panel : InstanceManager.getDefault(EditorManager.class).getAll(LayoutEditor.class)) { 1099 for (LayoutTurntable turntable : panel.getLayoutTurntables()) { 1100 if (!turntable.isDispatcherManaged()) { 1101 continue; 1102 } 1103 1104 // Case 1: Source is the turntable's Exit Mast 1105 if (sourceMast.equals(turntable.getExitSignalMast())) { 1106 log.debug("Source is an exit mast for turntable {}", turntable.getName()); 1107 List<NamedBean> destinations = new ArrayList<>(); 1108 LayoutBlock turntableBlock = turntable.getLayoutBlock(); 1109 for (LayoutTurntable.RayTrack ray : turntable.getRayTrackList()) { 1110 TrackSegment track = ray.getConnect(); 1111 if (track != null && track.getLayoutBlock() != null) { 1112 LayoutBlock rayBlock = track.getLayoutBlock(); 1113 for (int i = 0; i < rayBlock.getNumberOfNeighbours(); i++) { 1114 Block neighbor = rayBlock.getNeighbourAtIndex(i); 1115 if (neighbor != turntableBlock.getBlock()) { 1116 SignalMast nextMast = lbm.getFacingSignalMast(rayBlock.getBlock(), neighbor, panel); 1117 if (nextMast != null) destinations.add(nextMast); 1118 } 1119 } 1120 if (rayBlock.getNumberOfNeighbours() == 1) { // End of line buffer 1121 SignalMast bufferMast = lbm.getSignalMastAtEndBumper(rayBlock.getBlock(), panel); 1122 if (bufferMast != null) destinations.add(bufferMast); 1123 } 1124 } 1125 } 1126 return destinations; 1127 } 1128 1129 // Case 2: Source is an Approach Mast for one of the rays 1130 for (LayoutTurntable.RayTrack ray : turntable.getRayTrackList()) { 1131 if (sourceMast.equals(ray.getApproachMast())) { 1132 log.debug("Source is an approach mast for turntable {}", turntable.getName()); 1133 List<NamedBean> destinations = new ArrayList<>(); 1134 if (turntable.getBufferMast() != null) destinations.add(turntable.getBufferMast()); 1135 return destinations; 1136 } 1137 } 1138 } 1139 } 1140 } 1141 // First, check if the source is a traverser mast. If so, handle it specially. 1142 if (T == SignalMast.class) { 1143 if (! (source instanceof SignalMast)) { 1144 throw new IllegalArgumentException("source is not a SignalMast: " + (source != null ? source.getClass().getName() : "null")); 1145 } 1146 SignalMast sourceMast = (SignalMast) source; 1147 for (LayoutEditor panel : InstanceManager.getDefault(EditorManager.class).getAll(LayoutEditor.class)) { 1148 for (LayoutTraverser traverser : panel.getLayoutTraversers()) { 1149 if (!traverser.isDispatcherManaged()) continue; 1150 1151 // Case 1: Source is the traverser's Exit Mast 1152 if (sourceMast.equals(traverser.getExitSignalMast())) { 1153 List<NamedBean> destinations = new ArrayList<>(); 1154 LayoutBlock traverserBlock = traverser.getLayoutBlock(); 1155 for (int i = 0; i < traverser.getNumberSlots(); i++) { 1156 TrackSegment track = traverser.getSlotConnectOrdered(i); 1157 if (track != null && track.getLayoutBlock() != null) { 1158 LayoutBlock slotBlock = track.getLayoutBlock(); 1159 for (int j = 0; j < slotBlock.getNumberOfNeighbours(); j++) { 1160 Block neighbor = slotBlock.getNeighbourAtIndex(j); 1161 if (neighbor != traverserBlock.getBlock()) { 1162 SignalMast nextMast = lbm.getFacingSignalMast(slotBlock.getBlock(), neighbor, panel); 1163 if (nextMast != null) destinations.add(nextMast); 1164 } 1165 } 1166 if (slotBlock.getNumberOfNeighbours() == 1) { // End of line buffer 1167 SignalMast bufferMast = lbm.getSignalMastAtEndBumper(slotBlock.getBlock(), panel); 1168 if (bufferMast != null) destinations.add(bufferMast); 1169 } 1170 } 1171 } 1172 return destinations; 1173 } 1174 1175 // Case 2: Source is an Approach Mast for one of the slots 1176 for (LayoutTraverser.SlotTrack slot : traverser.getSlotList()) { 1177 if (sourceMast.equals(slot.getApproachMast())) { 1178 log.debug("Source is an approach mast for traverser {}", traverser.getName()); 1179 List<NamedBean> destinations = new ArrayList<>(); 1180 if (traverser.getBufferMast() != null) destinations.add(traverser.getBufferMast()); 1181 return destinations; 1182 } 1183 } 1184 } 1185 } 1186 } 1187 1188 LayoutBlock lFacing = lbm.getFacingBlockByNamedBean(source, editor); 1189 List<LayoutBlock> lProtecting = lbm.getProtectingBlocksByNamedBean(source, editor); 1190 List<NamedBean> ret = new ArrayList<>(); 1191 List<FacingProtecting> beanList = generateBlocksWithBeans(editor, T); 1192 1193 // may throw JmriException here 1194 for (LayoutBlock lb : lProtecting) { 1195 ret.addAll(discoverPairDest(source, lb, lFacing, beanList, pathMethod)); 1196 } 1197 return ret; 1198 } 1199 1200 List<NamedBean> discoverPairDest(NamedBean source, LayoutBlock lProtecting, LayoutBlock lFacing, List<FacingProtecting> blockList, Routing pathMethod) throws JmriException { 1201 LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class); 1202 if (!lbm.isAdvancedRoutingEnabled()) { 1203 throw new JmriException("advanced routing not enabled"); 1204 } 1205 if (!lbm.routingStablised()) { 1206 throw new JmriException("routing not stabilised"); 1207 } 1208 List<NamedBean> validDestBean = new ArrayList<>(); 1209 for (FacingProtecting facingProtecting : blockList) { 1210 if (facingProtecting.getBean() != source) { 1211 NamedBean destObj = facingProtecting.getBean(); 1212 if (log.isDebugEnabled()) { 1213 log.debug("looking for pair {} {}", source.getDisplayName(), destObj.getDisplayName()); 1214 } 1215 try { 1216 if (checkValidDest(lFacing, lProtecting, facingProtecting, pathMethod)) { 1217 if (log.isDebugEnabled()) { 1218 log.debug("Valid pair {} {}", source.getDisplayName(), destObj.getDisplayName()); 1219 } 1220 LayoutBlock ldstBlock = lbm.getLayoutBlock(facingProtecting.getFacing()); 1221 try { 1222 List<LayoutBlock> lblks = getLayoutBlocks(lFacing, ldstBlock, lProtecting, true, pathMethod); 1223 if (log.isDebugEnabled()) { 1224 log.debug("Adding block {} to paths, current size {}", destObj.getDisplayName(), lblks.size()); 1225 } 1226 validDestBean.add(destObj); 1227 } catch (JmriException e) { // Considered normal if route not found. 1228 log.debug("not a valid route through {} - {}", source.getDisplayName(), destObj.getDisplayName()); 1229 } 1230 } 1231 } catch (JmriException ex) { 1232 log.debug("caught exception in discoverPairsDest", ex); 1233 } 1234 } 1235 } 1236 return validDestBean; 1237 } 1238 1239 List<FacingProtecting> generateBlocksWithBeans(LayoutEditor editor, Class<?> T) { 1240 LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class); 1241 List<FacingProtecting> beanList = new ArrayList<>(); 1242 1243 for (LayoutBlock curLblk : lbm.getNamedBeanSet()) { 1244 Block curBlk = curLblk.getBlock(); 1245 LayoutEditor useEdit = editor; 1246 if (editor == null) { 1247 useEdit = curLblk.getMaxConnectedPanel(); 1248 } 1249 if (curBlk != null) { 1250 int noNeigh = curLblk.getNumberOfNeighbours(); 1251 for (int x = 0; x < noNeigh; x++) { 1252 Block blk = curLblk.getNeighbourAtIndex(x); 1253 List<Block> proBlk = new ArrayList<>(); 1254 NamedBean bean = null; 1255 if (T == null) { 1256 proBlk.add(blk); 1257 bean = lbm.getFacingNamedBean(curBlk, blk, useEdit); 1258 } else if (T.equals(SignalMast.class)) { 1259 bean = lbm.getFacingSignalMast(curBlk, blk, useEdit); 1260 if (bean != null) { 1261 if (log.isDebugEnabled()) { 1262 log.debug("Get list of protecting blocks for {} facing {}", bean.getDisplayName(), curBlk.getDisplayName()); 1263 } 1264 List<LayoutBlock> lProBlk = lbm.getProtectingBlocksByNamedBean(bean, useEdit); 1265 for (LayoutBlock lb : lProBlk) { 1266 if (lb != null) { 1267 proBlk.add(lb.getBlock()); 1268 } 1269 } 1270 } 1271 } else if (T.equals(Sensor.class)) { 1272 bean = lbm.getFacingSensor(curBlk, blk, useEdit); 1273 if (bean != null) { 1274 if (log.isDebugEnabled()) { 1275 log.debug("Get list of protecting blocks for {}", bean.getDisplayName()); 1276 } 1277 List<LayoutBlock> lProBlk = lbm.getProtectingBlocksByNamedBean(bean, useEdit); 1278 for (LayoutBlock lb : lProBlk) { 1279 if (lb != null) { 1280 proBlk.add(lb.getBlock()); 1281 } 1282 } 1283 } 1284 } else { 1285 log.error("Past bean type is unknown {}", T); 1286 } 1287 if (bean != null) { 1288 FacingProtecting toadd = new FacingProtecting(curBlk, proBlk, bean); 1289 boolean found = false; 1290 for (FacingProtecting fp : beanList) { 1291 if (fp.equals(toadd)) { 1292 found = true; 1293 break; 1294 } 1295 } 1296 if (!found) { 1297 beanList.add(toadd); 1298 } 1299 } 1300 } 1301 if (noNeigh == 1) { 1302 NamedBean bean = null; 1303 if (log.isDebugEnabled()) { 1304 log.debug("We have a dead end {}", curBlk.getDisplayName()); 1305 } 1306 if (T == null) { 1307 bean = lbm.getNamedBeanAtEndBumper(curBlk, useEdit); 1308 } else if (T.equals(SignalMast.class)) { 1309 bean = lbm.getSignalMastAtEndBumper(curBlk, useEdit); 1310 } else if (T.equals(Sensor.class)) { 1311 bean = lbm.getSensorAtEndBumper(curBlk, useEdit); 1312 } else { 1313 log.error("Past bean type is unknown {}", T); 1314 } 1315 if (bean != null) { 1316 FacingProtecting toadd = new FacingProtecting(curBlk, null, bean); 1317 boolean found = false; 1318 for (FacingProtecting fp : beanList) { 1319 if (fp.equals(toadd)) { 1320 found = true; 1321 break; 1322 } 1323 } 1324 if (!found) { 1325 beanList.add(toadd); 1326 } 1327 } 1328 } 1329 } 1330 } 1331 return beanList; 1332 } 1333 1334 static class FacingProtecting { 1335 1336 Block facing; 1337 List<Block> protectingBlocks; 1338 NamedBean bean; 1339 1340 FacingProtecting(Block facing, List<Block> protecting, NamedBean bean) { 1341 this.facing = facing; 1342 if (protecting == null) { 1343 this.protectingBlocks = new ArrayList<>(0); 1344 } else { 1345 this.protectingBlocks = protecting; 1346 } 1347 this.bean = bean; 1348 } 1349 1350 Block getFacing() { 1351 return facing; 1352 } 1353 1354 List<Block> getProtectingBlocks() { 1355 return protectingBlocks; 1356 } 1357 1358 NamedBean getBean() { 1359 return bean; 1360 } 1361 1362 @Override 1363 public boolean equals(Object obj) { 1364 1365 if (obj == this) { 1366 return true; 1367 } 1368 if (obj == null) { 1369 return false; 1370 } 1371 1372 if (!(getClass() == obj.getClass())) { 1373 return false; 1374 } else { 1375 FacingProtecting tmp = (FacingProtecting) obj; 1376 if (tmp.getBean() != this.bean) { 1377 return false; 1378 } 1379 if (tmp.getFacing() != this.facing) { 1380 return false; 1381 } 1382 if (!tmp.getProtectingBlocks().equals(this.protectingBlocks)) { 1383 return false; 1384 } 1385 } 1386 return true; 1387 } 1388 1389 @Override 1390 public int hashCode() { 1391 int hash = 7; 1392 hash = 37 * hash + (this.bean != null ? this.bean.hashCode() : 0); 1393 hash = 37 * hash + (this.facing != null ? this.facing.hashCode() : 0); 1394 hash = 37 * hash + (this.protectingBlocks != null ? this.protectingBlocks.hashCode() : 0); 1395 return hash; 1396 } 1397 } 1398 1399 private static final Logger log 1400 = LoggerFactory.getLogger(LayoutBlockConnectivityTools.class); 1401}