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