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