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