001package jmri.jmrit.entryexit; 002 003import java.awt.Color; 004import java.awt.event.MouseAdapter; 005import java.awt.event.MouseEvent; 006import java.beans.PropertyChangeEvent; 007import java.beans.PropertyChangeListener; 008import java.beans.PropertyVetoException; 009import java.util.*; 010import java.util.Map.Entry; 011 012import javax.annotation.CheckReturnValue; 013import javax.annotation.Nonnull; 014import javax.annotation.OverridingMethodsMustInvokeSuper; 015import javax.swing.JDialog; 016import javax.swing.JPanel; 017 018import jmri.*; 019import jmri.beans.VetoableChangeSupport; 020import jmri.jmrit.display.EditorManager; 021import jmri.jmrit.display.layoutEditor.LayoutBlock; 022import jmri.jmrit.display.layoutEditor.LayoutBlockConnectivityTools; 023import jmri.jmrit.display.layoutEditor.LayoutBlockManager; 024import jmri.jmrit.display.layoutEditor.LayoutEditor; 025import jmri.jmrix.internal.InternalSystemConnectionMemo; 026import jmri.util.swing.JmriJOptionPane; 027 028/** 029 * Implements an Entry Exit based method of setting turnouts, setting up signal 030 * logic and allocating blocks through a path based on the Layout Editor. 031 * <p> 032 * The route is based upon having a sensor assigned at a known location on the 033 * panel (set at the boundary of two different blocks) through to a sensor at a 034 * remote location on the same panel. Using the layout block routing, a path can 035 * then be set between the two sensors so long as one exists and no 036 * section of track is set occupied. If available an alternative route will be 037 * used when the direct path is occupied (blocked). 038 * <p> 039 * Initial implementation only handles the setting up of turnouts on a path. 040 * 041 * @author Kevin Dickerson Copyright (C) 2011 042 */ 043public class EntryExitPairs extends VetoableChangeSupport implements Manager<DestinationPoints>, jmri.InstanceManagerAutoDefault, 044 PropertyChangeListener { 045 046 public LayoutBlockConnectivityTools.Metric routingMethod = LayoutBlockConnectivityTools.Metric.METRIC; 047 048 public final static int NXBUTTONSELECTED = 0x08; 049 public final static int NXBUTTONACTIVE = Sensor.ACTIVE; 050 public final static int NXBUTTONINACTIVE = Sensor.INACTIVE; 051 private final SystemConnectionMemo memo; 052 private final Map<String, Boolean> silencedProperties = new HashMap<>(); 053 054 private int settingTimer = 2000; 055 056 public int getSettingTimer() { 057 return settingTimer; 058 } 059 060 public void setSettingTimer(int i) { 061 settingTimer = i; 062 } 063 064 private Color settingRouteColor = null; 065 066 public boolean useDifferentColorWhenSetting() { 067 return (settingRouteColor != null); 068 } 069 070 public Color getSettingRouteColor() { 071 return settingRouteColor; 072 } 073 074 public void setSettingRouteColor(Color col) { 075 settingRouteColor = col; 076 } 077 078 /** 079 * Constant value to represent that the entryExit will only set up the 080 * turnouts between two different points. 081 */ 082 public final static int SETUPTURNOUTSONLY = 0x00; 083 084 /** 085 * Constant value to represent that the entryExit will set up the turnouts 086 * between two different points and configure the Signal Mast Logic to use 087 * the correct blocks. 088 */ 089 public final static int SETUPSIGNALMASTLOGIC = 0x01; 090 091 /** 092 * Constant value to represent that the entryExit will do full interlocking. 093 * It will set the turnouts and "reserve" the blocks. 094 */ 095 public final static int FULLINTERLOCK = 0x02; 096 097 boolean allocateToDispatcher = false; 098 boolean absSignalMode = false; 099 100 public final static int PROMPTUSER = 0x00; 101 public final static int AUTOCLEAR = 0x01; 102 public final static int AUTOCANCEL = 0x02; 103 public final static int AUTOSTACK = 0x03; 104 105 public final static int OVERLAP_CANCEL = 0x01; 106 public final static int OVERLAP_STACK = 0x02; 107 108 int routeClearOption = PROMPTUSER; 109 int routeOverlapOption = PROMPTUSER; 110 String memoryOption = ""; // Optional memory variable to receive allocation messages 111 int memoryClearDelay = 0; // Delay before clearing memory, 0 for clearing disabled 112 113 static JPanel glassPane = new JPanel(); 114 115 /** 116 * Delay between issuing Turnout commands 117 */ 118 public int turnoutSetDelay = 0; 119 120 /** 121 * Constructor for creating an EntryExitPairs object and create a transparent JPanel for it. 122 */ 123 public EntryExitPairs() { 124 memo = InstanceManager.getDefault(InternalSystemConnectionMemo.class); 125 InstanceManager.getOptionalDefault(ConfigureManager.class).ifPresent(cm -> cm.registerUser(this)); 126 InstanceManager.getDefault(LayoutBlockManager.class).addPropertyChangeListener(propertyBlockManagerListener); 127 128 glassPane.setOpaque(false); 129 glassPane.setLayout(null); 130 glassPane.addMouseListener(new MouseAdapter() { 131 @Override 132 public void mousePressed(MouseEvent e) { 133 e.consume(); 134 } 135 }); 136 } 137 138 public void setDispatcherIntegration(boolean boo) { 139 allocateToDispatcher = boo; 140 } 141 142 public boolean getDispatcherIntegration() { 143 return allocateToDispatcher; 144 } 145 146 public void setAbsSignalMode(boolean absMode) { 147 absSignalMode = absMode; 148 } 149 150 public boolean isAbsSignalMode() { 151 return absSignalMode; 152 } 153 154 /** 155 * Get the transparent JPanel for this EntryExitPairs. 156 * @return JPanel overlay 157 */ 158 public JPanel getGlassPane() { 159 return glassPane; 160 } 161 162 HashMap<PointDetails, Source> nxpair = new HashMap<>(); 163 164 public void addNXSourcePoint(LayoutBlock facing, List<LayoutBlock> protecting, NamedBean loc, LayoutEditor panel) { 165 PointDetails point = providePoint(facing, protecting, panel); 166 point.setRefObject(loc); 167 } 168 169 public void addNXSourcePoint(NamedBean source) { 170 PointDetails point = null; 171 for (LayoutEditor editor : InstanceManager.getDefault(EditorManager.class).getAll(LayoutEditor.class)) { 172 point = providePoint(source, editor); 173 } 174 if (point == null) { 175 log.error("Unable to find a location on any panel for item {}", source.getDisplayName()); // NOI18N 176 } 177 } 178 179 public void addNXSourcePoint(NamedBean source, LayoutEditor panel) { 180 if (source == null) { 181 log.error("source bean supplied is null"); // NOI18N 182 return; 183 } 184 if (panel == null) { 185 log.error("panel supplied is null"); // NOI18N 186 return; 187 } 188 PointDetails point; 189 point = providePoint(source, panel); 190 if (point == null) { 191 log.error("Unable to find a location on the panel {} for item {}", panel.getLayoutName(), source.getDisplayName()); // NOI18N 192 } 193 } 194 195 public Object getEndPointLocation(NamedBean source, LayoutEditor panel) { 196 if (source == null) { 197 log.error("Source bean past is null"); // NOI18N 198 return null; 199 } 200 if (panel == null) { 201 log.error("panel passed is null"); // NOI18N 202 return null; 203 } 204 PointDetails sourcePoint = getPointDetails(source, panel); 205 if (sourcePoint == null) { 206 log.error("Point is not located"); // NOI18N 207 return null; 208 } 209 return sourcePoint.getRefLocation(); 210 } 211 212 /** {@inheritDoc} */ 213 @Override 214 public int getXMLOrder() { 215 return ENTRYEXIT; 216 } 217 218 /** {@inheritDoc} */ 219 @Override 220 public DestinationPoints getBySystemName(String systemName) { 221 for (Source e : nxpair.values()) { 222 DestinationPoints pd = e.getByUniqueId(systemName); 223 if (pd != null) { 224 return pd; 225 } 226 } 227 return null; 228 } 229 230 /** {@inheritDoc} */ 231 @Override 232 public DestinationPoints getByUserName(@Nonnull String userName) { 233 for (Source e : nxpair.values()) { 234 DestinationPoints pd = e.getByUserName(userName); 235 if (pd != null) { 236 return pd; 237 } 238 } 239 return null; 240 } 241 242 /** {@inheritDoc} */ 243 @Override 244 public DestinationPoints getNamedBean(@Nonnull String name) { 245 DestinationPoints b = getByUserName(name); 246 if (b != null) { 247 return b; 248 } 249 return getBySystemName(name); 250 } 251 252 /** {@inheritDoc} */ 253 @Nonnull 254 @Override 255 public SystemConnectionMemo getMemo() { 256 return memo; 257 } 258 259 /** {@inheritDoc} */ 260 @Override 261 @Nonnull 262 public String getSystemPrefix() { 263 return memo.getSystemPrefix(); 264 } 265 266 /** {@inheritDoc} */ 267 @Override 268 public char typeLetter() { 269 throw new UnsupportedOperationException("Not supported yet."); // NOI18N 270 } 271 272 /** {@inheritDoc} */ 273 @Override 274 @Nonnull 275 public String makeSystemName(@Nonnull String s) { 276 throw new UnsupportedOperationException("Not supported yet."); // NOI18N 277 } 278 279 /** {@inheritDoc} */ 280 @Override 281 @CheckReturnValue 282 public int getObjectCount() { 283 return getNamedBeanSet().size(); 284 } 285 286 /** 287 * Implemented to support the Conditional combo box name list 288 * @since 4.9.3 289 * @return a list of Destination Point beans 290 */ 291 @Override 292 @Nonnull 293 public SortedSet<DestinationPoints> getNamedBeanSet() { 294 TreeSet<DestinationPoints> beanList = new TreeSet<>(new jmri.util.NamedBeanComparator<>()); 295 for (Source e : nxpair.values()) { 296 List<String> uidList = e.getDestinationUniqueId(); 297 for (String uid : uidList) { 298 beanList.add(e.getByUniqueId(uid)); 299 } 300 } 301 return beanList; 302 } 303 304 /** {@inheritDoc} */ 305 @Override 306 public void register(@Nonnull DestinationPoints n) { 307 throw new UnsupportedOperationException("Not supported yet."); // NOI18N 308 } 309 310 /** {@inheritDoc} */ 311 @Override 312 public void deregister(@Nonnull DestinationPoints n) { 313 throw new UnsupportedOperationException("Not supported yet."); // NOI18N 314 } 315 316 public void setClearDownOption(int i) { 317 routeClearOption = i; 318 } 319 320 public int getClearDownOption() { 321 return routeClearOption; 322 } 323 324 public void setOverlapOption(int i) { 325 routeOverlapOption = i; 326 } 327 328 public int getOverlapOption() { 329 return routeOverlapOption; 330 } 331 332 public void setMemoryOption(String memoryName) { 333 memoryOption = memoryName; 334 } 335 336 public String getMemoryOption() { 337 return memoryOption; 338 } 339 340 public void setMemoryClearDelay(int secs) { 341 memoryClearDelay = secs; 342 } 343 344 public int getMemoryClearDelay() { 345 return memoryClearDelay; 346 } 347 348 /** {@inheritDoc} */ 349 @Override 350 public void dispose() { 351 } 352 353 /** 354 * Generate the point details, given a known source and a 355 * Layout Editor panel. 356 * 357 * @param source Origin of movement 358 * @param panel A Layout Editor panel 359 * @return A PointDetails object 360 */ 361 public PointDetails providePoint(NamedBean source, LayoutEditor panel) { 362 PointDetails sourcePoint = getPointDetails(source, panel); 363 if (sourcePoint == null) { 364 LayoutBlock facing = InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager.class).getFacingBlockByNamedBean(source, null); 365 List<LayoutBlock> protecting = InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager.class).getProtectingBlocksByNamedBean(source, null); 366// log.info("facing = {}, protecting = {}", facing, protecting); 367 if (facing == null && protecting.size() == 0) { 368 log.error("Unable to find facing and protecting blocks"); // NOI18N 369 return null; 370 } 371 sourcePoint = providePoint(facing, protecting, panel); 372 sourcePoint.setRefObject(source); 373 } 374 return sourcePoint; 375 } 376 377 /** 378 * Return a list of all source (origin) points on a given 379 * Layout Editor panel. 380 * 381 * @param panel A Layout Editor panel 382 * @return A list of source objects 383 */ 384 public List<Object> getSourceList(LayoutEditor panel) { 385 List<Object> list = new ArrayList<>(); 386 387 for (Entry<PointDetails, Source> e : nxpair.entrySet()) { 388 Object obj = (e.getKey()).getRefObject(); 389 LayoutEditor pan = (e.getKey()).getPanel(); 390 if (pan == panel) { 391 if (!list.contains(obj)) { 392 list.add(obj); 393 } 394 } // end while 395 } 396 return list; 397 } 398 399 public Source getSourceForPoint(PointDetails pd) { 400 return nxpair.get(pd); 401 } 402 403 public int getNxPairNumbers(LayoutEditor panel) { 404 int total = 0; 405 for (Entry<PointDetails, Source> e : nxpair.entrySet()) { 406 PointDetails key = e.getKey(); 407 LayoutEditor pan = key.getPanel(); 408 if (pan == panel) { 409 total = total + e.getValue().getNumberOfDestinations(); 410 } // end while 411 } 412 413 return total; 414 } 415 416 /** 417 * Set the route between the two points represented by the Destination Point name. 418 * 419 * @since 4.11.1 420 * @param nxPair The system or user name of the destination point. 421 */ 422 public void setSingleSegmentRoute(String nxPair) { 423 DestinationPoints dp = getNamedBean(nxPair); 424 if (dp != null) { 425 String destUUID = dp.getUniqueId(); 426 nxpair.forEach((pd, src) -> { 427 for (String srcUUID : src.getDestinationUniqueId()) { 428 if (destUUID.equals(srcUUID)) { 429 log.debug("Found the correct source: src = {}, dest = {}", 430 pd.getSensor().getDisplayName(), dp.getDestPoint().getSensor().getDisplayName()); 431 setMultiPointRoute(pd, dp.getDestPoint()); 432 return; 433 } 434 } 435 }); 436 } 437 } 438 439 public void setMultiPointRoute(PointDetails requestpd, LayoutEditor panel) { 440 for (PointDetails pd : pointDetails) { 441 if (pd != requestpd) { 442 if (pd.getNXState() == NXBUTTONSELECTED) { 443 setMultiPointRoute(pd, requestpd); 444 return; 445 } 446 } 447 } 448 } 449 450 private void setMultiPointRoute(PointDetails fromPd, PointDetails toPd) { 451 boolean cleardown = false; 452 if (fromPd.isRouteFromPointSet() && toPd.isRouteToPointSet()) { 453 cleardown = true; 454 } 455 for (LayoutBlock pro : fromPd.getProtecting()) { 456 try { 457 jmri.jmrit.display.layoutEditor.LayoutBlockManager lbm = InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager.class); 458 LayoutBlock toProt = null; 459 if (!toPd.getProtecting().isEmpty()) { 460 toProt = toPd.getProtecting().get(0); 461 } 462 boolean result = lbm.getLayoutBlockConnectivityTools().checkValidDest(fromPd.getFacing(), pro, toPd.getFacing(), toProt, LayoutBlockConnectivityTools.Routing.SENSORTOSENSOR); 463 if (result) { 464 List<LayoutBlock> blkList = lbm.getLayoutBlockConnectivityTools().getLayoutBlocks(fromPd.getFacing(), toPd.getFacing(), pro, cleardown, LayoutBlockConnectivityTools.Routing.NONE); 465 if (!blkList.isEmpty()) { 466 if (log.isDebugEnabled()) { 467 for (LayoutBlock blk : blkList) { 468 log.debug("blk = {}", blk.getDisplayName()); 469 } 470 } 471 List<jmri.NamedBean> beanList = lbm.getLayoutBlockConnectivityTools().getBeansInPath(blkList, null, jmri.Sensor.class); 472 PointDetails fromPoint = fromPd; 473 refCounter++; 474 if (!beanList.isEmpty()) { 475 if (log.isDebugEnabled()) { 476 for (NamedBean xnb : beanList) { 477 log.debug("xnb = {}", xnb.getDisplayName()); 478 } 479 } 480 for (int i = 1; i < beanList.size(); i++) { 481 NamedBean nb = beanList.get(i); 482 PointDetails cur = getPointDetails(nb, fromPd.getPanel()); 483 Source s = nxpair.get(fromPoint); 484 if (s != null) { 485 routesToSet.add(new SourceToDest(s, s.getDestForPoint(cur), false, refCounter)); 486 } 487 fromPoint = cur; 488 } 489 } 490 Source s = nxpair.get(fromPoint); 491 if (s != null) { 492 if (s.getDestForPoint(toPd) != null) { 493 routesToSet.add(new SourceToDest(s, s.getDestForPoint(toPd), false, refCounter)); 494 } 495 } 496 processRoutesToSet(); 497 return; 498 } 499 } 500 } catch (jmri.JmriException e) { 501 //Can be considered normal if route is blocked 502 } 503 } 504 fromPd.setNXButtonState(NXBUTTONINACTIVE); 505 toPd.setNXButtonState(NXBUTTONINACTIVE); 506 } 507 508 int refCounter = 0; 509 510 /** 511 * List holding SourceToDest sets of routes between two points. 512 */ 513 List<SourceToDest> routesToSet = new ArrayList<>(); 514 515 /** 516 * Class to store NX sets consisting of a source point, a destination point, 517 * a direction and a reference. 518 */ 519 static class SourceToDest { 520 521 Source s = null; 522 DestinationPoints dp = null; 523 boolean direction = false; 524 int ref = -1; 525 526 /** 527 * Constructor for a SourceToDest element. 528 * 529 * @param s a source point 530 * @param dp a destination point 531 * @param dir a direction 532 * @param ref Integer used as reference 533 */ 534 SourceToDest(Source s, DestinationPoints dp, boolean dir, int ref) { 535 this.s = s; 536 this.dp = dp; 537 this.direction = dir; 538 this.ref = ref; 539 } 540 } 541 542 int currentDealing = 0; 543 544 /** 545 * Activate each SourceToDest set in routesToSet 546 */ 547 synchronized void processRoutesToSet() { 548 if (log.isDebugEnabled()) { 549 for (SourceToDest sd : routesToSet) { 550 String dpName = (sd.dp == null) ? "- null -" : sd.dp.getDestPoint().getSensor().getDisplayName(); 551 log.debug("processRoutesToSet: {} -- {} -- {}", sd.s.getPoint().getSensor().getDisplayName(), dpName, sd.ref); 552 } 553 } 554 555 if (routesToSet.isEmpty()) { 556 return; 557 } 558 Source s = routesToSet.get(0).s; 559 DestinationPoints dp = routesToSet.get(0).dp; 560 boolean dir = routesToSet.get(0).direction; 561 currentDealing = routesToSet.get(0).ref; 562 routesToSet.remove(0); 563 564 dp.addPropertyChangeListener(propertyDestinationListener); 565 s.activeBean(dp, dir); 566 } 567 568 /** 569 * Remove remaining SourceToDest sets in routesToSet 570 */ 571 synchronized void removeRemainingRoute() { 572 List<SourceToDest> toRemove = new ArrayList<>(); 573 for (SourceToDest rts : routesToSet) { 574 if (rts.ref == currentDealing) { 575 toRemove.add(rts); 576 rts.dp.getDestPoint().setNXButtonState(NXBUTTONINACTIVE); 577 } 578 } 579 for (SourceToDest rts : toRemove) { 580 routesToSet.remove(rts); 581 } 582 } 583 584 protected PropertyChangeListener propertyDestinationListener = new PropertyChangeListener() { 585 @Override 586 public void propertyChange(PropertyChangeEvent e) { 587 ((DestinationPoints) e.getSource()).removePropertyChangeListener(this); 588 if (e.getPropertyName().equals("active")) { 589 processRoutesToSet(); 590 } else if (e.getPropertyName().equals("stacked") || e.getPropertyName().equals("failed") || e.getPropertyName().equals("noChange")) { // NOI18N 591 removeRemainingRoute(); 592 } 593 } 594 }; 595 596 List<Object> destinationList = new ArrayList<>(); 597 598 // Need to sort out the presentation of the name here rather than using the point ID. 599 // This is used for the creation and display of information in the table. 600 // The presentation of the name might have to be done at the table level. 601 public List<Object> getNxSource(LayoutEditor panel) { 602 List<Object> source = new ArrayList<>(); 603 destinationList = new ArrayList<>(); 604 605 for (Entry<PointDetails, Source> e : nxpair.entrySet()) { 606 PointDetails key = e.getKey(); 607 LayoutEditor pan = key.getPanel(); 608 if (pan == panel) { 609 List<PointDetails> dest = nxpair.get(key).getDestinationPoints(); 610 for (int i = 0; i < dest.size(); i++) { 611 destinationList.add(dest.get(i).getRefObject()); 612 source.add(key.getRefObject()); 613 } 614 } 615 } 616 return source; 617 } 618 619 public List<Object> getNxDestination() { 620 return destinationList; 621 } 622 623 public List<LayoutEditor> getSourcePanelList() { 624 List<LayoutEditor> list = new ArrayList<>(); 625 626 for (Entry<PointDetails, Source> e : nxpair.entrySet()) { 627 PointDetails key = e.getKey(); 628 LayoutEditor pan = key.getPanel(); 629 if (!list.contains(pan)) { 630 list.add(pan); 631 } 632 } 633 return list; 634 } 635 636 /** 637 * Return a point if it already exists, or create a new one if not. 638 */ 639 private PointDetails providePoint(LayoutBlock source, List<LayoutBlock> protecting, LayoutEditor panel) { 640 PointDetails sourcePoint = getPointDetails(source, protecting, panel); 641 if (sourcePoint == null) { 642 sourcePoint = new PointDetails(source, protecting); 643 sourcePoint.setPanel(panel); 644 } 645 return sourcePoint; 646 } 647 648 /** 649 * @since 4.17.4 650 */ 651 @Override 652 public void propertyChange(PropertyChangeEvent evt) { 653 firePropertyChange("active", evt.getOldValue(), evt.getNewValue()); 654 } 655 656 657 public void addNXDestination(NamedBean source, NamedBean destination, LayoutEditor panel) { 658 addNXDestination(source, destination, panel, null); 659 } 660 661 /** 662 * @since 4.17.4 663 * Register in Property Change Listener. 664 * @param source the source bean. 665 * @param destination the destination bean. 666 * @param panel the layout editor panel. 667 * @param id the points details id. 668 */ 669 public void addNXDestination(NamedBean source, NamedBean destination, LayoutEditor panel, String id) { 670 if (source == null) { 671 log.error("no source Object provided"); // NOI18N 672 return; 673 } 674 if (destination == null) { 675 log.error("no destination Object provided"); // NOI18N 676 return; 677 } 678 PointDetails sourcePoint = providePoint(source, panel); 679 if (sourcePoint == null) { 680 log.error("source point for {} not created addNXDes", source.getDisplayName()); // NOI18N 681 return; 682 } 683 684 sourcePoint.setPanel(panel); 685 sourcePoint.setRefObject(source); 686 PointDetails destPoint = providePoint(destination, panel); 687 if (destPoint != null) { 688 destPoint.setPanel(panel); 689 destPoint.setRefObject(destination); 690 destPoint.getSignal(); 691 if (!nxpair.containsKey(sourcePoint)) { 692 Source sp = new Source(sourcePoint); 693 nxpair.put(sourcePoint, sp); 694 sp.removePropertyChangeListener(this); 695 sp.addPropertyChangeListener(this); 696 } 697 nxpair.get(sourcePoint).addDestination(destPoint, id); 698 } 699 700 firePropertyChange("length", null, null); // NOI18N 701 } 702 703 public List<Object> getDestinationList(Object obj, LayoutEditor panel) { 704 List<Object> list = new ArrayList<>(); 705 if (nxpair.containsKey(getPointDetails(obj, panel))) { 706 List<PointDetails> from = nxpair.get(getPointDetails(obj, panel)).getDestinationPoints(); 707 for (int i = 0; i < from.size(); i++) { 708 list.add(from.get(i).getRefObject()); 709 } 710 } 711 return list; 712 } 713 714 public void removeNXSensor(Sensor sensor) { 715 log.info("panel maintenance has resulting in the request to remove a sensor: {}", sensor.getDisplayName()); 716 } 717 718 // ============ NX Pair Delete Methods ============ 719 // The request will be for all NX Pairs containing a sensor or 720 // a specific entry and exit sensor pair. 721 722 /** 723 * Entry point to delete all of the NX pairs for a specific sensor. 724 * 1) Build a list of affected NX pairs. 725 * 2) Check for Conditional references. 726 * 3) If no references, do the delete process with user approval. 727 * @since 4.11.2 728 * @param sensor The sensor whose pairs should be deleted. 729 * @return true if the delete was successful. False if prevented by 730 * Conditional references or user choice. 731 */ 732 public boolean deleteNxPair(NamedBean sensor) { 733 if (sensor == null) { 734 log.error("deleteNxPair: sensor is null"); // NOI18N 735 return false; 736 } 737 createDeletePairList(sensor); 738 if (checkNxPairs()) { 739 // No Conditional references. 740 if (confirmDeletePairs()) { 741 deleteNxPairs(); 742 return true; 743 } 744 } 745 return false; 746 } 747 748 /** 749 * Entry point to delete a specific NX pair. 750 * 751 * @since 4.11.2 752 * @param entrySensor The sensor that acts as the entry point. 753 * @param exitSensor The sensor that acts as the exit point. 754 * @param panel The layout editor panel that contains the entry sensor. 755 * @return true if the delete was successful. False if there are Conditional references. 756 */ 757 public boolean deleteNxPair(NamedBean entrySensor, NamedBean exitSensor, LayoutEditor panel) { 758 if (entrySensor == null || exitSensor == null || panel == null) { 759 log.error("deleteNxPair: One or more null inputs"); // NOI18N 760 return false; 761 } 762 deletePairList.clear(); 763 deletePairList.add(new DeletePair(entrySensor, exitSensor, panel)); 764 if (checkNxPairs()) { 765 // No Conditional references. 766 deleteNxPairs(); // Delete with no prompt 767 return true; 768 } 769 return false; 770 } 771 772 /** 773 * Find Logix Conditionals that have Variables or Actions for the affected NX Pairs 774 * If any are found, display a dialog box listing the Conditionals and return false. 775 * <p> 776 * @since 4.11.2 777 * @return true if there are no references. 778 */ 779 private boolean checkNxPairs() { 780 jmri.LogixManager mgr = InstanceManager.getDefault(jmri.LogixManager.class); 781 List<String> conditionalReferences = new ArrayList<>(); 782 for (DeletePair dPair : deletePairList) { 783 if (dPair.dp == null) { 784 continue; 785 } 786 for (jmri.Logix lgx : mgr.getNamedBeanSet()) { 787 for (int i = 0; i < lgx.getNumConditionals(); i++) { 788 String cdlName = lgx.getConditionalByNumberOrder(i); 789 jmri.implementation.DefaultConditional cdl = (jmri.implementation.DefaultConditional) lgx.getConditional(cdlName); 790 String cdlUserName = cdl.getUserName(); 791 if (cdlUserName == null) { 792 cdlUserName = ""; 793 } 794 for (jmri.ConditionalVariable var : cdl.getStateVariableList()) { 795 if (var.getBean() == dPair.dp) { 796 String refName = (cdlUserName.equals("")) ? cdlName : cdlName + " ( " + cdlUserName + " )"; 797 if (!conditionalReferences.contains(refName)) { 798 conditionalReferences.add(refName); 799 } 800 } 801 } 802 for (jmri.ConditionalAction act : cdl.getActionList()) { 803 if (act.getBean() == dPair.dp) { 804 String refName = (cdlUserName.equals("")) ? cdlName : cdlName + " ( " + cdlUserName + " )"; 805 if (!conditionalReferences.contains(refName)) { 806 conditionalReferences.add(refName); 807 } 808 } 809 } 810 } 811 } 812 } 813 if (conditionalReferences.isEmpty()) { 814 return true; 815 } 816 817 conditionalReferences.sort(null); 818 StringBuilder msg = new StringBuilder(Bundle.getMessage("DeleteReferences")); 819 for (String ref : conditionalReferences) { 820 msg.append("\n " + ref); // NOI18N 821 } 822 JmriJOptionPane.showMessageDialog(null, 823 msg.toString(), 824 Bundle.getMessage("WarningTitle"), // NOI18N 825 JmriJOptionPane.WARNING_MESSAGE); 826 827 return false; 828 } 829 830 /** 831 * Display a list of pending deletes and ask for confirmation. 832 * @since 4.11.2 833 * @return true if deletion confirmation is Yes. 834 */ 835 private boolean confirmDeletePairs() { 836 if (!deletePairList.isEmpty()) { 837 StringBuilder msg = new StringBuilder(Bundle.getMessage("DeletePairs")); // NOI18N 838 for (DeletePair dPair : deletePairList) { 839 if (dPair.dp != null) { 840 msg.append("\n ").append(dPair.dp.getDisplayName()); // NOI18N 841 } 842 } 843 msg.append("\n").append(Bundle.getMessage("DeleteContinue")); // NOI18N 844 int resp = JmriJOptionPane.showConfirmDialog(null, 845 msg.toString(), 846 Bundle.getMessage("WarningTitle"), // NOI18N 847 JmriJOptionPane.YES_NO_OPTION, 848 JmriJOptionPane.QUESTION_MESSAGE); 849 if (resp != JmriJOptionPane.YES_OPTION ) { 850 return false; 851 } 852 } 853 return true; 854 } 855 856 /** 857 * Delete the pairs in the delete pair list. 858 * @since 4.11.2 859 * @since 4.17.4 860 * Remove from Change Listener. 861 */ 862 private void deleteNxPairs() { 863 for (DeletePair dp : deletePairList) { 864 PointDetails sourcePoint = getPointDetails(dp.src, dp.pnl); 865 PointDetails destPoint = getPointDetails(dp.dest, dp.pnl); 866 nxpair.get(sourcePoint).removeDestination(destPoint); 867 firePropertyChange("length", null, null); // NOI18N 868 if (nxpair.get(sourcePoint).getDestinationPoints().isEmpty()) { 869 nxpair.get(sourcePoint).removePropertyChangeListener(this); 870 nxpair.remove(sourcePoint); 871 } 872 } 873 } 874 875 /** 876 * List of NX pairs that are scheduled for deletion. 877 * @since 4.11.2 878 */ 879 List<DeletePair> deletePairList = new ArrayList<>(); 880 881 /** 882 * Class to store NX pair components. 883 * @since 4.11.2 884 */ 885 class DeletePair { 886 NamedBean src = null; 887 NamedBean dest = null; 888 LayoutEditor pnl = null; 889 DestinationPoints dp = null; 890 891 /** 892 * Constructor for a DeletePair row. 893 * 894 * @param src Source sensor bean 895 * @param dest Ddestination sensor bean 896 * @param pnl The LayoutEditor panel for the source bean 897 */ 898 DeletePair(NamedBean src, NamedBean dest, LayoutEditor pnl) { 899 this.src = src; 900 this.dest = dest; 901 this.pnl = pnl; 902 903 // Get the actual destination point, if any. 904 PointDetails sourcePoint = getPointDetails(src, pnl); 905 PointDetails destPoint = getPointDetails(dest, pnl); 906 if (sourcePoint != null && destPoint != null) { 907 if (nxpair.containsKey(sourcePoint)) { 908 this.dp = nxpair.get(sourcePoint).getDestForPoint(destPoint); 909 } 910 } 911 } 912 } 913 914 /** 915 * Rebuild the delete pair list based on the supplied sensor. 916 * Find all of the NX pairs that use this sensor as either a source or 917 * destination. They will be candidates for deletion. 918 * 919 * @since 4.11.2 920 * @param sensor The sensor being deleted, 921 */ 922 void createDeletePairList(NamedBean sensor) { 923 deletePairList.clear(); 924 nxpair.forEach((pdSrc, src) -> { 925 Sensor sBean = pdSrc.getSensor(); 926 LayoutEditor sPanel = pdSrc.getPanel(); 927 for (PointDetails pdDest : src.getDestinationPoints()) { 928 Sensor dBean = pdDest.getSensor(); 929 if (sensor == sBean || sensor == dBean) { 930 log.debug("Delete pair: {} to {}, panel = {}", // NOI18N 931 sBean.getDisplayName(), dBean.getDisplayName(), sPanel.getLayoutName()); 932 deletePairList.add(new DeletePair(sBean, dBean, sPanel)); 933 } 934 } 935 }); 936 } 937 938 // ============ End NX Pair Delete Methods ============ 939 940 /** 941 * Create a list of sensors that have the layout block as either 942 * facing or protecting. 943 * Called by {@link jmri.jmrit.display.layoutEditor.LayoutEditorDialogs.LayoutTrackEditor#hasNxSensorPairs}. 944 * @since 4.11.2 945 * @param layoutBlock The layout block to be checked. 946 * @return the a list of sensors affected by the layout block or an empty list. 947 */ 948 public List<String> layoutBlockSensors(@Nonnull LayoutBlock layoutBlock) { 949 log.debug("layoutBlockSensors: {}", layoutBlock.getDisplayName()); 950 List<String> blockSensors = new ArrayList<>(); 951 nxpair.forEach((pdSrc, src) -> { 952 Sensor sBean = pdSrc.getSensor(); 953 for (LayoutBlock sProtect : pdSrc.getProtecting()) { 954 if (layoutBlock == pdSrc.getFacing() || layoutBlock == sProtect) { 955 log.debug(" Source = '{}', Facing = '{}', Protecting = '{}' ", 956 sBean.getDisplayName(), pdSrc.getFacing().getDisplayName(), sProtect.getDisplayName()); 957 blockSensors.add(sBean.getDisplayName()); 958 } 959 } 960 961 for (PointDetails pdDest : src.getDestinationPoints()) { 962 Sensor dBean = pdDest.getSensor(); 963 for (LayoutBlock dProtect : pdDest.getProtecting()) { 964 if (layoutBlock == pdDest.getFacing() || layoutBlock == dProtect) { 965 log.debug(" Destination = '{}', Facing = '{}', Protecting = '{}' ", 966 dBean.getDisplayName(), pdDest.getFacing().getDisplayName(), dProtect.getDisplayName()); 967 blockSensors.add(dBean.getDisplayName()); 968 } 969 } 970 } 971 }); 972 return blockSensors; 973 } 974 975 public boolean isDestinationValid(Object source, Object dest, LayoutEditor panel) { 976 if (nxpair.containsKey(getPointDetails(source, panel))) { 977 return nxpair.get(getPointDetails(source, panel)).isDestinationValid(getPointDetails(dest, panel)); 978 } 979 return false; 980 } 981 982 public boolean isUniDirection(Object source, LayoutEditor panel, Object dest) { 983 if (nxpair.containsKey(getPointDetails(source, panel))) { 984 return nxpair.get(getPointDetails(source, panel)).getUniDirection(dest, panel); 985 } 986 return false; 987 } 988 989 public void setUniDirection(Object source, LayoutEditor panel, Object dest, boolean set) { 990 if (nxpair.containsKey(getPointDetails(source, panel))) { 991 nxpair.get(getPointDetails(source, panel)).setUniDirection(dest, panel, set); 992 } 993 } 994 995 public boolean canBeBiDirectional(Object source, LayoutEditor panel, Object dest) { 996 if (nxpair.containsKey(getPointDetails(source, panel))) { 997 return nxpair.get(getPointDetails(source, panel)).canBeBiDirection(dest, panel); 998 } 999 return false; 1000 } 1001 1002 public boolean isEnabled(Object source, LayoutEditor panel, Object dest) { 1003 if (nxpair.containsKey(getPointDetails(source, panel))) { 1004 return nxpair.get(getPointDetails(source, panel)).isEnabled(dest, panel); 1005 } 1006 return false; 1007 } 1008 1009 public void setEnabled(Object source, LayoutEditor panel, Object dest, boolean set) { 1010 if (nxpair.containsKey(getPointDetails(source, panel))) { 1011 nxpair.get(getPointDetails(source, panel)).setEnabled(dest, panel, set); 1012 } 1013 } 1014 1015 public void setEntryExitType(Object source, LayoutEditor panel, Object dest, int set) { 1016 if (nxpair.containsKey(getPointDetails(source, panel))) { 1017 nxpair.get(getPointDetails(source, panel)).setEntryExitType(dest, panel, set); 1018 } 1019 } 1020 1021 public int getEntryExitType(Object source, LayoutEditor panel, Object dest) { 1022 if (nxpair.containsKey(getPointDetails(source, panel))) { 1023 return nxpair.get(getPointDetails(source, panel)).getEntryExitType(dest, panel); 1024 } 1025 return 0x00; 1026 } 1027 1028 public String getUniqueId(Object source, LayoutEditor panel, Object dest) { 1029 if (nxpair.containsKey(getPointDetails(source, panel))) { 1030 return nxpair.get(getPointDetails(source, panel)).getUniqueId(dest, panel); 1031 } 1032 return null; 1033 } 1034 1035 public List<String> getEntryExitList() { 1036 List<String> destlist = new ArrayList<>(); 1037 for (Source e : nxpair.values()) { 1038 destlist.addAll(e.getDestinationUniqueId()); 1039 } 1040 return destlist; 1041 } 1042 1043 // protecting helps us to determine which direction we are going. 1044 // validateOnly flag is used, if all we are doing is simply checking to see if the source/destpoints are valid 1045 // when creating the pairs in the user GUI 1046 public boolean isPathActive(Object sourceObj, Object destObj, LayoutEditor panel) { 1047 PointDetails pd = getPointDetails(sourceObj, panel); 1048 if (nxpair.containsKey(pd)) { 1049 Source source = nxpair.get(pd); 1050 return source.isRouteActive(getPointDetails(destObj, panel)); 1051 } 1052 return false; 1053 } 1054 1055 public void cancelInterlock(Object source, LayoutEditor panel, Object dest) { 1056 if (nxpair.containsKey(getPointDetails(source, panel))) { 1057 nxpair.get(getPointDetails(source, panel)).cancelInterlock(dest, panel); 1058 } 1059 1060 } 1061 1062 jmri.SignalMastLogicManager smlm = InstanceManager.getDefault(jmri.SignalMastLogicManager.class); 1063 1064 public final static int CANCELROUTE = 0; 1065 public final static int CLEARROUTE = 1; 1066 public final static int EXITROUTE = 2; 1067 public final static int STACKROUTE = 4; 1068 1069 /** 1070 * Return a point from a given LE Panel. 1071 * 1072 * @param obj The point object 1073 * @param panel The Layout Editor panel on which the point was placed 1074 * @return the point object, null if the point is not found 1075 */ 1076 public PointDetails getPointDetails(Object obj, LayoutEditor panel) { 1077 for (int i = 0; i < pointDetails.size(); i++) { 1078 if ((pointDetails.get(i).getRefObject() == obj)) { 1079 return pointDetails.get(i); 1080 1081 } 1082 } 1083 return null; 1084 } 1085 1086 /** 1087 * Return either an existing point stored in pointDetails, or create a new one as required. 1088 * 1089 * @param source The Layout Block functioning as the source (origin) 1090 * @param destination A (list of) Layout Blocks functioning as destinations 1091 * @param panel The Layout Editor panel on which the point is to be placed 1092 * @return the point object 1093 */ 1094 PointDetails getPointDetails(LayoutBlock source, List<LayoutBlock> destination, LayoutEditor panel) { 1095 PointDetails newPoint = new PointDetails(source, destination); 1096 newPoint.setPanel(panel); 1097 for (int i = 0; i < pointDetails.size(); i++) { 1098 if (pointDetails.get(i).equals(newPoint)) { 1099 return pointDetails.get(i); 1100 } 1101 } 1102 //Not found so will add 1103 pointDetails.add(newPoint); 1104 return newPoint; 1105 } 1106 1107 //No point can have multiple copies of what is the same thing. 1108 static List<PointDetails> pointDetails = new ArrayList<PointDetails>(); 1109 1110 /** 1111 * Get the name of a destinationPoint on a LE Panel. 1112 * 1113 * @param obj the point object 1114 * @param panel The Layout Editor panel on which it is expected to be placed 1115 * @return the name of the point 1116 */ 1117 public String getPointAsString(NamedBean obj, LayoutEditor panel) { 1118 if (obj == null) { 1119 return "null"; // NOI18N 1120 } 1121 PointDetails valid = getPointDetails(obj, panel); //was just plain getPoint 1122 if (valid != null) { 1123 return valid.getDisplayName(); 1124 } 1125 return "empty"; // NOI18N 1126 } 1127 1128 List<StackDetails> stackList = new ArrayList<>(); 1129 1130 /** 1131 * If a route is requested but is currently blocked, ask user 1132 * if it should be added to stackList. 1133 * 1134 * @param dp DestinationPoints object 1135 * @param reverse true for a reversed running direction, mostly false 1136 */ 1137 synchronized public void stackNXRoute(DestinationPoints dp, boolean reverse) { 1138 if (isRouteStacked(dp, reverse)) { 1139 return; 1140 } 1141 stackList.add(new StackDetails(dp, reverse)); 1142 checkTimer.start(); 1143 if (stackPanel == null) { 1144 stackPanel = new StackNXPanel(); 1145 } 1146 if (stackDialog == null) { 1147 stackDialog = new JDialog(); 1148 stackDialog.setTitle(Bundle.getMessage("WindowTitleStackRoutes")); // NOI18N 1149 stackDialog.add(stackPanel); 1150 } 1151 stackPanel.updateGUI(); 1152 1153 stackDialog.pack(); 1154 stackDialog.setModal(false); 1155 stackDialog.setVisible(true); 1156 } 1157 1158 StackNXPanel stackPanel = null; 1159 JDialog stackDialog = null; 1160 1161 /** 1162 * Get a list of all stacked routes from stackList. 1163 * 1164 * @return an List containing destinationPoint elements 1165 */ 1166 public List<DestinationPoints> getStackedInterlocks() { 1167 List<DestinationPoints> dpList = new ArrayList<>(); 1168 for (StackDetails st : stackList) { 1169 dpList.add(st.getDestinationPoint()); 1170 } 1171 return dpList; 1172 } 1173 1174 /** 1175 * Query if a stacked route is in stackList. 1176 * 1177 * @param dp DestinationPoints object 1178 * @param reverse true for a reversed running direction, mostly false 1179 * @return true if dp is in stackList 1180 */ 1181 public boolean isRouteStacked(DestinationPoints dp, boolean reverse) { 1182 Iterator<StackDetails> iter = stackList.iterator(); 1183 while (iter.hasNext()) { 1184 StackDetails st = iter.next(); 1185 if (st.getDestinationPoint() == dp && st.getReverse() == reverse) { 1186 return true; 1187 } 1188 } 1189 return false; 1190 } 1191 1192 /** 1193 * Remove a stacked route from stackList. 1194 * 1195 * @param dp DestinationPoints object 1196 * @param reverse true for a reversed running direction, mostly false 1197 */ 1198 synchronized public void cancelStackedRoute(DestinationPoints dp, boolean reverse) { 1199 Iterator<StackDetails> iter = stackList.iterator(); 1200 while (iter.hasNext()) { 1201 StackDetails st = iter.next(); 1202 if (st.getDestinationPoint() == dp && st.getReverse() == reverse) { 1203 iter.remove(); 1204 } 1205 } 1206 stackPanel.updateGUI(); 1207 if (stackList.isEmpty()) { 1208 stackDialog.setVisible(false); 1209 checkTimer.stop(); 1210 } 1211 } 1212 1213 /** 1214 * Class to collect (stack) routes when they are requested but blocked. 1215 */ 1216 static class StackDetails { 1217 1218 DestinationPoints dp; 1219 boolean reverse; 1220 1221 StackDetails(DestinationPoints dp, boolean reverse) { 1222 this.dp = dp; 1223 this.reverse = reverse; 1224 } 1225 1226 boolean getReverse() { 1227 return reverse; 1228 } 1229 1230 DestinationPoints getDestinationPoint() { 1231 return dp; 1232 } 1233 } 1234 1235 javax.swing.Timer checkTimer = new javax.swing.Timer(10000, (java.awt.event.ActionEvent e) -> { 1236 checkRoute(); 1237 }); 1238 1239 /** 1240 * Step through stackList and activate the first stacked route in line 1241 * if it is no longer blocked. 1242 */ 1243 synchronized void checkRoute() { 1244 checkTimer.stop(); 1245 StackDetails[] tmp = new StackDetails[stackList.size()]; 1246 stackList.toArray(tmp); 1247 1248 for (StackDetails st : tmp) { 1249 if (!st.getDestinationPoint().isActive()) { 1250 // If the route is not already active, then check. 1251 // If the route does get set, then the setting process will remove the route from the stack. 1252 st.getDestinationPoint().setInterlockRoute(st.getReverse()); 1253 } 1254 } 1255 1256 if (!stackList.isEmpty()) { 1257 checkTimer.start(); 1258 } else { 1259 stackDialog.setVisible(false); 1260 } 1261 } 1262 1263 public void removePropertyChangeListener(PropertyChangeListener list, NamedBean obj, LayoutEditor panel) { 1264 if (obj == null) { 1265 return; 1266 } 1267 PointDetails valid = getPointDetails(obj, panel); 1268 if (valid != null) { 1269 valid.removePropertyChangeListener(list); 1270 } 1271 } 1272 1273 boolean runWhenStabilised = false; 1274 LayoutEditor toUseWhenStable; 1275 int interlockTypeToUseWhenStable; 1276 1277 /** 1278 * Discover all possible valid source and destination Signal Mast Logic pairs 1279 * on all Layout Editor panels. 1280 * 1281 * @param editor The Layout Editor panel 1282 * @param interlockType Integer value representing the type of interlocking, one of 1283 * SETUPTURNOUTSONLY, SETUPSIGNALMASTLOGIC or FULLINTERLOCK 1284 * @throws JmriException when an error occurs during discovery 1285 */ 1286 public void automaticallyDiscoverEntryExitPairs(LayoutEditor editor, int interlockType) throws JmriException { 1287 //This is almost a duplicate of that in the DefaultSignalMastLogicManager 1288 runWhenStabilised = false; 1289 jmri.jmrit.display.layoutEditor.LayoutBlockManager lbm = InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager.class); 1290 if (!lbm.isAdvancedRoutingEnabled()) { 1291 throw new JmriException("advanced routing not enabled"); // NOI18N 1292 } 1293 if (!lbm.routingStablised()) { 1294 runWhenStabilised = true; 1295 toUseWhenStable = editor; 1296 interlockTypeToUseWhenStable = interlockType; 1297 log.debug("Layout block routing has not yet stabilised, discovery will happen once it has"); // NOI18N 1298 return; 1299 } 1300 HashMap<NamedBean, List<NamedBean>> validPaths = lbm.getLayoutBlockConnectivityTools(). 1301 discoverValidBeanPairs(null, Sensor.class, LayoutBlockConnectivityTools.Routing.SENSORTOSENSOR); 1302 EntryExitPairs eep = this; 1303 for (Entry<NamedBean, List<NamedBean>> entry : validPaths.entrySet()) { 1304 NamedBean key = entry.getKey(); 1305 List<NamedBean> validDestMast = validPaths.get(key); 1306 if (validDestMast.size() > 0) { 1307 eep.addNXSourcePoint(key, editor); 1308 for (int i = 0; i < validDestMast.size(); i++) { 1309 if (!eep.isDestinationValid(key, validDestMast.get(i), editor)) { 1310 eep.addNXDestination(key, validDestMast.get(i), editor); 1311 eep.setEntryExitType(key, editor, validDestMast.get(i), interlockType); 1312 } 1313 } 1314 } 1315 } 1316 1317 firePropertyChange("autoGenerateComplete", null, null); // NOI18N 1318 } 1319 1320 protected PropertyChangeListener propertyBlockManagerListener = new PropertyChangeListener() { 1321 @Override 1322 public void propertyChange(PropertyChangeEvent e) { 1323 if (e.getPropertyName().equals("topology")) { // NOI18N 1324 //boolean newValue = new Boolean.parseBoolean(String.valueOf(e.getNewValue())); 1325 boolean newValue = (Boolean) e.getNewValue(); 1326 if (newValue) { 1327 if (runWhenStabilised) { 1328 try { 1329 automaticallyDiscoverEntryExitPairs(toUseWhenStable, interlockTypeToUseWhenStable); 1330 } catch (JmriException je) { 1331 //Considered normal if routing not enabled 1332 } 1333 } 1334 } 1335 } 1336 } 1337 }; 1338 1339 public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException { 1340 1341 } 1342 1343 @Override 1344 public void deleteBean(@Nonnull DestinationPoints bean, @Nonnull String property) throws PropertyVetoException { 1345 1346 } 1347 1348 @Override 1349 @Nonnull 1350 public String getBeanTypeHandled(boolean plural) { 1351 return Bundle.getMessage(plural ? "BeanNameTransits" : "BeanNameTransit"); // NOI18N 1352 } 1353 1354 /** 1355 * {@inheritDoc} 1356 */ 1357 @Override 1358 public Class<DestinationPoints> getNamedBeanClass() { 1359 return DestinationPoints.class; 1360 } 1361 1362 /** 1363 * {@inheritDoc} 1364 */ 1365 @Override 1366 @OverridingMethodsMustInvokeSuper 1367 public void setPropertyChangesSilenced(@Nonnull String propertyName, boolean silenced) { 1368 if (!"beans".equals(propertyName)) { 1369 throw new IllegalArgumentException("Property " + propertyName + " cannot be silenced."); 1370 } 1371 silencedProperties.put(propertyName, silenced); 1372 if (propertyName.equals("beans") && !silenced) { 1373 fireIndexedPropertyChange("beans", getNamedBeanSet().size(), null, null); 1374 } 1375 } 1376 1377 /** {@inheritDoc} */ 1378 @Override 1379 public void addDataListener(ManagerDataListener<DestinationPoints> e) { 1380 if (e != null) listeners.add(e); 1381 } 1382 1383 /** {@inheritDoc} */ 1384 @Override 1385 public void removeDataListener(ManagerDataListener<DestinationPoints> e) { 1386 if (e != null) listeners.remove(e); 1387 } 1388 1389 final List<ManagerDataListener<DestinationPoints>> listeners = new ArrayList<>(); 1390 1391 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(EntryExitPairs.class); 1392 1393}