001package jmri.jmrit.display.layoutEditor; 002 003import java.awt.Color; 004import java.awt.Component; 005import java.awt.event.ActionEvent; 006import java.beans.*; 007import java.util.*; 008 009import javax.annotation.CheckForNull; 010import javax.annotation.Nonnull; 011import javax.swing.*; 012import javax.swing.colorchooser.AbstractColorChooserPanel; 013 014import jmri.*; 015import jmri.implementation.AbstractNamedBean; 016import jmri.jmrit.beantable.beanedit.*; 017import jmri.jmrit.roster.RosterEntry; 018import jmri.swing.NamedBeanComboBox; 019import jmri.util.MathUtil; 020import jmri.util.swing.JmriColorChooser; 021import jmri.util.swing.JmriJOptionPane; 022import jmri.util.swing.SplitButtonColorChooserPanel; 023 024import org.slf4j.MDC; 025 026/** 027 * A LayoutBlock is a group of track segments and turnouts on a LayoutEditor 028 * panel corresponding to a 'block'. LayoutBlock is a LayoutEditor specific 029 * extension of the JMRI Block object. 030 * <p> 031 * LayoutBlocks may have an occupancy Sensor. The getOccupancy method returns 032 * the occupancy state of the LayoutBlock - OCCUPIED, EMPTY, or UNKNOWN. If no 033 * occupancy sensor is provided, UNKNOWN is returned. The occupancy sensor if 034 * there is one, is the same as the occupancy sensor of the corresponding JMRI 035 * Block. 036 * <p> 037 * The name of each Layout Block is the same as that of the corresponding block 038 * as defined in Layout Editor. A corresponding JMRI Block object is created 039 * when a LayoutBlock is created. The JMRI Block uses the name of the block 040 * defined in Layout Editor as its user name and a unique IBnnn system name. The 041 * JMRI Block object and its associated Path objects are useful in tracking a 042 * train around the layout. Blocks may be viewed in the Block Table. 043 * <p> 044 * A LayoutBlock may have an associated Memory object. This Memory object 045 * contains a string representing the current "value" of the corresponding JMRI 046 * Block object. If the value contains a train name, for example, displaying 047 * Memory objects associated with LayoutBlocks, and displayed near each Layout 048 * Block can follow a train around the layout, displaying its name when it is in 049 * the LayoutBlock. 050 * <p> 051 * LayoutBlocks are "cross-panel", similar to sensors and turnouts. A 052 * LayoutBlock may be used by more than one Layout Editor panel simultaneously. 053 * As a consequence, LayoutBlocks are saved with the configuration, not with a 054 * panel. 055 * <p> 056 * LayoutBlocks are used by TrackSegments, LevelXings, and LayoutTurnouts. 057 * LevelXings carry two LayoutBlock designations, which may be the same. 058 * LayoutTurnouts carry LayoutBlock designations also, one per turnout, except 059 * for double crossovers and slips which can have up to four. 060 * <p> 061 * LayoutBlocks carry a use count. The use count counts the number of track 062 * segments, layout turnouts, and levelcrossings which use the LayoutBlock. Only 063 * LayoutBlocks which have a use count greater than zero are saved when the 064 * configuration is saved. 065 * 066 * @author Dave Duchamp Copyright (c) 2004-2008 067 * @author George Warner Copyright (c) 2017-2019 068 */ 069public class LayoutBlock extends AbstractNamedBean implements PropertyChangeListener { 070 071 private final boolean enableAddRouteLogging = false; 072 private final boolean enableUpdateRouteLogging = false; 073 private boolean enableDeleteRouteLogging = false; 074 private final boolean enableSearchRouteLogging = false; 075 076 private static final List<Integer> updateReferences = new ArrayList<>(500); 077 078 // might want to use the jmri ordered HashMap, so that we can add at the top 079 // and remove at the bottom. 080 private final List<Integer> actedUponUpdates = new ArrayList<>(500); 081 082 public void enableDeleteRouteLog() { 083 enableDeleteRouteLogging = false; 084 } 085 086 public void disableDeleteRouteLog() { 087 enableDeleteRouteLogging = false; 088 } 089 090 // constants 091 public static final int OCCUPIED = Block.OCCUPIED; 092 public static final int EMPTY = Block.UNOCCUPIED; 093 // operational instance variables (not saved to disk) 094 private int useCount = 0; 095 private NamedBeanHandle<Sensor> occupancyNamedSensor = null; 096 private NamedBeanHandle<Memory> namedMemory = null; 097 private boolean setSensorFromBlockEnabled = true; // Controls whether getOccupancySensor should get the sensor from the block 098 099 private Block block = null; 100 101 private final List<LayoutEditor> panels = new ArrayList<>(); // panels using this block 102 private PropertyChangeListener mBlockListener = null; 103 private int jmriblknum = 1; 104 private boolean useExtraColor = false; 105 private boolean suppressNameUpdate = false; 106 107 // persistent instances variables (saved between sessions) 108 private String occupancySensorName = ""; 109 private String memoryName = ""; 110 private int occupiedSense = Sensor.ACTIVE; 111 private Color blockTrackColor = Color.darkGray; 112 private Color blockOccupiedColor = Color.red; 113 private Color blockExtraColor = Color.white; 114 115 /** 116 * Creates a LayoutBlock object. 117 * 118 * Note: initializeLayoutBlock() must be called to complete the process. They are split 119 * so that loading of panel files will be independent of whether LayoutBlocks or 120 * Blocks are loaded first. 121 * @param sName System name of this LayoutBlock 122 * @param uName User name of this LayoutBlock but also the user name of the associated Block 123 */ 124 public LayoutBlock(String sName, String uName) { 125 super(sName, uName); 126 } 127 128 /** 129 * Completes the creation of a LayoutBlock object by adding a Block to it. 130 * 131 * The block create process takes into account that the _bean register 132 * process considers IB1 and IB01 to be the same name which results in a 133 * silent failure. 134 */ 135 public void initializeLayoutBlock() { 136 // get/create a Block object corresponding to this LayoutBlock 137 block = null; // assume failure (pessimist!) 138 String userName = getUserName(); 139 if ((userName != null) && !userName.isEmpty()) { 140 block = InstanceManager.getDefault(BlockManager.class).getByUserName(userName); 141 } 142 143 if (block == null) { 144 // Not found, create a new Block 145 BlockManager bm = InstanceManager.getDefault(BlockManager.class); 146 String s; 147 while (true) { 148 if (jmriblknum > 50000) { 149 throw new IndexOutOfBoundsException("Run away prevented while trying to create a block"); 150 } 151 s = "IB" + jmriblknum; 152 jmriblknum++; 153 154 // Find an unused system name 155 block = bm.getBySystemName(s); 156 if (block != null) { 157 log.debug("System name is already used: {}", s); 158 continue; 159 } 160 161 // Create a new block. User name is null to prevent user name checking. 162 block = bm.createNewBlock(s, null); 163 if (block == null) { 164 log.debug("Null block returned: {}", s); 165 continue; 166 } 167 168 // Verify registration 169 Block testGet = bm.getBySystemName(s); 170 if ( testGet!=null && bm.getNamedBeanSet().contains(testGet) ) { 171 log.debug("Block is valid: {}", s); 172 break; 173 } 174 log.debug("Registration failed: {}", s); 175 } 176 block.setUserName(getUserName()); 177 } 178 179 // attach a listener for changes in the Block 180 mBlockListener = this::handleBlockChange; 181 block.addPropertyChangeListener(mBlockListener, 182 getUserName(), "Layout Block:" + getUserName()); 183 if (occupancyNamedSensor != null) { 184 block.setNamedSensor(occupancyNamedSensor); 185 } 186 } 187 188 /* initializeLayoutBlockRouting */ 189 public void initializeLayoutBlockRouting() { 190 if (!InstanceManager.getDefault(LayoutBlockManager.class).isAdvancedRoutingEnabled()) { 191 return; 192 } 193 setBlockMetric(); 194 195 block.getPaths().stream().forEach(this::addAdjacency); 196 } 197 198 /* 199 * Accessor methods 200 */ 201 // TODO: deprecate and just use getUserName() directly 202 public String getId() { 203 return getUserName(); 204 } 205 206 public Color getBlockTrackColor() { 207 return blockTrackColor; 208 } 209 210 public void setBlockTrackColor(Color color) { 211 blockTrackColor = color; 212 JmriColorChooser.addRecentColor(color); 213 } 214 215 public Color getBlockOccupiedColor() { 216 return blockOccupiedColor; 217 } 218 219 public void setBlockOccupiedColor(Color color) { 220 blockOccupiedColor = color; 221 JmriColorChooser.addRecentColor(color); 222 } 223 224 public Color getBlockExtraColor() { 225 return blockExtraColor; 226 } 227 228 public void setBlockExtraColor(Color color) { 229 blockExtraColor = color; 230 JmriColorChooser.addRecentColor(color); 231 } 232 233 // TODO: Java standard pattern for boolean getters is "useExtraColor()" 234 public boolean getUseExtraColor() { 235 return useExtraColor; 236 } 237 238 public void setUseExtraColor(boolean b) { 239 useExtraColor = b; 240 241 if (InstanceManager.getDefault(LayoutBlockManager.class).isAdvancedRoutingEnabled()) { 242 stateUpdate(); 243 } 244 if (getBlock() != null) { 245 getBlock().setAllocated(b); 246 } 247 } 248 249 /* setUseExtraColor */ 250 public void incrementUse() { 251 useCount++; 252 } 253 254 public void decrementUse() { 255 --useCount; 256 if (useCount <= 0) { 257 useCount = 0; 258 } 259 } 260 261 public int getUseCount() { 262 return useCount; 263 } 264 265 /** 266 * Keep track of LayoutEditor panels that are using this LayoutBlock. 267 * 268 * @param panel to keep track of 269 */ 270 public void addLayoutEditor(LayoutEditor panel) { 271 // add to the panels list if not already there 272 if (!panels.contains(panel)) { 273 panels.add(panel); 274 } 275 } 276 277 public void deleteLayoutEditor(LayoutEditor panel) { 278 // remove from the panels list if there 279 if (panels.contains(panel)) { 280 panels.remove(panel); 281 } 282 } 283 284 public boolean isOnPanel(LayoutEditor panel) { 285 // returns true if this Layout Block is used on panel 286 return panels.contains(panel); 287 } 288 289 /** 290 * Redraw panels using this layout block. 291 */ 292 public void redrawLayoutBlockPanels() { 293 panels.stream().forEach(LayoutEditor::redrawPanel); 294 firePropertyChange("redraw", null, null); 295 } 296 297 /** 298 * Validate that the supplied occupancy sensor name corresponds to an 299 * existing sensor and is unique among all blocks. If valid, returns the 300 * sensor and sets the block sensor name in the block. Else returns null, 301 * and does nothing to the block. 302 * 303 * @param sensorName to check 304 * @param openFrame determines the <code>Frame</code> in which the dialog 305 * is displayed; if <code>null</code>, or if the 306 * <code>parentComponent</code> has no <code>Frame</code>, 307 * a default <code>Frame</code> is used 308 * @return the validated sensor 309 */ 310 public Sensor validateSensor(String sensorName, Component openFrame) { 311 // check if anything entered 312 if ((sensorName == null) || sensorName.isEmpty()) { 313 // no sensor name entered 314 if (occupancyNamedSensor != null) { 315 setOccupancySensorName(null); 316 } 317 return null; 318 } 319 320 // get the sensor corresponding to this name 321 Sensor s = InstanceManager.sensorManagerInstance().getSensor(sensorName); 322 if (s == null) { 323 // There is no sensor corresponding to this name 324 JmriJOptionPane.showMessageDialog(openFrame, 325 java.text.MessageFormat.format(Bundle.getMessage("Error7"), 326 new Object[]{sensorName}), 327 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 328 return null; 329 } 330 331 // ensure that this sensor is unique among defined Layout Blocks 332 NamedBeanHandle<Sensor> savedNamedSensor = occupancyNamedSensor; 333 occupancyNamedSensor = null; 334 LayoutBlock b = InstanceManager.getDefault(LayoutBlockManager.class). 335 getBlockWithSensorAssigned(s); 336 337 if (b != this) { 338 if (b != null) { 339 if (b.getUseCount() > 0) { 340 // new sensor is not unique, return to the old one 341 occupancyNamedSensor = savedNamedSensor; 342 JmriJOptionPane.showMessageDialog(openFrame, 343 java.text.MessageFormat.format(Bundle.getMessage("Error6"), 344 new Object[]{sensorName, b.getId()}), 345 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 346 return null; 347 } else { 348 // the user is assigning a sensor which is already assigned to 349 // layout block b. Layout block b is no longer in use so this 350 // should be fine but it's technically possible to put 351 // this discarded layout block back into service (possibly 352 // by mistake) by entering its name in any edit layout block window. 353 // That would cause a problem with the sensor being in use in 354 // two active blocks, so as a precaution we remove the sensor 355 // from the discarded block here. 356 b.setOccupancySensorName(null); 357 } 358 } 359 // sensor is unique, or was only in use on a layout block not in use 360 setOccupancySensorName(sensorName); 361 } 362 return s; 363 } 364 365 /** 366 * Validate that the memory name corresponds to an existing memory. If 367 * valid, returns the memory. Else returns null, and notifies the user. 368 * 369 * @param memName the memory name 370 * @param openFrame the frame to display any error dialog in 371 * @return the memory 372 */ 373 public Memory validateMemory(String memName, Component openFrame) { 374 // check if anything entered 375 if ((memName == null) || memName.isEmpty()) { 376 // no memory entered 377 return null; 378 } 379 // get the memory corresponding to this name 380 Memory m = InstanceManager.memoryManagerInstance().getMemory(memName); 381 if (m == null) { 382 // There is no memory corresponding to this name 383 JmriJOptionPane.showMessageDialog(openFrame, 384 java.text.MessageFormat.format(Bundle.getMessage("Error16"), 385 new Object[]{memName}), 386 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 387 return null; 388 } 389 memoryName = memName; 390 391 // Go through the memory icons on the panel and see if any are linked to this layout block 392 if ((m != getMemory()) && (panels.size() > 0)) { 393 boolean updateall = false; 394 boolean found = false; 395 for (LayoutEditor panel : panels) { 396 for (MemoryIcon memIcon : panel.getMemoryLabelList()) { 397 if (memIcon.getLayoutBlock() == this) { 398 if (!updateall && !found) { 399 int n = JmriJOptionPane.showConfirmDialog( 400 openFrame, 401 "Would you like to update all memory icons on the panel linked to the block to use the new one?", 402 "Update Memory Icons", 403 JmriJOptionPane.YES_NO_OPTION); 404 // TODO I18N in Bundle.properties 405 found = true; 406 if (n == JmriJOptionPane.YES_OPTION ) { 407 updateall = true; 408 } 409 } 410 if (updateall) { 411 memIcon.setMemory(memoryName); 412 } 413 } 414 } 415 } 416 } 417 return m; 418 } 419 420 /** 421 * Get the color for drawing items in this block. Returns color based on 422 * block occupancy. 423 * 424 * @return color for block 425 */ 426 public Color getBlockColor() { 427 if (getOccupancy() == OCCUPIED) { 428 return blockOccupiedColor; 429 } else if (useExtraColor) { 430 return blockExtraColor; 431 } else { 432 return blockTrackColor; 433 } 434 } 435 436 /** 437 * Get the Block corresponding to this LayoutBlock. 438 * 439 * @return block 440 */ 441 public Block getBlock() { 442 return block; 443 } 444 445 /** 446 * Returns Memory name 447 * 448 * @return name of memory 449 */ 450 public String getMemoryName() { 451 if (namedMemory != null) { 452 return namedMemory.getName(); 453 } 454 return memoryName; 455 } 456 457 /** 458 * Get Memory. 459 * 460 * @return memory bean 461 */ 462 public Memory getMemory() { 463 if (namedMemory == null) { 464 setMemoryName(memoryName); 465 } 466 if (namedMemory != null) { 467 return namedMemory.getBean(); 468 } 469 return null; 470 } 471 472 /** 473 * Add Memory by name. 474 * 475 * @param name for memory 476 */ 477 public void setMemoryName(String name) { 478 if ((name == null) || name.isEmpty()) { 479 namedMemory = null; 480 memoryName = ""; 481 return; 482 } 483 memoryName = name; 484 Memory memory = InstanceManager.memoryManagerInstance().getMemory(name); 485 if (memory != null) { 486 namedMemory = InstanceManager.getDefault(NamedBeanHandleManager.class).getNamedBeanHandle(name, memory); 487 } 488 } 489 490 public void setMemory(Memory m, String name) { 491 if (m == null) { 492 namedMemory = null; 493 memoryName = name == null ? "" : name; 494 return; 495 } 496 namedMemory = InstanceManager.getDefault(NamedBeanHandleManager.class).getNamedBeanHandle(name, m); 497 } 498 499 /** 500 * Get occupancy Sensor name. 501 * 502 * @return name of occupancy sensor 503 */ 504 public String getOccupancySensorName() { 505 if (occupancyNamedSensor == null) { 506 if (block != null) { 507 occupancyNamedSensor = block.getNamedSensor(); 508 } 509 } 510 if (occupancyNamedSensor != null) { 511 return occupancyNamedSensor.getName(); 512 } 513 return occupancySensorName; 514 } 515 516 /** 517 * Get occupancy Sensor. 518 * <p> 519 * If a sensor has not been assigned, try getting the sensor from the related 520 * block. 521 * <p> 522 * When setting the layout block sensor from the block itself using the OccupancySensorChange 523 * event, the automatic assignment has to be disabled for the sensor checking performed by 524 * {@link jmri.jmrit.display.layoutEditor.LayoutBlockManager#getBlockWithSensorAssigned} 525 * 526 * @return occupancy sensor or null 527 */ 528 public Sensor getOccupancySensor() { 529 if (occupancyNamedSensor == null && setSensorFromBlockEnabled) { 530 if (block != null) { 531 occupancyNamedSensor = block.getNamedSensor(); 532 } 533 } 534 if (occupancyNamedSensor != null) { 535 return occupancyNamedSensor.getBean(); 536 } 537 return null; 538 } 539 540 /** 541 * Add occupancy sensor by name. 542 * 543 * @param name for senor to add 544 */ 545 public void setOccupancySensorName(String name) { 546 if ((name == null) || name.isEmpty()) { 547 if (occupancyNamedSensor != null) { 548 occupancyNamedSensor.getBean().removePropertyChangeListener(mBlockListener); 549 } 550 occupancyNamedSensor = null; 551 occupancySensorName = ""; 552 553 if (block != null) { 554 block.setNamedSensor(null); 555 } 556 return; 557 } 558 occupancySensorName = name; 559 Sensor sensor = InstanceManager.sensorManagerInstance().getSensor(name); 560 if (sensor != null) { 561 occupancyNamedSensor = InstanceManager.getDefault( 562 NamedBeanHandleManager.class).getNamedBeanHandle(name, sensor); 563 if (block != null) { 564 block.setNamedSensor(occupancyNamedSensor); 565 } 566 } 567 } 568 569 /** 570 * Get occupied sensor state. 571 * 572 * @return occupied sensor state, defaults to Sensor.ACTIVE 573 */ 574 public int getOccupiedSense() { 575 return occupiedSense; 576 } 577 578 /** 579 * Set occupied sensor state. 580 * 581 * @param sense eg. Sensor.INACTIVE 582 */ 583 public void setOccupiedSense(int sense) { 584 occupiedSense = sense; 585 } 586 587 /** 588 * Test block occupancy. 589 * 590 * @return occupancy state 591 */ 592 public int getOccupancy() { 593 if (occupancyNamedSensor == null) { 594 Sensor s = null; 595 if (!occupancySensorName.isEmpty()) { 596 s = InstanceManager.sensorManagerInstance().getSensor(occupancySensorName); 597 } 598 if (s == null) { 599 // no occupancy sensor, so base upon block occupancy state 600 if (block != null) { 601 return block.getState(); 602 } 603 // if no block or sensor return unknown 604 return UNKNOWN; 605 } 606 occupancyNamedSensor = InstanceManager.getDefault( 607 NamedBeanHandleManager.class).getNamedBeanHandle(occupancySensorName, s); 608 if (block != null) { 609 block.setNamedSensor(occupancyNamedSensor); 610 } 611 } 612 613 Sensor s = getOccupancySensor(); 614 if ( s == null) { 615 return UNKNOWN; 616 } 617 618 if (s.getKnownState() != occupiedSense) { 619 return EMPTY; 620 } else if (s.getKnownState() == occupiedSense) { 621 return OCCUPIED; 622 } 623 return UNKNOWN; 624 } 625 626 @Override 627 public int getState() { 628 return getOccupancy(); 629 } 630 631 /** 632 * Does nothing, do not use.Dummy for completion of NamedBean interface 633 * @param i does nothing 634 */ 635 @Override 636 public void setState(int i) { 637 log.error("this state does nothing {}", getDisplayName()); 638 } 639 640 /** 641 * Get the panel with the highest connectivity to this Layout Block. 642 * 643 * @return panel with most connections to this block 644 */ 645 public LayoutEditor getMaxConnectedPanel() { 646 LayoutEditor result = null; 647 // a block is attached and this LayoutBlock is used 648 if ((block != null) && (panels.size() > 0)) { 649 // initialize connectivity as defined in first Layout Editor panel 650 int maxConnectivity = Integer.MIN_VALUE; 651 for (LayoutEditor panel : panels) { 652 List<LayoutConnectivity> c = panel.getLEAuxTools().getConnectivityList(this); 653 if (maxConnectivity < c.size()) { 654 maxConnectivity = c.size(); 655 result = panel; 656 } 657 } 658 } 659 return result; 660 } 661 662 /** 663 * Check/Update Path objects for the attached Block 664 * <p> 665 * If multiple panels are present, Paths are set according to the panel with 666 * the highest connectivity (most LayoutConnectivity objects). 667 */ 668 public void updatePaths() { 669 // Update paths is called by the panel, turnouts, xings, track segments etc 670 if ((block != null) && !panels.isEmpty()) { 671 // a block is attached and this LayoutBlock is used 672 // initialize connectivity as defined in first Layout Editor panel 673 LayoutEditor panel = panels.get(0); 674 List<LayoutConnectivity> c = panel.getLEAuxTools().getConnectivityList(this); 675 676 // if more than one panel, find panel with the highest connectivity 677 if (panels.size() > 1) { 678 for (int i = 1; i < panels.size(); i++) { 679 if (c.size() < panels.get(i).getLEAuxTools(). 680 getConnectivityList(this).size()) { 681 panel = panels.get(i); 682 c = panel.getLEAuxTools().getConnectivityList(this); 683 } 684 } 685 686 // Now try to determine if this block is across two panels due to a linked point 687 PositionablePoint point = panel.getFinder().findPositionableLinkPoint(this); 688 if ((point != null) && (point.getLinkedEditor() != null) && panels.contains(point.getLinkedEditor())) { 689 c = panel.getLEAuxTools().getConnectivityList(this); 690 c.addAll(point.getLinkedEditor().getLEAuxTools().getConnectivityList(this)); 691 } else { 692 // check that this connectivity is compatible with that of other panels. 693 for (LayoutEditor tPanel : panels) { 694 if ((tPanel != panel) && InstanceManager.getDefault( 695 LayoutBlockManager.class).warn() 696 && (!compareConnectivity(c, tPanel.getLEAuxTools().getConnectivityList(this)))) { 697 // send user an error message 698 int response = JmriJOptionPane.showOptionDialog(null, 699 java.text.MessageFormat.format(Bundle.getMessage("Warn1"), 700 new Object[]{getUserName(), tPanel.getLayoutName(), panel.getLayoutName()}), 701 Bundle.getMessage("WarningTitle"), 702 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, 703 null, 704 new Object[]{Bundle.getMessage("ButtonOK"), Bundle.getMessage("ButtonOKPlus")}, 705 Bundle.getMessage("ButtonOK")); 706 if (response == 1 ) { // ButtokOKPlus pressed, user elected to disable messages 707 InstanceManager.getDefault( 708 LayoutBlockManager.class).turnOffWarning(); 709 } 710 } 711 } 712 } 713 } 714 // update block Paths to reflect connectivity as needed 715 updateBlockPaths(c, panel); 716 } 717 } 718 719 /** 720 * Check/Update Path objects for the attached Block using the connectivity 721 * in the specified Layout Editor panel. 722 * 723 * @param panel to extract paths 724 */ 725 public void updatePathsUsingPanel(LayoutEditor panel) { 726 if (panel == null) { 727 log.error("Null panel in call to updatePathsUsingPanel"); 728 return; 729 } 730 List<LayoutConnectivity> c = panel.getLEAuxTools().getConnectivityList(this); 731 updateBlockPaths(c, panel); 732 733 } 734 735 private void updateBlockPaths(List<LayoutConnectivity> c, LayoutEditor panel) { 736 if (enableAddRouteLogging) { 737 log.info("From {} updateBlockPaths Called", this.getDisplayName()); 738 } 739 auxTools = panel.getLEAuxTools(); 740 List<Path> paths = block.getPaths(); 741 boolean[] used = new boolean[c.size()]; 742 int[] need = new int[paths.size()]; 743 Arrays.fill(used, false); 744 Arrays.fill(need, -1); 745 746 // cycle over existing Paths, checking against LayoutConnectivity 747 for (int i = 0; i < paths.size(); i++) { 748 Path p = paths.get(i); 749 750 // cycle over LayoutConnectivity matching to this Path 751 for (int j = 0; ((j < c.size()) && (need[i] == -1)); j++) { 752 if (!used[j]) { 753 // this LayoutConnectivity not used yet 754 LayoutConnectivity lc = c.get(j); 755 if ((lc.getBlock1().getBlock() == p.getBlock()) || (lc.getBlock2().getBlock() == p.getBlock())) { 756 // blocks match - record 757 used[j] = true; 758 need[i] = j; 759 } 760 } 761 } 762 } 763 764 // update needed Paths 765 for (int i = 0; i < paths.size(); i++) { 766 if (need[i] >= 0) { 767 Path p = paths.get(i); 768 LayoutConnectivity lc = c.get(need[i]); 769 if (lc.getBlock1() == this) { 770 p.setToBlockDirection(lc.getDirection()); 771 p.setFromBlockDirection(lc.getReverseDirection()); 772 } else { 773 p.setToBlockDirection(lc.getReverseDirection()); 774 p.setFromBlockDirection(lc.getDirection()); 775 } 776 List<BeanSetting> beans = new ArrayList<>(p.getSettings()); 777 for (BeanSetting bean : beans) { 778 p.removeSetting(bean); 779 } 780 auxTools.addBeanSettings(p, lc, this); 781 } 782 } 783 // delete unneeded Paths 784 for (int i = 0; i < paths.size(); i++) { 785 if (need[i] < 0) { 786 block.removePath(paths.get(i)); 787 if (InstanceManager.getDefault( 788 LayoutBlockManager.class).isAdvancedRoutingEnabled()) { 789 removeAdjacency(paths.get(i)); 790 } 791 } 792 } 793 794 // add Paths as required 795 for (int j = 0; j < c.size(); j++) { 796 if (!used[j]) { 797 // there is no corresponding Path, add one. 798 LayoutConnectivity lc = c.get(j); 799 Path newp; 800 801 if (lc.getBlock1() == this) { 802 newp = new Path(lc.getBlock2().getBlock(), lc.getDirection(), 803 lc.getReverseDirection()); 804 } else { 805 newp = new Path(lc.getBlock1().getBlock(), lc.getReverseDirection(), 806 lc.getDirection()); 807 } 808 block.addPath(newp); 809 810 if (enableAddRouteLogging) { 811 log.info("From {} addPath({})", this.getDisplayName(), newp.toString()); 812 } 813 814 if (InstanceManager.getDefault(LayoutBlockManager.class).isAdvancedRoutingEnabled()) { 815 addAdjacency(newp); 816 } 817 auxTools.addBeanSettings(newp, lc, this); 818 } 819 } 820 821 // djd debugging - lists results of automatic initialization of Paths and BeanSettings 822 if (log.isDebugEnabled()) { 823 block.getPaths().stream().forEach((p) -> log.debug("From {} to {}", getDisplayName(), p.toString())); 824 } 825 } 826 827 /** 828 * Make sure all the layout connectivity objects in test are in main. 829 * 830 * @param main the main list of LayoutConnectivity objects 831 * @param test the test list of LayoutConnectivity objects 832 * @return true if all test layout connectivity objects are in main 833 */ 834 private boolean compareConnectivity(List<LayoutConnectivity> main, List<LayoutConnectivity> test) { 835 boolean result = false; // assume failure (pessimsit!) 836 if (!main.isEmpty() && !test.isEmpty()) { 837 result = true; // assume success (optimist!) 838 // loop over connectivities in test list 839 for (LayoutConnectivity tc : test) { 840 LayoutBlock tlb1 = tc.getBlock1(), tlb2 = tc.getBlock2(); 841 // loop over main list to make sure the same blocks are connected 842 boolean found = false; // assume failure (pessimsit!) 843 for (LayoutConnectivity mc : main) { 844 LayoutBlock mlb1 = mc.getBlock1(), mlb2 = mc.getBlock2(); 845 if (((tlb1 == mlb1) && (tlb2 == mlb2)) 846 || ((tlb1 == mlb2) && (tlb2 == mlb1))) { 847 found = true; // success! 848 break; 849 } 850 } 851 if (!found) { 852 result = false; 853 break; 854 } 855 } 856 } else if (main.isEmpty() && test.isEmpty()) { 857 result = true; // OK if both have no neighbors, common for turntable rays 858 } 859 return result; 860 } 861 862 /** 863 * Handle tasks when block changes 864 * 865 * @param e propChgEvent 866 */ 867 void handleBlockChange(PropertyChangeEvent e) { 868 // Update memory object if there is one 869 Memory m = getMemory(); 870 if ((m != null) && (block != null) && !suppressNameUpdate) { 871 // copy block value to memory if there is a value 872 Object val = block.getValue(); 873 if (val != null) { 874 if (!(val instanceof RosterEntry) && !(val instanceof Reportable)) { 875 val = val.toString(); 876 } 877 } 878 m.setValue(val); 879 } 880 881 if (e.getPropertyName().equals("UserName")) { 882 setUserName(e.getNewValue().toString()); 883 InstanceManager.getDefault(NamedBeanHandleManager.class). 884 renameBean(e.getOldValue().toString(), e.getNewValue().toString(), this); 885 } 886 887 if (e.getPropertyName().equals("OccupancySensorChange")) { 888 if (e.getNewValue() == null){ 889 // Remove Sensor 890 setOccupancySensorName(null); 891 } else { 892 // Set/change sensor 893 Sensor sensor = (Sensor) e.getNewValue(); 894 setSensorFromBlockEnabled = false; 895 if (validateSensor(sensor.getSystemName(), null) == null) { 896 // Sensor change rejected, reset block sensor assignment 897 Sensor origSensor = (Sensor) e.getOldValue(); 898 block.setSensor(origSensor == null ? "" : origSensor.getSystemName()); 899 } 900 setSensorFromBlockEnabled = true; 901 } 902 } 903 904 // Redraw all Layout Editor panels using this Layout Block 905 redrawLayoutBlockPanels(); 906 907 if (InstanceManager.getDefault(LayoutBlockManager.class).isAdvancedRoutingEnabled()) { 908 stateUpdate(); 909 } 910 } 911 912 /** 913 * Deactivate block listener for redraw of panels and update of memories on 914 * change of state 915 */ 916 private void deactivateBlock() { 917 if ((mBlockListener != null) && (block != null)) { 918 block.removePropertyChangeListener(mBlockListener); 919 } 920 mBlockListener = null; 921 } 922 923 /** 924 * Set/reset update of memory name when block goes from occupied to 925 * unoccupied or vice versa. If set is true, name update is suppressed. If 926 * set is false, name update works normally. 927 * 928 * @param set true, update suppress. false, update normal 929 */ 930 public void setSuppressNameUpdate(boolean set) { 931 suppressNameUpdate = set; 932 } 933 934 935 private final NamedBeanComboBox<Memory> memoryComboBox = new NamedBeanComboBox<>( 936 InstanceManager.getDefault(MemoryManager.class), null, DisplayOptions.DISPLAYNAME); 937 938 private final JTextField metricField = new JTextField(10); 939 940 private final JComboBox<String> senseBox = new JComboBox<>(); 941 942 // TODO I18N in Bundle.properties 943 private int senseActiveIndex; 944 private int senseInactiveIndex; 945 946 private JColorChooser trackColorChooser = null; 947 private JColorChooser occupiedColorChooser = null; 948 private JColorChooser extraColorChooser = null; 949 950 public void editLayoutBlock(Component callingPane) { 951 LayoutBlockEditAction beanEdit = new LayoutBlockEditAction(); 952 if (block == null) { 953 // Block may not have been initialised due to an error so manually set it in the edit window 954 String userName = getUserName(); 955 if ((userName != null) && !userName.isEmpty()) { 956 Block b = InstanceManager.getDefault(BlockManager.class).getBlock(userName); 957 if (b != null) { 958 beanEdit.setBean(b); 959 } 960 } 961 } else { 962 beanEdit.setBean(block); 963 } 964 beanEdit.actionPerformed(null); 965 } 966 967 private final String[] working = {"Bi-Directional", "Receive Only", "Send Only"}; 968 969 // TODO I18N in ManagersBundle.properties 970 protected List<JComboBox<String>> neighbourDir; 971 972 protected class LayoutBlockEditAction extends BlockEditAction { 973 974 @Override 975 public String helpTarget() { 976 return "package.jmri.jmrit.display.EditLayoutBlock"; 977 } // NOI18N 978 979 @Override 980 protected void initPanels() { 981 super.initPanels(); 982 BeanItemPanel ld = layoutDetails(); 983 if (InstanceManager.getDefault(LayoutBlockManager.class).isAdvancedRoutingEnabled()) { 984 blockRoutingDetails(); 985 } 986 setSelectedComponent(ld); 987 } 988 989 BeanItemPanel layoutDetails() { 990 BeanItemPanel layout = new BeanItemPanel(); 991 layout.setName(Bundle.getMessage("LayoutEditor")); 992 993 LayoutEditor.setupComboBox(memoryComboBox, false, true, false); 994 995 layout.addItem(new BeanEditItem(new JLabel("" + useCount), Bundle.getMessage("UseCount"), null)); 996 layout.addItem(new BeanEditItem(memoryComboBox, Bundle.getMessage("BeanNameMemory"), 997 Bundle.getMessage("MemoryVariableTip"))); 998 999 senseBox.removeAllItems(); 1000 senseBox.addItem(Bundle.getMessage("SensorStateActive")); 1001 senseActiveIndex = 0; 1002 senseBox.addItem(Bundle.getMessage("SensorStateInactive")); 1003 senseInactiveIndex = 1; 1004 1005 layout.addItem(new BeanEditItem(senseBox, Bundle.getMessage("OccupiedSense"), Bundle.getMessage("OccupiedSenseHint"))); 1006 1007 trackColorChooser = new JColorChooser(blockTrackColor); 1008 trackColorChooser.setPreviewPanel(new JPanel()); // remove the preview panel 1009 AbstractColorChooserPanel[] trackColorPanels = {new SplitButtonColorChooserPanel()}; 1010 trackColorChooser.setChooserPanels(trackColorPanels); 1011 layout.addItem(new BeanEditItem(trackColorChooser, Bundle.getMessage("TrackColor"), Bundle.getMessage("TrackColorHint"))); 1012 1013 occupiedColorChooser = new JColorChooser(blockOccupiedColor); 1014 occupiedColorChooser.setPreviewPanel(new JPanel()); // remove the preview panel 1015 AbstractColorChooserPanel[] occupiedColorPanels = {new SplitButtonColorChooserPanel()}; 1016 occupiedColorChooser.setChooserPanels(occupiedColorPanels); 1017 layout.addItem(new BeanEditItem(occupiedColorChooser, Bundle.getMessage("OccupiedColor"), Bundle.getMessage("OccupiedColorHint"))); 1018 1019 extraColorChooser = new JColorChooser(blockExtraColor); 1020 extraColorChooser.setPreviewPanel(new JPanel()); // remove the preview panel 1021 AbstractColorChooserPanel[] extraColorPanels = {new SplitButtonColorChooserPanel()}; 1022 extraColorChooser.setChooserPanels(extraColorPanels); 1023 layout.addItem(new BeanEditItem(extraColorChooser, Bundle.getMessage("ExtraColor"), Bundle.getMessage("ExtraColorHint"))); 1024 1025 layout.setSaveItem(new AbstractAction() { 1026 @Override 1027 public void actionPerformed(ActionEvent e) { 1028 boolean needsRedraw = false; 1029 int k = senseBox.getSelectedIndex(); 1030 int oldSense = occupiedSense; 1031 1032 if (k == senseActiveIndex) { 1033 occupiedSense = Sensor.ACTIVE; 1034 } else { 1035 occupiedSense = Sensor.INACTIVE; 1036 } 1037 1038 if (oldSense != occupiedSense) { 1039 needsRedraw = true; 1040 } 1041 // check if track color changed 1042 Color oldColor = blockTrackColor; 1043 blockTrackColor = trackColorChooser.getColor(); 1044 if (oldColor != blockTrackColor) { 1045 needsRedraw = true; 1046 JmriColorChooser.addRecentColor(blockTrackColor); 1047 } 1048 // check if occupied color changed 1049 oldColor = blockOccupiedColor; 1050 blockOccupiedColor = occupiedColorChooser.getColor(); 1051 if (oldColor != blockOccupiedColor) { 1052 needsRedraw = true; 1053 JmriColorChooser.addRecentColor(blockOccupiedColor); 1054 } 1055 // check if extra color changed 1056 oldColor = blockExtraColor; 1057 blockExtraColor = extraColorChooser.getColor(); 1058 if (oldColor != blockExtraColor) { 1059 needsRedraw = true; 1060 JmriColorChooser.addRecentColor(blockExtraColor); 1061 } 1062 // check if Memory changed 1063 String newName = memoryComboBox.getSelectedItemDisplayName(); 1064 if (newName == null) { 1065 newName = ""; 1066 } 1067 if (!memoryName.equals(newName)) { 1068 // memory has changed 1069 setMemory(validateMemory(newName, null), newName); 1070 if (getMemory() == null) { 1071 // invalid memory entered 1072 memoryName = ""; 1073 memoryComboBox.setSelectedItem(null); 1074 return; 1075 } else { 1076 memoryComboBox.setSelectedItem(getMemory()); 1077 needsRedraw = true; 1078 } 1079 } 1080 1081 if (needsRedraw) { 1082 redrawLayoutBlockPanels(); 1083 } 1084 } 1085 }); 1086 1087 layout.setResetItem(new AbstractAction() { 1088 @Override 1089 public void actionPerformed(ActionEvent e) { 1090 memoryComboBox.setSelectedItem(getMemory()); 1091 trackColorChooser.setColor(blockTrackColor); 1092 occupiedColorChooser.setColor(blockOccupiedColor); 1093 extraColorChooser.setColor(blockExtraColor); 1094 if (occupiedSense == Sensor.ACTIVE) { 1095 senseBox.setSelectedIndex(senseActiveIndex); 1096 } else { 1097 senseBox.setSelectedIndex(senseInactiveIndex); 1098 } 1099 } 1100 }); 1101 bei.add(layout); 1102 return layout; 1103 } 1104 1105 BeanItemPanel blockRoutingDetails() { 1106 BeanItemPanel routing = new BeanItemPanel(); 1107 routing.setName("Routing"); 1108 1109 routing.addItem(new BeanEditItem(metricField, "Block Metric", "set the cost for going over this block")); 1110 1111 routing.addItem(new BeanEditItem(null, null, "Set the direction of the connection to the neighbouring block")); 1112 neighbourDir = new ArrayList<>(getNumberOfNeighbours()); 1113 for (int i = 0; i < getNumberOfNeighbours(); i++) { 1114 JComboBox<String> dir = new JComboBox<>(working); 1115 routing.addItem(new BeanEditItem(dir, getNeighbourAtIndex(i).getDisplayName(), null)); 1116 neighbourDir.add(dir); 1117 } 1118 1119 routing.setResetItem(new AbstractAction() { 1120 @Override 1121 public void actionPerformed(ActionEvent e) { 1122 metricField.setText(Integer.toString(metric)); 1123 for (int i = 0; i < getNumberOfNeighbours(); i++) { 1124 JComboBox<String> dir = neighbourDir.get(i); 1125 Block blk = neighbours.get(i).getBlock(); 1126 if (block.isBlockDenied(blk)) { 1127 dir.setSelectedIndex(2); 1128 } else if (blk.isBlockDenied(block)) { 1129 dir.setSelectedIndex(1); 1130 } else { 1131 dir.setSelectedIndex(0); 1132 } 1133 } 1134 } 1135 }); 1136 1137 routing.setSaveItem(new AbstractAction() { 1138 @Override 1139 public void actionPerformed(ActionEvent e) { 1140 int m = Integer.parseInt(metricField.getText().trim()); 1141 if (m != metric) { 1142 setBlockMetric(m); 1143 } 1144 if (neighbourDir != null) { 1145 for (int i = 0; i < neighbourDir.size(); i++) { 1146 int neigh = neighbourDir.get(i).getSelectedIndex(); 1147 neighbours.get(i).getBlock().removeBlockDenyList(block); 1148 block.removeBlockDenyList(neighbours.get(i).getBlock()); 1149 switch (neigh) { 1150 case 0: { 1151 updateNeighbourPacketFlow(neighbours.get(i), RXTX); 1152 break; 1153 } 1154 1155 case 1: { 1156 neighbours.get(i).getBlock().addBlockDenyList(block.getDisplayName()); 1157 updateNeighbourPacketFlow(neighbours.get(i), TXONLY); 1158 break; 1159 } 1160 1161 case 2: { 1162 block.addBlockDenyList(neighbours.get(i).getBlock().getDisplayName()); 1163 updateNeighbourPacketFlow(neighbours.get(i), RXONLY); 1164 break; 1165 } 1166 1167 default: { 1168 break; 1169 } 1170 } 1171 /* switch */ 1172 } 1173 } 1174 } 1175 }); 1176 bei.add(routing); 1177 return routing; 1178 } 1179 } 1180 1181 /** 1182 * Remove this object from display and persistance. 1183 */ 1184 void remove() { 1185 // if an occupancy sensor has been activated, deactivate it 1186 deactivateBlock(); 1187 // remove from persistance by flagging inactive 1188 active = false; 1189 } 1190 1191 boolean active = true; 1192 1193 /** 1194 * "active" is true if the object is still displayed, and should be stored. 1195 * 1196 * @return active 1197 */ 1198 public boolean isActive() { 1199 return active; 1200 } 1201 1202 /* 1203 The code below relates to the layout block routing protocol 1204 */ 1205 /** 1206 * Set the block metric based upon the track segment that the block is 1207 * associated with if the (200 if Side, 50 if Main). If the block is 1208 * assigned against multiple track segments all with different types then 1209 * the highest type will be used. In theory no reason why it couldn't be a 1210 * compromise. 1211 */ 1212 void setBlockMetric() { 1213 if (!defaultMetric) { 1214 return; 1215 } 1216 if (enableUpdateRouteLogging) { 1217 log.info("From '{}' default set block metric called", this.getDisplayName()); 1218 } 1219 LayoutEditor panel = getMaxConnectedPanel(); 1220 if (panel == null) { 1221 if (enableUpdateRouteLogging) { 1222 log.info("From '{}' unable to set metric as we are not connected to a panel yet", this.getDisplayName()); 1223 } 1224 return; 1225 } 1226 String userName = getUserName(); 1227 if (userName == null) { 1228 log.info("From '{}': unable to get user name", this.getDisplayName()); 1229 return; 1230 } 1231 List<TrackSegment> ts = panel.getFinder().findTrackSegmentByBlock(userName); 1232 int mainline = 0; 1233 int side = 0; 1234 1235 for (TrackSegment t : ts) { 1236 if (t.isMainline()) { 1237 mainline++; 1238 } else { 1239 side++; 1240 } 1241 } 1242 1243 if (mainline > side) { 1244 metric = 50; 1245 } else if (mainline < side) { 1246 metric = 200; 1247 } else { 1248 // They must both be equal so will set as a mainline. 1249 metric = 50; 1250 } 1251 1252 if (enableUpdateRouteLogging) { 1253 log.info("From '{}' metric set to {}", this.getDisplayName(), metric); 1254 } 1255 1256 // What we need to do here, is resend our routing packets with the new metric 1257 RoutingPacket update = new RoutingPacket(UPDATE, this.getBlock(), -1, metric, -1, -1, getNextPacketID()); 1258 firePropertyChange("routing", null, update); 1259 } 1260 1261 private boolean defaultMetric = true; 1262 1263 public boolean useDefaultMetric() { 1264 return defaultMetric; 1265 } 1266 1267 public void useDefaultMetric(boolean boo) { 1268 if (boo == defaultMetric) { 1269 return; 1270 } 1271 defaultMetric = boo; 1272 if (boo) { 1273 setBlockMetric(); 1274 } 1275 } 1276 1277 /** 1278 * Set a metric cost against a block, this is used in the calculation of a 1279 * path between two location on the layout, a lower path cost is always 1280 * preferred For Layout blocks defined as Mainline the default metric is 50. 1281 * For Layout blocks defined as a Siding the default metric is 200. 1282 * 1283 * @param m metric value 1284 */ 1285 public void setBlockMetric(int m) { 1286 if (metric == m) { 1287 return; 1288 } 1289 metric = m; 1290 defaultMetric = false; 1291 RoutingPacket update = new RoutingPacket(UPDATE, this.getBlock(), -1, metric, -1, -1, getNextPacketID()); 1292 firePropertyChange("routing", null, update); 1293 } 1294 1295 /** 1296 * Get the layout block metric cost 1297 * 1298 * @return metric cost of block 1299 */ 1300 public int getBlockMetric() { 1301 return metric; 1302 } 1303 1304 // re work this so that is makes beter us of existing code. 1305 // This is no longer required currently, but might be used at a later date. 1306 public void addAllThroughPaths() { 1307 if (enableAddRouteLogging) { 1308 log.info("Add all ThroughPaths {}", this.getDisplayName()); 1309 } 1310 1311 if ((block != null) && (panels.size() > 0)) { 1312 // a block is attached and this LayoutBlock is used 1313 // initialize connectivity as defined in first Layout Editor panel 1314 LayoutEditor panel = panels.get(0); 1315 List<LayoutConnectivity> c = panel.getLEAuxTools().getConnectivityList(this); 1316 1317 // if more than one panel, find panel with the highest connectivity 1318 if (panels.size() > 1) { 1319 for (int i = 1; i < panels.size(); i++) { 1320 if (c.size() < panels.get(i).getLEAuxTools(). 1321 getConnectivityList(this).size()) { 1322 panel = panels.get(i); 1323 c = panel.getLEAuxTools().getConnectivityList(this); 1324 } 1325 } 1326 1327 // check that this connectivity is compatible with that of other panels. 1328 for (LayoutEditor tPanel : panels) { 1329 if ((tPanel != panel) 1330 && InstanceManager.getDefault(LayoutBlockManager.class). 1331 warn() && (!compareConnectivity(c, tPanel.getLEAuxTools().getConnectivityList(this)))) { 1332 1333 // send user an error message 1334 int response = JmriJOptionPane.showOptionDialog(null, 1335 java.text.MessageFormat.format(Bundle.getMessage("Warn1"), 1336 new Object[]{getUserName(), tPanel.getLayoutName(), 1337 panel.getLayoutName()}), Bundle.getMessage("WarningTitle"), 1338 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, 1339 null, 1340 new Object[]{Bundle.getMessage("ButtonOK"), Bundle.getMessage("ButtonOKPlus")}, 1341 Bundle.getMessage("ButtonOK")); 1342 if (response == 1) { // array position 1 ButtonOKPlus pressed, user elected to disable messages 1343 InstanceManager.getDefault(LayoutBlockManager.class).turnOffWarning(); 1344 } 1345 } 1346 } 1347 } 1348 auxTools = panel.getLEAuxTools(); 1349 List<LayoutConnectivity> d = auxTools.getConnectivityList(this); 1350 List<LayoutBlock> attachedBlocks = new ArrayList<>(); 1351 1352 for (LayoutConnectivity connectivity : d) { 1353 if (connectivity.getBlock1() != this) { 1354 attachedBlocks.add(connectivity.getBlock1()); 1355 } else { 1356 attachedBlocks.add(connectivity.getBlock2()); 1357 } 1358 } 1359 // Will need to re-look at this to cover both way and single way routes 1360 for (LayoutBlock attachedBlock : attachedBlocks) { 1361 if (enableAddRouteLogging) { 1362 log.info("From {} block is attached {}", this.getDisplayName(), attachedBlock.getDisplayName()); 1363 } 1364 1365 for (LayoutBlock layoutBlock : attachedBlocks) { 1366 addThroughPath(attachedBlock.getBlock(), layoutBlock.getBlock(), panel); 1367 } 1368 } 1369 } 1370 } 1371 1372 // TODO: if the block already exists, we still may want to re-work the through paths 1373 // With this bit we need to get our neighbour to send new routes 1374 private void addNeighbour(Block addBlock, int direction, int workingDirection) { 1375 boolean layoutConnectivityBefore = layoutConnectivity; 1376 1377 if (enableAddRouteLogging) { 1378 log.info("From {} asked to add block {} as new neighbour {}", this.getDisplayName(), 1379 addBlock.getDisplayName(), decodePacketFlow(workingDirection)); 1380 } 1381 1382 if (getAdjacency(addBlock) != null) { 1383 if (enableAddRouteLogging) { 1384 log.info("Block is already registered"); 1385 } 1386 addThroughPath(getAdjacency(addBlock)); 1387 } else { 1388 Adjacencies adj = new Adjacencies(addBlock, direction, workingDirection); 1389 neighbours.add(adj); 1390 1391 // Add the neighbour to our routing table. 1392 LayoutBlock blk = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlock(addBlock); 1393 LayoutEditor editor = getMaxConnectedPanel(); 1394 1395 if ((editor != null) && (connection == null)) { 1396 // We should be able to determine block metric now as the tracksegment should be valid 1397 connection = editor.getConnectivityUtil(); 1398 } 1399 1400 // Need to inform our neighbours of our new addition 1401 // We only add an entry into the routing table if we are able to reach the next working block. 1402 // If we only transmit routes to it, then we can not route to it therefore it is not added 1403 Routes route = null; 1404 1405 if ((workingDirection == RXTX) || (workingDirection == RXONLY)) { 1406 if (blk != null) { 1407 route = new Routes(addBlock, this.getBlock(), 1, direction, blk.getBlockMetric(), addBlock.getLengthMm()); 1408 } else { 1409 route = new Routes(addBlock, this.getBlock(), 1, direction, 0, 0); 1410 } 1411 routes.add(route); 1412 } 1413 1414 if (blk != null) { 1415 boolean mutual = blk.informNeighbourOfAttachment(this, this.getBlock(), workingDirection); 1416 1417 // The propertychange listener will have to be modified depending upon RX or TX selection. 1418 // if we only transmit routes to this neighbour then we do not want to listen to thier broadcast messages 1419 if ((workingDirection == RXTX) || (workingDirection == RXONLY)) { 1420 blk.addPropertyChangeListener(this); 1421 // log.info("From {} add property change {}", this.getDisplayName(), blk.getDisplayName()); 1422 } else { 1423 blk.removePropertyChangeListener(this); 1424 } 1425 1426 int neighwork = blk.getAdjacencyPacketFlow(this.getBlock()); 1427 if (enableAddRouteLogging) { 1428 log.info("{}.getAdjacencyPacketFlow({}): {}, {}", 1429 blk.getDisplayName(), this.getBlock().getDisplayName(), decodePacketFlow(neighwork), neighwork); 1430 } 1431 1432 if (neighwork != -1) { 1433 if (enableAddRouteLogging) { 1434 log.info("From {} Updating flow direction to {} for block {} choice of {} {}", this.getDisplayName(), 1435 decodePacketFlow(determineAdjPacketFlow(workingDirection, neighwork)), 1436 blk.getDisplayName(), decodePacketFlow(workingDirection), decodePacketFlow(neighwork)); 1437 } 1438 int newPacketFlow = determineAdjPacketFlow(workingDirection, neighwork); 1439 adj.setPacketFlow(newPacketFlow); 1440 1441 if (newPacketFlow == TXONLY) { 1442 for (int j = routes.size() - 1; j > -1; j--) { 1443 Routes ro = routes.get(j); 1444 if ((ro.getDestBlock() == addBlock) 1445 && (ro.getNextBlock() == this.getBlock())) { 1446 adj.removeRouteAdvertisedToNeighbour(ro); 1447 routes.remove(j); 1448 } 1449 } 1450 RoutingPacket newUpdate = new RoutingPacket(REMOVAL, addBlock, -1, -1, -1, -1, getNextPacketID()); 1451 neighbours.forEach((adja) -> adja.removeRouteAdvertisedToNeighbour(addBlock)); 1452 firePropertyChange("routing", null, newUpdate); 1453 } 1454 } else { 1455 if (enableAddRouteLogging) { 1456 log.info("From {} neighbour {} working direction is not valid", 1457 this.getDisplayName(), addBlock.getDisplayName()); 1458 } 1459 return; 1460 } 1461 adj.setMutual(mutual); 1462 1463 if (route != null) { 1464 route.stateChange(); 1465 } 1466 addThroughPath(getAdjacency(addBlock)); 1467 // We get our new neighbour to send us a list of valid routes that they have. 1468 // This might have to be re-written as a property change event? 1469 // Also only inform our neighbour if they have us down as a mutual, otherwise it will just reject the packet. 1470 if (((workingDirection == RXTX) || (workingDirection == TXONLY)) && mutual) { 1471 blk.informNeighbourOfValidRoutes(getBlock()); 1472 } 1473 } else if (enableAddRouteLogging) { 1474 log.info("From {} neighbour {} has no layoutBlock associated, metric set to {}", 1475 this.getDisplayName(), addBlock.getDisplayName(), adj.getMetric()); 1476 } 1477 } 1478 1479 /* If the connectivity before has not completed and produced an error with 1480 setting up through Paths, we will cycle through them */ 1481 if (enableAddRouteLogging) { 1482 log.info("From {} layout connectivity before {}", this.getDisplayName(), layoutConnectivityBefore); 1483 } 1484 if (!layoutConnectivityBefore) { 1485 for (Adjacencies neighbour : neighbours) { 1486 addThroughPath(neighbour); 1487 } 1488 } 1489 /* We need to send our new neighbour our copy of the routing table however 1490 we can only send valid routes that would be able to traverse as definded by 1491 through paths table */ 1492 } 1493 1494 private boolean informNeighbourOfAttachment(LayoutBlock lBlock, Block block, int workingDirection) { 1495 Adjacencies adj = getAdjacency(block); 1496 if (adj == null) { 1497 if (enableAddRouteLogging) { 1498 log.info("From {} neighbour {} has informed us of its attachment to us, however we do not yet have it registered", 1499 this.getDisplayName(), lBlock.getDisplayName()); 1500 } 1501 return false; 1502 } 1503 1504 if (!adj.isMutual()) { 1505 if (enableAddRouteLogging) { 1506 log.info("From {} neighbour {} wants us to {}; we have it set as {}", 1507 this.getDisplayName(), block.getDisplayName(), 1508 decodePacketFlow(workingDirection), decodePacketFlow(adj.getPacketFlow())); 1509 } 1510 1511 // Simply if both the neighbour and us both want to do the same thing with sending routing information, 1512 // in one direction then no routes will be passed 1513 int newPacketFlow = determineAdjPacketFlow(adj.getPacketFlow(), workingDirection); 1514 if (enableAddRouteLogging) { 1515 log.info("From {} neighbour {} passed {} we have {} this will be updated to {}", this.getDisplayName(), block.getDisplayName(), decodePacketFlow(workingDirection), decodePacketFlow(adj.getPacketFlow()), decodePacketFlow(newPacketFlow)); 1516 } 1517 adj.setPacketFlow(newPacketFlow); 1518 1519 // If we are only set to transmit routing information to the adj, then 1520 // we will not have it appearing in the routing table 1521 if (newPacketFlow != TXONLY) { 1522 Routes neighRoute = getValidRoute(this.getBlock(), adj.getBlock()); 1523 // log.info("From " + this.getDisplayName() + " neighbour " + adj.getBlock().getDisplayName() + " valid routes returned as " + neighRoute); 1524 if (neighRoute == null) { 1525 log.info("Null route so will bomb out"); 1526 return false; 1527 } 1528 1529 if (neighRoute.getMetric() != adj.getMetric()) { 1530 if (enableAddRouteLogging) { 1531 log.info("From {} The value of the metric we have for this route is not correct {}, stored {} v {}", this.getDisplayName(), this.getBlock().getDisplayName(), neighRoute.getMetric(), adj.getMetric()); 1532 } 1533 neighRoute.setMetric(adj.getMetric()); 1534 // This update might need to be more selective 1535 RoutingPacket update = new RoutingPacket(UPDATE, adj.getBlock(), -1, (adj.getMetric() + metric), -1, -1, getNextPacketID()); 1536 firePropertyChange("routing", null, update); 1537 } 1538 1539 if (neighRoute.getMetric() != (int) adj.getLength()) { 1540 if (enableAddRouteLogging) { 1541 log.info("From {} The value of the length we have for this route is not correct {}, stored {} v {}", this.getDisplayName(), this.getBlock().getDisplayName(), neighRoute.getMetric(), adj.getMetric()); 1542 } 1543 neighRoute.setLength(adj.getLength()); 1544 // This update might need to be more selective 1545 RoutingPacket update = new RoutingPacket(UPDATE, adj.getBlock(), -1, -1, 1546 adj.getLength() + block.getLengthMm(), -1, getNextPacketID()); 1547 firePropertyChange("routing", null, update); 1548 } 1549 Routes r = getRouteByDestBlock(block); 1550 if (r != null) { 1551 r.setMetric(lBlock.getBlockMetric()); 1552 } else { 1553 log.warn("No getRouteByDestBlock('{}')", block.getDisplayName()); 1554 } 1555 } 1556 1557 if (enableAddRouteLogging) { 1558 log.info("From {} We were not a mutual adjacency with {} but now are", this.getDisplayName(), lBlock.getDisplayName()); 1559 } 1560 1561 if ((newPacketFlow == RXTX) || (newPacketFlow == RXONLY)) { 1562 lBlock.addPropertyChangeListener(this); 1563 } else { 1564 lBlock.removePropertyChangeListener(this); 1565 } 1566 1567 if (newPacketFlow == TXONLY) { 1568 for (int j = routes.size() - 1; j > -1; j--) { 1569 Routes ro = routes.get(j); 1570 if ((ro.getDestBlock() == block) && (ro.getNextBlock() == this.getBlock())) { 1571 adj.removeRouteAdvertisedToNeighbour(ro); 1572 routes.remove(j); 1573 } 1574 } 1575 1576 for (int j = throughPaths.size() - 1; j > -1; j--) { 1577 if ((throughPaths.get(j).getDestinationBlock() == block)) { 1578 if (enableAddRouteLogging) { 1579 log.info("From {} removed throughpath {} {}", this.getDisplayName(), throughPaths.get(j).getSourceBlock().getDisplayName(), throughPaths.get(j).getDestinationBlock().getDisplayName()); 1580 } 1581 throughPaths.remove(j); 1582 } 1583 } 1584 RoutingPacket newUpdate = new RoutingPacket(REMOVAL, block, -1, -1, -1, -1, getNextPacketID()); 1585 neighbours.forEach((adja) -> adja.removeRouteAdvertisedToNeighbour(block)); 1586 firePropertyChange("routing", null, newUpdate); 1587 } 1588 1589 adj.setMutual(true); 1590 addThroughPath(adj); 1591 1592 // As we are now mutual we will send our neigh a list of valid routes. 1593 if ((newPacketFlow == RXTX) || (newPacketFlow == TXONLY)) { 1594 if (enableAddRouteLogging) { 1595 log.info("From {} inform neighbour of valid routes", this.getDisplayName()); 1596 } 1597 informNeighbourOfValidRoutes(block); 1598 } 1599 } 1600 return true; 1601 } 1602 1603 private int determineAdjPacketFlow(int our, int neigh) { 1604 // Both are the same 1605 if (enableUpdateRouteLogging) { 1606 log.info("From {} values passed our {} neigh {}", this.getDisplayName(), decodePacketFlow(our), decodePacketFlow(neigh)); 1607 } 1608 if ((our == RXTX) && (neigh == RXTX)) { 1609 return RXTX; 1610 } 1611 1612 /*First off reverse the neighbour flow, as it will be telling us if it will allow or deny traffic from us. 1613 So if it is set to RX, then we can TX to it.*/ 1614 if (neigh == RXONLY) { 1615 neigh = TXONLY; 1616 } else if (neigh == TXONLY) { 1617 neigh = RXONLY; 1618 } 1619 1620 if (our == neigh) { 1621 return our; 1622 } 1623 return NONE; 1624 } 1625 1626 private void informNeighbourOfValidRoutes(Block newblock) { 1627 // java.sql.Timestamp t1 = new java.sql.Timestamp(System.nanoTime()); 1628 List<Block> validFromPath = new ArrayList<>(); 1629 if (enableAddRouteLogging) { 1630 log.info("From {} new block {}", this.getDisplayName(), newblock.getDisplayName()); 1631 } 1632 1633 for (ThroughPaths tp : throughPaths) { 1634 if (enableAddRouteLogging) { 1635 log.info("From {} B through routes {} {}", this.getDisplayName(), tp.getSourceBlock().getDisplayName(), tp.getDestinationBlock().getDisplayName()); 1636 } 1637 1638 if (tp.getSourceBlock() == newblock) { 1639 validFromPath.add(tp.getDestinationBlock()); 1640 } else if (tp.getDestinationBlock() == newblock) { 1641 validFromPath.add(tp.getSourceBlock()); 1642 } 1643 } 1644 1645 if (enableAddRouteLogging) { 1646 log.info("From {} ===== valid from size path {} ====", this.getDisplayName(), validFromPath.size()); 1647 log.info(newblock.getDisplayName()); 1648 } 1649 1650 // We only send packets on to our neighbour that are registered as being on a valid through path and are mutual. 1651 LayoutBlock lBnewblock = null; 1652 Adjacencies adj = getAdjacency(newblock); 1653 if (adj.isMutual()) { 1654 if (enableAddRouteLogging) { 1655 log.info("From {} adj with {} is mutual", this.getDisplayName(), newblock.getDisplayName()); 1656 } 1657 lBnewblock = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlock(newblock); 1658 } else if (enableAddRouteLogging) { 1659 log.info("From {} adj with {} is NOT mutual", this.getDisplayName(), newblock.getDisplayName()); 1660 } 1661 1662 if (lBnewblock == null) { 1663 return; 1664 } 1665 1666 for (Routes ro : new ArrayList<>(routes)) { 1667 if (enableAddRouteLogging) { 1668 log.info("next:{} dest:{}", ro.getNextBlock().getDisplayName(), ro.getDestBlock().getDisplayName()); 1669 } 1670 1671 if (ro.getNextBlock() == getBlock()) { 1672 if (enableAddRouteLogging) { 1673 log.info("From {} ro next block is this", this.getDisplayName()); 1674 } 1675 if (validFromPath.contains(ro.getDestBlock())) { 1676 if (enableAddRouteLogging) { 1677 log.info("From {} route to {} we have it with a metric of {} we will add our metric of {} this will be sent to {} a", this.getDisplayName(), ro.getDestBlock().getDisplayName(), ro.getMetric(), metric, lBnewblock.getDisplayName()); 1678 } // we added +1 to hop count and our metric. 1679 1680 RoutingPacket update = new RoutingPacket(ADDITION, ro.getDestBlock(), ro.getHopCount() + 1, (ro.getMetric() + metric), (ro.getLength() + block.getLengthMm()), -1, getNextPacketID()); 1681 lBnewblock.addRouteFromNeighbour(this, update); 1682 } 1683 } else { 1684 // Don't know if this might need changing so that we only send out our best 1685 // route to the neighbour, rather than cycling through them all. 1686 if (validFromPath.contains(ro.getNextBlock())) { 1687 if (enableAddRouteLogging) { 1688 log.info("From {} route to {} we have it with a metric of {} we will add our metric of {} this will be sent to {} b", this.getDisplayName(), ro.getDestBlock().getDisplayName(), ro.getMetric(), metric, lBnewblock.getDisplayName()); 1689 } // we added +1 to hop count and our metric. 1690 if (adj.advertiseRouteToNeighbour(ro)) { 1691 if (enableAddRouteLogging) { 1692 log.info("Told to advertise to neighbour"); 1693 } 1694 // this should keep track of the routes we sent to our neighbour. 1695 adj.addRouteAdvertisedToNeighbour(ro); 1696 RoutingPacket update = new RoutingPacket(ADDITION, ro.getDestBlock(), ro.getHopCount() + 1, (ro.getMetric() + metric), (ro.getLength() + block.getLengthMm()), -1, getNextPacketID()); 1697 lBnewblock.addRouteFromNeighbour(this, update); 1698 } else { 1699 if (enableAddRouteLogging) { 1700 log.info("Not advertised to neighbour"); 1701 } 1702 } 1703 } else if (enableAddRouteLogging) { 1704 log.info("failed valid from path Not advertised/added"); 1705 } 1706 } 1707 } 1708 } 1709 1710 static long time = 0; 1711 1712 /** 1713 * Work out our direction of route flow correctly. 1714 */ 1715 private void addAdjacency(Path addPath) { 1716 if (enableAddRouteLogging) { 1717 log.info("From {} path to be added {} {}", this.getDisplayName(), addPath.getBlock().getDisplayName(), Path.decodeDirection(addPath.getToBlockDirection())); 1718 } 1719 1720 Block destBlockToAdd = addPath.getBlock(); 1721 int ourWorkingDirection = RXTX; 1722 if (destBlockToAdd == null) { 1723 log.error("Found null destination block for path from {}", this.getDisplayName()); 1724 return; 1725 } 1726 1727 if (this.getBlock().isBlockDenied(destBlockToAdd.getDisplayName())) { 1728 ourWorkingDirection = RXONLY; 1729 } else if (destBlockToAdd.isBlockDenied(this.getBlock().getDisplayName())) { 1730 ourWorkingDirection = TXONLY; 1731 } 1732 1733 if (enableAddRouteLogging) { 1734 log.info("From {} to block {} we should therefore be... {}", this.getDisplayName(), addPath.getBlock().getDisplayName(), decodePacketFlow(ourWorkingDirection)); 1735 } 1736 addNeighbour(addPath.getBlock(), addPath.getToBlockDirection(), ourWorkingDirection); 1737 1738 } 1739 1740 // Might be possible to refactor the removal to use a bit of common code. 1741 private void removeAdjacency(Path removedPath) { 1742 Block ablock = removedPath.getBlock(); 1743 if (ablock != null) { 1744 if (enableDeleteRouteLogging) { 1745 log.info("From {} Adjacency to be removed {} {}", this.getDisplayName(), ablock.getDisplayName(), Path.decodeDirection(removedPath.getToBlockDirection())); 1746 } 1747 LayoutBlock layoutBlock = InstanceManager.getDefault( 1748 LayoutBlockManager.class).getLayoutBlock(ablock); 1749 if (layoutBlock != null) { 1750 removeAdjacency(layoutBlock); 1751 } 1752 } else { 1753 log.debug("removeAdjacency() removedPath.getBlock() is null"); 1754 } 1755 } 1756 1757 private void removeAdjacency(LayoutBlock layoutBlock) { 1758 if (enableDeleteRouteLogging) { 1759 log.info("From {} Adjacency to be removed {}", this.getDisplayName(), layoutBlock.getDisplayName()); 1760 } 1761 Block removedBlock = layoutBlock.getBlock(); 1762 1763 // Work our way backward through the list of neighbours 1764 // We need to work out which routes to remove first. 1765 // here we simply remove the routes which are advertised from the removed neighbour 1766 List<Routes> tmpBlock = removeRouteReceivedFromNeighbour(removedBlock); 1767 1768 for (int i = neighbours.size() - 1; i > -1; i--) { 1769 // Use to check against direction but don't now. 1770 if ((neighbours.get(i).getBlock() == removedBlock)) { 1771 // Was previously before the for loop. 1772 // Pos move the remove list and remove thoughpath out of this for loop. 1773 layoutBlock.removePropertyChangeListener(this); 1774 if (enableDeleteRouteLogging) { 1775 log.info("From {} block {} found and removed", this.getDisplayName(), removedBlock.getDisplayName()); 1776 } 1777 LayoutBlock layoutBlockToNotify = InstanceManager.getDefault( 1778 LayoutBlockManager.class).getLayoutBlock(neighbours.get(i).getBlock()); 1779 if (layoutBlockToNotify==null){ // move to provides? 1780 log.error("Unable to notify neighbours for block {}",neighbours.get(i).getBlock()); 1781 continue; 1782 } 1783 getAdjacency(neighbours.get(i).getBlock()).dispose(); 1784 neighbours.remove(i); 1785 layoutBlockToNotify.notifiedNeighbourNoLongerMutual(this); 1786 } 1787 } 1788 1789 for (int i = throughPaths.size() - 1; i > -1; i--) { 1790 if (throughPaths.get(i).getSourceBlock() == removedBlock) { 1791 // only mark for removal if the source isn't in the adjcency table 1792 if (getAdjacency(throughPaths.get(i).getSourceBlock()) == null) { 1793 if (enableDeleteRouteLogging) { 1794 log.info("remove {} to {}", throughPaths.get(i).getSourceBlock().getDisplayName(), throughPaths.get(i).getDestinationBlock().getDisplayName()); 1795 } 1796 throughPaths.remove(i); 1797 } 1798 } else if (throughPaths.get(i).getDestinationBlock() == removedBlock) { 1799 // only mark for removal if the destination isn't in the adjcency table 1800 if (getAdjacency(throughPaths.get(i).getDestinationBlock()) == null) { 1801 if (enableDeleteRouteLogging) { 1802 log.info("remove {} to {}", throughPaths.get(i).getSourceBlock().getDisplayName(), throughPaths.get(i).getDestinationBlock().getDisplayName()); 1803 } 1804 throughPaths.remove(i); 1805 } 1806 } 1807 } 1808 1809 if (enableDeleteRouteLogging) { 1810 log.info("From {} neighbour has been removed - Number of routes to this neighbour removed{}", this.getDisplayName(), tmpBlock.size()); 1811 } 1812 notifyNeighboursOfRemoval(tmpBlock, removedBlock); 1813 } 1814 1815 // This is used when a property event change is triggered for a removed route. 1816 // Not sure that bulk removals will be necessary 1817 private void removeRouteFromNeighbour(LayoutBlock src, RoutingPacket update) { 1818 InstanceManager.getDefault(LayoutBlockManager.class).setLastRoutingChange(); 1819 Block srcblk = src.getBlock(); 1820 Block destblk = update.getBlock(); 1821 String msgPrefix = "From " + this.getDisplayName() + " notify block " + srcblk.getDisplayName() + " "; 1822 1823 if (enableDeleteRouteLogging) { 1824 log.info("{} remove route from neighbour called", msgPrefix); 1825 } 1826 1827 if (InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlock(srcblk) == this) { 1828 if (enableDeleteRouteLogging) { 1829 log.info("From {} source block is the same as our block! {}", this.getDisplayName(), destblk.getDisplayName()); 1830 } 1831 return; 1832 } 1833 1834 if (enableDeleteRouteLogging) { 1835 log.info("{} (Direct Notification) neighbour {} has removed route to {}", msgPrefix, srcblk.getDisplayName(), destblk.getDisplayName()); 1836 log.info("{} routes in table {} Remove route from neighbour", msgPrefix, routes.size()); 1837 } 1838 List<Routes> routesToRemove = new ArrayList<>(); 1839 for (int i = routes.size() - 1; i > -1; i--) { 1840 Routes ro = routes.get(i); 1841 if ((ro.getNextBlock() == srcblk) && (ro.getDestBlock() == destblk)) { 1842 routesToRemove.add(new Routes(routes.get(i).getDestBlock(), routes.get(i).getNextBlock(), 0, 0, 0, 0)); 1843 if (enableDeleteRouteLogging) { 1844 log.info("{} route to {} from block {} to be removed triggered by propertyChange", msgPrefix, ro.getDestBlock().getDisplayName(), ro.getNextBlock().getDisplayName()); 1845 } 1846 routes.remove(i); 1847 // We only fire off routing update the once 1848 } 1849 } 1850 notifyNeighboursOfRemoval(routesToRemove, srcblk); 1851 } 1852 1853 private List<Routes> removeRouteReceivedFromNeighbour(Block removedBlock) { 1854 List<Routes> tmpBlock = new ArrayList<>(); 1855 1856 // here we simply remove the routes which are advertised from the removed neighbour 1857 for (int j = routes.size() - 1; j > -1; j--) { 1858 Routes ro = routes.get(j); 1859 if (enableDeleteRouteLogging) { 1860 log.info("From {} route to check {} from Block {}", this.getDisplayName(), routes.get(j).getDestBlock().getDisplayName(), routes.get(j).getNextBlock().getDisplayName()); 1861 } 1862 1863 if (ro.getDestBlock() == removedBlock) { 1864 if (enableDeleteRouteLogging) { 1865 log.info("From {} route to {} from block {} to be removed triggered by adjancey removal as dest block has been removed", this.getDisplayName(), routes.get(j).getDestBlock().getDisplayName(), routes.get(j).getNextBlock().getDisplayName()); 1866 } 1867 1868 if (!tmpBlock.contains(ro)) { 1869 tmpBlock.add(ro); 1870 } 1871 routes.remove(j); 1872 // This will need to be removed fromth directly connected 1873 } else if (ro.getNextBlock() == removedBlock) { 1874 if (enableDeleteRouteLogging) { 1875 log.info("From {} route to {} from block {} to be removed triggered by adjancey removal", this.getDisplayName(), routes.get(j).getDestBlock().getDisplayName(), routes.get(j).getNextBlock().getDisplayName()); 1876 } 1877 1878 if (!tmpBlock.contains(ro)) { 1879 tmpBlock.add(ro); 1880 } 1881 routes.remove(j); 1882 // This will also need to be removed from the directly connected list as well. 1883 } 1884 } 1885 return tmpBlock; 1886 } 1887 1888 private void updateNeighbourPacketFlow(Block neighbour, int flow) { 1889 // Packet flow from neighbour will need to be reversed. 1890 Adjacencies neighAdj = getAdjacency(neighbour); 1891 1892 if (flow == RXONLY) { 1893 flow = TXONLY; 1894 } else if (flow == TXONLY) { 1895 flow = RXONLY; 1896 } 1897 1898 if (neighAdj.getPacketFlow() == flow) { 1899 return; 1900 } 1901 updateNeighbourPacketFlow(neighAdj, flow); 1902 } 1903 1904 protected void updateNeighbourPacketFlow(Adjacencies neighbour, final int flow) { 1905 if (neighbour.getPacketFlow() == flow) { 1906 return; 1907 } 1908 1909 final LayoutBlock neighLBlock = neighbour.getLayoutBlock(); 1910 Runnable r = () -> neighLBlock.updateNeighbourPacketFlow(block, flow); 1911 1912 Block neighBlock = neighbour.getBlock(); 1913 int oldPacketFlow = neighbour.getPacketFlow(); 1914 1915 neighbour.setPacketFlow(flow); 1916 1917 SwingUtilities.invokeLater(r); 1918 1919 if (flow == TXONLY) { 1920 neighBlock.addBlockDenyList(this.block); 1921 neighLBlock.removePropertyChangeListener(this); 1922 1923 // This should remove routes learned from our neighbour 1924 List<Routes> tmpBlock = removeRouteReceivedFromNeighbour(neighBlock); 1925 1926 notifyNeighboursOfRemoval(tmpBlock, neighBlock); 1927 1928 // Need to also remove all through paths to this neighbour 1929 for (int i = throughPaths.size() - 1; i > -1; i--) { 1930 if (throughPaths.get(i).getDestinationBlock() == neighBlock) { 1931 throughPaths.remove(i); 1932 firePropertyChange("through-path-removed", null, null); 1933 } 1934 } 1935 1936 // We potentially will need to re-advertise routes to this neighbour 1937 if (oldPacketFlow == RXONLY) { 1938 addThroughPath(neighbour); 1939 } 1940 } else if (flow == RXONLY) { 1941 neighLBlock.addPropertyChangeListener(this); 1942 neighBlock.removeBlockDenyList(this.block); 1943 this.block.addBlockDenyList(neighBlock); 1944 1945 for (int i = throughPaths.size() - 1; i > -1; i--) { 1946 if (throughPaths.get(i).getSourceBlock() == neighBlock) { 1947 throughPaths.remove(i); 1948 firePropertyChange("through-path-removed", null, null); 1949 } 1950 } 1951 1952 // Might need to rebuild through paths. 1953 if (oldPacketFlow == TXONLY) { 1954 routes.add(new Routes(neighBlock, this.getBlock(), 1955 1, neighbour.getDirection(), neighLBlock.getBlockMetric(), neighBlock.getLengthMm())); 1956 addThroughPath(neighbour); 1957 } 1958 // We would need to withdraw the routes that we advertise to the neighbour 1959 } else if (flow == RXTX) { 1960 neighBlock.removeBlockDenyList(this.block); 1961 this.block.removeBlockDenyList(neighBlock); 1962 neighLBlock.addPropertyChangeListener(this); 1963 1964 // Might need to rebuild through paths. 1965 if (oldPacketFlow == TXONLY) { 1966 routes.add(new Routes(neighBlock, this.getBlock(), 1967 1, neighbour.getDirection(), neighLBlock.getBlockMetric(), neighBlock.getLengthMm())); 1968 } 1969 addThroughPath(neighbour); 1970 } 1971 } 1972 1973 private void notifyNeighboursOfRemoval(List<Routes> routesToRemove, Block notifyingblk) { 1974 String msgPrefix = "From " + this.getDisplayName() + " notify block " + notifyingblk.getDisplayName() + " "; 1975 1976 if (enableDeleteRouteLogging) { 1977 log.info("{} notifyNeighboursOfRemoval called for routes from {} ===", msgPrefix, notifyingblk.getDisplayName()); 1978 } 1979 boolean notifyvalid = false; 1980 1981 for (int i = neighbours.size() - 1; i > -1; i--) { 1982 if (neighbours.get(i).getBlock() == notifyingblk) { 1983 notifyvalid = true; 1984 } 1985 } 1986 1987 if (enableDeleteRouteLogging) { 1988 log.info("{} The notifying block is still valid? {}", msgPrefix, notifyvalid); 1989 } 1990 1991 for (int j = routesToRemove.size() - 1; j > -1; j--) { 1992 boolean stillexist = false; 1993 Block destBlock = routesToRemove.get(j).getDestBlock(); 1994 Block sourceBlock = routesToRemove.get(j).getNextBlock(); 1995 RoutingPacket newUpdate = new RoutingPacket(REMOVAL, destBlock, -1, -1, -1, -1, getNextPacketID()); 1996 1997 if (enableDeleteRouteLogging) { 1998 log.info("From {} notify block {} checking {} from {}", this.getDisplayName(), notifyingblk.getDisplayName(), destBlock.getDisplayName(), sourceBlock.getDisplayName()); 1999 } 2000 List<Routes> validroute = new ArrayList<>(); 2001 List<Routes> destRoutes = getDestRoutes(destBlock); 2002 for (Routes r : destRoutes) { 2003 // We now know that we still have a valid route to the dest 2004 if (r.getNextBlock() == this.getBlock()) { 2005 if (enableDeleteRouteLogging) { 2006 log.info("{} The destBlock {} is our neighbour", msgPrefix, destBlock.getDisplayName()); 2007 } 2008 validroute.add(new Routes(r.getDestBlock(), r.getNextBlock(), 0, 0, 0, 0)); 2009 stillexist = true; 2010 } else { 2011 // At this stage do we need to check if the valid route comes from a neighbour? 2012 if (enableDeleteRouteLogging) { 2013 log.info("{} we still have a route to {} via {} in our list", msgPrefix, destBlock.getDisplayName(), r.getNextBlock().getDisplayName()); 2014 } 2015 validroute.add(new Routes(destBlock, r.getNextBlock(), 0, 0, 0, 0)); 2016 stillexist = true; 2017 } 2018 } 2019 // We may need to find out who else we could of sent the route to by checking in the through paths 2020 2021 if (stillexist) { 2022 if (enableDeleteRouteLogging) { 2023 log.info("{}A Route still exists", msgPrefix); 2024 log.info("{} the number of routes installed to block {} is {}", msgPrefix, destBlock.getDisplayName(), validroute.size()); 2025 } 2026 2027 if (validroute.size() == 1) { 2028 // Specific routing update. 2029 Block nextHop = validroute.get(0).getNextBlock(); 2030 LayoutBlock layoutBlock; 2031 if (validroute.get(0).getNextBlock() != this.getBlock()) { 2032 layoutBlock = InstanceManager.getDefault( 2033 LayoutBlockManager.class).getLayoutBlock(nextHop); 2034 if (enableDeleteRouteLogging) { 2035 log.info("{} We only have a single valid route left to {} So will tell {} we no longer have it", msgPrefix, destBlock.getDisplayName(), layoutBlock.getDisplayName()); 2036 } 2037 2038 if (layoutBlock != null) { 2039 layoutBlock.removeRouteFromNeighbour(this, newUpdate); 2040 } 2041 getAdjacency(nextHop).removeRouteAdvertisedToNeighbour(routesToRemove.get(j)); 2042 } 2043 2044 // At this point we could probably do with checking for other valid paths from the notifyingblock 2045 // Have a feeling that this is pretty much the same as above! 2046 List<Block> validNeighboursToNotify = new ArrayList<>(); 2047 2048 // Problem we have here is that although we only have one valid route, one of our neighbours 2049 // could still hold a valid through path. 2050 for (int i = neighbours.size() - 1; i > -1; i--) { 2051 // Need to ignore if the dest block is our neighour in this instance 2052 if ((neighbours.get(i).getBlock() != destBlock) && (neighbours.get(i).getBlock() != nextHop)) { 2053 if (validThroughPath(notifyingblk, neighbours.get(i).getBlock())) { 2054 Block neighblock = neighbours.get(i).getBlock(); 2055 2056 if (enableDeleteRouteLogging) { 2057 log.info("{} we could of potentially sent the route to {}", msgPrefix, neighblock.getDisplayName()); 2058 } 2059 2060 if (!validThroughPath(nextHop, neighblock)) { 2061 if (enableDeleteRouteLogging) { 2062 log.info("{} there is no other valid path so will mark for removal", msgPrefix); 2063 } 2064 validNeighboursToNotify.add(neighblock); 2065 } else { 2066 if (enableDeleteRouteLogging) { 2067 log.info("{} there is another valid path so will NOT mark for removal", msgPrefix); 2068 } 2069 } 2070 } 2071 } 2072 } 2073 2074 if (enableDeleteRouteLogging) { 2075 log.info("{} the next block is our selves so we won't remove!", msgPrefix); 2076 log.info("{} do we need to find out if we could of send the route to another neighbour such as?", msgPrefix); 2077 } 2078 2079 for (Block value : validNeighboursToNotify) { 2080 // If the neighbour has a valid through path to the dest 2081 // we will not notify the neighbour of our loss of route 2082 if (!validThroughPath(value, destBlock)) { 2083 layoutBlock = InstanceManager.getDefault(LayoutBlockManager.class). 2084 getLayoutBlock(value); 2085 if (layoutBlock != null) { 2086 layoutBlock.removeRouteFromNeighbour(this, newUpdate); 2087 } 2088 getAdjacency(value).removeRouteAdvertisedToNeighbour(routesToRemove.get(j)); 2089 } else { 2090 if (enableDeleteRouteLogging) { 2091 log.info("{}{} has a valid path to {}", msgPrefix, value.getDisplayName(), destBlock.getDisplayName()); 2092 } 2093 } 2094 } 2095 } else { 2096 // Need to deal with having multiple routes left. 2097 if (enableDeleteRouteLogging) { 2098 log.info("{} routes left to block {}", msgPrefix, destBlock.getDisplayName()); 2099 } 2100 2101 for (Routes item : validroute) { 2102 // We need to see if we have valid routes. 2103 if (validThroughPath(notifyingblk, item.getNextBlock())) { 2104 if (enableDeleteRouteLogging) { 2105 log.info("{} to {} Is a valid route", msgPrefix, item.getNextBlock().getDisplayName()); 2106 } 2107 // Will mark the route for potential removal 2108 item.setMiscFlags(0x02); 2109 } else { 2110 if (enableDeleteRouteLogging) { 2111 log.info("{} to {} Is not a valid route", msgPrefix, item.getNextBlock().getDisplayName()); 2112 } 2113 // Mark the route to not be removed. 2114 item.setMiscFlags(0x01); 2115 2116 // Given that the route to this is not valid, we do not want to 2117 // be notifying this next block about the loss of route. 2118 } 2119 } 2120 2121 // We have marked all the routes for either potential notification of route removal, or definate no removal; 2122 // Now need to get through the list and cross reference each one. 2123 for (int i = 0; i < validroute.size(); i++) { 2124 if (validroute.get(i).getMiscFlags() == 0x02) { 2125 Block nextblk = validroute.get(i).getNextBlock(); 2126 2127 if (enableDeleteRouteLogging) { 2128 log.info("{} route from {} has been flagged for removal", msgPrefix, nextblk.getDisplayName()); 2129 } 2130 2131 // Need to cross reference it with the routes that are left. 2132 boolean leaveroute = false; 2133 for (Routes value : validroute) { 2134 if (value.getMiscFlags() == 0x01) { 2135 if (validThroughPath(nextblk, value.getNextBlock())) { 2136 if (enableDeleteRouteLogging) { 2137 log.info("{} we have a valid path from {} to {}", msgPrefix, nextblk.getDisplayName(), value.getNextBlock()); 2138 } 2139 leaveroute = true; 2140 } 2141 } 2142 } 2143 2144 if (!leaveroute) { 2145 LayoutBlock layoutBlock = InstanceManager.getDefault( 2146 LayoutBlockManager.class).getLayoutBlock(nextblk); 2147 if (enableDeleteRouteLogging) { 2148 log.info("{}############ We need to send notification to {} to remove route ########### haven't found an example of this yet!", msgPrefix, nextblk.getDisplayName()); 2149 } 2150 if (layoutBlock==null) { // change to provides 2151 log.error("Unable to fetch block {}",nextblk); 2152 continue; 2153 } 2154 layoutBlock.removeRouteFromNeighbour(this, newUpdate); 2155 getAdjacency(nextblk).removeRouteAdvertisedToNeighbour(routesToRemove.get(j)); 2156 2157 } else { 2158 if (enableDeleteRouteLogging) { 2159 log.info("{} a valid path through exists {} so we will not remove route.", msgPrefix, nextblk.getDisplayName()); 2160 } 2161 } 2162 } 2163 } 2164 } 2165 } else { 2166 if (enableDeleteRouteLogging) { 2167 log.info("{} We have no other routes to {} Therefore we will broadast this to our neighbours", msgPrefix, destBlock.getDisplayName()); 2168 } 2169 2170 for (Adjacencies adj : neighbours) { 2171 adj.removeRouteAdvertisedToNeighbour(destBlock); 2172 } 2173 firePropertyChange("routing", null, newUpdate); 2174 } 2175 } 2176 2177 if (enableDeleteRouteLogging) { 2178 log.info("{} finshed check and notifying of removed routes from {} ===", msgPrefix, notifyingblk.getDisplayName()); 2179 } 2180 } 2181 2182 private void addThroughPath(Adjacencies adj) { 2183 Block newAdj = adj.getBlock(); 2184 int packetFlow = adj.getPacketFlow(); 2185 2186 if (enableAddRouteLogging) { 2187 log.debug("From {} addThroughPathCalled with adj {}", this.getDisplayName(), adj.getBlock().getDisplayName()); 2188 } 2189 2190 for (Adjacencies neighbour : neighbours) { 2191 // cycle through all the neighbours 2192 if (neighbour.getBlock() != newAdj) { 2193 int neighPacketFlow = neighbour.getPacketFlow(); 2194 2195 if (enableAddRouteLogging) { 2196 log.info("From {} our direction: {}, neighbour direction: {}", this.getDisplayName(), decodePacketFlow(packetFlow), decodePacketFlow(neighPacketFlow)); 2197 } 2198 2199 if ((packetFlow == RXTX) && (neighPacketFlow == RXTX)) { 2200 // if both are RXTX then add flow in both directions 2201 addThroughPath(neighbour.getBlock(), newAdj); 2202 addThroughPath(newAdj, neighbour.getBlock()); 2203 } else if ((packetFlow == RXONLY) && (neighPacketFlow == TXONLY)) { 2204 addThroughPath(neighbour.getBlock(), newAdj); 2205 } else if ((packetFlow == TXONLY) && (neighPacketFlow == RXONLY)) { 2206 addThroughPath(newAdj, neighbour.getBlock()); 2207 } else if ((packetFlow == RXTX) && (neighPacketFlow == TXONLY)) { // was RX 2208 addThroughPath(neighbour.getBlock(), newAdj); 2209 } else if ((packetFlow == RXTX) && (neighPacketFlow == RXONLY)) { // was TX 2210 addThroughPath(newAdj, neighbour.getBlock()); 2211 } else if ((packetFlow == RXONLY) && (neighPacketFlow == RXTX)) { 2212 addThroughPath(neighbour.getBlock(), newAdj); 2213 } else if ((packetFlow == TXONLY) && (neighPacketFlow == RXTX)) { 2214 addThroughPath(newAdj, neighbour.getBlock()); 2215 } else { 2216 if (enableAddRouteLogging) { 2217 log.info("Invalid combination {} and {}", decodePacketFlow(packetFlow), decodePacketFlow(neighPacketFlow)); 2218 } 2219 } 2220 } 2221 } 2222 } 2223 2224 /** 2225 * Add a path between two blocks, but without spec a panel. 2226 */ 2227 private void addThroughPath(Block srcBlock, Block dstBlock) { 2228 if (enableAddRouteLogging) { 2229 log.info("Block {}.addThroughPath(src:{}, dst: {})", 2230 this.getDisplayName(), srcBlock.getDisplayName(), dstBlock.getDisplayName()); 2231 } 2232 2233 if ((block != null) && (panels.size() > 0)) { 2234 // a block is attached and this LayoutBlock is used 2235 // initialize connectivity as defined in first Layout Editor panel 2236 LayoutEditor panel = panels.get(0); 2237 List<LayoutConnectivity> c = panel.getLEAuxTools().getConnectivityList(this); 2238 2239 // if more than one panel, find panel with the highest connectivity 2240 if (panels.size() > 1) { 2241 for (int i = 1; i < panels.size(); i++) { 2242 if (c.size() < panels.get(i).getLEAuxTools(). 2243 getConnectivityList(this).size()) { 2244 panel = panels.get(i); 2245 c = panel.getLEAuxTools().getConnectivityList(this); 2246 } 2247 } 2248 2249 // check that this connectivity is compatible with that of other panels. 2250 for (LayoutEditor tPanel : panels) { 2251 if ((tPanel != panel) && InstanceManager.getDefault(LayoutBlockManager.class). 2252 warn() && (!compareConnectivity(c, 2253 tPanel.getLEAuxTools().getConnectivityList(this)))) { 2254 // send user an error message 2255 int response = JmriJOptionPane.showOptionDialog(null, 2256 java.text.MessageFormat.format(Bundle.getMessage("Warn1"), 2257 new Object[]{getUserName(), tPanel.getLayoutName(), 2258 panel.getLayoutName()}), Bundle.getMessage("WarningTitle"), 2259 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, 2260 null, 2261 new Object[]{Bundle.getMessage("ButtonOK"), Bundle.getMessage("ButtonOKPlus")}, 2262 Bundle.getMessage("ButtonOK")); 2263 if (response == 1 ) { // array position 1 ButtonOKPlus pressed, user elected to disable messages 2264 InstanceManager.getDefault(LayoutBlockManager.class).turnOffWarning(); 2265 } 2266 } 2267 } 2268 } 2269 // update block Paths to reflect connectivity as needed 2270 addThroughPath(srcBlock, dstBlock, panel); 2271 } 2272 } 2273 2274 private LayoutEditorAuxTools auxTools = null; 2275 private ConnectivityUtil connection = null; 2276 private boolean layoutConnectivity = true; 2277 2278 /** 2279 * Add a through path on this layout block, going from the source block to 2280 * the destination block, using a specific panel. Note: If the reverse path 2281 * is required, then this needs to be added seperately. 2282 */ 2283 // Was public 2284 private void addThroughPath(Block srcBlock, Block dstBlock, LayoutEditor panel) { 2285 // Reset connectivity flag. 2286 layoutConnectivity = true; 2287 2288 if (srcBlock == dstBlock) { 2289 // Do not do anything if the blocks are the same! 2290 return; 2291 } 2292 2293 if (enableAddRouteLogging) { 2294 log.info("Block {}.addThroughPath(src:{}, dst: {}, <panel>)", 2295 this.getDisplayName(), srcBlock.getDisplayName(), dstBlock.getDisplayName()); 2296 } 2297 2298 // Initally check to make sure that the through path doesn't already exist. 2299 // no point in going through the checks if the path already exists. 2300 boolean add = true; 2301 for (ThroughPaths throughPath : throughPaths) { 2302 if (throughPath.getSourceBlock() == srcBlock) { 2303 if (throughPath.getDestinationBlock() == dstBlock) { 2304 add = false; 2305 } 2306 } 2307 } 2308 2309 if (!add) { 2310 return; 2311 } 2312 2313 if (enableAddRouteLogging) { 2314 log.info("Block {}, src: {}, dst: {}", 2315 block.getDisplayName(), srcBlock.getDisplayName(), dstBlock.getDisplayName()); 2316 } 2317 connection = panel.getConnectivityUtil(); 2318 List<LayoutTrackExpectedState<LayoutTurnout>> stod; 2319 2320 try { 2321 MDC.put("loggingDisabled", connection.getClass().getCanonicalName()); 2322 stod = connection.getTurnoutList(block, srcBlock, dstBlock, true); 2323 MDC.remove("loggingDisabled"); 2324 } catch (java.lang.NullPointerException ex) { 2325 MDC.remove("loggingDisabled"); 2326 if (enableAddRouteLogging) { 2327 log.error("Exception ({}) caught while trying to discover turnout connectivity\nBlock: {}, srcBlock ({}) to dstBlock ({})", ex.toString(), block.getDisplayName(), srcBlock.getDisplayName(), dstBlock.getDisplayName()); 2328 log.error("@ Line # {}", ex.getStackTrace()[1].getLineNumber()); 2329 } 2330 return; 2331 } 2332 2333 if (!connection.isTurnoutConnectivityComplete()) { 2334 layoutConnectivity = false; 2335 } 2336 List<LayoutTrackExpectedState<LayoutTurnout>> tmpdtos; 2337 2338 try { 2339 MDC.put("loggingDisabled", connection.getClass().getName()); 2340 tmpdtos = connection.getTurnoutList(block, dstBlock, srcBlock, true); 2341 MDC.remove("loggingDisabled"); 2342 } catch (java.lang.NullPointerException ex) { 2343 MDC.remove("loggingDisabled"); 2344 if (enableAddRouteLogging) { 2345 log.error("Exception ({}) caught while trying to discover turnout connectivity\nBlock: {}, dstBlock ({}) to srcBlock ({})", ex.toString(), block.getDisplayName(), dstBlock.getDisplayName(), srcBlock.getDisplayName()); 2346 log.error("@ Line # {}", ex.getStackTrace()[1].getLineNumber()); 2347 } 2348 return; 2349 } 2350 2351 if (!connection.isTurnoutConnectivityComplete()) { 2352 layoutConnectivity = false; 2353 } 2354 2355 if (stod.size() == tmpdtos.size()) { 2356 // Need to reorder the tmplist (dst-src) to be the same order as src-dst 2357 List<LayoutTrackExpectedState<LayoutTurnout>> dtos = new ArrayList<>(); 2358 for (int i = tmpdtos.size(); i > 0; i--) { 2359 dtos.add(tmpdtos.get(i - 1)); 2360 } 2361 2362 // check to make sure that we pass through the same turnouts 2363 if (enableAddRouteLogging) { 2364 log.info("From {} destination size {} v source size {}", this.getDisplayName(), dtos.size(), stod.size()); 2365 } 2366 2367 for (int i = 0; i < dtos.size(); i++) { 2368 if (dtos.get(i).getObject() != stod.get(i).getObject()) { 2369 if (enableAddRouteLogging) { 2370 log.info("{} != {}: will quit", dtos.get(i).getObject(), stod.get(i).getObject()); 2371 } 2372 return; 2373 } 2374 } 2375 2376 for (int i = 0; i < dtos.size(); i++) { 2377 int x = stod.get(i).getExpectedState(); 2378 int y = dtos.get(i).getExpectedState(); 2379 2380 if (x != y) { 2381 if (enableAddRouteLogging) { 2382 log.info("{} not on setting equal will quit {}, {}", block.getDisplayName(), x, y); 2383 } 2384 return; 2385 } else if (x == Turnout.UNKNOWN) { 2386 if (enableAddRouteLogging) { 2387 log.info("{} turnout state returned as UNKNOWN", block.getDisplayName()); 2388 } 2389 return; 2390 } 2391 } 2392 Set<LayoutTurnout> set = new HashSet<>(); 2393 2394 for (LayoutTrackExpectedState<LayoutTurnout> layoutTurnoutLayoutTrackExpectedState : stod) { 2395 boolean val = set.add(layoutTurnoutLayoutTrackExpectedState.getObject()); 2396 if (val == false) { 2397 // Duplicate found. will not add 2398 return; 2399 } 2400 } 2401 // for (LayoutTurnout turn : stod) { 2402 // if (turn.type == LayoutTurnout.DOUBLE_XOVER) { 2403 // // Further checks might be required. 2404 // } 2405 //} 2406 addThroughPathPostChecks(srcBlock, dstBlock, stod); 2407 } else { 2408 // We know that a path that contains a double cross-over, is not reported correctly, 2409 // therefore we shall do some additional checks and add it. 2410 if (enableAddRouteLogging) { 2411 log.info("sizes are not the same therefore, we will do some further checks"); 2412 } 2413 List<LayoutTrackExpectedState<LayoutTurnout>> maxt; 2414 if (stod.size() >= tmpdtos.size()) { 2415 maxt = stod; 2416 } else { 2417 maxt = tmpdtos; 2418 } 2419 2420 Set<LayoutTrackExpectedState<LayoutTurnout>> set = new HashSet<>(maxt); 2421 2422 if (set.size() == maxt.size()) { 2423 if (enableAddRouteLogging) { 2424 log.info("All turnouts are unique so potentially a valid path"); 2425 } 2426 boolean allowAddition = false; 2427 for (LayoutTrackExpectedState<LayoutTurnout> layoutTurnoutLayoutTrackExpectedState : maxt) { 2428 LayoutTurnout turn = layoutTurnoutLayoutTrackExpectedState.getObject(); 2429 if (turn.type == LayoutTurnout.TurnoutType.DOUBLE_XOVER) { 2430 allowAddition = true; 2431 // The double crossover gets reported in the opposite setting. 2432 if (layoutTurnoutLayoutTrackExpectedState.getExpectedState() == 2) { 2433 layoutTurnoutLayoutTrackExpectedState.setExpectedState(4); 2434 } else { 2435 layoutTurnoutLayoutTrackExpectedState.setExpectedState(2); 2436 } 2437 } 2438 } 2439 2440 if (allowAddition) { 2441 if (enableAddRouteLogging) { 2442 log.info("addition allowed"); 2443 } 2444 addThroughPathPostChecks(srcBlock, dstBlock, maxt); 2445 } else if (enableAddRouteLogging) { 2446 log.info("No double cross-over so not a valid path"); 2447 } 2448 } 2449 } 2450 } // addThroughPath 2451 2452 private void addThroughPathPostChecks(Block srcBlock, 2453 Block dstBlock, List<LayoutTrackExpectedState<LayoutTurnout>> stod) { 2454 List<Path> paths = block.getPaths(); 2455 Path srcPath = null; 2456 2457 for (Path item : paths) { 2458 if (item.getBlock() == srcBlock) { 2459 srcPath = item; 2460 } 2461 } 2462 Path dstPath = null; 2463 2464 for (Path value : paths) { 2465 if (value.getBlock() == dstBlock) { 2466 dstPath = value; 2467 } 2468 } 2469 ThroughPaths path = new ThroughPaths(srcBlock, srcPath, dstBlock, dstPath); 2470 path.setTurnoutList(stod); 2471 2472 if (enableAddRouteLogging) { 2473 log.info("From {} added Throughpath {} {}", this.getDisplayName(), path.getSourceBlock().getDisplayName(), path.getDestinationBlock().getDisplayName()); 2474 } 2475 throughPaths.add(path); 2476 firePropertyChange("through-path-added", null, null); 2477 2478 // update our neighbours of the new valid paths; 2479 informNeighbourOfValidRoutes(srcBlock); 2480 informNeighbourOfValidRoutes(dstBlock); 2481 } 2482 2483 void notifiedNeighbourNoLongerMutual(LayoutBlock srcBlock) { 2484 if (enableDeleteRouteLogging) { 2485 log.info("From {}Notification from neighbour that it is no longer our friend {}", this.getDisplayName(), srcBlock.getDisplayName()); 2486 } 2487 Block blk = srcBlock.getBlock(); 2488 2489 for (int i = neighbours.size() - 1; i > -1; i--) { 2490 // Need to check if the block we are being informed about has already been removed or not 2491 if (neighbours.get(i).getBlock() == blk) { 2492 removeAdjacency(srcBlock); 2493 break; 2494 } 2495 } 2496 } 2497 2498 public static final int RESERVED = 0x08; 2499 2500 void stateUpdate() { 2501 // Need to find a way to fire off updates to the various tables 2502 if (enableUpdateRouteLogging) { 2503 log.debug("From {} A block state change ({}) has occurred", this.getDisplayName(), getBlockStatusString()); 2504 } 2505 RoutingPacket update = new RoutingPacket(UPDATE, this.getBlock(), -1, -1, -1, getBlockStatus(), getNextPacketID()); 2506 firePropertyChange("routing", null, update); 2507 } 2508 2509 int getBlockStatus() { 2510 if (getOccupancy() == OCCUPIED) { 2511 useExtraColor = false; 2512 // Our section of track is occupied 2513 return OCCUPIED; 2514 } else if (useExtraColor) { 2515 return RESERVED; 2516 } else if (getOccupancy() == EMPTY) { 2517 return EMPTY; 2518 } else { 2519 return UNKNOWN; 2520 } 2521 } 2522 2523 String getBlockStatusString() { 2524 String result = "UNKNOWN"; 2525 if (getOccupancy() == OCCUPIED) { 2526 result = "OCCUPIED"; 2527 } else if (useExtraColor) { 2528 result = "RESERVED"; 2529 } else if (getOccupancy() == EMPTY) { 2530 result = "EMPTY"; 2531 } 2532 return result; 2533 } 2534 2535 Integer getNextPacketID() { 2536 Integer lastID; 2537 2538 if (updateReferences.isEmpty()) { 2539 lastID = 0; 2540 } else { 2541 int lastIDPos = updateReferences.size() - 1; 2542 lastID = updateReferences.get(lastIDPos) + 1; 2543 } 2544 2545 if (lastID > 2000) { 2546 lastID = 0; 2547 } 2548 updateReferences.add(lastID); 2549 2550 /*As we are originating a packet, we will added to the acted upion list 2551 thus making sure if the packet gets back to us we do knowing with it.*/ 2552 actedUponUpdates.add(lastID); 2553 2554 if (updateReferences.size() > 500) { 2555 // log.info("flush update references"); 2556 updateReferences.subList(0, 250).clear(); 2557 } 2558 2559 if (actedUponUpdates.size() > 500) { 2560 actedUponUpdates.subList(0, 250).clear(); 2561 } 2562 return lastID; 2563 } 2564 2565 boolean updatePacketActedUpon(Integer packetID) { 2566 return actedUponUpdates.contains(packetID); 2567 } 2568 2569 public List<Block> getActiveNextBlocks(Block source) { 2570 List<Block> currentPath = new ArrayList<>(); 2571 2572 for (ThroughPaths path : throughPaths) { 2573 if ((path.getSourceBlock() == source) && (path.isPathActive())) { 2574 currentPath.add(path.getDestinationBlock()); 2575 } 2576 } 2577 return currentPath; 2578 } 2579 2580 public Path getThroughPathSourcePathAtIndex(int i) { 2581 return throughPaths.get(i).getSourcePath(); 2582 } 2583 2584 public Path getThroughPathDestinationPathAtIndex(int i) { 2585 return throughPaths.get(i).getDestinationPath(); 2586 } 2587 2588 public boolean validThroughPath(Block sourceBlock, Block destinationBlock) { 2589 for (ThroughPaths throughPath : throughPaths) { 2590 if ((throughPath.getSourceBlock() == sourceBlock) && (throughPath.getDestinationBlock() == destinationBlock)) { 2591 return true; 2592 } else if ((throughPath.getSourceBlock() == destinationBlock) && (throughPath.getDestinationBlock() == sourceBlock)) { 2593 return true; 2594 } 2595 } 2596 return false; 2597 } 2598 2599 public int getThroughPathIndex(Block sourceBlock, Block destinationBlock) { 2600 for (int i = 0; i < throughPaths.size(); i++) { 2601 if ((throughPaths.get(i).getSourceBlock() == sourceBlock) 2602 && (throughPaths.get(i).getDestinationBlock() == destinationBlock)) { 2603 return i; 2604 } else if ((throughPaths.get(i).getSourceBlock() == destinationBlock) 2605 && (throughPaths.get(i).getDestinationBlock() == sourceBlock)) { 2606 return i; 2607 } 2608 } 2609 return -1; 2610 } 2611 2612 List<Adjacencies> neighbours = new ArrayList<>(); 2613 2614 List<ThroughPaths> throughPaths = new ArrayList<>(); 2615 2616 // A sub class that holds valid routes through the block. 2617 // Possibly want to store the path direction in here as well. 2618 // or we store the ref to the path, so we can get the directions. 2619 List<Routes> routes = new ArrayList<>(); 2620 2621 String decodePacketFlow(int value) { 2622 switch (value) { 2623 case RXTX: { 2624 return "Bi-Direction Operation"; 2625 } 2626 2627 case RXONLY: { 2628 return "Uni-Directional - Trains can only exit to this block (RX) "; 2629 } 2630 2631 case TXONLY: { 2632 return "Uni-Directional - Trains can not be sent down this block (TX) "; 2633 } 2634 2635 case NONE: { 2636 return "None routing updates will be passed"; 2637 } 2638 default: 2639 log.warn("Unhandled packet flow value: {}", value); 2640 break; 2641 } 2642 return "Unknown"; 2643 } 2644 2645 /** 2646 * Provide an output to the console of all the valid paths through this 2647 * block. 2648 */ 2649 public void printValidThroughPaths() { 2650 log.info("Through paths for block {}", this.getDisplayName()); 2651 log.info("Current Block, From Block, To Block"); 2652 for (ThroughPaths tp : throughPaths) { 2653 String activeStr = ""; 2654 if (tp.isPathActive()) { 2655 activeStr = ", *"; 2656 } 2657 log.info("From {}, {}, {}{}", this.getDisplayName(), (tp.getSourceBlock()).getDisplayName(), (tp.getDestinationBlock()).getDisplayName(), activeStr); 2658 } 2659 } 2660 2661 /** 2662 * Provide an output to the console of all our neighbouring blocks. 2663 */ 2664 public void printAdjacencies() { 2665 log.info("Adjacencies for block {}", this.getDisplayName()); 2666 log.info("Neighbour, Direction, mutual, relationship, metric"); 2667 for (Adjacencies neighbour : neighbours) { 2668 log.info(" neighbor: {}, {}, {}, {}, {}",neighbour.getBlock().getDisplayName(), Path.decodeDirection(neighbour.getDirection()), neighbour.isMutual(), decodePacketFlow(neighbour.getPacketFlow()), neighbour.getMetric()); 2669 } 2670 } 2671 2672 /** 2673 * Provide an output to the console of all the remote blocks reachable from 2674 * our block. 2675 */ 2676 public void printRoutes() { 2677 log.info("Routes for block {}", this.getDisplayName()); 2678 log.info("Destination, Next Block, Hop Count, Direction, State, Metric"); 2679 for (Routes r : routes) { 2680 String nexthop = r.getNextBlock().getDisplayName(); 2681 2682 if (r.getNextBlock() == this.getBlock()) { 2683 nexthop = "Directly Connected"; 2684 } 2685 String activeString = ""; 2686 if (r.isRouteCurrentlyValid()) { 2687 activeString = ", *"; 2688 } 2689 2690 log.info(" neighbor: {}, {}, {}, {}, {}, {}{}", (r.getDestBlock()).getDisplayName(), nexthop, r.getHopCount(), Path.decodeDirection(r.getDirection()), r.getState(), r.getMetric(), activeString); 2691 } 2692 } 2693 2694 /** 2695 * Provide an output to the console of how to reach a specific block from 2696 * our block. 2697 * 2698 * @param inBlockName to find in route 2699 */ 2700 public void printRoutes(String inBlockName) { 2701 log.info("Routes for block {}", this.getDisplayName()); 2702 log.info("Our Block, Destination, Next Block, Hop Count, Direction, Metric"); 2703 for (Routes route : routes) { 2704 if (route.getDestBlock().getDisplayName().equals(inBlockName)) { 2705 log.info("From {}, {}, {}, {}, {}, {}", this.getDisplayName(), (route.getDestBlock()).getDisplayName(), (route.getNextBlock()).getDisplayName(), route.getHopCount(), Path.decodeDirection(route.getDirection()), route.getMetric()); 2706 } 2707 } 2708 } 2709 2710 /** 2711 * @param destBlock is the destination of the block we are following 2712 * @param direction is the direction of travel from the previous block 2713 * @return next block 2714 */ 2715 public Block getNextBlock(Block destBlock, int direction) { 2716 int bestMetric = 965000; 2717 Block bestBlock = null; 2718 2719 for (Routes r : routes) { 2720 if ((r.getDestBlock() == destBlock) && (r.getDirection() == direction)) { 2721 if (r.getMetric() < bestMetric) { 2722 bestMetric = r.getMetric(); 2723 bestBlock = r.getNextBlock(); 2724 // bestBlock=r.getDestBlock(); 2725 } 2726 } 2727 } 2728 return bestBlock; 2729 } 2730 2731 /** 2732 * Used if we already know the block prior to our block, and the destination 2733 * block. direction, is optional and is used where the previousBlock is 2734 * equal to our block. 2735 * 2736 * @param previousBlock start block 2737 * @param destBlock finish block 2738 * @return next block 2739 */ 2740 @CheckForNull 2741 public Block getNextBlock(Block previousBlock, Block destBlock) { 2742 int bestMetric = 965000; 2743 Block bestBlock = null; 2744 2745 for (Routes r : routes) { 2746 if (r.getDestBlock() == destBlock) { 2747 // Check that the route through from the previous block, to the next hop is valid 2748 if (validThroughPath(previousBlock, r.getNextBlock())) { 2749 if (r.getMetric() < bestMetric) { 2750 bestMetric = r.getMetric(); 2751 // bestBlock=r.getDestBlock(); 2752 bestBlock = r.getNextBlock(); 2753 } 2754 } 2755 } 2756 } 2757 return bestBlock; 2758 } 2759 2760 public int getConnectedBlockRouteIndex(Block destBlock, int direction) { 2761 for (int i = 0; i < routes.size(); i++) { 2762 if (routes.get(i).getNextBlock() == this.getBlock()) { 2763 log.info("Found a block that is directly connected"); 2764 2765 if ((routes.get(i).getDestBlock() == destBlock)) { 2766 log.info("In getConnectedBlockRouteIndex, {}", Integer.toString(routes.get(i).getDirection() & direction)); 2767 if ((routes.get(i).getDirection() & direction) != 0) { 2768 return i; 2769 } 2770 } 2771 } 2772 2773 if (log.isDebugEnabled()) { 2774 log.debug("From {}, {}, nexthop {}, {}, {}, {}", this.getDisplayName(), (routes.get(i).getDestBlock()).getDisplayName(), routes.get(i).getHopCount(), Path.decodeDirection(routes.get(i).getDirection()), routes.get(i).getState(), routes.get(i).getMetric()); 2775 } 2776 } 2777 return -1; 2778 } 2779 2780 // Need to work on this to deal with the method of routing 2781 public int getNextBlockByIndex(Block destBlock, int direction, int offSet) { 2782 for (int i = offSet; i < routes.size(); i++) { 2783 Routes ro = routes.get(i); 2784 if ((ro.getDestBlock() == destBlock)) { 2785 log.info("getNextBlockByIndex {}", Integer.toString(ro.getDirection() & direction)); 2786 if ((ro.getDirection() & direction) != 0) { 2787 return i; 2788 } 2789 } 2790 } 2791 return -1; 2792 } 2793 2794 // Need to work on this to deal with the method of routing 2795 /* 2796 * 2797 */ 2798 public int getNextBlockByIndex(Block previousBlock, Block destBlock, int offSet) { 2799 for (int i = offSet; i < routes.size(); i++) { 2800 Routes ro = routes.get(i); 2801 // log.info(r.getDestBlock().getDisplayName() + " vs " + destBlock.getDisplayName()); 2802 if (ro.getDestBlock() == destBlock) { 2803 // Check that the route through from the previous block, to the next hop is valid 2804 if (validThroughPath(previousBlock, ro.getNextBlock())) { 2805 log.debug("valid through path"); 2806 return i; 2807 } 2808 2809 if (ro.getNextBlock() == this.getBlock()) { 2810 log.debug("getNextBlock is this block therefore directly connected"); 2811 return i; 2812 } 2813 } 2814 } 2815 return -1; 2816 } 2817 2818 /** 2819 * last index - the index of the last block we returned ie we last returned 2820 * index 10, so we don't want to return it again. The block returned will 2821 * have a hopcount or metric equal to or greater than the one of the last 2822 * block returned. if the exclude block list is empty this is the first 2823 * time, it has been used. The parameters for the best last block are based 2824 * upon the last entry in the excludedBlock list. 2825 * 2826 * @param previousBlock starting block 2827 * @param destBlock finish block 2828 * @param excludeBlock blocks to skip 2829 * @param routingMethod value to match metric 2830 * @return next block 2831 */ 2832 public int getNextBestBlock(Block previousBlock, Block destBlock, List<Integer> excludeBlock, LayoutBlockConnectivityTools.Metric routingMethod) { 2833 if (enableSearchRouteLogging) { 2834 log.info("From {} find best route from {} to {} index {} routingMethod {}", this.getDisplayName(), previousBlock.getDisplayName(), destBlock.getDisplayName(), excludeBlock, routingMethod); 2835 } 2836 int bestCount = 965255; // set stupidly high 2837 int bestIndex = -1; 2838 int lastValue = 0; 2839 List<Block> nextBlocks = new ArrayList<>(5); 2840 if (!excludeBlock.isEmpty() && (excludeBlock.get(excludeBlock.size() - 1) < routes.size())) { 2841 if (routingMethod == LayoutBlockConnectivityTools.Metric.METRIC) { 2842 lastValue = routes.get(excludeBlock.get(excludeBlock.size() - 1)).getMetric(); 2843 } else /* if (routingMethod==LayoutBlockManager.HOPCOUNT)*/ { 2844 lastValue = routes.get(excludeBlock.get(excludeBlock.size() - 1)).getHopCount(); 2845 } 2846 2847 for (int i : excludeBlock) { 2848 nextBlocks.add(routes.get(i).getNextBlock()); 2849 } 2850 2851 if (enableSearchRouteLogging) { 2852 log.info("last index is {} {}", excludeBlock.get(excludeBlock.size() - 1), 2853 routes.get(excludeBlock.get(excludeBlock.size() - 1)).getDestBlock().getDisplayName()); 2854 } 2855 } 2856 2857 for (int i = 0; i < routes.size(); i++) { 2858 if (!excludeBlock.contains(i)) { 2859 Routes ro = routes.get(i); 2860 if (!nextBlocks.contains(ro.getNextBlock())) { 2861 // if(ro.getNextBlock()!=nextBlock){ 2862 int currentValue; 2863 if (routingMethod == LayoutBlockConnectivityTools.Metric.METRIC) { 2864 currentValue = routes.get(i).getMetric(); 2865 } else /*if (routingMethod==InstanceManager.getDefault( 2866 LayoutBlockManager.class).HOPCOUNT)*/ { 2867 currentValue = routes.get(i).getHopCount(); // was lastindex changed to i 2868 } 2869 2870 if (currentValue >= lastValue) { 2871 if (ro.getDestBlock() == destBlock) { 2872 if (enableSearchRouteLogging) { 2873 log.info("Match on dest blocks"); 2874 // Check that the route through from the previous block, to the next hop is valid 2875 log.info("Is valid through path previous block {} to {}", previousBlock.getDisplayName(), ro.getNextBlock().getDisplayName()); 2876 } 2877 2878 if (validThroughPath(previousBlock, ro.getNextBlock())) { 2879 if (enableSearchRouteLogging) { 2880 log.info("valid through path"); 2881 } 2882 2883 if (routingMethod == LayoutBlockConnectivityTools.Metric.METRIC) { 2884 if (ro.getMetric() < bestCount) { 2885 bestIndex = i; 2886 bestCount = ro.getMetric(); 2887 } 2888 } else /*if (routingMethod==InstanceManager.getDefault( 2889 LayoutBlockManager.class).HOPCOUNT)*/ { 2890 if (ro.getHopCount() < bestCount) { 2891 bestIndex = i; 2892 bestCount = ro.getHopCount(); 2893 } 2894 } 2895 } 2896 2897 if (ro.getNextBlock() == this.getBlock()) { 2898 if (enableSearchRouteLogging) { 2899 log.info("getNextBlock is this block therefore directly connected"); 2900 } 2901 return i; 2902 } 2903 } 2904 } 2905 } 2906 } 2907 } 2908 2909 if (enableSearchRouteLogging) { 2910 log.info("returning {} best count {}", bestIndex, bestCount); 2911 } 2912 return bestIndex; 2913 } 2914 2915 @CheckForNull 2916 Routes getRouteByDestBlock(Block blk) { 2917 for (int i = routes.size() - 1; i > -1; i--) { 2918 if (routes.get(i).getDestBlock() == blk) { 2919 return routes.get(i); 2920 } 2921 } 2922 return null; 2923 } 2924 2925 @Nonnull 2926 List<Routes> getRouteByNeighbour(Block blk) { 2927 List<Routes> rtr = new ArrayList<>(); 2928 for (Routes route : routes) { 2929 if (route.getNextBlock() == blk) { 2930 rtr.add(route); 2931 } 2932 } 2933 return rtr; 2934 } 2935 2936 int getAdjacencyPacketFlow(Block blk) { 2937 for (Adjacencies neighbour : neighbours) { 2938 if (neighbour.getBlock() == blk) { 2939 return neighbour.getPacketFlow(); 2940 } 2941 } 2942 return -1; 2943 } 2944 2945 boolean isValidNeighbour(Block blk) { 2946 for (Adjacencies neighbour : neighbours) { 2947 if (neighbour.getBlock() == blk) { 2948 return true; 2949 } 2950 } 2951 return false; 2952 } 2953 2954 @Override 2955 public synchronized void addPropertyChangeListener(PropertyChangeListener listener) { 2956 if (listener == this) { 2957 log.debug("adding ourselves as a listener for some strange reason! Skipping"); 2958 return; 2959 } 2960 super.addPropertyChangeListener(listener); 2961 } 2962 2963 @Override 2964 public void propertyChange(PropertyChangeEvent e) { 2965 2966 switch (e.getPropertyName()) { 2967 case "NewRoute": { 2968 if (enableUpdateRouteLogging) { 2969 log.info("==Event type {} New {}", e.getPropertyName(), ((LayoutBlock) e.getNewValue()).getDisplayName()); 2970 } 2971 break; 2972 } 2973 case "through-path-added": { 2974 if (enableUpdateRouteLogging) { 2975 log.info("neighbour has new through path"); 2976 } 2977 break; 2978 } 2979 case "through-path-removed": { 2980 if (enableUpdateRouteLogging) { 2981 log.info("neighbour has through removed"); 2982 } 2983 break; 2984 } 2985 case "routing": { 2986 if (e.getSource() instanceof LayoutBlock) { 2987 LayoutBlock sourceLayoutBlock = (LayoutBlock) e.getSource(); 2988 if (enableUpdateRouteLogging) { 2989 log.info("From {} we have a routing packet update from neighbour {}", this.getDisplayName(), sourceLayoutBlock.getDisplayName()); 2990 } 2991 RoutingPacket update = (RoutingPacket) e.getNewValue(); 2992 int updateType = update.getPacketType(); 2993 switch (updateType) { 2994 case ADDITION: { 2995 if (enableUpdateRouteLogging) { 2996 log.info("\t updateType: Addition"); 2997 } 2998 // InstanceManager.getDefault( 2999 // LayoutBlockManager.class).setLastRoutingChange(); 3000 addRouteFromNeighbour(sourceLayoutBlock, update); 3001 break; 3002 } 3003 case UPDATE: { 3004 if (enableUpdateRouteLogging) { 3005 log.info("\t updateType: Update"); 3006 } 3007 updateRoutingInfo(sourceLayoutBlock, update); 3008 break; 3009 } 3010 case REMOVAL: { 3011 if (enableUpdateRouteLogging) { 3012 log.info("\t updateType: Removal"); 3013 } 3014 InstanceManager.getDefault(LayoutBlockManager.class).setLastRoutingChange(); 3015 removeRouteFromNeighbour(sourceLayoutBlock, update); 3016 break; 3017 } 3018 default: { 3019 break; 3020 } 3021 } // switch (updateType) 3022 } // if (e.getSource() instanceof LayoutBlock) 3023 break; 3024 } 3025 default: { 3026 log.debug("Unhandled propertyChange({}): ", e); 3027 break; 3028 } 3029 } // switch (e.getPropertyName()) 3030 } // propertyChange 3031 3032 /** 3033 * Get valid Routes, based upon the next block and destination block 3034 * 3035 * @param nxtBlock next block 3036 * @param dstBlock final block 3037 * @return routes that fit, or null 3038 */ 3039 @CheckForNull 3040 Routes getValidRoute(Block nxtBlock, Block dstBlock) { 3041 if ((nxtBlock != null) && (dstBlock != null)) { 3042 List<Routes> rtr = getRouteByNeighbour(nxtBlock); 3043 3044 if (rtr.isEmpty()) { 3045 log.debug("From {}, no routes returned for getRouteByNeighbour({})", 3046 this.getDisplayName(), 3047 nxtBlock.getDisplayName()); 3048 return null; 3049 } 3050 3051 for (Routes rt : rtr) { 3052 if (rt.getDestBlock() == dstBlock) { 3053 log.debug("From {}, found dest {}.", this.getDisplayName(), dstBlock.getDisplayName()); 3054 return rt; 3055 } 3056 } 3057 log.debug("From {}, no routes to {}.", this.getDisplayName(), nxtBlock.getDisplayName()); 3058 } else { 3059 log.warn("getValidRoute({}, {}", 3060 (nxtBlock != null) ? nxtBlock.getDisplayName() : "<null>", 3061 (dstBlock != null) ? dstBlock.getDisplayName() : "<null>"); 3062 } 3063 return null; 3064 } 3065 3066 /** 3067 * Is the route to the destination block, going via our neighbouring block 3068 * valid. ie Does the block have a route registered via neighbour 3069 * "protecting" to the destination block. 3070 * 3071 * @param protecting neighbour block that might protect 3072 * @param destination block 3073 * @return true if we have valid path to block 3074 */ 3075 public boolean isRouteToDestValid(Block protecting, Block destination) { 3076 if (protecting == destination) { 3077 log.debug("protecting and destination blocks are the same therefore we need to check if we have a valid neighbour"); 3078 3079 // We are testing for a directly connected block. 3080 if (getAdjacency(protecting) != null) { 3081 return true; 3082 } 3083 } else if (getValidRoute(protecting, destination) != null) { 3084 return true; 3085 } 3086 return false; 3087 } 3088 3089 /** 3090 * Get a list of valid Routes to our destination block 3091 * 3092 * @param dstBlock target to find 3093 * @return routes between this and dstBlock 3094 */ 3095 List<Routes> getDestRoutes(Block dstBlock) { 3096 List<Routes> rtr = new ArrayList<>(); 3097 for (Routes route : routes) { 3098 if (route.getDestBlock() == dstBlock) { 3099 rtr.add(route); 3100 } 3101 } 3102 return rtr; 3103 } 3104 3105 /** 3106 * Get a list of valid Routes via our next block 3107 * 3108 * @param nxtBlock target block 3109 * @return list of routes to target block 3110 */ 3111 List<Routes> getNextRoutes(Block nxtBlock) { 3112 List<Routes> rtr = new ArrayList<>(); 3113 for (Routes route : routes) { 3114 if (route.getNextBlock() == nxtBlock) { 3115 rtr.add(route); 3116 } 3117 } 3118 return rtr; 3119 } 3120 3121 void updateRoutingInfo(Routes route) { 3122 if (route.getHopCount() >= 254) { 3123 return; 3124 } 3125 Block destBlock = route.getDestBlock(); 3126 3127 RoutingPacket update = new RoutingPacket(UPDATE, destBlock, getBestRouteByHop(destBlock).getHopCount() + 1, 3128 ((getBestRouteByMetric(destBlock).getMetric()) + metric), 3129 ((getBestRouteByMetric(destBlock).getMetric()) 3130 + block.getLengthMm()), -1, 3131 getNextPacketID()); 3132 firePropertyChange("routing", null, update); 3133 } 3134 3135 // This lot might need changing to only forward on the best route details. 3136 void updateRoutingInfo(LayoutBlock src, RoutingPacket update) { 3137 if (enableUpdateRouteLogging) { 3138 log.info("From {} src: {}, block: {}, hopCount: {}, metric: {}, status: {}, packetID: {}", this.getDisplayName(), src.getDisplayName(), update.getBlock().getDisplayName(), update.getHopCount(), update.getMetric(), update.getBlockState(), update.getPacketId()); 3139 } 3140 Block srcblk = src.getBlock(); 3141 Adjacencies adj = getAdjacency(srcblk); 3142 3143 if (adj == null) { 3144 if (enableUpdateRouteLogging) { 3145 log.info("From {} packet is from a src that is not registered {}", this.getDisplayName(), srcblk.getDisplayName()); 3146 } 3147 // If the packet is from a src that is not registered as a neighbour 3148 // Then we will simply reject it. 3149 return; 3150 } 3151 3152 if (updatePacketActedUpon(update.getPacketId())) { 3153 if (adj.updatePacketActedUpon(update.getPacketId())) { 3154 if (enableUpdateRouteLogging) { 3155 log.info("Reject packet update as we have already acted up on it from this neighbour"); 3156 } 3157 return; 3158 } 3159 } 3160 3161 if (enableUpdateRouteLogging) { 3162 log.info("From {} an Update packet from neighbour {}", this.getDisplayName(), src.getDisplayName()); 3163 } 3164 3165 Block updateBlock = update.getBlock(); 3166 3167 // Block srcblk = src.getBlock(); 3168 // Need to add in a check to make sure that we have a route registered from the source neighbour 3169 // for the block that they are referring too. 3170 if (updateBlock == this.getBlock()) { 3171 if (enableUpdateRouteLogging) { 3172 log.info("Reject packet update as it is a route advertised by our selves"); 3173 } 3174 return; 3175 } 3176 3177 Routes ro; 3178 boolean neighbour = false; 3179 if (updateBlock == srcblk) { 3180 // Very likely that this update is from a neighbour about its own status. 3181 ro = getValidRoute(this.getBlock(), updateBlock); 3182 neighbour = true; 3183 } else { 3184 ro = getValidRoute(srcblk, updateBlock); 3185 } 3186 3187 if (ro == null) { 3188 if (enableUpdateRouteLogging) { 3189 log.info("From {} update is from a source that we do not have listed as a route to the destination", this.getDisplayName()); 3190 log.info("From {} update packet is for a block that we do not have route registered for {}", this.getDisplayName(), updateBlock.getDisplayName()); 3191 } 3192 // If the packet is for a dest that is not in the routing table 3193 // Then we will simply reject it. 3194 return; 3195 } 3196 /*This prevents us from entering into an update loop. 3197 We only add it to our list once it has passed through as being a valid 3198 packet, otherwise we may get the same packet id back, but from a valid source 3199 which would end up be rejected*/ 3200 3201 actedUponUpdates.add(update.getPacketId()); 3202 adj.addPacketReceivedFromNeighbour(update.getPacketId()); 3203 3204 int hopCount = update.getHopCount(); 3205 int packetmetric = update.getMetric(); 3206 int blockstate = update.getBlockState(); 3207 float length = update.getLength(); 3208 3209 // Need to add in a check for a block that is directly connected. 3210 if (hopCount != -1) { 3211 // Was increase hop count before setting it 3212 // int oldHop = ro.getHopCount(); 3213 if (ro.getHopCount() != hopCount) { 3214 if (enableUpdateRouteLogging) { 3215 log.info("{} Hop counts to {} not the same so will change from {} to {}", this.getDisplayName(), ro.getDestBlock().getDisplayName(), ro.getHopCount(), hopCount); 3216 } 3217 ro.setHopCount(hopCount); 3218 hopCount++; 3219 } else { 3220 // No point in forwarding on the update if the hopcount hasn't changed 3221 hopCount = -1; 3222 } 3223 } 3224 3225 // bad to use values as errors, but it's pre-existing code, and code wins 3226 if ((int) length != -1) { 3227 // Length is added at source 3228 float oldLength = ro.getLength(); 3229 if (!MathUtil.equals(oldLength, length)) { 3230 ro.setLength(length); 3231 boolean forwardUpdate = true; 3232 3233 if (ro != getBestRouteByLength(update.getBlock())) { 3234 forwardUpdate = false; 3235 } 3236 3237 if (enableUpdateRouteLogging) { 3238 log.info("From {} updating length from {} to {}", this.getDisplayName(), oldLength, length); 3239 } 3240 3241 if (neighbour) { 3242 length = srcblk.getLengthMm(); 3243 adj.setLength(length); 3244 3245 // ro.setLength(length); 3246 // Also if neighbour we need to update the cost of the routes via it to reflect the new metric 02/20/2011 3247 if (forwardUpdate) { 3248 List<Routes> neighbourRoute = getNextRoutes(srcblk); 3249 3250 // neighbourRoutes, contains all the routes that have been advertised by the neighbour 3251 // that will need to have their metric updated to reflect the change. 3252 for (Routes nRo : neighbourRoute) { 3253 // Need to remove old metric to the neigbour, then add the new one on 3254 float updateLength = nRo.getLength(); 3255 updateLength = (updateLength - oldLength) + length; 3256 3257 if (enableUpdateRouteLogging) { 3258 log.info("From {} update metric for route {} from {} to {}", this.getDisplayName(), nRo.getDestBlock().getDisplayName(), nRo.getLength(), updateLength); 3259 } 3260 nRo.setLength(updateLength); 3261 List<Block> messageRecipients = getThroughPathDestinationBySource(srcblk); 3262 RoutingPacket newUpdate = new RoutingPacket(UPDATE, nRo.getDestBlock(), -1, -1, updateLength + block.getLengthMm(), -1, getNextPacketID()); 3263 updateRoutesToNeighbours(messageRecipients, nRo, newUpdate); 3264 } 3265 } 3266 } else if (forwardUpdate) { 3267 // This can cause a loop, if the layout is in a loop, so we send out the same packetID. 3268 List<Block> messageRecipients = getThroughPathSourceByDestination(srcblk); 3269 RoutingPacket newUpdate = new RoutingPacket(UPDATE, updateBlock, -1, -1, 3270 length + block.getLengthMm(), -1, update.getPacketId()); 3271 updateRoutesToNeighbours(messageRecipients, ro, newUpdate); 3272 } 3273 length += metric; 3274 } else { 3275 length = -1; 3276 } 3277 } 3278 3279 if (packetmetric != -1) { 3280 // Metric is added at source 3281 // Keep a reference of the old metric. 3282 int oldmetric = ro.getMetric(); 3283 if (oldmetric != packetmetric) { 3284 ro.setMetric(packetmetric); 3285 3286 if (enableUpdateRouteLogging) { 3287 log.info("From {} updating metric from {} to {}", this.getDisplayName(), oldmetric, packetmetric); 3288 } 3289 boolean forwardUpdate = true; 3290 3291 if (ro != getBestRouteByMetric(update.getBlock())) { 3292 forwardUpdate = false; 3293 } 3294 3295 // if the metric update is for a neighbour then we will go directly to the neighbour for the value, 3296 // rather than trust what is in the message at this stage. 3297 if (neighbour) { 3298 packetmetric = src.getBlockMetric(); 3299 adj.setMetric(packetmetric); 3300 3301 if (forwardUpdate) { 3302 // ro.setMetric(packetmetric); 3303 // Also if neighbour we need to update the cost of the routes via it to 3304 // reflect the new metric 02/20/2011 3305 List<Routes> neighbourRoute = getNextRoutes(srcblk); 3306 3307 // neighbourRoutes, contains all the routes that have been advertised by the neighbour that 3308 // will need to have their metric updated to reflect the change. 3309 for (Routes nRo : neighbourRoute) { 3310 // Need to remove old metric to the neigbour, then add the new one on 3311 int updatemet = nRo.getMetric(); 3312 updatemet = (updatemet - oldmetric) + packetmetric; 3313 3314 if (enableUpdateRouteLogging) { 3315 log.info("From {} update metric for route {} from {} to {}", this.getDisplayName(), nRo.getDestBlock().getDisplayName(), nRo.getMetric(), updatemet); 3316 } 3317 nRo.setMetric(updatemet); 3318 List<Block> messageRecipients = getThroughPathDestinationBySource(srcblk); 3319 RoutingPacket newUpdate = new RoutingPacket(UPDATE, nRo.getDestBlock(), hopCount, updatemet + metric, -1, -1, getNextPacketID()); 3320 updateRoutesToNeighbours(messageRecipients, nRo, newUpdate); 3321 } 3322 } 3323 } else if (forwardUpdate) { 3324 // This can cause a loop, if the layout is in a loop, so we send out the same packetID. 3325 List<Block> messageRecipients = getThroughPathSourceByDestination(srcblk); 3326 RoutingPacket newUpdate = new RoutingPacket(UPDATE, updateBlock, hopCount, 3327 packetmetric + metric, -1, -1, update.getPacketId()); 3328 updateRoutesToNeighbours(messageRecipients, ro, newUpdate); 3329 } 3330 packetmetric += metric; 3331 // Think we need a list of routes that originate from this source neighbour 3332 } else { 3333 // No point in forwarding on the update if the metric hasn't changed 3334 packetmetric = -1; 3335 // Potentially when we do this we need to update all the routes that go via this block, not just this route. 3336 } 3337 } 3338 3339 if (blockstate != -1) { 3340 // We will update all the destination blocks with the new state, it 3341 // saves re-firing off new updates block status 3342 boolean stateUpdated = false; 3343 List<Routes> rtr = getDestRoutes(updateBlock); 3344 3345 for (Routes rt : rtr) { 3346 if (rt.getState() != blockstate) { 3347 stateUpdated = true; 3348 rt.stateChange(); 3349 } 3350 } 3351 3352 if (stateUpdated) { 3353 RoutingPacket newUpdate = new RoutingPacket(UPDATE, updateBlock, -1, -1, -1, blockstate, getNextPacketID()); 3354 firePropertyChange("routing", null, newUpdate); 3355 } 3356 } 3357 3358 // We need to expand on this so that any update to routing metric is propergated correctly 3359 if ((packetmetric != -1) || (hopCount != -1) || (length != -1)) { 3360 // We only want to send the update on to neighbours that we have advertised the route to. 3361 List<Block> messageRecipients = getThroughPathSourceByDestination(srcblk); 3362 RoutingPacket newUpdate = new RoutingPacket(UPDATE, updateBlock, hopCount, packetmetric, 3363 length, blockstate, update.getPacketId()); 3364 updateRoutesToNeighbours(messageRecipients, ro, newUpdate); 3365 } 3366 // Was just pass on hop count 3367 } 3368 3369 void updateRoutesToNeighbours(List<Block> messageRecipients, Routes ro, RoutingPacket update) { 3370 for (Block messageRecipient : messageRecipients) { 3371 Adjacencies adj = getAdjacency(messageRecipient); 3372 if (adj.advertiseRouteToNeighbour(ro)) { 3373 adj.addRouteAdvertisedToNeighbour(ro); 3374 LayoutBlock recipient = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlock(messageRecipient); 3375 if (recipient != null) { 3376 recipient.updateRoutingInfo(this, update); 3377 } 3378 } 3379 } 3380 } 3381 3382 Routes getBestRouteByMetric(Block dest) { 3383 // int bestHopCount = 255; 3384 int bestMetric = 965000; 3385 int bestIndex = -1; 3386 3387 List<Routes> destRoutes = getDestRoutes(dest); 3388 for (int i = 0; i < destRoutes.size(); i++) { 3389 if (destRoutes.get(i).getMetric() < bestMetric) { 3390 bestMetric = destRoutes.get(i).getMetric(); 3391 bestIndex = i; 3392 } 3393 } 3394 3395 if (bestIndex == -1) { 3396 return null; 3397 } 3398 return destRoutes.get(bestIndex); 3399 } 3400 3401 Routes getBestRouteByHop(Block dest) { 3402 int bestHopCount = 255; 3403 // int bestMetric = 965000; 3404 int bestIndex = -1; 3405 3406 List<Routes> destRoutes = getDestRoutes(dest); 3407 for (int i = 0; i < destRoutes.size(); i++) { 3408 if (destRoutes.get(i).getHopCount() < bestHopCount) { 3409 bestHopCount = destRoutes.get(i).getHopCount(); 3410 bestIndex = i; 3411 } 3412 } 3413 3414 if (bestIndex == -1) { 3415 return null; 3416 } 3417 return destRoutes.get(bestIndex); 3418 } 3419 3420 Routes getBestRouteByLength(Block dest) { 3421 // int bestHopCount = 255; 3422 // int bestMetric = 965000; 3423 // long bestLength = 999999999; 3424 int bestIndex = -1; 3425 List<Routes> destRoutes = getDestRoutes(dest); 3426 float bestLength = destRoutes.get(0).getLength(); 3427 3428 for (int i = 0; i < destRoutes.size(); i++) { 3429 if (destRoutes.get(i).getLength() < bestLength) { 3430 bestLength = destRoutes.get(i).getLength(); 3431 bestIndex = i; 3432 } 3433 } 3434 3435 if (bestIndex == -1) { 3436 return null; 3437 } 3438 return destRoutes.get(bestIndex); 3439 } 3440 3441 void addRouteToNeighbours(Routes ro) { 3442 if (enableAddRouteLogging) { 3443 log.info("From {} Add route to neighbour", this.getDisplayName()); 3444 } 3445 Block nextHop = ro.getNextBlock(); 3446 List<LayoutBlock> validFromPath = new ArrayList<>(); 3447 3448 if (enableAddRouteLogging) { 3449 log.info("From {} new block {}", this.getDisplayName(), nextHop.getDisplayName()); 3450 } 3451 3452 for (int i = 0; i < throughPaths.size(); i++) { 3453 LayoutBlock validBlock = null; 3454 3455 if (enableAddRouteLogging) { 3456 log.info("Through routes index {}", i); 3457 log.info("From {} A through routes {} {}", this.getDisplayName(), throughPaths.get(i).getSourceBlock().getDisplayName(), throughPaths.get(i).getDestinationBlock().getDisplayName()); 3458 } 3459 3460 /*As the through paths include each possible path, ie 2 > 3 and 3 > 2 3461 as seperate entries then we only need to forward the new route to those 3462 source blocks that have a desination of the next hop*/ 3463 if (throughPaths.get(i).getDestinationBlock() == nextHop) { 3464 if (getAdjacency(throughPaths.get(i).getSourceBlock()).isMutual()) { 3465 validBlock = InstanceManager.getDefault( 3466 LayoutBlockManager.class). 3467 getLayoutBlock(throughPaths.get(i).getSourceBlock()); 3468 } 3469 } 3470 3471 // only need to add it the once. Not sure if the contains is required. 3472 if ((validBlock != null) && (!validFromPath.contains(validBlock))) { 3473 validFromPath.add(validBlock); 3474 } 3475 } 3476 3477 if (enableAddRouteLogging) { 3478 log.info("From {} ===== valid from size path {} ==== (addroutetoneigh)", this.getDisplayName(), validFromPath.size()); 3479 3480 validFromPath.forEach((valid) -> log.info("fromPath: {}", valid.getDisplayName())); 3481 log.info("Next Hop {}", nextHop.getDisplayName()); 3482 } 3483 RoutingPacket update = new RoutingPacket(ADDITION, ro.getDestBlock(), ro.getHopCount() + 1, 3484 ro.getMetric() + metric, 3485 (ro.getLength() + getBlock().getLengthMm()), -1, getNextPacketID()); 3486 3487 for (LayoutBlock layoutBlock : validFromPath) { 3488 Adjacencies adj = getAdjacency(layoutBlock.getBlock()); 3489 if (adj.advertiseRouteToNeighbour(ro)) { 3490 // getBestRouteByHop(destBlock).getHopCount()+1, ((getBestRouteByMetric(destBlock).getMetric())+metric), 3491 //((getBestRouteByMetric(destBlock).getMetric())+block.getLengthMm()) 3492 if (enableAddRouteLogging) { 3493 log.info("From {} Sending update to {} As this has a better hop count or metric", this.getDisplayName(), layoutBlock.getDisplayName()); 3494 } 3495 adj.addRouteAdvertisedToNeighbour(ro); 3496 layoutBlock.addRouteFromNeighbour(this, update); 3497 } 3498 } 3499 } 3500 3501 void addRouteFromNeighbour(LayoutBlock src, RoutingPacket update) { 3502 if (enableAddRouteLogging) { 3503 // log.info("From " + this.getDisplayName() + " packet to be added from neighbour " + src.getDisplayName()); 3504 log.info("From {} src: {}, block: {}, hopCount: {}, metric: {}, status: {}, packetID: {}", this.getDisplayName(), src.getDisplayName(), update.getBlock().getDisplayName(), update.getHopCount(), update.getMetric(), update.getBlockState(), update.getPacketId()); 3505 } 3506 InstanceManager.getDefault(LayoutBlockManager.class).setLastRoutingChange(); 3507 Block destBlock = update.getBlock(); 3508 Block srcblk = src.getBlock(); 3509 3510 if (destBlock == this.getBlock()) { 3511 if (enableAddRouteLogging) { 3512 log.info("Reject packet update as it is to a route advertised by our selves"); 3513 } 3514 return; 3515 } 3516 3517 Adjacencies adj = getAdjacency(srcblk); 3518 if (adj == null) { 3519 if (enableAddRouteLogging) { 3520 log.info("From {} packet is from a src that is not registered {}", this.getDisplayName(), srcblk.getDisplayName()); 3521 } 3522 // If the packet is from a src that is not registered as a neighbour 3523 // Then we will simply reject it. 3524 return; 3525 } else if (adj.getPacketFlow() == TXONLY) { 3526 if (enableAddRouteLogging) { 3527 log.info("From {} packet is from a src {} that is registered as one that we should be transmitting to only", this.getDisplayName(), src.getDisplayName()); 3528 } 3529 // we should only be transmitting routes to this neighbour not receiving them 3530 return; 3531 } 3532 int hopCount = update.getHopCount(); 3533 int updatemetric = update.getMetric(); 3534 float length = update.getLength(); 3535 3536 if (hopCount > 255) { 3537 if (enableAddRouteLogging) { 3538 log.info("From {} hop count exceeded {}", this.getDisplayName(), destBlock.getDisplayName()); 3539 } 3540 return; 3541 } 3542 3543 for (Routes ro : routes) { 3544 if ((ro.getNextBlock() == srcblk) && (ro.getDestBlock() == destBlock)) { 3545 if (enableAddRouteLogging) { 3546 log.info("From {} Route to {} is already configured", this.getDisplayName(), destBlock.getDisplayName()); 3547 log.info("{} v {}", ro.getHopCount(), hopCount); 3548 log.info("{} v {}", ro.getMetric(), updatemetric); 3549 } 3550 updateRoutingInfo(src, update); 3551 return; 3552 } 3553 } 3554 3555 if (enableAddRouteLogging) { 3556 log.info("From {} We should be adding route {}", this.getDisplayName(), destBlock.getDisplayName()); 3557 } 3558 3559 // We need to propergate out the routes that we have added to our neighbour 3560 int direction = adj.getDirection(); 3561 Routes route = new Routes(destBlock, srcblk, hopCount, direction, updatemetric, length); 3562 routes.add(route); 3563 3564 // Need to propergate the route down to our neighbours 3565 addRouteToNeighbours(route); 3566 } 3567 3568 /* this should look after removal of a specific next hop from our neighbour*/ 3569 /** 3570 * Get the direction of travel to our neighbouring block. 3571 * 3572 * @param neigh neighbor block 3573 * @return direction to get to neighbor block 3574 */ 3575 public int getNeighbourDirection(LayoutBlock neigh) { 3576 if (neigh == null) { 3577 return Path.NONE; 3578 } 3579 Block neighbourBlock = neigh.getBlock(); 3580 return getNeighbourDirection(neighbourBlock); 3581 } 3582 3583 public int getNeighbourDirection(Block neighbourBlock) { 3584 for (Adjacencies neighbour : neighbours) { 3585 if (neighbour.getBlock() == neighbourBlock) { 3586 return neighbour.getDirection(); 3587 } 3588 } 3589 return Path.NONE; 3590 } 3591 3592 Adjacencies getAdjacency(Block blk) { 3593 for (Adjacencies neighbour : neighbours) { 3594 if (neighbour.getBlock() == blk) { 3595 return neighbour; 3596 } 3597 } 3598 return null; 3599 } 3600 3601 final static int ADDITION = 0x00; 3602 final static int UPDATE = 0x02; 3603 final static int REMOVAL = 0x04; 3604 3605 final static int RXTX = 0x00; 3606 final static int RXONLY = 0x02; 3607 final static int TXONLY = 0x04; 3608 final static int NONE = 0x08; 3609 int metric = 100; 3610 3611 private static class RoutingPacket { 3612 3613 int packetType; 3614 Block block; 3615 int hopCount = -1; 3616 int packetMetric = -1; 3617 int blockstate = -1; 3618 float length = -1; 3619 Integer packetRef = -1; 3620 3621 RoutingPacket(int packetType, Block blk, int hopCount, int packetMetric, float length, int blockstate, Integer packetRef) { 3622 this.packetType = packetType; 3623 this.block = blk; 3624 this.hopCount = hopCount; 3625 this.packetMetric = packetMetric; 3626 this.blockstate = blockstate; 3627 this.packetRef = packetRef; 3628 this.length = length; 3629 } 3630 3631 int getPacketType() { 3632 return packetType; 3633 } 3634 3635 Block getBlock() { 3636 return block; 3637 } 3638 3639 int getHopCount() { 3640 return hopCount; 3641 } 3642 3643 int getMetric() { 3644 return packetMetric; 3645 } 3646 3647 int getBlockState() { 3648 return blockstate; 3649 } 3650 3651 float getLength() { 3652 return length; 3653 } 3654 3655 Integer getPacketId() { 3656 return packetRef; 3657 } 3658 } 3659 3660 /** 3661 * Get the number of neighbor blocks attached to this block. 3662 * 3663 * @return count of neighbor 3664 */ 3665 public int getNumberOfNeighbours() { 3666 return neighbours.size(); 3667 } 3668 3669 /** 3670 * Get the neighboring block at index i. 3671 * 3672 * @param i index to neighbor 3673 * @return neighbor block 3674 */ 3675 public Block getNeighbourAtIndex(int i) { 3676 return neighbours.get(i).getBlock(); 3677 } 3678 3679 /** 3680 * Get the direction of travel to neighbouring block at index i. 3681 * 3682 * @param i index in neighbors 3683 * @return neighbor block 3684 */ 3685 public int getNeighbourDirection(int i) { 3686 return neighbours.get(i).getDirection(); 3687 } 3688 3689 /** 3690 * Get the metric/cost to neighbouring block at index i. 3691 * 3692 * @param i index in neighbors 3693 * @return metric of neighbor 3694 */ 3695 public int getNeighbourMetric(int i) { 3696 return neighbours.get(i).getMetric(); 3697 } 3698 3699 /** 3700 * Get the flow of traffic to and from neighbouring block at index i RXTX - 3701 * Means Traffic can flow both ways between the blocks RXONLY - Means we can 3702 * only receive traffic from our neighbour, we can not send traffic to it 3703 * TXONLY - Means we do not receive traffic from our neighbour, but can send 3704 * traffic to it. 3705 * 3706 * @param i index in neighbors 3707 * @return direction of traffic 3708 */ 3709 public String getNeighbourPacketFlowAsString(int i) { 3710 return decodePacketFlow(neighbours.get(i).getPacketFlow()); 3711 } 3712 3713 /** 3714 * Is our neighbouring block at index i a mutual neighbour, ie both blocks 3715 * have each other registered as neighbours and are exchanging information. 3716 * 3717 * @param i index of neighbor 3718 * @return true if both are mutual neighbors 3719 */ 3720 public boolean isNeighbourMutual(int i) { 3721 return neighbours.get(i).isMutual(); 3722 } 3723 3724 int getNeighbourIndex(Adjacencies adj) { 3725 for (int i = 0; i < neighbours.size(); i++) { 3726 if (neighbours.get(i) == adj) { 3727 return i; 3728 } 3729 } 3730 return -1; 3731 } 3732 3733 private class Adjacencies { 3734 3735 Block adjBlock; 3736 LayoutBlock adjLayoutBlock; 3737 int direction; 3738 int packetFlow = RXTX; 3739 boolean mutualAdjacency = false; 3740 3741 HashMap<Block, Routes> adjDestRoutes = new HashMap<>(); 3742 List<Integer> actedUponUpdates = new ArrayList<>(501); 3743 3744 Adjacencies(Block block, int dir, int packetFlow) { 3745 adjBlock = block; 3746 direction = dir; 3747 this.packetFlow = packetFlow; 3748 } 3749 3750 Block getBlock() { 3751 return adjBlock; 3752 } 3753 3754 LayoutBlock getLayoutBlock() { 3755 return adjLayoutBlock; 3756 } 3757 3758 int getDirection() { 3759 return direction; 3760 } 3761 3762 // If a set true on mutual, then we could go through the list of what to send out to neighbour 3763 void setMutual(boolean mut) { 3764 if (mut == mutualAdjacency) { // No change will exit 3765 return; 3766 } 3767 mutualAdjacency = mut; 3768 if (mutualAdjacency) { 3769 adjLayoutBlock = InstanceManager.getDefault( 3770 LayoutBlockManager.class).getLayoutBlock(adjBlock); 3771 } 3772 } 3773 3774 boolean isMutual() { 3775 return mutualAdjacency; 3776 } 3777 3778 int getPacketFlow() { 3779 return packetFlow; 3780 } 3781 3782 void setPacketFlow(int flow) { 3783 if (flow != packetFlow) { 3784 int oldFlow = packetFlow; 3785 packetFlow = flow; 3786 firePropertyChange("neighbourpacketflow", oldFlow, packetFlow); 3787 } 3788 } 3789 3790 // The metric could just be read directly from the neighbour as we have no 3791 // need to specifically keep a copy of it here this is here just to fire off the change 3792 void setMetric(int met) { 3793 firePropertyChange("neighbourmetric", null, getNeighbourIndex(this)); 3794 } 3795 3796 int getMetric() { 3797 if (adjLayoutBlock != null) { 3798 return adjLayoutBlock.getBlockMetric(); 3799 } 3800 adjLayoutBlock = InstanceManager.getDefault( 3801 LayoutBlockManager.class).getLayoutBlock(adjBlock); 3802 if (adjLayoutBlock != null) { 3803 return adjLayoutBlock.getBlockMetric(); 3804 } 3805 3806 if (log.isDebugEnabled()) { 3807 log.debug("Layout Block {} returned as null", adjBlock.getDisplayName()); 3808 } 3809 return -1; 3810 } 3811 3812 void setLength(float len) { 3813 firePropertyChange("neighbourlength", null, getNeighbourIndex(this)); 3814 } 3815 3816 float getLength() { 3817 if (adjLayoutBlock != null) { 3818 return adjLayoutBlock.getBlock().getLengthMm(); 3819 } 3820 adjLayoutBlock = InstanceManager.getDefault( 3821 LayoutBlockManager.class).getLayoutBlock(adjBlock); 3822 if (adjLayoutBlock != null) { 3823 return adjLayoutBlock.getBlock().getLengthMm(); 3824 } 3825 3826 if (log.isDebugEnabled()) { 3827 log.debug("Layout Block {} returned as null", adjBlock.getDisplayName()); 3828 } 3829 return -1; 3830 } 3831 3832 void removeRouteAdvertisedToNeighbour(Routes removeRoute) { 3833 Block dest = removeRoute.getDestBlock(); 3834 3835 if (adjDestRoutes.get(dest) == removeRoute) { 3836 adjDestRoutes.remove(dest); 3837 } 3838 } 3839 3840 void removeRouteAdvertisedToNeighbour(Block block) { 3841 adjDestRoutes.remove(block); 3842 } 3843 3844 void addRouteAdvertisedToNeighbour(Routes addedRoute) { 3845 adjDestRoutes.put(addedRoute.getDestBlock(), addedRoute); 3846 } 3847 3848 boolean advertiseRouteToNeighbour(Routes routeToAdd) { 3849 if (!isMutual()) { 3850 log.debug("In block {}: Neighbour is not mutual so will not advertise it (Routes {})", getDisplayName(), routeToAdd); 3851 3852 return false; 3853 } 3854 3855 // Just wonder if this should forward on the new packet to the neighbour? 3856 Block dest = routeToAdd.getDestBlock(); 3857 if (!adjDestRoutes.containsKey(dest)) { 3858 log.debug("In block {}: We are not currently advertising a route to the destination to neighbour: {}", getDisplayName(), dest.getDisplayName()); 3859 3860 return true; 3861 } 3862 3863 if (routeToAdd.getHopCount() > 255) { 3864 log.debug("Hop count is gereater than 255 we will therefore do nothing with this route"); 3865 return false; 3866 } 3867 Routes existingRoute = adjDestRoutes.get(dest); 3868 if (existingRoute.getMetric() > routeToAdd.getMetric()) { 3869 return true; 3870 } 3871 if (existingRoute.getHopCount() > routeToAdd.getHopCount()) { 3872 return true; 3873 } 3874 3875 if (existingRoute == routeToAdd) { 3876 // We return true as the metric might have changed 3877 return false; 3878 } 3879 return false; 3880 } 3881 3882 boolean updatePacketActedUpon(Integer packetID) { 3883 return actedUponUpdates.contains(packetID); 3884 } 3885 3886 void addPacketReceivedFromNeighbour(Integer packetID) { 3887 actedUponUpdates.add(packetID); 3888 if (actedUponUpdates.size() > 500) { 3889 actedUponUpdates.subList(0, 250).clear(); 3890 } 3891 } 3892 3893 void dispose() { 3894 adjBlock = null; 3895 adjLayoutBlock = null; 3896 mutualAdjacency = false; 3897 adjDestRoutes = null; 3898 actedUponUpdates = null; 3899 } 3900 } 3901 3902 /** 3903 * Get the number of routes that the block has registered. 3904 * 3905 * @return count of routes 3906 */ 3907 public int getNumberOfRoutes() { 3908 return routes.size(); 3909 } 3910 3911 /** 3912 * Get the direction of route i. 3913 * 3914 * @param i index in routes 3915 * @return direction 3916 */ 3917 public int getRouteDirectionAtIndex(int i) { 3918 return routes.get(i).getDirection(); 3919 } 3920 3921 /** 3922 * Get the destination block at route i 3923 * 3924 * @param i index in routes 3925 * @return dest block from route 3926 */ 3927 public Block getRouteDestBlockAtIndex(int i) { 3928 return routes.get(i).getDestBlock(); 3929 } 3930 3931 /** 3932 * Get the next block at route i 3933 * 3934 * @param i index in routes 3935 * @return next block from route 3936 */ 3937 public Block getRouteNextBlockAtIndex(int i) { 3938 return routes.get(i).getNextBlock(); 3939 } 3940 3941 /** 3942 * Get the hop count of route i.<br> 3943 * The Hop count is the number of other blocks that we traverse to get to 3944 * the destination 3945 * 3946 * @param i index in routes 3947 * @return hop count 3948 */ 3949 public int getRouteHopCountAtIndex(int i) { 3950 return routes.get(i).getHopCount(); 3951 } 3952 3953 /** 3954 * Get the length of route i.<br> 3955 * The length is the combined length of all the blocks that we traverse to 3956 * get to the destination 3957 * 3958 * @param i index in routes 3959 * @return length of block in route 3960 */ 3961 public float getRouteLengthAtIndex(int i) { 3962 return routes.get(i).getLength(); 3963 } 3964 3965 /** 3966 * Get the metric/cost at route i 3967 * 3968 * @param i index in routes 3969 * @return metric 3970 */ 3971 public int getRouteMetric(int i) { 3972 return routes.get(i).getMetric(); 3973 } 3974 3975 /** 3976 * Get the state (Occupied, unoccupied) of the destination layout block at 3977 * index i 3978 * 3979 * @param i index in routes 3980 * @return state of block 3981 */ 3982 public int getRouteState(int i) { 3983 return routes.get(i).getState(); 3984 } 3985 3986 /** 3987 * Is the route to the destination potentially valid from our block. 3988 * 3989 * @param i index in route 3990 * @return true if route is valid 3991 */ 3992 // TODO: Java standard pattern for boolean getters is "isRouteValid()" 3993 public boolean getRouteValid(int i) { 3994 return routes.get(i).isRouteCurrentlyValid(); 3995 } 3996 3997 /** 3998 * Get the state of the destination layout block at index i as a string. 3999 * 4000 * @param i index in routes 4001 * @return dest status 4002 */ 4003 public String getRouteStateAsString(int i) { 4004 int state = routes.get(i).getState(); 4005 switch (state) { 4006 case OCCUPIED: { 4007 return Bundle.getMessage("TrackOccupied"); // i18n using NamedBeanBundle.properties TODO remove duplicate keys 4008 } 4009 4010 case RESERVED: { 4011 return Bundle.getMessage("StateReserved"); // "Reserved" 4012 } 4013 4014 case EMPTY: { 4015 return Bundle.getMessage("StateFree"); // "Free" 4016 } 4017 4018 default: { 4019 return Bundle.getMessage("BeanStateUnknown"); // "Unknown" 4020 } 4021 } 4022 } 4023 4024 int getRouteIndex(Routes r) { 4025 for (int i = 0; i < routes.size(); i++) { 4026 if (routes.get(i) == r) { 4027 return i; 4028 } 4029 } 4030 return -1; 4031 } 4032 4033 /** 4034 * Get the number of layout blocks to our desintation block going from the 4035 * next directly connected block. If the destination block and nextblock are 4036 * the same and the block is also registered as a neighbour then 1 is 4037 * returned. If no valid route to the destination block can be found via the 4038 * next block then -1 is returned. If more than one route exists to the 4039 * destination then the route with the lowest count is returned. 4040 * 4041 * @param destination final block 4042 * @param nextBlock adjcent block 4043 * @return hop count to final, -1 if not available 4044 */ 4045 public int getBlockHopCount(Block destination, Block nextBlock) { 4046 if ((destination == nextBlock) && (isValidNeighbour(nextBlock))) { 4047 return 1; 4048 } 4049 4050 for (Routes route : routes) { 4051 if (route.getDestBlock() == destination) { 4052 if (route.getNextBlock() == nextBlock) { 4053 return route.getHopCount(); 4054 } 4055 } 4056 } 4057 return -1; 4058 } 4059 4060 /** 4061 * Get the metric to our desintation block going from the next directly 4062 * connected block. If the destination block and nextblock are the same and 4063 * the block is also registered as a neighbour then 1 is returned. If no 4064 * valid route to the destination block can be found via the next block then 4065 * -1 is returned. If more than one route exists to the destination then the 4066 * route with the lowest count is returned. 4067 * 4068 * @param destination final block 4069 * @param nextBlock adjcent block 4070 * @return metric to final block, -1 if not available 4071 */ 4072 public int getBlockMetric(Block destination, Block nextBlock) { 4073 if ((destination == nextBlock) && (isValidNeighbour(nextBlock))) { 4074 return 1; 4075 } 4076 4077 for (Routes route : routes) { 4078 if (route.getDestBlock() == destination) { 4079 if (route.getNextBlock() == nextBlock) { 4080 return route.getMetric(); 4081 } 4082 } 4083 } 4084 return -1; 4085 } 4086 4087 /** 4088 * Get the distance to our desintation block going from the next directly 4089 * connected block. If the destination block and nextblock are the same and 4090 * the block is also registered as a neighbour then 1 is returned. If no 4091 * valid route to the destination block can be found via the next block then 4092 * -1 is returned. If more than one route exists to the destination then the 4093 * route with the lowest count is returned. 4094 * 4095 * @param destination final block 4096 * @param nextBlock adjcent block 4097 * @return lenght to final, -1 if not viable 4098 */ 4099 public float getBlockLength(Block destination, Block nextBlock) { 4100 if ((destination == nextBlock) && (isValidNeighbour(nextBlock))) { 4101 return 1; 4102 } 4103 4104 for (Routes route : routes) { 4105 if (route.getDestBlock() == destination) { 4106 if (route.getNextBlock() == nextBlock) { 4107 return route.getLength(); 4108 } 4109 } 4110 } 4111 return -1; 4112 } 4113 4114 // TODO This needs a propertychange listener adding 4115 private class Routes implements PropertyChangeListener { 4116 4117 int direction; 4118 Block destBlock; 4119 Block nextBlock; 4120 int hopCount; 4121 int routeMetric; 4122 float length; 4123 4124 // int state =-1; 4125 int miscflags = 0x00; 4126 boolean validCurrentRoute = false; 4127 4128 public Routes(Block dstBlock, Block nxtBlock, int hop, int dir, int met, float len) { 4129 destBlock = dstBlock; 4130 nextBlock = nxtBlock; 4131 hopCount = hop; 4132 direction = dir; 4133 routeMetric = met; 4134 length = len; 4135 init(); 4136 } 4137 4138 final void init() { 4139 validCurrentRoute = checkIsRouteOnValidThroughPath(this); 4140 firePropertyChange("length", null, null); 4141 destBlock.addPropertyChangeListener(this); 4142 } 4143 4144 @Override 4145 public String toString() { 4146 return "Routes(dst:" + destBlock + ", nxt:" + nextBlock 4147 + ", hop:" + hopCount + ", dir:" + direction 4148 + ", met:" + routeMetric + ", len: " + length + ")"; 4149 } 4150 4151 @Override 4152 public void propertyChange(PropertyChangeEvent e) { 4153 if (e.getPropertyName().equals("state")) { 4154 stateChange(); 4155 } 4156 } 4157 4158 public Block getDestBlock() { 4159 return destBlock; 4160 } 4161 4162 public Block getNextBlock() { 4163 return nextBlock; 4164 } 4165 4166 public int getHopCount() { 4167 return hopCount; 4168 } 4169 4170 public int getDirection() { 4171 return direction; 4172 } 4173 4174 public int getMetric() { 4175 return routeMetric; 4176 } 4177 4178 public float getLength() { 4179 return length; 4180 } 4181 4182 public void setMetric(int met) { 4183 if (met == routeMetric) { 4184 return; 4185 } 4186 routeMetric = met; 4187 firePropertyChange("metric", null, getRouteIndex(this)); 4188 } 4189 4190 public void setHopCount(int hop) { 4191 if (hopCount == hop) { 4192 return; 4193 } 4194 hopCount = hop; 4195 firePropertyChange("hop", null, getRouteIndex(this)); 4196 } 4197 4198 public void setLength(float len) { 4199 if (len == length) { 4200 return; 4201 } 4202 length = len; 4203 firePropertyChange("length", null, getRouteIndex(this)); 4204 } 4205 4206 // This state change is only here for the routing table view 4207 void stateChange() { 4208 firePropertyChange("state", null, getRouteIndex(this)); 4209 } 4210 4211 int getState() { 4212 LayoutBlock destLBlock = InstanceManager.getDefault( 4213 LayoutBlockManager.class).getLayoutBlock(destBlock); 4214 if (destLBlock != null) { 4215 return destLBlock.getBlockStatus(); 4216 } 4217 4218 if (log.isDebugEnabled()) { 4219 log.debug("Layout Block {} returned as null", destBlock.getDisplayName()); 4220 } 4221 return -1; 4222 } 4223 4224 void setValidCurrentRoute(boolean boo) { 4225 if (validCurrentRoute == boo) { 4226 return; 4227 } 4228 validCurrentRoute = boo; 4229 firePropertyChange("valid", null, getRouteIndex(this)); 4230 } 4231 4232 boolean isRouteCurrentlyValid() { 4233 return validCurrentRoute; 4234 } 4235 4236 // Misc flags is not used in general routing, but is used for determining route removals 4237 void setMiscFlags(int f) { 4238 miscflags = f; 4239 } 4240 4241 int getMiscFlags() { 4242 return miscflags; 4243 } 4244 } 4245 4246 /** 4247 * Get the number of valid through paths on this block. 4248 * 4249 * @return count of paths through this block 4250 */ 4251 public int getNumberOfThroughPaths() { 4252 return throughPaths.size(); 4253 } 4254 4255 /** 4256 * Get the source block at index i 4257 * 4258 * @param i index in throughPaths 4259 * @return source block 4260 */ 4261 public Block getThroughPathSource(int i) { 4262 return throughPaths.get(i).getSourceBlock(); 4263 } 4264 4265 /** 4266 * Get the destination block at index i 4267 * 4268 * @param i index in throughPaths 4269 * @return final block 4270 */ 4271 public Block getThroughPathDestination(int i) { 4272 return throughPaths.get(i).getDestinationBlock(); 4273 } 4274 4275 /** 4276 * Is the through path at index i active? 4277 * 4278 * @param i index in path 4279 * @return active or not 4280 */ 4281 public Boolean isThroughPathActive(int i) { 4282 return throughPaths.get(i).isPathActive(); 4283 } 4284 4285 private class ThroughPaths implements PropertyChangeListener { 4286 4287 Block sourceBlock; 4288 Block destinationBlock; 4289 Path sourcePath; 4290 Path destinationPath; 4291 4292 boolean pathActive = false; 4293 4294 HashMap<Turnout, Integer> _turnouts = new HashMap<>(); 4295 4296 ThroughPaths(Block srcBlock, Path srcPath, Block destBlock, Path dstPath) { 4297 sourceBlock = srcBlock; 4298 destinationBlock = destBlock; 4299 sourcePath = srcPath; 4300 destinationPath = dstPath; 4301 } 4302 4303 Block getSourceBlock() { 4304 return sourceBlock; 4305 } 4306 4307 Block getDestinationBlock() { 4308 return destinationBlock; 4309 } 4310 4311 Path getSourcePath() { 4312 return sourcePath; 4313 } 4314 4315 Path getDestinationPath() { 4316 return destinationPath; 4317 } 4318 4319 boolean isPathActive() { 4320 return pathActive; 4321 } 4322 4323 void setTurnoutList(List<LayoutTrackExpectedState<LayoutTurnout>> turnouts) { 4324 if (!_turnouts.isEmpty()) { 4325 Set<Turnout> en = _turnouts.keySet(); 4326 en.forEach((listTurnout) -> listTurnout.removePropertyChangeListener(this)); 4327 } 4328 4329 // If we have no turnouts in this path, then this path is always active 4330 if (turnouts.isEmpty()) { 4331 pathActive = true; 4332 setRoutesValid(sourceBlock, true); 4333 setRoutesValid(destinationBlock, true); 4334 return; 4335 } 4336 _turnouts = new HashMap<>(turnouts.size()); 4337 for (LayoutTrackExpectedState<LayoutTurnout> turnout : turnouts) { 4338 if (turnout.getObject() instanceof LayoutSlip) { 4339 int slipState = turnout.getExpectedState(); 4340 LayoutSlip ls = (LayoutSlip) turnout.getObject(); 4341 int taState = ls.getTurnoutState(slipState); 4342 _turnouts.put(ls.getTurnout(), taState); 4343 ls.getTurnout().addPropertyChangeListener(this, ls.getTurnoutName(), "Layout Block Routing"); 4344 4345 int tbState = ls.getTurnoutBState(slipState); 4346 _turnouts.put(ls.getTurnoutB(), tbState); 4347 ls.getTurnoutB().addPropertyChangeListener(this, ls.getTurnoutBName(), "Layout Block Routing"); 4348 } else { 4349 LayoutTurnout lt = turnout.getObject(); 4350 if (lt.getTurnout() != null) { 4351 _turnouts.put(lt.getTurnout(), turnout.getExpectedState()); 4352 lt.getTurnout().addPropertyChangeListener(this, lt.getTurnoutName(), "Layout Block Routing"); 4353 } else { 4354 log.error("{} has no physical turnout allocated, block = {}", lt, block.getDisplayName()); 4355 } 4356 } 4357 } 4358 } 4359 4360 @Override 4361 public void propertyChange(PropertyChangeEvent e) { 4362 if (e.getPropertyName().equals("KnownState")) { 4363 Turnout srcTurnout = (Turnout) e.getSource(); 4364 int newVal = (Integer) e.getNewValue(); 4365 int values = _turnouts.get(srcTurnout); 4366 boolean allset = false; 4367 pathActive = false; 4368 4369 if (newVal == values) { 4370 allset = true; 4371 4372 if (_turnouts.size() > 1) { 4373 for (Map.Entry<Turnout, Integer> entry : _turnouts.entrySet()) { 4374 if (srcTurnout != entry.getKey()) { 4375 int state = entry.getKey().getState(); 4376 if (state != entry.getValue()) { 4377 allset = false; 4378 break; 4379 } 4380 } 4381 } 4382 } 4383 } 4384 updateActiveThroughPaths(this, allset); 4385 pathActive = allset; 4386 } 4387 } 4388 } 4389 4390 @Nonnull 4391 List<Block> getThroughPathSourceByDestination(Block dest) { 4392 List<Block> a = new ArrayList<>(); 4393 4394 for (ThroughPaths throughPath : throughPaths) { 4395 if (throughPath.getDestinationBlock() == dest) { 4396 a.add(throughPath.getSourceBlock()); 4397 } 4398 } 4399 return a; 4400 } 4401 4402 @Nonnull 4403 List<Block> getThroughPathDestinationBySource(Block source) { 4404 List<Block> a = new ArrayList<>(); 4405 4406 for (ThroughPaths throughPath : throughPaths) { 4407 if (throughPath.getSourceBlock() == source) { 4408 a.add(throughPath.getDestinationBlock()); 4409 } 4410 } 4411 return a; 4412 } 4413 4414 /** 4415 * When a route is created, check to see if the through path that this route 4416 * relates to is active. 4417 * @param r The route to check 4418 * @return true if that route is active 4419 */ 4420 boolean checkIsRouteOnValidThroughPath(Routes r) { 4421 for (ThroughPaths t : throughPaths) { 4422 if (t.isPathActive()) { 4423 if (t.getDestinationBlock() == r.getNextBlock()) { 4424 return true; 4425 } 4426 if (t.getSourceBlock() == r.getNextBlock()) { 4427 return true; 4428 } 4429 } 4430 } 4431 return false; 4432 } 4433 4434 /** 4435 * Go through all the routes and refresh the valid flag. 4436 */ 4437 public void refreshValidRoutes() { 4438 for (int i = 0; i < throughPaths.size(); i++) { 4439 ThroughPaths t = throughPaths.get(i); 4440 setRoutesValid(t.getDestinationBlock(), t.isPathActive()); 4441 setRoutesValid(t.getSourceBlock(), t.isPathActive()); 4442 firePropertyChange("path", null, i); 4443 } 4444 } 4445 4446 // We keep a track of what is paths are active, only so that we can easily mark 4447 // which routes are also potentially valid 4448 List<ThroughPaths> activePaths; 4449 4450 void updateActiveThroughPaths(ThroughPaths tp, boolean active) { 4451 if (enableUpdateRouteLogging) { 4452 log.info("We have been notified that a through path has changed state"); 4453 } 4454 4455 if (activePaths == null) { 4456 activePaths = new ArrayList<>(); 4457 } 4458 4459 if (active) { 4460 activePaths.add(tp); 4461 setRoutesValid(tp.getSourceBlock(), active); 4462 setRoutesValid(tp.getDestinationBlock(), active); 4463 } else { 4464 // We need to check if either our source or des is in use by another path. 4465 activePaths.remove(tp); 4466 boolean SourceInUse = false; 4467 boolean DestinationInUse = false; 4468 4469 for (ThroughPaths activePath : activePaths) { 4470 Block testSour = activePath.getSourceBlock(); 4471 Block testDest = activePath.getDestinationBlock(); 4472 if ((testSour == tp.getSourceBlock()) || (testDest == tp.getSourceBlock())) { 4473 SourceInUse = true; 4474 } 4475 if ((testSour == tp.getDestinationBlock()) || (testDest == tp.getDestinationBlock())) { 4476 DestinationInUse = true; 4477 } 4478 } 4479 4480 if (!SourceInUse) { 4481 setRoutesValid(tp.getSourceBlock(), active); 4482 } 4483 4484 if (!DestinationInUse) { 4485 setRoutesValid(tp.getDestinationBlock(), active); 4486 } 4487 } 4488 4489 for (int i = 0; i < throughPaths.size(); i++) { 4490 // This is processed simply for the throughpath table. 4491 if (tp == throughPaths.get(i)) { 4492 firePropertyChange("path", null, i); 4493 } 4494 } 4495 } 4496 4497 /** 4498 * Set the valid flag for routes that are on a valid through path. 4499 * @param nxtHopActive the start of the route 4500 * @param state the state to set into the valid flag 4501 */ 4502 void setRoutesValid(Block nxtHopActive, boolean state) { 4503 List<Routes> rtr = getRouteByNeighbour(nxtHopActive); 4504 rtr.forEach((rt) -> rt.setValidCurrentRoute(state)); 4505 } 4506 4507 @Override 4508 public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException { 4509 if ("CanDelete".equals(evt.getPropertyName())) { // NOI18N 4510 if (evt.getOldValue() instanceof Sensor) { 4511 if (evt.getOldValue().equals(getOccupancySensor())) { 4512 throw new PropertyVetoException(getDisplayName(), evt); 4513 } 4514 } 4515 4516 if (evt.getOldValue() instanceof Memory) { 4517 if (evt.getOldValue().equals(getMemory())) { 4518 throw new PropertyVetoException(getDisplayName(), evt); 4519 } 4520 } 4521 } else if ("DoDelete".equals(evt.getPropertyName())) { // NOI18N 4522 // Do nothing at this stage 4523 if (evt.getOldValue() instanceof Sensor) { 4524 if (evt.getOldValue().equals(getOccupancySensor())) { 4525 setOccupancySensorName(null); 4526 } 4527 } 4528 4529 if (evt.getOldValue() instanceof Memory) { 4530 if (evt.getOldValue().equals(getMemory())) { 4531 setMemoryName(null); 4532 } 4533 } 4534 } 4535 } 4536 4537 @Override 4538 public List<NamedBeanUsageReport> getUsageReport(NamedBean bean) { 4539 List<NamedBeanUsageReport> report = new ArrayList<>(); 4540 if (bean != null) { 4541 if (bean.equals(getBlock())) { 4542 report.add(new NamedBeanUsageReport("LayoutBlockBlock")); // NOI18N 4543 } 4544 if (bean.equals(getMemory())) { 4545 report.add(new NamedBeanUsageReport("LayoutBlockMemory")); // NOI18N 4546 } 4547 if (bean.equals(getOccupancySensor())) { 4548 report.add(new NamedBeanUsageReport("LayoutBlockSensor")); // NOI18N 4549 } 4550 for (int i = 0; i < getNumberOfNeighbours(); i++) { 4551 if (bean.equals(getNeighbourAtIndex(i))) { 4552 report.add(new NamedBeanUsageReport("LayoutBlockNeighbor", "Neighbor")); // NOI18N 4553 } 4554 } 4555 } 4556 return report; 4557 } 4558 4559 @Override 4560 public String getBeanType() { 4561 return Bundle.getMessage("BeanNameLayoutBlock"); 4562 } 4563 4564 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LayoutBlock.class); 4565 4566}