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