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