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