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