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 if (updateReferences.isEmpty()) { 2600 lastID = 0; 2601 } else { 2602 int lastIDPos = updateReferences.size() - 1; 2603 lastID = updateReferences.get(lastIDPos) + 1; 2604 } 2605 2606 if (lastID > 2000) { 2607 lastID = 0; 2608 } 2609 updateReferences.add(lastID); 2610 2611 /*As we are originating a packet, we will added to the acted upion list 2612 thus making sure if the packet gets back to us we do knowing with it.*/ 2613 actedUponUpdates.add(lastID); 2614 2615 if (updateReferences.size() > 500) { 2616 // log.info("flush update references"); 2617 updateReferences.subList(0, 250).clear(); 2618 } 2619 2620 if (actedUponUpdates.size() > 500) { 2621 actedUponUpdates.subList(0, 250).clear(); 2622 } 2623 return lastID; 2624 } 2625 2626 boolean updatePacketActedUpon(Integer packetID) { 2627 return actedUponUpdates.contains(packetID); 2628 } 2629 2630 public List<Block> getActiveNextBlocks(Block source) { 2631 List<Block> currentPath = new ArrayList<>(); 2632 2633 for (ThroughPaths path : throughPaths) { 2634 if ((path.getSourceBlock() == source) && (path.isPathActive())) { 2635 currentPath.add(path.getDestinationBlock()); 2636 } 2637 } 2638 return currentPath; 2639 } 2640 2641 public Path getThroughPathSourcePathAtIndex(int i) { 2642 return throughPaths.get(i).getSourcePath(); 2643 } 2644 2645 public Path getThroughPathDestinationPathAtIndex(int i) { 2646 return throughPaths.get(i).getDestinationPath(); 2647 } 2648 2649 public boolean validThroughPath(Block sourceBlock, Block destinationBlock) { 2650 for (ThroughPaths throughPath : throughPaths) { 2651 if ((throughPath.getSourceBlock() == sourceBlock) && (throughPath.getDestinationBlock() == destinationBlock)) { 2652 return true; 2653 } else if ((throughPath.getSourceBlock() == destinationBlock) && (throughPath.getDestinationBlock() == sourceBlock)) { 2654 return true; 2655 } 2656 } 2657 return false; 2658 } 2659 2660 public int getThroughPathIndex(Block sourceBlock, Block destinationBlock) { 2661 for (int i = 0; i < throughPaths.size(); i++) { 2662 if ((throughPaths.get(i).getSourceBlock() == sourceBlock) 2663 && (throughPaths.get(i).getDestinationBlock() == destinationBlock)) { 2664 return i; 2665 } else if ((throughPaths.get(i).getSourceBlock() == destinationBlock) 2666 && (throughPaths.get(i).getDestinationBlock() == sourceBlock)) { 2667 return i; 2668 } 2669 } 2670 return -1; 2671 } 2672 2673 private final List<Adjacencies> neighbours = new ArrayList<>(); 2674 2675 private final List<ThroughPaths> throughPaths = new ArrayList<>(); 2676 2677 // A sub class that holds valid routes through the block. 2678 // Possibly want to store the path direction in here as well. 2679 // or we store the ref to the path, so we can get the directions. 2680 private final List<Routes> routes = new ArrayList<>(); 2681 2682 String decodePacketFlow(int value) { 2683 switch (value) { 2684 case RXTX: { 2685 return "Bi-Direction Operation"; 2686 } 2687 2688 case RXONLY: { 2689 return "Uni-Directional - Trains can only exit to this block (RX) "; 2690 } 2691 2692 case TXONLY: { 2693 return "Uni-Directional - Trains can not be sent down this block (TX) "; 2694 } 2695 2696 case NONE: { 2697 return "None routing updates will be passed"; 2698 } 2699 default: 2700 log.warn("Unhandled packet flow value: {}", value); 2701 break; 2702 } 2703 return "Unknown"; 2704 } 2705 2706 /** 2707 * Provide an output to the console of all the valid paths through this 2708 * block. 2709 */ 2710 public void printValidThroughPaths() { 2711 log.info("Through paths for block {}", this.getDisplayName()); 2712 log.info("Current Block, From Block, To Block"); 2713 for (ThroughPaths tp : throughPaths) { 2714 String activeStr = ""; 2715 if (tp.isPathActive()) { 2716 activeStr = ", *"; 2717 } 2718 log.info("From {}, {}, {}{}", this.getDisplayName(), 2719 (tp.getSourceBlock()).getDisplayName(), (tp.getDestinationBlock()).getDisplayName(), activeStr); 2720 } 2721 } 2722 2723 /** 2724 * Provide an output to the console of all our neighbouring blocks. 2725 */ 2726 public void printAdjacencies() { 2727 log.info("Adjacencies for block {}", this.getDisplayName()); 2728 log.info("Neighbour, Direction, mutual, relationship, metric"); 2729 for (Adjacencies neighbour : neighbours) { 2730 log.info(" neighbor: {}, {}, {}, {}, {}", neighbour.getBlock().getDisplayName(), 2731 Path.decodeDirection(neighbour.getDirection()), neighbour.isMutual(), 2732 decodePacketFlow(neighbour.getPacketFlow()), neighbour.getMetric()); 2733 } 2734 } 2735 2736 /** 2737 * Provide an output to the console of all the remote blocks reachable from 2738 * our block. 2739 */ 2740 public void printRoutes() { 2741 log.info("Routes for block {}", this.getDisplayName()); 2742 log.info("Destination, Next Block, Hop Count, Direction, State, Metric"); 2743 for (Routes r : routes) { 2744 String nexthop = r.getNextBlock().getDisplayName(); 2745 2746 if (r.getNextBlock() == this.getBlock()) { 2747 nexthop = "Directly Connected"; 2748 } 2749 String activeString = ""; 2750 if (r.isRouteCurrentlyValid()) { 2751 activeString = ", *"; 2752 } 2753 2754 log.info(" neighbor: {}, {}, {}, {}, {}, {}{}", r.getDestBlock().getDisplayName(), 2755 nexthop, r.getHopCount(), Path.decodeDirection(r.getDirection()), 2756 r.getState(), r.getMetric(), activeString); 2757 } 2758 } 2759 2760 /** 2761 * Provide an output to the console of how to reach a specific block from 2762 * our block. 2763 * 2764 * @param inBlockName to find in route 2765 */ 2766 public void printRoutes(String inBlockName) { 2767 log.info("Routes for block {}", this.getDisplayName()); 2768 log.info("Our Block, Destination, Next Block, Hop Count, Direction, Metric"); 2769 for (Routes route : routes) { 2770 if (route.getDestBlock().getDisplayName().equals(inBlockName)) { 2771 log.info("From {}, {}, {}, {}, {}, {}", 2772 getDisplayName(), (route.getDestBlock()).getDisplayName(), 2773 route.getNextBlock().getDisplayName(), route.getHopCount(), 2774 Path.decodeDirection(route.getDirection()), route.getMetric()); 2775 } 2776 } 2777 } 2778 2779 /** 2780 * @param destBlock is the destination of the block we are following 2781 * @param direction is the direction of travel from the previous block 2782 * @return next block 2783 */ 2784 public Block getNextBlock(Block destBlock, int direction) { 2785 int bestMetric = 965000; 2786 Block bestBlock = null; 2787 2788 for (Routes r : routes) { 2789 if ((r.getDestBlock() == destBlock) && (r.getDirection() == direction)) { 2790 if (r.getMetric() < bestMetric) { 2791 bestMetric = r.getMetric(); 2792 bestBlock = r.getNextBlock(); 2793 // bestBlock=r.getDestBlock(); 2794 } 2795 } 2796 } 2797 return bestBlock; 2798 } 2799 2800 /** 2801 * Used if we already know the block prior to our block, and the destination 2802 * block. direction, is optional and is used where the previousBlock is 2803 * equal to our block. 2804 * 2805 * @param previousBlock start block 2806 * @param destBlock finish block 2807 * @return next block 2808 */ 2809 @CheckForNull 2810 public Block getNextBlock(Block previousBlock, Block destBlock) { 2811 int bestMetric = 965000; 2812 Block bestBlock = null; 2813 2814 for (Routes r : routes) { 2815 if (r.getDestBlock() == destBlock) { 2816 // Check that the route through from the previous block, to the next hop is valid 2817 if (validThroughPath(previousBlock, r.getNextBlock())) { 2818 if (r.getMetric() < bestMetric) { 2819 bestMetric = r.getMetric(); 2820 // bestBlock=r.getDestBlock(); 2821 bestBlock = r.getNextBlock(); 2822 } 2823 } 2824 } 2825 } 2826 return bestBlock; 2827 } 2828 2829 public int getConnectedBlockRouteIndex(Block destBlock, int direction) { 2830 for (int i = 0; i < routes.size(); i++) { 2831 if (routes.get(i).getNextBlock() == this.getBlock()) { 2832 log.info("Found a block that is directly connected"); 2833 2834 if ((routes.get(i).getDestBlock() == destBlock)) { 2835 log.info("In getConnectedBlockRouteIndex, {}", 2836 Integer.toString(routes.get(i).getDirection() & direction)); 2837 if ((routes.get(i).getDirection() & direction) != 0) { 2838 return i; 2839 } 2840 } 2841 } 2842 2843 if (log.isDebugEnabled()) { 2844 log.debug("From {}, {}, nexthop {}, {}, {}, {}", getDisplayName(), 2845 routes.get(i).getDestBlock().getDisplayName(), 2846 routes.get(i).getHopCount(), 2847 Path.decodeDirection(routes.get(i).getDirection()), 2848 routes.get(i).getState(), routes.get(i).getMetric()); 2849 } 2850 } 2851 return -1; 2852 } 2853 2854 // Need to work on this to deal with the method of routing 2855 public int getNextBlockByIndex(Block destBlock, int direction, int offSet) { 2856 for (int i = offSet; i < routes.size(); i++) { 2857 Routes ro = routes.get(i); 2858 if ((ro.getDestBlock() == destBlock)) { 2859 log.info("getNextBlockByIndex {}", Integer.toString(ro.getDirection() & direction)); 2860 if ((ro.getDirection() & direction) != 0) { 2861 return i; 2862 } 2863 } 2864 } 2865 return -1; 2866 } 2867 2868 // Need to work on this to deal with the method of routing 2869 /* 2870 * 2871 */ 2872 public int getNextBlockByIndex(Block previousBlock, Block destBlock, int offSet) { 2873 for (int i = offSet; i < routes.size(); i++) { 2874 Routes ro = routes.get(i); 2875 // log.info(r.getDestBlock().getDisplayName() + " vs " + destBlock.getDisplayName()); 2876 if (ro.getDestBlock() == destBlock) { 2877 // Check that the route through from the previous block, to the next hop is valid 2878 if (validThroughPath(previousBlock, ro.getNextBlock())) { 2879 log.debug("valid through path"); 2880 return i; 2881 } 2882 2883 if (ro.getNextBlock() == this.getBlock()) { 2884 log.debug("getNextBlock is this block therefore directly connected"); 2885 return i; 2886 } 2887 } 2888 } 2889 return -1; 2890 } 2891 2892 /** 2893 * last index - the index of the last block we returned ie we last returned 2894 * index 10, so we don't want to return it again. The block returned will 2895 * have a hopcount or metric equal to or greater than the one of the last 2896 * block returned. if the exclude block list is empty this is the first 2897 * time, it has been used. The parameters for the best last block are based 2898 * upon the last entry in the excludedBlock list. 2899 * 2900 * @param previousBlock starting block 2901 * @param destBlock finish block 2902 * @param excludeBlock blocks to skip 2903 * @param routingMethod value to match metric 2904 * @return next block 2905 */ 2906 public int getNextBestBlock(Block previousBlock, Block destBlock, List<Integer> excludeBlock, LayoutBlockConnectivityTools.Metric routingMethod) { 2907 searchRouteLog.debug("From {} find best route from {} to {} index {} routingMethod {}", 2908 getDisplayName(), previousBlock.getDisplayName(), destBlock.getDisplayName(), excludeBlock, routingMethod); 2909 2910 int bestCount = 965255; // set stupidly high 2911 int bestIndex = -1; 2912 int lastValue = 0; 2913 List<Block> nextBlocks = new ArrayList<>(5); 2914 if (!excludeBlock.isEmpty() && (excludeBlock.get(excludeBlock.size() - 1) < routes.size())) { 2915 if (routingMethod == LayoutBlockConnectivityTools.Metric.METRIC) { 2916 lastValue = routes.get(excludeBlock.get(excludeBlock.size() - 1)).getMetric(); 2917 } else /* if (routingMethod==LayoutBlockManager.HOPCOUNT)*/ { 2918 lastValue = routes.get(excludeBlock.get(excludeBlock.size() - 1)).getHopCount(); 2919 } 2920 2921 for (int i : excludeBlock) { 2922 nextBlocks.add(routes.get(i).getNextBlock()); 2923 } 2924 2925 searchRouteLog.debug("last index is {} {}", excludeBlock.get(excludeBlock.size() - 1), 2926 routes.get(excludeBlock.get(excludeBlock.size() - 1)).getDestBlock().getDisplayName()); 2927 } 2928 2929 for (int i = 0; i < routes.size(); i++) { 2930 if (!excludeBlock.contains(i)) { 2931 Routes ro = routes.get(i); 2932 if (!nextBlocks.contains(ro.getNextBlock())) { 2933 // if(ro.getNextBlock()!=nextBlock){ 2934 int currentValue; 2935 if (routingMethod == LayoutBlockConnectivityTools.Metric.METRIC) { 2936 currentValue = routes.get(i).getMetric(); 2937 } else /*if (routingMethod==InstanceManager.getDefault( 2938 LayoutBlockManager.class).HOPCOUNT)*/ { 2939 currentValue = routes.get(i).getHopCount(); // was lastindex changed to i 2940 } 2941 2942 if (currentValue >= lastValue) { 2943 if (ro.getDestBlock() == destBlock) { 2944 searchRouteLog.debug("Match on dest blocks"); 2945 // Check that the route through from the previous block, to the next hop is valid 2946 searchRouteLog.debug("Is valid through path previous block {} to {}", 2947 previousBlock.getDisplayName(), ro.getNextBlock().getDisplayName()); 2948 2949 if (validThroughPath(previousBlock, ro.getNextBlock())) { 2950 searchRouteLog.debug("valid through path"); 2951 2952 if (routingMethod == LayoutBlockConnectivityTools.Metric.METRIC) { 2953 if (ro.getMetric() < bestCount) { 2954 bestIndex = i; 2955 bestCount = ro.getMetric(); 2956 } 2957 } else /*if (routingMethod==InstanceManager.getDefault( 2958 LayoutBlockManager.class).HOPCOUNT)*/ { 2959 if (ro.getHopCount() < bestCount) { 2960 bestIndex = i; 2961 bestCount = ro.getHopCount(); 2962 } 2963 } 2964 } 2965 2966 if (ro.getNextBlock() == this.getBlock()) { 2967 searchRouteLog.debug("getNextBlock is this block therefore directly connected"); 2968 return i; 2969 } 2970 } 2971 } 2972 } 2973 } 2974 } 2975 2976 searchRouteLog.debug("returning {} best count {}", bestIndex, bestCount); 2977 return bestIndex; 2978 } 2979 2980 @CheckForNull 2981 Routes getRouteByDestBlock(Block blk) { 2982 for (int i = routes.size() - 1; i > -1; i--) { 2983 if (routes.get(i).getDestBlock() == blk) { 2984 return routes.get(i); 2985 } 2986 } 2987 return null; 2988 } 2989 2990 @Nonnull 2991 List<Routes> getRouteByNeighbour(Block blk) { 2992 List<Routes> rtr = new ArrayList<>(); 2993 for (Routes route : routes) { 2994 if (route.getNextBlock() == blk) { 2995 rtr.add(route); 2996 } 2997 } 2998 return rtr; 2999 } 3000 3001 int getAdjacencyPacketFlow(Block blk) { 3002 for (Adjacencies neighbour : neighbours) { 3003 if (neighbour.getBlock() == blk) { 3004 return neighbour.getPacketFlow(); 3005 } 3006 } 3007 return -1; 3008 } 3009 3010 boolean isValidNeighbour(Block blk) { 3011 for (Adjacencies neighbour : neighbours) { 3012 if (neighbour.getBlock() == blk) { 3013 return true; 3014 } 3015 } 3016 return false; 3017 } 3018 3019 @Override 3020 public synchronized void addPropertyChangeListener(PropertyChangeListener listener) { 3021 if (listener == this) { 3022 log.debug("adding ourselves as a listener for some strange reason! Skipping"); 3023 return; 3024 } 3025 super.addPropertyChangeListener(listener); 3026 } 3027 3028 // TODO - check "NewRoute" - only appears in Bundle strings 3029 @Override 3030 public void propertyChange(PropertyChangeEvent e) { 3031 3032 switch (e.getPropertyName()) { 3033 case "NewRoute": { 3034 updateRouteLog.debug("==Event type {} New {}", 3035 e.getPropertyName(), ((LayoutBlock) e.getNewValue()).getDisplayName()); 3036 break; 3037 } 3038 case PROPERTY_THROUGH_PATH_ADDED: { 3039 updateRouteLog.debug("neighbour has new through path"); 3040 break; 3041 } 3042 case PROPERTY_THROUGH_PATH_REMOVED: { 3043 updateRouteLog.debug("neighbour has through removed"); 3044 break; 3045 } 3046 case PROPERTY_ROUTING: { 3047 if (e.getSource() instanceof LayoutBlock) { 3048 LayoutBlock sourceLayoutBlock = (LayoutBlock) e.getSource(); 3049 updateRouteLog.debug("From {} we have a routing packet update from neighbour {}", 3050 getDisplayName(), sourceLayoutBlock.getDisplayName()); 3051 RoutingPacket update = (RoutingPacket) e.getNewValue(); 3052 int updateType = update.getPacketType(); 3053 switch (updateType) { 3054 case ADDITION: { 3055 updateRouteLog.debug("\t updateType: Addition"); 3056 // InstanceManager.getDefault( 3057 // LayoutBlockManager.class).setLastRoutingChange(); 3058 addRouteFromNeighbour(sourceLayoutBlock, update); 3059 break; 3060 } 3061 case UPDATE: { 3062 updateRouteLog.debug("\t updateType: Update"); 3063 updateRoutingInfo(sourceLayoutBlock, update); 3064 break; 3065 } 3066 case REMOVAL: { 3067 updateRouteLog.debug("\t updateType: Removal"); 3068 InstanceManager.getDefault(LayoutBlockManager.class).setLastRoutingChange(); 3069 removeRouteFromNeighbour(sourceLayoutBlock, update); 3070 break; 3071 } 3072 default: { 3073 break; 3074 } 3075 } // switch (updateType) 3076 } // if (e.getSource() instanceof LayoutBlock) 3077 break; 3078 } 3079 default: { 3080 log.debug("Unhandled propertyChange({}): ", e); 3081 break; 3082 } 3083 } // switch (e.getPropertyName()) 3084 } // propertyChange 3085 3086 /** 3087 * Get valid Routes, based upon the next block and destination block 3088 * 3089 * @param nxtBlock next block 3090 * @param dstBlock final block 3091 * @return routes that fit, or null 3092 */ 3093 @CheckForNull 3094 Routes getValidRoute(Block nxtBlock, Block dstBlock) { 3095 if ( nxtBlock != null && dstBlock != null ) { 3096 List<Routes> rtr = getRouteByNeighbour(nxtBlock); 3097 3098 if (rtr.isEmpty()) { 3099 log.debug("From {}, no routes returned for getRouteByNeighbour({})", 3100 this.getDisplayName(), 3101 nxtBlock.getDisplayName()); 3102 return null; 3103 } 3104 3105 for (Routes rt : rtr) { 3106 if (rt.getDestBlock() == dstBlock) { 3107 log.debug("From {}, found dest {}.", this.getDisplayName(), dstBlock.getDisplayName()); 3108 return rt; 3109 } 3110 } 3111 log.debug("From {}, no routes to {}.", this.getDisplayName(), nxtBlock.getDisplayName()); 3112 } else { 3113 log.warn("getValidRoute({}, {}", 3114 (nxtBlock != null) ? nxtBlock.getDisplayName() : "<null>", 3115 (dstBlock != null) ? dstBlock.getDisplayName() : "<null>"); 3116 } 3117 return null; 3118 } 3119 3120 /** 3121 * Is the route to the destination block, going via our neighbouring block 3122 * valid. ie Does the block have a route registered via neighbour 3123 * "protecting" to the destination block. 3124 * 3125 * @param protecting neighbour block that might protect 3126 * @param destination block 3127 * @return true if we have valid path to block 3128 */ 3129 public boolean isRouteToDestValid(Block protecting, Block destination) { 3130 if (protecting == destination) { 3131 log.debug("protecting and destination blocks are the same " 3132 + "therefore we need to check if we have a valid neighbour"); 3133 3134 // We are testing for a directly connected block. 3135 if (getAdjacency(protecting) != null) { 3136 return true; 3137 } 3138 } else if (getValidRoute(protecting, destination) != null) { 3139 return true; 3140 } 3141 return false; 3142 } 3143 3144 /** 3145 * Get a list of valid Routes to our destination block 3146 * 3147 * @param dstBlock target to find 3148 * @return routes between this and dstBlock 3149 */ 3150 List<Routes> getDestRoutes(Block dstBlock) { 3151 List<Routes> rtr = new ArrayList<>(); 3152 var tempRouteList = new ArrayList<>(routes); 3153 for (Routes route : tempRouteList) { 3154 if (route.getDestBlock() == dstBlock) { 3155 rtr.add(route); 3156 } 3157 } 3158 return rtr; 3159 } 3160 3161 /** 3162 * Get a list of valid Routes via our next block 3163 * 3164 * @param nxtBlock target block 3165 * @return list of routes to target block 3166 */ 3167 List<Routes> getNextRoutes(Block nxtBlock) { 3168 List<Routes> rtr = new ArrayList<>(); 3169 for (Routes route : routes) { 3170 if (route.getNextBlock() == nxtBlock) { 3171 rtr.add(route); 3172 } 3173 } 3174 return rtr; 3175 } 3176 3177 void updateRoutingInfo(Routes route) { 3178 if (route.getHopCount() >= 254) { 3179 return; 3180 } 3181 Block destBlock = route.getDestBlock(); 3182 3183 RoutingPacket update = new RoutingPacket(UPDATE, destBlock, getBestRouteByHop(destBlock).getHopCount() + 1, 3184 ((getBestRouteByMetric(destBlock).getMetric()) + metric), 3185 ((getBestRouteByMetric(destBlock).getMetric()) 3186 + block.getLengthMm()), -1, 3187 getNextPacketID()); 3188 firePropertyChange(PROPERTY_ROUTING, null, update); 3189 } 3190 3191 // This lot might need changing to only forward on the best route details. 3192 void updateRoutingInfo( @Nonnull LayoutBlock src, @Nonnull RoutingPacket update) { 3193 updateRouteLog.debug("From {} src: {}, block: {}, hopCount: {}, metric: {}, status: {}, packetID: {}", 3194 getDisplayName(), src.getDisplayName(), update.getBlock().getDisplayName(), 3195 update.getHopCount(), update.getMetric(), update.getBlockState(), update.getPacketId()); 3196 Block srcblk = src.getBlock(); 3197 Adjacencies adj = getAdjacency(srcblk); 3198 3199 if (adj == null) { 3200 updateRouteLog.debug("From {} packet is from a src that is not registered {}", 3201 getDisplayName(), srcblk.getDisplayName()); 3202 // If the packet is from a src that is not registered as a neighbour 3203 // Then we will simply reject it. 3204 return; 3205 } 3206 3207 if (updatePacketActedUpon(update.getPacketId())) { 3208 if (adj.updatePacketActedUpon(update.getPacketId())) { 3209 updateRouteLog.debug("Reject packet update as we have already acted up on it from this neighbour"); 3210 return; 3211 } 3212 } 3213 3214 updateRouteLog.debug("From {} an Update packet from neighbour {}", getDisplayName(), src.getDisplayName()); 3215 3216 Block updateBlock = update.getBlock(); 3217 3218 // Block srcblk = src.getBlock(); 3219 // Need to add in a check to make sure that we have a route registered from the source neighbour 3220 // for the block that they are referring too. 3221 if (updateBlock == this.getBlock()) { 3222 updateRouteLog.debug("Reject packet update as it is a route advertised by our selves"); 3223 return; 3224 } 3225 3226 Routes ro; 3227 boolean neighbour = false; 3228 if (updateBlock == srcblk) { 3229 // Very likely that this update is from a neighbour about its own status. 3230 ro = getValidRoute(this.getBlock(), updateBlock); 3231 neighbour = true; 3232 } else { 3233 ro = getValidRoute(srcblk, updateBlock); 3234 } 3235 3236 if (ro == null) { 3237 updateRouteLog.debug("From {} update is from a source that we do not have listed as a route to the destination", getDisplayName()); 3238 updateRouteLog.debug("From {} update packet is for a block that we do not have route registered for {}", getDisplayName(), updateBlock.getDisplayName()); 3239 // If the packet is for a dest that is not in the routing table 3240 // Then we will simply reject it. 3241 return; 3242 } 3243 /*This prevents us from entering into an update loop. 3244 We only add it to our list once it has passed through as being a valid 3245 packet, otherwise we may get the same packet id back, but from a valid source 3246 which would end up be rejected*/ 3247 3248 actedUponUpdates.add(update.getPacketId()); 3249 adj.addPacketReceivedFromNeighbour(update.getPacketId()); 3250 3251 int hopCount = update.getHopCount(); 3252 int packetmetric = update.getMetric(); 3253 int blockstate = update.getBlockState(); 3254 float length = update.getLength(); 3255 3256 // Need to add in a check for a block that is directly connected. 3257 if (hopCount != -1) { 3258 // Was increase hop count before setting it 3259 // int oldHop = ro.getHopCount(); 3260 if (ro.getHopCount() != hopCount) { 3261 updateRouteLog.debug("{} Hop counts to {} not the same so will change from {} to {}", getDisplayName(), ro.getDestBlock().getDisplayName(), ro.getHopCount(), hopCount); 3262 ro.setHopCount(hopCount); 3263 hopCount++; 3264 } else { 3265 // No point in forwarding on the update if the hopcount hasn't changed 3266 hopCount = -1; 3267 } 3268 } 3269 3270 // bad to use values as errors, but it's pre-existing code, and code wins 3271 if ((int) length != -1) { 3272 // Length is added at source 3273 float oldLength = ro.getLength(); 3274 if (!MathUtil.equals(oldLength, length)) { 3275 ro.setLength(length); 3276 boolean forwardUpdate = true; 3277 3278 if (ro != getBestRouteByLength(update.getBlock())) { 3279 forwardUpdate = false; 3280 } 3281 3282 updateRouteLog.debug("From {} updating length from {} to {}", getDisplayName(), oldLength, length); 3283 3284 if (neighbour) { 3285 length = srcblk.getLengthMm(); 3286 adj.setLength(length); 3287 3288 // ro.setLength(length); 3289 // Also if neighbour we need to update the cost of the routes via it to reflect the new metric 02/20/2011 3290 if (forwardUpdate) { 3291 List<Routes> neighbourRoute = getNextRoutes(srcblk); 3292 3293 // neighbourRoutes, contains all the routes that have been advertised by the neighbour 3294 // that will need to have their metric updated to reflect the change. 3295 for (Routes nRo : neighbourRoute) { 3296 // Need to remove old metric to the neigbour, then add the new one on 3297 float updateLength = nRo.getLength(); 3298 updateLength = (updateLength - oldLength) + length; 3299 3300 updateRouteLog.debug("From {} update metric for route {} from {} to {}", 3301 getDisplayName(), nRo.getDestBlock().getDisplayName(), nRo.getLength(), updateLength); 3302 nRo.setLength(updateLength); 3303 List<Block> messageRecipients = getThroughPathDestinationBySource(srcblk); 3304 RoutingPacket newUpdate = new RoutingPacket(UPDATE, nRo.getDestBlock(), -1, -1, updateLength + block.getLengthMm(), -1, getNextPacketID()); 3305 updateRoutesToNeighbours(messageRecipients, nRo, newUpdate); 3306 } 3307 } 3308 } else if (forwardUpdate) { 3309 // This can cause a loop, if the layout is in a loop, so we send out the same packetID. 3310 List<Block> messageRecipients = getThroughPathSourceByDestination(srcblk); 3311 RoutingPacket newUpdate = new RoutingPacket(UPDATE, updateBlock, -1, -1, 3312 length + block.getLengthMm(), -1, update.getPacketId()); 3313 updateRoutesToNeighbours(messageRecipients, ro, newUpdate); 3314 } 3315 length += metric; 3316 } else { 3317 length = -1; 3318 } 3319 } 3320 3321 if (packetmetric != -1) { 3322 // Metric is added at source 3323 // Keep a reference of the old metric. 3324 int oldmetric = ro.getMetric(); 3325 if (oldmetric != packetmetric) { 3326 ro.setMetric(packetmetric); 3327 3328 updateRouteLog.debug("From {} updating metric from {} to {}", getDisplayName(), oldmetric, packetmetric); 3329 boolean forwardUpdate = true; 3330 3331 if (ro != getBestRouteByMetric(update.getBlock())) { 3332 forwardUpdate = false; 3333 } 3334 3335 // if the metric update is for a neighbour then we will go directly to the neighbour for the value, 3336 // rather than trust what is in the message at this stage. 3337 if (neighbour) { 3338 packetmetric = src.getBlockMetric(); 3339 adj.setMetric(packetmetric); 3340 3341 if (forwardUpdate) { 3342 // ro.setMetric(packetmetric); 3343 // Also if neighbour we need to update the cost of the routes via it to 3344 // reflect the new metric 02/20/2011 3345 List<Routes> neighbourRoute = getNextRoutes(srcblk); 3346 3347 // neighbourRoutes, contains all the routes that have been advertised by the neighbour that 3348 // will need to have their metric updated to reflect the change. 3349 for (Routes nRo : neighbourRoute) { 3350 // Need to remove old metric to the neigbour, then add the new one on 3351 int updatemet = nRo.getMetric(); 3352 updatemet = (updatemet - oldmetric) + packetmetric; 3353 3354 updateRouteLog.debug("From {} update metric for route {} from {} to {}", getDisplayName(), nRo.getDestBlock().getDisplayName(), nRo.getMetric(), updatemet); 3355 nRo.setMetric(updatemet); 3356 List<Block> messageRecipients = getThroughPathDestinationBySource(srcblk); 3357 RoutingPacket newUpdate = new RoutingPacket(UPDATE, nRo.getDestBlock(), hopCount, updatemet + metric, -1, -1, getNextPacketID()); 3358 updateRoutesToNeighbours(messageRecipients, nRo, newUpdate); 3359 } 3360 } 3361 } else if (forwardUpdate) { 3362 // This can cause a loop, if the layout is in a loop, so we send out the same packetID. 3363 List<Block> messageRecipients = getThroughPathSourceByDestination(srcblk); 3364 RoutingPacket newUpdate = new RoutingPacket(UPDATE, updateBlock, hopCount, 3365 packetmetric + metric, -1, -1, update.getPacketId()); 3366 updateRoutesToNeighbours(messageRecipients, ro, newUpdate); 3367 } 3368 packetmetric += metric; 3369 // Think we need a list of routes that originate from this source neighbour 3370 } else { 3371 // No point in forwarding on the update if the metric hasn't changed 3372 packetmetric = -1; 3373 // Potentially when we do this we need to update all the routes that go via this block, not just this route. 3374 } 3375 } 3376 3377 if (blockstate != -1) { 3378 // We will update all the destination blocks with the new state, it 3379 // saves re-firing off new updates block status 3380 boolean stateUpdated = false; 3381 List<Routes> rtr = getDestRoutes(updateBlock); 3382 3383 for (Routes rt : rtr) { 3384 if (rt.getState() != blockstate) { 3385 stateUpdated = true; 3386 rt.stateChange(); 3387 } 3388 } 3389 3390 if (stateUpdated) { 3391 RoutingPacket newUpdate = new RoutingPacket(UPDATE, updateBlock, -1, -1, -1, blockstate, getNextPacketID()); 3392 firePropertyChange(PROPERTY_ROUTING, null, newUpdate); 3393 } 3394 } 3395 3396 // We need to expand on this so that any update to routing metric is propergated correctly 3397 if ((packetmetric != -1) || (hopCount != -1) || (length != -1)) { 3398 // We only want to send the update on to neighbours that we have advertised the route to. 3399 List<Block> messageRecipients = getThroughPathSourceByDestination(srcblk); 3400 RoutingPacket newUpdate = new RoutingPacket(UPDATE, updateBlock, hopCount, packetmetric, 3401 length, blockstate, update.getPacketId()); 3402 updateRoutesToNeighbours(messageRecipients, ro, newUpdate); 3403 } 3404 // Was just pass on hop count 3405 } 3406 3407 void updateRoutesToNeighbours(List<Block> messageRecipients, Routes ro, RoutingPacket update) { 3408 for (Block messageRecipient : messageRecipients) { 3409 Adjacencies adj = getAdjacency(messageRecipient); 3410 if (adj.advertiseRouteToNeighbour(ro)) { 3411 adj.addRouteAdvertisedToNeighbour(ro); 3412 LayoutBlock recipient = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlock(messageRecipient); 3413 if (recipient != null) { 3414 recipient.updateRoutingInfo(this, update); 3415 } 3416 } 3417 } 3418 } 3419 3420 Routes getBestRouteByMetric(Block dest) { 3421 // int bestHopCount = 255; 3422 int bestMetric = 965000; 3423 int bestIndex = -1; 3424 3425 List<Routes> destRoutes = getDestRoutes(dest); 3426 for (int i = 0; i < destRoutes.size(); i++) { 3427 if (destRoutes.get(i).getMetric() < bestMetric) { 3428 bestMetric = destRoutes.get(i).getMetric(); 3429 bestIndex = i; 3430 } 3431 } 3432 3433 if (bestIndex == -1) { 3434 return null; 3435 } 3436 return destRoutes.get(bestIndex); 3437 } 3438 3439 Routes getBestRouteByHop(Block dest) { 3440 int bestHopCount = 255; 3441 // int bestMetric = 965000; 3442 int bestIndex = -1; 3443 3444 List<Routes> destRoutes = getDestRoutes(dest); 3445 for (int i = 0; i < destRoutes.size(); i++) { 3446 if (destRoutes.get(i).getHopCount() < bestHopCount) { 3447 bestHopCount = destRoutes.get(i).getHopCount(); 3448 bestIndex = i; 3449 } 3450 } 3451 3452 if (bestIndex == -1) { 3453 return null; 3454 } 3455 return destRoutes.get(bestIndex); 3456 } 3457 3458 Routes getBestRouteByLength(Block dest) { 3459 // int bestHopCount = 255; 3460 // int bestMetric = 965000; 3461 // long bestLength = 999999999; 3462 int bestIndex = -1; 3463 List<Routes> destRoutes = getDestRoutes(dest); 3464 float bestLength = destRoutes.get(0).getLength(); 3465 3466 for (int i = 0; i < destRoutes.size(); i++) { 3467 if (destRoutes.get(i).getLength() < bestLength) { 3468 bestLength = destRoutes.get(i).getLength(); 3469 bestIndex = i; 3470 } 3471 } 3472 3473 if (bestIndex == -1) { 3474 return null; 3475 } 3476 return destRoutes.get(bestIndex); 3477 } 3478 3479 void addRouteToNeighbours(Routes ro) { 3480 addRouteLog.debug("From {} Add route to neighbour", getDisplayName()); 3481 Block nextHop = ro.getNextBlock(); 3482 List<LayoutBlock> validFromPath = new ArrayList<>(); 3483 3484 addRouteLog.debug("From {} new block {}", getDisplayName(), nextHop.getDisplayName()); 3485 3486 for (int i = 0; i < throughPaths.size(); i++) { 3487 LayoutBlock validBlock = null; 3488 3489 addRouteLog.debug("Through routes index {}", i); 3490 addRouteLog.debug("From {} A through routes {} {}", getDisplayName(), 3491 throughPaths.get(i).getSourceBlock().getDisplayName(), 3492 throughPaths.get(i).getDestinationBlock().getDisplayName()); 3493 3494 /*As the through paths include each possible path, ie 2 > 3 and 3 > 2 3495 as seperate entries then we only need to forward the new route to those 3496 source blocks that have a desination of the next hop*/ 3497 if (throughPaths.get(i).getDestinationBlock() == nextHop) { 3498 if (getAdjacency(throughPaths.get(i).getSourceBlock()).isMutual()) { 3499 validBlock = InstanceManager.getDefault( 3500 LayoutBlockManager.class). 3501 getLayoutBlock(throughPaths.get(i).getSourceBlock()); 3502 } 3503 } 3504 3505 // only need to add it the once. Not sure if the contains is required. 3506 if ((validBlock != null) && (!validFromPath.contains(validBlock))) { 3507 validFromPath.add(validBlock); 3508 } 3509 } 3510 3511 if ( addRouteLog.isDebugEnabled() ) { 3512 addRouteLog.debug("From {} ===== valid from size path {} ==== (addroutetoneigh)", this.getDisplayName(), validFromPath.size()); 3513 3514 validFromPath.forEach( valid -> addRouteLog.debug("fromPath: {}", valid.getDisplayName())); 3515 addRouteLog.debug("Next Hop {}", nextHop.getDisplayName()); 3516 } 3517 RoutingPacket update = new RoutingPacket(ADDITION, ro.getDestBlock(), ro.getHopCount() + 1, 3518 ro.getMetric() + metric, 3519 (ro.getLength() + getBlock().getLengthMm()), -1, getNextPacketID()); 3520 3521 for (LayoutBlock layoutBlock : validFromPath) { 3522 Adjacencies adj = getAdjacency(layoutBlock.getBlock()); 3523 if (adj.advertiseRouteToNeighbour(ro)) { 3524 // getBestRouteByHop(destBlock).getHopCount()+1, ((getBestRouteByMetric(destBlock).getMetric())+metric), 3525 //((getBestRouteByMetric(destBlock).getMetric())+block.getLengthMm()) 3526 addRouteLog.debug("From {} Sending update to {} As this has a better hop count or metric", 3527 getDisplayName(), layoutBlock.getDisplayName()); 3528 adj.addRouteAdvertisedToNeighbour(ro); 3529 layoutBlock.addRouteFromNeighbour(this, update); 3530 } 3531 } 3532 } 3533 3534 void addRouteFromNeighbour(LayoutBlock src, RoutingPacket update) { 3535 // log.info("From " + this.getDisplayName() + " packet to be added from neighbour " + src.getDisplayName()); 3536 addRouteLog.debug("From {} src: {}, block: {}, hopCount: {}, metric: {}, status: {}, packetID: {}", 3537 getDisplayName(), src.getDisplayName(), update.getBlock().getDisplayName(), 3538 update.getHopCount(), update.getMetric(), update.getBlockState(), update.getPacketId()); 3539 InstanceManager.getDefault(LayoutBlockManager.class).setLastRoutingChange(); 3540 Block destBlock = update.getBlock(); 3541 Block srcblk = src.getBlock(); 3542 3543 if (destBlock == this.getBlock()) { 3544 addRouteLog.debug("Reject packet update as it is to a route advertised by our selves"); 3545 return; 3546 } 3547 3548 Adjacencies adj = getAdjacency(srcblk); 3549 if (adj == null) { 3550 addRouteLog.debug("From {} packet is from a src that is not registered {}", 3551 getDisplayName(), srcblk.getDisplayName()); 3552 // If the packet is from a src that is not registered as a neighbour 3553 // Then we will simply reject it. 3554 return; 3555 } else if (adj.getPacketFlow() == TXONLY) { 3556 addRouteLog.debug("From {} packet is from a src {} that is registered as one that we should be transmitting to only", 3557 getDisplayName(), src.getDisplayName()); 3558 // we should only be transmitting routes to this neighbour not receiving them 3559 return; 3560 } 3561 int hopCount = update.getHopCount(); 3562 int updatemetric = update.getMetric(); 3563 float length = update.getLength(); 3564 3565 if (hopCount > 255) { 3566 addRouteLog.debug("From {} hop count exceeded {}", getDisplayName(), destBlock.getDisplayName()); 3567 return; 3568 } 3569 3570 for (Routes ro : routes) { 3571 if ((ro.getNextBlock() == srcblk) && (ro.getDestBlock() == destBlock)) { 3572 addRouteLog.debug("From {} Route to {} is already configured", 3573 getDisplayName(), destBlock.getDisplayName()); 3574 addRouteLog.debug("{} v {}", ro.getHopCount(), hopCount); 3575 addRouteLog.debug("{} v {}", ro.getMetric(), updatemetric); 3576 updateRoutingInfo(src, update); 3577 return; 3578 } 3579 } 3580 3581 addRouteLog.debug("From {} We should be adding route {}", getDisplayName(), destBlock.getDisplayName()); 3582 3583 // We need to propergate out the routes that we have added to our neighbour 3584 int direction = adj.getDirection(); 3585 Routes route = new Routes(destBlock, srcblk, hopCount, direction, updatemetric, length); 3586 routes.add(route); 3587 3588 // Need to propergate the route down to our neighbours 3589 addRouteToNeighbours(route); 3590 } 3591 3592 /* this should look after removal of a specific next hop from our neighbour*/ 3593 /** 3594 * Get the direction of travel to our neighbouring block. 3595 * 3596 * @param neigh neighbor block 3597 * @return direction to get to neighbor block 3598 */ 3599 public int getNeighbourDirection(LayoutBlock neigh) { 3600 if (neigh == null) { 3601 return Path.NONE; 3602 } 3603 Block neighbourBlock = neigh.getBlock(); 3604 return getNeighbourDirection(neighbourBlock); 3605 } 3606 3607 public int getNeighbourDirection(Block neighbourBlock) { 3608 for (Adjacencies neighbour : neighbours) { 3609 if (neighbour.getBlock() == neighbourBlock) { 3610 return neighbour.getDirection(); 3611 } 3612 } 3613 return Path.NONE; 3614 } 3615 3616 Adjacencies getAdjacency(Block blk) { 3617 for (Adjacencies neighbour : neighbours) { 3618 if (neighbour.getBlock() == blk) { 3619 return neighbour; 3620 } 3621 } 3622 return null; 3623 } 3624 3625 static final int ADDITION = 0x00; 3626 static final int UPDATE = 0x02; 3627 static final int REMOVAL = 0x04; 3628 3629 static final int RXTX = 0x00; 3630 static final int RXONLY = 0x02; 3631 static final int TXONLY = 0x04; 3632 static final int NONE = 0x08; 3633 int metric = 100; 3634 3635 private static class RoutingPacket { 3636 3637 int packetType; 3638 Block block; 3639 int hopCount = -1; 3640 int packetMetric = -1; 3641 int blockstate = -1; 3642 float length = -1; 3643 Integer packetRef = -1; 3644 3645 RoutingPacket(int packetType, Block blk, int hopCount, int packetMetric, 3646 float length, int blockstate, Integer packetRef) { 3647 this.packetType = packetType; 3648 this.block = blk; 3649 this.hopCount = hopCount; 3650 this.packetMetric = packetMetric; 3651 this.blockstate = blockstate; 3652 this.packetRef = packetRef; 3653 this.length = length; 3654 } 3655 3656 int getPacketType() { 3657 return packetType; 3658 } 3659 3660 Block getBlock() { 3661 return block; 3662 } 3663 3664 int getHopCount() { 3665 return hopCount; 3666 } 3667 3668 int getMetric() { 3669 return packetMetric; 3670 } 3671 3672 int getBlockState() { 3673 return blockstate; 3674 } 3675 3676 float getLength() { 3677 return length; 3678 } 3679 3680 Integer getPacketId() { 3681 return packetRef; 3682 } 3683 } 3684 3685 /** 3686 * Get the number of neighbor blocks attached to this block. 3687 * 3688 * @return count of neighbor 3689 */ 3690 public int getNumberOfNeighbours() { 3691 return neighbours.size(); 3692 } 3693 3694 /** 3695 * Get the neighboring block at index i. 3696 * 3697 * @param i index to neighbor 3698 * @return neighbor block 3699 */ 3700 public Block getNeighbourAtIndex(int i) { 3701 return neighbours.get(i).getBlock(); 3702 } 3703 3704 /** 3705 * Get the direction of travel to neighbouring block at index i. 3706 * 3707 * @param i index in neighbors 3708 * @return neighbor block 3709 */ 3710 public int getNeighbourDirection(int i) { 3711 return neighbours.get(i).getDirection(); 3712 } 3713 3714 /** 3715 * Get the metric/cost to neighbouring block at index i. 3716 * 3717 * @param i index in neighbors 3718 * @return metric of neighbor 3719 */ 3720 public int getNeighbourMetric(int i) { 3721 return neighbours.get(i).getMetric(); 3722 } 3723 3724 /** 3725 * Get the flow of traffic to and from neighbouring block at index i RXTX - 3726 * Means Traffic can flow both ways between the blocks RXONLY - Means we can 3727 * only receive traffic from our neighbour, we can not send traffic to it 3728 * TXONLY - Means we do not receive traffic from our neighbour, but can send 3729 * traffic to it. 3730 * 3731 * @param i index in neighbors 3732 * @return direction of traffic 3733 */ 3734 public String getNeighbourPacketFlowAsString(int i) { 3735 return decodePacketFlow(neighbours.get(i).getPacketFlow()); 3736 } 3737 3738 /** 3739 * Is our neighbouring block at index i a mutual neighbour, ie both blocks 3740 * have each other registered as neighbours and are exchanging information. 3741 * 3742 * @param i index of neighbor 3743 * @return true if both are mutual neighbors 3744 */ 3745 public boolean isNeighbourMutual(int i) { 3746 return neighbours.get(i).isMutual(); 3747 } 3748 3749 int getNeighbourIndex(Adjacencies adj) { 3750 for (int i = 0; i < neighbours.size(); i++) { 3751 if (neighbours.get(i) == adj) { 3752 return i; 3753 } 3754 } 3755 return -1; 3756 } 3757 3758 private class Adjacencies { 3759 3760 Block adjBlock; 3761 LayoutBlock adjLayoutBlock; 3762 int direction; 3763 int packetFlow = RXTX; 3764 boolean mutualAdjacency = false; 3765 3766 HashMap<Block, Routes> adjDestRoutes = new HashMap<>(); 3767 List<Integer> actedUponUpdates = new ArrayList<>(501); 3768 3769 Adjacencies(Block block, int dir, int packetFlow) { 3770 adjBlock = block; 3771 direction = dir; 3772 this.packetFlow = packetFlow; 3773 } 3774 3775 Block getBlock() { 3776 return adjBlock; 3777 } 3778 3779 LayoutBlock getLayoutBlock() { 3780 return adjLayoutBlock; 3781 } 3782 3783 int getDirection() { 3784 return direction; 3785 } 3786 3787 // If a set true on mutual, then we could go through the list of what to send out to neighbour 3788 void setMutual(boolean mut) { 3789 if (mut == mutualAdjacency) { // No change will exit 3790 return; 3791 } 3792 mutualAdjacency = mut; 3793 if (mutualAdjacency) { 3794 adjLayoutBlock = InstanceManager.getDefault( 3795 LayoutBlockManager.class).getLayoutBlock(adjBlock); 3796 } 3797 } 3798 3799 boolean isMutual() { 3800 return mutualAdjacency; 3801 } 3802 3803 int getPacketFlow() { 3804 return packetFlow; 3805 } 3806 3807 void setPacketFlow(int flow) { 3808 if (flow != packetFlow) { 3809 int oldFlow = packetFlow; 3810 packetFlow = flow; 3811 firePropertyChange(PROPERTY_NEIGHBOUR_PACKET_FLOW, oldFlow, packetFlow); 3812 } 3813 } 3814 3815 // The metric could just be read directly from the neighbour as we have no 3816 // need to specifically keep a copy of it here this is here just to fire off the change 3817 void setMetric(int met) { 3818 firePropertyChange(PROPERTY_NEIGHBOUR_METRIC, null, getNeighbourIndex(this)); 3819 } 3820 3821 int getMetric() { 3822 if (adjLayoutBlock != null) { 3823 return adjLayoutBlock.getBlockMetric(); 3824 } 3825 adjLayoutBlock = InstanceManager.getDefault( 3826 LayoutBlockManager.class).getLayoutBlock(adjBlock); 3827 if (adjLayoutBlock != null) { 3828 return adjLayoutBlock.getBlockMetric(); 3829 } 3830 3831 if (log.isDebugEnabled()) { 3832 log.debug("Layout Block {} returned as null", adjBlock.getDisplayName()); 3833 } 3834 return -1; 3835 } 3836 3837 void setLength(float len) { 3838 firePropertyChange(PROPERTY_NEIGHBOUR_LENGTH, null, getNeighbourIndex(this)); 3839 } 3840 3841 float getLength() { 3842 if (adjLayoutBlock != null) { 3843 return adjLayoutBlock.getBlock().getLengthMm(); 3844 } 3845 adjLayoutBlock = InstanceManager.getDefault( 3846 LayoutBlockManager.class).getLayoutBlock(adjBlock); 3847 if (adjLayoutBlock != null) { 3848 return adjLayoutBlock.getBlock().getLengthMm(); 3849 } 3850 3851 if (log.isDebugEnabled()) { 3852 log.debug("Layout Block {} returned as null", adjBlock.getDisplayName()); 3853 } 3854 return -1; 3855 } 3856 3857 void removeRouteAdvertisedToNeighbour(Routes removeRoute) { 3858 Block dest = removeRoute.getDestBlock(); 3859 3860 if (adjDestRoutes.get(dest) == removeRoute) { 3861 adjDestRoutes.remove(dest); 3862 } 3863 } 3864 3865 void removeRouteAdvertisedToNeighbour(Block block) { 3866 adjDestRoutes.remove(block); 3867 } 3868 3869 void addRouteAdvertisedToNeighbour(Routes addedRoute) { 3870 adjDestRoutes.put(addedRoute.getDestBlock(), addedRoute); 3871 } 3872 3873 boolean advertiseRouteToNeighbour(Routes routeToAdd) { 3874 if (!isMutual()) { 3875 log.debug("In block {}: Neighbour is not mutual so will not advertise it (Routes {})", 3876 getDisplayName(), routeToAdd); 3877 return false; 3878 } 3879 3880 // Just wonder if this should forward on the new packet to the neighbour? 3881 Block dest = routeToAdd.getDestBlock(); 3882 if (!adjDestRoutes.containsKey(dest)) { 3883 log.debug("In block {}: We are not currently advertising a route to the destination to neighbour: {}", 3884 getDisplayName(), dest.getDisplayName()); 3885 return true; 3886 } 3887 3888 if (routeToAdd.getHopCount() > 255) { 3889 log.debug("Hop count is gereater than 255 we will therefore do nothing with this route"); 3890 return false; 3891 } 3892 Routes existingRoute = adjDestRoutes.get(dest); 3893 if (existingRoute.getMetric() > routeToAdd.getMetric()) { 3894 return true; 3895 } 3896 if (existingRoute.getHopCount() > routeToAdd.getHopCount()) { 3897 return true; 3898 } 3899 3900 if (existingRoute == routeToAdd) { 3901 // We return true as the metric might have changed 3902 return false; 3903 } 3904 return false; 3905 } 3906 3907 boolean updatePacketActedUpon(Integer packetID) { 3908 return actedUponUpdates.contains(packetID); 3909 } 3910 3911 void addPacketReceivedFromNeighbour(Integer packetID) { 3912 actedUponUpdates.add(packetID); 3913 if (actedUponUpdates.size() > 500) { 3914 actedUponUpdates.subList(0, 250).clear(); 3915 } 3916 } 3917 3918 void dispose() { 3919 adjBlock = null; 3920 adjLayoutBlock = null; 3921 mutualAdjacency = false; 3922 adjDestRoutes = null; 3923 actedUponUpdates = null; 3924 } 3925 } 3926 3927 /** 3928 * Get the number of routes that the block has registered. 3929 * 3930 * @return count of routes 3931 */ 3932 public int getNumberOfRoutes() { 3933 return routes.size(); 3934 } 3935 3936 /** 3937 * Get the direction of route i. 3938 * 3939 * @param i index in routes 3940 * @return direction 3941 */ 3942 public int getRouteDirectionAtIndex(int i) { 3943 return routes.get(i).getDirection(); 3944 } 3945 3946 /** 3947 * Get the destination block at route i 3948 * 3949 * @param i index in routes 3950 * @return dest block from route 3951 */ 3952 public Block getRouteDestBlockAtIndex(int i) { 3953 return routes.get(i).getDestBlock(); 3954 } 3955 3956 /** 3957 * Get the next block at route i 3958 * 3959 * @param i index in routes 3960 * @return next block from route 3961 */ 3962 public Block getRouteNextBlockAtIndex(int i) { 3963 return routes.get(i).getNextBlock(); 3964 } 3965 3966 /** 3967 * Get the hop count of route i.<br> 3968 * The Hop count is the number of other blocks that we traverse to get to 3969 * the destination 3970 * 3971 * @param i index in routes 3972 * @return hop count 3973 */ 3974 public int getRouteHopCountAtIndex(int i) { 3975 return routes.get(i).getHopCount(); 3976 } 3977 3978 /** 3979 * Get the length of route i.<br> 3980 * The length is the combined length of all the blocks that we traverse to 3981 * get to the destination 3982 * 3983 * @param i index in routes 3984 * @return length of block in route 3985 */ 3986 public float getRouteLengthAtIndex(int i) { 3987 return routes.get(i).getLength(); 3988 } 3989 3990 /** 3991 * Get the metric/cost at route i 3992 * 3993 * @param i index in routes 3994 * @return metric 3995 */ 3996 public int getRouteMetric(int i) { 3997 return routes.get(i).getMetric(); 3998 } 3999 4000 /** 4001 * Get the state (Occupied, unoccupied) of the destination layout block at 4002 * index i 4003 * 4004 * @param i index in routes 4005 * @return state of block 4006 */ 4007 public int getRouteState(int i) { 4008 return routes.get(i).getState(); 4009 } 4010 4011 /** 4012 * Is the route to the destination potentially valid from our block. 4013 * 4014 * @param i index in route 4015 * @return true if route is valid 4016 */ 4017 // TODO: Java standard pattern for boolean getters is "isRouteValid()" 4018 public boolean getRouteValid(int i) { 4019 return routes.get(i).isRouteCurrentlyValid(); 4020 } 4021 4022 /** 4023 * Get the state of the destination layout block at index i as a string. 4024 * 4025 * @param i index in routes 4026 * @return dest status 4027 */ 4028 public String getRouteStateAsString(int i) { 4029 int state = routes.get(i).getState(); 4030 switch (state) { 4031 case OCCUPIED: { 4032 return Bundle.getMessage("TrackOccupied"); // i18n using NamedBeanBundle.properties TODO remove duplicate keys 4033 } 4034 4035 case RESERVED: { 4036 return Bundle.getMessage("StateReserved"); // "Reserved" 4037 } 4038 4039 case EMPTY: { 4040 return Bundle.getMessage("StateFree"); // "Free" 4041 } 4042 4043 default: { 4044 return Bundle.getMessage("BeanStateUnknown"); // "Unknown" 4045 } 4046 } 4047 } 4048 4049 int getRouteIndex(Routes r) { 4050 for (int i = 0; i < routes.size(); i++) { 4051 if (routes.get(i) == r) { 4052 return i; 4053 } 4054 } 4055 return -1; 4056 } 4057 4058 /** 4059 * Get the number of layout blocks to our destintation block going from the 4060 * next directly connected block. If the destination block and nextblock are 4061 * the same and the block is also registered as a neighbour then 1 is 4062 * returned. If no valid route to the destination block can be found via the 4063 * next block then -1 is returned. If more than one route exists to the 4064 * destination then the route with the lowest count is returned. 4065 * 4066 * @param destination final block 4067 * @param nextBlock adjcent block 4068 * @return hop count to final, -1 if not available 4069 */ 4070 public int getBlockHopCount(Block destination, Block nextBlock) { 4071 if ((destination == nextBlock) && (isValidNeighbour(nextBlock))) { 4072 return 1; 4073 } 4074 4075 for (Routes route : routes) { 4076 if (route.getDestBlock() == destination) { 4077 if (route.getNextBlock() == nextBlock) { 4078 return route.getHopCount(); 4079 } 4080 } 4081 } 4082 return -1; 4083 } 4084 4085 /** 4086 * Get the metric to our desintation block going from the next directly 4087 * connected block. If the destination block and nextblock are the same and 4088 * the block is also registered as a neighbour then 1 is returned. If no 4089 * valid route to the destination block can be found via the next block then 4090 * -1 is returned. If more than one route exists to the destination then the 4091 * route with the lowest count is returned. 4092 * 4093 * @param destination final block 4094 * @param nextBlock adjcent block 4095 * @return metric to final block, -1 if not available 4096 */ 4097 public int getBlockMetric(Block destination, Block nextBlock) { 4098 if ((destination == nextBlock) && (isValidNeighbour(nextBlock))) { 4099 return 1; 4100 } 4101 4102 for (Routes route : routes) { 4103 if (route.getDestBlock() == destination) { 4104 if (route.getNextBlock() == nextBlock) { 4105 return route.getMetric(); 4106 } 4107 } 4108 } 4109 return -1; 4110 } 4111 4112 /** 4113 * Get the distance to our desintation block going from the next directly 4114 * connected block. If the destination block and nextblock are the same and 4115 * the block is also registered as a neighbour then 1 is returned. If no 4116 * valid route to the destination block can be found via the next block then 4117 * -1 is returned. If more than one route exists to the destination then the 4118 * route with the lowest count is returned. 4119 * 4120 * @param destination final block 4121 * @param nextBlock adjcent block 4122 * @return length to final, -1 if not viable 4123 */ 4124 public float getBlockLength(Block destination, Block nextBlock) { 4125 if ((destination == nextBlock) && (isValidNeighbour(nextBlock))) { 4126 return 1; 4127 } 4128 4129 for (Routes route : routes) { 4130 if (route.getDestBlock() == destination) { 4131 if (route.getNextBlock() == nextBlock) { 4132 return route.getLength(); 4133 } 4134 } 4135 } 4136 return -1; 4137 } 4138 4139 // TODO This needs a propertychange listener adding 4140 private class Routes implements PropertyChangeListener { 4141 4142 int direction; 4143 Block destBlock; 4144 Block nextBlock; 4145 int hopCount; 4146 int routeMetric; 4147 float length; 4148 4149 // int state =-1; 4150 int miscflags = 0x00; 4151 boolean validCurrentRoute = false; 4152 4153 Routes(Block dstBlock, Block nxtBlock, int hop, int dir, int met, float len) { 4154 destBlock = dstBlock; 4155 nextBlock = nxtBlock; 4156 hopCount = hop; 4157 direction = dir; 4158 routeMetric = met; 4159 length = len; 4160 init(); 4161 } 4162 4163 final void init() { 4164 validCurrentRoute = checkIsRouteOnValidThroughPath(this); 4165 firePropertyChange(PROPERTY_LENGTH, null, null); 4166 destBlock.addPropertyChangeListener(this); 4167 } 4168 4169 @Override 4170 public String toString() { 4171 return "Routes(dst:" + destBlock + ", nxt:" + nextBlock 4172 + ", hop:" + hopCount + ", dir:" + direction 4173 + ", met:" + routeMetric + ", len: " + length + ")"; 4174 } 4175 4176 @Override 4177 public void propertyChange(PropertyChangeEvent e) { 4178 if ( Block.PROPERTY_STATE.equals(e.getPropertyName())) { 4179 stateChange(); 4180 } 4181 } 4182 4183 public Block getDestBlock() { 4184 return destBlock; 4185 } 4186 4187 public Block getNextBlock() { 4188 return nextBlock; 4189 } 4190 4191 public int getHopCount() { 4192 return hopCount; 4193 } 4194 4195 public int getDirection() { 4196 return direction; 4197 } 4198 4199 public int getMetric() { 4200 return routeMetric; 4201 } 4202 4203 public float getLength() { 4204 return length; 4205 } 4206 4207 public void setMetric(int met) { 4208 if (met == routeMetric) { 4209 return; 4210 } 4211 routeMetric = met; 4212 firePropertyChange(PROPERTY_METRIC, null, getRouteIndex(this)); 4213 } 4214 4215 public void setHopCount(int hop) { 4216 if (hopCount == hop) { 4217 return; 4218 } 4219 hopCount = hop; 4220 firePropertyChange(PROPERTY_HOP, null, getRouteIndex(this)); 4221 } 4222 4223 public void setLength(float len) { 4224 if (len == length) { 4225 return; 4226 } 4227 length = len; 4228 firePropertyChange(PROPERTY_LENGTH, null, getRouteIndex(this)); 4229 } 4230 4231 // This state change is only here for the routing table view 4232 void stateChange() { 4233 firePropertyChange(PROPERTY_STATE, null, getRouteIndex(this)); 4234 } 4235 4236 int getState() { 4237 LayoutBlock destLBlock = InstanceManager.getDefault( 4238 LayoutBlockManager.class).getLayoutBlock(destBlock); 4239 if (destLBlock != null) { 4240 return destLBlock.getBlockStatus(); 4241 } 4242 4243 log.debug("Layout Block {} returned as null", destBlock.getDisplayName()); 4244 return -1; 4245 } 4246 4247 void setValidCurrentRoute(boolean boo) { 4248 if (validCurrentRoute == boo) { 4249 return; 4250 } 4251 validCurrentRoute = boo; 4252 firePropertyChange(PROPERTY_VALID, null, getRouteIndex(this)); 4253 } 4254 4255 boolean isRouteCurrentlyValid() { 4256 return validCurrentRoute; 4257 } 4258 4259 // Misc flags is not used in general routing, but is used for determining route removals 4260 void setMiscFlags(int f) { 4261 miscflags = f; 4262 } 4263 4264 int getMiscFlags() { 4265 return miscflags; 4266 } 4267 } 4268 4269 /** 4270 * Get the number of valid through paths on this block. 4271 * 4272 * @return count of paths through this block 4273 */ 4274 public int getNumberOfThroughPaths() { 4275 return throughPaths.size(); 4276 } 4277 4278 /** 4279 * Get the source block at index i 4280 * 4281 * @param i index in throughPaths 4282 * @return source block 4283 */ 4284 public Block getThroughPathSource(int i) { 4285 return throughPaths.get(i).getSourceBlock(); 4286 } 4287 4288 /** 4289 * Get the destination block at index i 4290 * 4291 * @param i index in throughPaths 4292 * @return final block 4293 */ 4294 public Block getThroughPathDestination(int i) { 4295 return throughPaths.get(i).getDestinationBlock(); 4296 } 4297 4298 /** 4299 * Is the through path at index i active? 4300 * 4301 * @param i index in path 4302 * @return active or not 4303 */ 4304 public Boolean isThroughPathActive(int i) { 4305 return throughPaths.get(i).isPathActive(); 4306 } 4307 4308 private class ThroughPaths implements PropertyChangeListener { 4309 4310 Block sourceBlock; 4311 Block destinationBlock; 4312 Path sourcePath; 4313 Path destinationPath; 4314 4315 boolean pathActive = false; 4316 4317 HashMap<Turnout, Integer> _turnouts = new HashMap<>(); 4318 4319 ThroughPaths(Block srcBlock, Path srcPath, Block destBlock, Path dstPath) { 4320 sourceBlock = srcBlock; 4321 destinationBlock = destBlock; 4322 sourcePath = srcPath; 4323 destinationPath = dstPath; 4324 } 4325 4326 Block getSourceBlock() { 4327 return sourceBlock; 4328 } 4329 4330 Block getDestinationBlock() { 4331 return destinationBlock; 4332 } 4333 4334 Path getSourcePath() { 4335 return sourcePath; 4336 } 4337 4338 Path getDestinationPath() { 4339 return destinationPath; 4340 } 4341 4342 boolean isPathActive() { 4343 return pathActive; 4344 } 4345 4346 void setTurnoutList(List<LayoutTrackExpectedState<LayoutTurnout>> turnouts) { 4347 if (!_turnouts.isEmpty()) { 4348 Set<Turnout> en = _turnouts.keySet(); 4349 en.forEach( listTurnout -> listTurnout.removePropertyChangeListener(this)); 4350 } 4351 4352 // If we have no turnouts in this path, then this path is always active 4353 if (turnouts.isEmpty()) { 4354 pathActive = true; 4355 setRoutesValid(sourceBlock, true); 4356 setRoutesValid(destinationBlock, true); 4357 return; 4358 } 4359 _turnouts = new HashMap<>(turnouts.size()); 4360 for (LayoutTrackExpectedState<LayoutTurnout> turnout : turnouts) { 4361 if (turnout.getObject() instanceof LayoutSlip) { 4362 int slipState = turnout.getExpectedState(); 4363 LayoutSlip ls = (LayoutSlip) turnout.getObject(); 4364 int taState = ls.getTurnoutState(slipState); 4365 _turnouts.put(ls.getTurnout(), taState); 4366 ls.getTurnout().addPropertyChangeListener(this, ls.getTurnoutName(), "Layout Block Routing"); 4367 4368 int tbState = ls.getTurnoutBState(slipState); 4369 _turnouts.put(ls.getTurnoutB(), tbState); 4370 ls.getTurnoutB().addPropertyChangeListener(this, ls.getTurnoutBName(), "Layout Block Routing"); 4371 } else { 4372 LayoutTurnout lt = turnout.getObject(); 4373 if (lt.getTurnout() != null) { 4374 _turnouts.put(lt.getTurnout(), turnout.getExpectedState()); 4375 lt.getTurnout().addPropertyChangeListener(this, lt.getTurnoutName(), "Layout Block Routing"); 4376 } else { 4377 log.error("{} has no physical turnout allocated, block = {}", lt, block.getDisplayName()); 4378 } 4379 } 4380 } 4381 } 4382 4383 @Override 4384 public void propertyChange(PropertyChangeEvent e) { 4385 if ( Turnout.PROPERTY_KNOWN_STATE.equals(e.getPropertyName())) { 4386 Turnout srcTurnout = (Turnout) e.getSource(); 4387 int newVal = (Integer) e.getNewValue(); 4388 int values = _turnouts.get(srcTurnout); 4389 boolean allset = false; 4390 pathActive = false; 4391 4392 if (newVal == values) { 4393 allset = true; 4394 4395 if (_turnouts.size() > 1) { 4396 for (Map.Entry<Turnout, Integer> entry : _turnouts.entrySet()) { 4397 if (srcTurnout != entry.getKey()) { 4398 int state = entry.getKey().getState(); 4399 if (state != entry.getValue()) { 4400 allset = false; 4401 break; 4402 } 4403 } 4404 } 4405 } 4406 } 4407 updateActiveThroughPaths(this, allset); 4408 pathActive = allset; 4409 } 4410 } 4411 4412 // We keep a track of what is paths are active, only so that we can easily mark 4413 // which routes are also potentially valid 4414 private List<ThroughPaths> activePaths; 4415 4416 private void updateActiveThroughPaths(ThroughPaths tp, boolean active) { 4417 updateRouteLog.debug("We have been notified that a through path has changed state"); 4418 4419 if (activePaths == null) { 4420 activePaths = new ArrayList<>(); 4421 } 4422 4423 if (active) { 4424 activePaths.add(tp); 4425 setRoutesValid(tp.getSourceBlock(), active); 4426 setRoutesValid(tp.getDestinationBlock(), active); 4427 } else { 4428 // We need to check if either our source or des is in use by another path. 4429 activePaths.remove(tp); 4430 boolean sourceInUse = false; 4431 boolean destinationInUse = false; 4432 4433 List<ThroughPaths> copyOfPaths = activePaths; 4434 for (ThroughPaths activePath : copyOfPaths) { 4435 Block testSour = activePath.getSourceBlock(); 4436 Block testDest = activePath.getDestinationBlock(); 4437 if ((testSour == tp.getSourceBlock()) || (testDest == tp.getSourceBlock())) { 4438 sourceInUse = true; 4439 } 4440 if ((testSour == tp.getDestinationBlock()) || (testDest == tp.getDestinationBlock())) { 4441 destinationInUse = true; 4442 } 4443 } 4444 4445 if (!sourceInUse) { 4446 setRoutesValid(tp.getSourceBlock(), active); 4447 } 4448 4449 if (!destinationInUse) { 4450 setRoutesValid(tp.getDestinationBlock(), active); 4451 } 4452 } 4453 4454 for (int i = 0; i < throughPaths.size(); i++) { 4455 // This is processed simply for the throughpath table. 4456 if (tp == throughPaths.get(i)) { 4457 firePropertyChange(PROPERTY_PATH, null, i); 4458 } 4459 } 4460 } 4461 4462 } 4463 4464 @Nonnull 4465 List<Block> getThroughPathSourceByDestination(Block dest) { 4466 List<Block> a = new ArrayList<>(); 4467 4468 for (ThroughPaths throughPath : throughPaths) { 4469 if (throughPath.getDestinationBlock() == dest) { 4470 a.add(throughPath.getSourceBlock()); 4471 } 4472 } 4473 return a; 4474 } 4475 4476 @Nonnull 4477 List<Block> getThroughPathDestinationBySource(Block source) { 4478 List<Block> a = new ArrayList<>(); 4479 4480 for (ThroughPaths throughPath : throughPaths) { 4481 if (throughPath.getSourceBlock() == source) { 4482 a.add(throughPath.getDestinationBlock()); 4483 } 4484 } 4485 return a; 4486 } 4487 4488 /** 4489 * When a route is created, check to see if the through path that this route 4490 * relates to is active. 4491 * @param r The route to check 4492 * @return true if that route is active 4493 */ 4494 boolean checkIsRouteOnValidThroughPath(Routes r) { 4495 for (ThroughPaths t : throughPaths) { 4496 if (t.isPathActive()) { 4497 if (t.getDestinationBlock() == r.getNextBlock()) { 4498 return true; 4499 } 4500 if (t.getSourceBlock() == r.getNextBlock()) { 4501 return true; 4502 } 4503 } 4504 } 4505 return false; 4506 } 4507 4508 /** 4509 * Go through all the routes and refresh the valid flag. 4510 */ 4511 public void refreshValidRoutes() { 4512 for (int i = 0; i < throughPaths.size(); i++) { 4513 ThroughPaths t = throughPaths.get(i); 4514 setRoutesValid(t.getDestinationBlock(), t.isPathActive()); 4515 setRoutesValid(t.getSourceBlock(), t.isPathActive()); 4516 firePropertyChange(PROPERTY_PATH, null, i); 4517 } 4518 } 4519 4520 /** 4521 * Set the valid flag for routes that are on a valid through path. 4522 * @param nxtHopActive the start of the route 4523 * @param state the state to set into the valid flag 4524 */ 4525 void setRoutesValid(Block nxtHopActive, boolean state) { 4526 List<Routes> rtr = getRouteByNeighbour(nxtHopActive); 4527 rtr.forEach( rt -> rt.setValidCurrentRoute(state)); 4528 } 4529 4530 @Override 4531 public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException { 4532 if (Manager.PROPERTY_CAN_DELETE.equals(evt.getPropertyName())) { 4533 if (evt.getOldValue() instanceof Sensor) { 4534 if (evt.getOldValue().equals(getOccupancySensor())) { 4535 throw new PropertyVetoException(getDisplayName(), evt); 4536 } 4537 } 4538 4539 if (evt.getOldValue() instanceof Memory) { 4540 if (evt.getOldValue().equals(getMemory())) { 4541 throw new PropertyVetoException(getDisplayName(), evt); 4542 } 4543 } 4544 } else if (Manager.PROPERTY_DO_DELETE.equals(evt.getPropertyName())) { 4545 // Do nothing at this stage 4546 if (evt.getOldValue() instanceof Sensor) { 4547 if (evt.getOldValue().equals(getOccupancySensor())) { 4548 setOccupancySensorName(null); 4549 } 4550 } 4551 4552 if (evt.getOldValue() instanceof Memory) { 4553 if (evt.getOldValue().equals(getMemory())) { 4554 setMemoryName(null); 4555 } 4556 } 4557 } 4558 } 4559 4560 @Override 4561 public List<NamedBeanUsageReport> getUsageReport(NamedBean bean) { 4562 List<NamedBeanUsageReport> report = new ArrayList<>(); 4563 if (bean != null) { 4564 if (bean.equals(getBlock())) { 4565 report.add(new NamedBeanUsageReport("LayoutBlockBlock")); // NOI18N 4566 } 4567 if (bean.equals(getMemory())) { 4568 report.add(new NamedBeanUsageReport("LayoutBlockMemory")); // NOI18N 4569 } 4570 if (bean.equals(getOccupancySensor())) { 4571 report.add(new NamedBeanUsageReport("LayoutBlockSensor")); // NOI18N 4572 } 4573 for (int i = 0; i < getNumberOfNeighbours(); i++) { 4574 if (bean.equals(getNeighbourAtIndex(i))) { 4575 report.add(new NamedBeanUsageReport("LayoutBlockNeighbor", "Neighbor")); // NOI18N 4576 } 4577 } 4578 } 4579 return report; 4580 } 4581 4582 @Override 4583 public String getBeanType() { 4584 return Bundle.getMessage("BeanNameLayoutBlock"); 4585 } 4586 4587 private static final Logger log = LoggerFactory.getLogger(LayoutBlock.class); 4588 private static final Logger searchRouteLog = LoggerFactory.getLogger(LayoutBlock.class.getName()+".SearchRouteLogging"); 4589 private static final Logger updateRouteLog = LoggerFactory.getLogger(LayoutBlock.class.getName()+".UpdateRouteLogging"); 4590 private static final Logger addRouteLog = LoggerFactory.getLogger(LayoutBlock.class.getName()+".AddRouteLogging"); 4591 private static final Logger deleteRouteLog = LoggerFactory.getLogger(LayoutBlock.class.getName()+".DeleteRouteLogging"); 4592 4593}