001package jmri.jmrit.logix; 002 003import java.beans.PropertyChangeListener; 004import java.beans.PropertyChangeSupport; 005import java.util.ArrayList; 006import java.util.List; 007 008import javax.annotation.Nonnull; 009import javax.annotation.CheckForNull; 010import javax.annotation.OverridingMethodsMustInvokeSuper; 011 012import jmri.Block; 013import jmri.InstanceManager; 014import jmri.NamedBean; 015import jmri.SignalHead; 016import jmri.SignalMast; 017import jmri.implementation.SignalSpeedMap; 018 019/** 020 * A Portal is a boundary between two Blocks. 021 * <p> 022 * A Portal has Lists of the {@link OPath}s that connect through it. 023 * The direction of trains passing through the portal is managed from the 024 * BlockOrders of the Warrant the train is running under. 025 * The Portal fires a PropertyChangeEvent that a 026 * {@link jmri.jmrit.display.controlPanelEditor.PortalIcon} can listen 027 * for to set direction arrows for a given route. 028 * 029 * The Portal also supplies speed information from any signals set at its 030 * location that the Warrant passes on the Engineer. 031 * 032 * @author Pete Cressman Copyright (C) 2009 033 */ 034public class Portal { 035 036 /** 037 * String constant for property name change. 038 */ 039 public static final String PROPERTY_NAME_CHANGE = "NameChange"; 040 041 /** 042 * String constant for property signal change. 043 */ 044 public static final String PROPERTY_SIGNAL_CHANGE = "signalChange"; 045 046 /** 047 * String constant for property direction. 048 */ 049 public static final String PROPERTY_DIRECTION = "Direction"; 050 051 /** 052 * String constant for property block changed. 053 */ 054 public static final String PROPERTY_BLOCK_CHANGED = "BlockChanged"; 055 056 /** 057 * String constant for property portal delete. 058 */ 059 public static final String PROPERTY_PORTAL_DELETE = "portalDelete"; 060 061 private static final String ENTRANCE = "entrance"; 062 private final ArrayList<OPath> _fromPaths = new ArrayList<>(); 063 private OBlock _fromBlock; 064 private NamedBean _fromSignal; // may be either SignalHead or SignalMast 065 private float _fromSignalOffset; // adjustment distance for speed change 066 private final ArrayList<OPath> _toPaths = new ArrayList<>(); 067 private OBlock _toBlock; 068 private NamedBean _toSignal; // may be either SignalHead or SignalMast 069 private float _toSignalOffset; // adjustment distance for speed change 070 private String _name; 071 private int _state = UNKNOWN; 072 private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); 073 074 public static final int UNKNOWN = 0x01; 075 public static final int ENTER_TO_BLOCK = 0x02; 076 public static final int ENTER_FROM_BLOCK = 0x04; 077 078 public Portal(String uName) { 079 _name = uName; 080 } 081 082 /** 083 * Determine which list the Path belongs to and add it to that list. 084 * 085 * @param path OPath to add 086 * @return false if Path does not have a matching block for this Portal 087 */ 088 public boolean addPath(@Nonnull OPath path) { 089 Block block = path.getBlock(); 090 if (block == null) { 091 log.error("Path \"{}\" has no block.", path.getName()); 092 return false; 093 } 094 if (!this.equals(path.getFromPortal()) 095 && !this.equals(path.getToPortal())) { 096 return false; 097 } 098 if ((_fromBlock != null) && _fromBlock.equals(block)) { 099 return addPath(_fromPaths, path); 100 } else if ((_toBlock != null) && _toBlock.equals(block)) { 101 return addPath(_toPaths, path); 102 } 103 // portal is incomplete or path block not in this portal 104 return false; 105 } 106 107 /** 108 * Utility for both path lists. 109 * Checks for duplicate name. 110 */ 111 private boolean addPath(@Nonnull List<OPath> list, @Nonnull OPath path) { 112 String pName = path.getName(); 113 for (OPath p : list) { 114 if (p.equals(path)) { 115 if (pName.equals(p.getName())) { 116 return true; // OK, everything equal 117 } else { 118 log.warn("Path \"{}\" is duplicate of path \"{}\" in Portal \"{}\" from block {}.", 119 path.getName(), p.getName(), _name, path.getBlock().getDisplayName()); 120 return false; 121 } 122 } else if (pName.equals(p.getName())) { 123 log.warn("Path \"{}\" is duplicate name for another path in Portal \"{}\" from block {}.", 124 path.getName(), _name, path.getBlock().getDisplayName()); 125 return false; 126 } 127 } 128 list.add(path); 129 return true; 130 } 131 132 /** 133 * Remove an OPath from this Portal. 134 * Checks both the _fromBlock list as the _toBlock list. 135 * 136 * @param path the OPath to remove 137 */ 138 public void removePath(@Nonnull OPath path) { 139 Block block = path.getBlock(); 140 if (block == null) { 141 log.error("Path \"{}\" has no block.", path.getName()); 142 return; 143 } 144 log.debug("removePath: {}", this); 145 if (!this.equals(path.getFromPortal()) 146 && !this.equals(path.getToPortal())) { 147 return; 148 } 149 if (_fromBlock != null && _fromBlock.equals(block)) { 150 _fromPaths.remove(path); 151 } else if (_toBlock != null && _toBlock.equals(block)) { 152 _toPaths.remove(path); 153 } 154// pcs.firePropertyChange("RemovePath", block, path); not needed 155 } 156 157 /** 158 * Set userName of this Portal. Checks if name is available. 159 * 160 * @param newName name for path 161 * @return return error message, null if name change is OK 162 */ 163 @CheckForNull 164 public String setName(@CheckForNull String newName) { 165 if (newName == null || newName.length() == 0) { 166 return null; 167 } 168 String oldName = _name; 169 if (newName.equals(oldName)) { 170 return null; 171 } 172 Portal p = InstanceManager.getDefault(PortalManager.class).getPortal(newName); 173 if (p != null) { 174 return Bundle.getMessage("DuplicatePortalName", newName, p.getDescription()); 175 } 176 _name = newName; 177 InstanceManager.getDefault(WarrantManager.class).portalNameChange(oldName, newName); 178 179 // for some unknown reason, PortalManager firePropertyChange is not read by PortalTableModel 180 // so let OBlock do it 181 if (_toBlock != null) { 182 _toBlock.pseudoPropertyChange(PROPERTY_NAME_CHANGE, oldName, this); 183 } else if (_fromBlock != null) { 184 _fromBlock.pseudoPropertyChange(PROPERTY_NAME_CHANGE, oldName, this); 185 } 186 // CircuitBuilder PortalList needs this property change 187 pcs.firePropertyChange(PROPERTY_NAME_CHANGE, oldName, newName); 188 return null; 189 } 190 191 public String getName() { 192 return _name; 193 } 194 195 /** 196 * Set this portal's toBlock. Remove this portal from old toBlock, if any. 197 * Add this portal in the new toBlock's list of portals. 198 * 199 * @param block to be the new toBlock 200 * @param changePaths if true, set block in paths. If false, 201 * verify that all toPaths are contained in the block. 202 * @return false if paths are not in the block 203 */ 204 public boolean setToBlock(@CheckForNull OBlock block, boolean changePaths) { 205 if (((block != null) && block.equals(_toBlock)) || ((block == null) && (_toBlock == null))) { 206 return true; 207 } 208 if (changePaths) { 209 // Switch paths to new block. User will need to verify connections 210 for (OPath opa : _toPaths) { 211 opa.setBlock(block); 212 } 213 } else if (!verify(_toPaths, block)) { 214 return false; 215 } 216 log.debug("setToBlock: oldBlock= \"{}\" newBlock \"{}\".", getToBlockName(), 217 (block != null ? block.getDisplayName() : null)); 218 OBlock oldBlock = _toBlock; 219 if (_toBlock != null) { 220 _toBlock.removePortal(this); // may should not 221 } 222 _toBlock = block; 223 if (_toBlock != null) { 224 _toBlock.addPortal(this); 225 } 226 pcs.firePropertyChange(PROPERTY_BLOCK_CHANGED, oldBlock, _toBlock); 227 return true; 228 } 229 230 public OBlock getToBlock() { 231 return _toBlock; 232 } 233 234 // @CheckForNull needs further dev 235 public String getToBlockName() { 236 return (_toBlock != null ? _toBlock.getDisplayName() : null); 237 } 238 239 public List<OPath> getToPaths() { 240 return _toPaths; 241 } 242 243 /** 244 * Set this portal's fromBlock. Remove this portal from old fromBlock, if any. 245 * Add this portal in the new toBlock's list of portals. 246 * 247 * @param block to be the new fromBlock 248 * @param changePaths if true, set block in paths. If false, 249 * verify that all toPaths are contained in the block. 250 * @return false if paths are not in the block 251 */ 252 public boolean setFromBlock(@CheckForNull OBlock block, boolean changePaths) { 253 if ((block != null && block.equals(_fromBlock)) || (block == null && _fromBlock == null)) { 254 return true; 255 } 256 if (changePaths) { 257 //Switch paths to new block. User will need to verify connections 258 for (OPath fromPath : _fromPaths) { 259 fromPath.setBlock(block); 260 } 261 } else if (!verify(_fromPaths, block)) { 262 return false; 263 } 264 log.debug("setFromBlock: oldBlock= \"{}\" newBlock \"{}\".", getFromBlockName(), 265 (block != null ? block.getDisplayName() : null)); 266 OBlock oldBlock = _fromBlock; 267 if (_fromBlock != null) { 268 _fromBlock.removePortal(this); 269 } 270 _fromBlock = block; 271 if (_fromBlock != null) { 272 _fromBlock.addPortal(this); 273 } 274 pcs.firePropertyChange(PROPERTY_BLOCK_CHANGED, oldBlock, _fromBlock); 275 return true; 276 } 277 278 public OBlock getFromBlock() { 279 return _fromBlock; 280 } 281 282 // @CheckForNull needs further dev 283 public String getFromBlockName() { 284 return (_fromBlock != null ? _fromBlock.getDisplayName() : null); 285 } 286 287 public List<OPath> getFromPaths() { 288 return _fromPaths; 289 } 290 291 /** 292 * Set a signal to protect an OBlock. Warrants look ahead for speed changes 293 * and change the train speed accordingly. 294 * 295 * @param signal either a SignalMast or a SignalHead. Set to null to remove (previous) signal from Portal 296 * @param length offset length in millimeters. This is additional 297 * entrance space for the block. This distance added to or subtracted 298 * from the calculation of the ramp distance when a warrant must slow 299 * the train in response to the aspect or appearance of the signal. 300 * @param protectedBlock OBlock the signal protects 301 * @return true if signal is set 302 */ 303 public boolean setProtectSignal(@CheckForNull NamedBean signal, float length, @CheckForNull OBlock protectedBlock) { 304 if (protectedBlock == null) { 305 return false; 306 } 307 boolean ret = false; 308 if ((_fromBlock != null) && _fromBlock.equals(protectedBlock)) { 309 _toSignal = signal; 310 _toSignalOffset = length; 311 log.debug("OPortal FromBlock Offset set to {} on signal {}", _toSignalOffset, 312 (_toSignal != null ? _toSignal.getDisplayName() : "<removed>")); 313 ret = true; 314 } 315 if ((_toBlock != null) && _toBlock.equals(protectedBlock)) { 316 _fromSignal = signal; 317 _fromSignalOffset = length; 318 log.debug("OPortal ToBlock Offset set to {} on signal {}", _fromSignalOffset, 319 (_fromSignal != null ? _fromSignal.getDisplayName() : "<removed>")); 320 ret = true; 321 } 322 if (ret) { 323 protectedBlock.pseudoPropertyChange(PROPERTY_SIGNAL_CHANGE, false, true); 324 pcs.firePropertyChange(PROPERTY_SIGNAL_CHANGE, false, true); 325 log.debug("setProtectSignal: \"{}\" for Block= {} at Portal {}", 326 (signal != null ? signal.getDisplayName() : "null"), protectedBlock.getDisplayName(), _name); 327 } 328 return ret; 329 } 330 331 /** 332 * Get the signal (either a SignalMast or a SignalHead) protecting an OBlock. 333 * 334 * @param block is the direction of entry, i.e. the protected block 335 * @return signal protecting block, if block is protected, otherwise null. 336 */ 337 @CheckForNull 338 public NamedBean getSignalProtectingBlock(@Nonnull OBlock block) { 339 if (block.equals(_toBlock)) { 340 return _fromSignal; 341 } else if (block.equals(_fromBlock)) { 342 return _toSignal; 343 } 344 return null; 345 } 346 347 /** 348 * Get the OBlock protected by a signal. 349 * 350 * @param signal is the signal, either a SignalMast or a SignalHead 351 * @return Protected OBlock, if it is protected, otherwise null. 352 */ 353 @CheckForNull 354 public OBlock getProtectedBlock(@CheckForNull NamedBean signal) { 355 if (signal == null) { 356 return null; 357 } 358 if (signal.equals(_fromSignal)) { 359 return _toBlock; 360 } else if (signal.equals(_toSignal)) { 361 return _fromBlock; 362 } 363 return null; 364 } 365 366 public NamedBean getFromSignal() { 367 return _fromSignal; 368 } 369 370 public String getFromSignalName() { 371 return (_fromSignal != null ? _fromSignal.getDisplayName() : null); 372 } 373 374 public float getFromSignalOffset() { 375 return _fromSignalOffset; // it seems clear that this method should return what is asks 376 } 377 378 public NamedBean getToSignal() { 379 return _toSignal; 380 } 381 382 @CheckForNull 383 public String getToSignalName() { 384 return (_toSignal != null ? _toSignal.getDisplayName() : null); 385 } 386 387 public float getToSignalOffset() { 388 return _toSignalOffset; 389 } 390 391 public void deleteSignal(@Nonnull NamedBean signal) { 392 if (signal.equals(_toSignal)) { 393 _toSignal = null; // set the 2 _tos 394 _toSignalOffset = 0; 395 if (_fromBlock != null) { 396 _fromBlock.pseudoPropertyChange(PROPERTY_SIGNAL_CHANGE, false, false); 397 pcs.firePropertyChange(PROPERTY_SIGNAL_CHANGE, false, false); 398 } 399 } else if (signal.equals(_fromSignal)) { 400 _fromSignal = null; // set the 2 _froms 401 _fromSignalOffset = 0; 402 if (_toBlock != null) { 403 _toBlock.pseudoPropertyChange(PROPERTY_SIGNAL_CHANGE, false, false); 404 pcs.firePropertyChange(PROPERTY_SIGNAL_CHANGE, false, false); 405 } 406 } 407 } 408 409 @CheckForNull 410 public static NamedBean getSignal(String name) { 411 NamedBean signal = InstanceManager.getDefault(jmri.SignalMastManager.class).getSignalMast(name); 412 if (signal == null) { 413 signal = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(name); 414 } 415 return signal; 416 } 417 418 /** 419 * Get the paths to the portal within the connected OBlock i.e. the paths in 420 * this (the param) block through the Portal. 421 * 422 * @param block OBlock 423 * @return null if portal does not connect to block 424 */ 425 // @CheckForNull requires further dev 426 public List<OPath> getPathsWithinBlock(@CheckForNull OBlock block) { 427 if (block == null) { 428 return null; 429 } 430 if (block.equals(_fromBlock)) { 431 return _fromPaths; 432 } else if (block.equals(_toBlock)) { 433 return _toPaths; 434 } 435 return null; 436 } 437 438 /** 439 * Get the OBlock on the other side of the Portal from the given 440 * OBlock. 441 * 442 * @param block starting OBlock 443 * @return the opposite block 444 */ 445 // @CheckForNull needs further dev 446 public OBlock getOpposingBlock(@Nonnull OBlock block) { 447 if (block.equals(_fromBlock)) { 448 return _toBlock; 449 } else if (block.equals(_toBlock)) { 450 return _fromBlock; 451 } 452 return null; 453 } 454 455 /** 456 * Get the paths from the portal in the next connected OBlock i.e. paths in 457 * the block on the other side of the portal from this (the param) block. 458 * 459 * @param block OBlock 460 * @return null if portal does not connect to block 461 */ 462 // @CheckForNull requires further dev 463 public List<OPath> getPathsFromOpposingBlock(@Nonnull OBlock block) { 464 if (block.equals(_fromBlock)) { 465 return _toPaths; 466 } else if (block.equals(_toBlock)) { 467 return _fromPaths; 468 } 469 return null; 470 } 471 472 /** 473 * Call is from BlockOrder when setting the path. 474 * 475 * @param block OBlock 476 */ 477 protected void setEntryState(@CheckForNull OBlock block) { 478 if (block == null) { 479 _state = UNKNOWN; 480 } else if (block.equals(_fromBlock)) { 481 setState(ENTER_FROM_BLOCK); 482 } else if (block.equals(_toBlock)) { 483 setState(ENTER_TO_BLOCK); 484 } 485 } 486 487 public void setState(int s) { 488 int old = _state; 489 _state = s; 490 pcs.firePropertyChange(PROPERTY_DIRECTION, old, _state); 491 } 492 493 public int getState() { 494 return _state; 495 } 496 497 @OverridingMethodsMustInvokeSuper 498 public synchronized void addPropertyChangeListener(PropertyChangeListener listener) { 499 pcs.addPropertyChangeListener(listener); 500 } 501 502 @OverridingMethodsMustInvokeSuper 503 public synchronized void removePropertyChangeListener(PropertyChangeListener listener) { 504 pcs.removePropertyChangeListener(listener); 505 } 506 507 /** 508 * Set the distance (plus or minus) in millimeters from the portal gap 509 * where the speed change indicated by the signal should be completed. 510 * 511 * @param block a protected OBlock 512 * @param distance length in millimeters, called Offset in the OBlock Signal Table 513 */ 514 public void setEntranceSpaceForBlock(@Nonnull OBlock block, float distance) { 515 if (block.equals(_toBlock)) { 516 if (_fromSignal != null) { 517 _fromSignalOffset = distance; 518 } 519 } else if (block.equals(_fromBlock)) { 520 if (_toSignal != null) { 521 _toSignalOffset = distance; 522 } 523 } 524 } 525 526 /** 527 * Get the distance (plus or minus) in millimeters from the portal gap 528 * where the speed change indicated by the signal should be completed. 529 * Property is called Offset in the OBlock Signal Table. 530 * 531 * @param block a protected OBlock 532 * @return distance 533 */ 534 public float getEntranceSpaceForBlock(@Nonnull OBlock block) { 535 if (block.equals(_toBlock)) { 536 if (_fromSignal != null) { 537 return _fromSignalOffset; 538 } 539 } else if (block.equals(_fromBlock)) { 540 if (_toSignal != null) { 541 return _toSignalOffset; 542 } 543 } 544 return 0; 545 } 546 547 /** 548 * Check signals, if any, for speed into/out of a given block. The signal that protects 549 * the "to" block is the signal facing the "from" Block, i.e. the "from" 550 * signal. (and vice-versa) 551 * 552 * @param block is the direction of entry, "from" block 553 * @param entrance true for EntranceSpeed, false for ExitSpeed 554 * @return permissible speed, null if no signal 555 */ 556 public String getPermissibleSpeed(@Nonnull OBlock block, boolean entrance) { 557 String speed = null; 558 String blockName = block.getDisplayName(); 559 if (block.equals(_toBlock)) { 560 if (_fromSignal != null) { 561 if (_fromSignal instanceof SignalHead) { 562 speed = getPermissibleSignalSpeed((SignalHead) _fromSignal, entrance); 563 } else { 564 speed = getPermissibleSignalSpeed((SignalMast) _fromSignal, entrance); 565 } 566 } 567 } else if (block.equals(_fromBlock)) { 568 if (_toSignal != null) { 569 if (_toSignal instanceof SignalHead) { 570 speed = getPermissibleSignalSpeed((SignalHead) _toSignal, entrance); 571 } else { 572 speed = getPermissibleSignalSpeed((SignalMast) _toSignal, entrance); 573 } 574 } 575 } else { 576 log.error("Block \"{}\" is not in Portal \"{}\".", blockName, _name); 577 } 578 if ( log.isDebugEnabled() && speed != null ) { 579 log.debug("Portal \"{}\" has {} speed= {} into \"{}\" from signal.", 580 _name, (entrance ? "ENTRANCE" : "EXIT"), speed, blockName); 581 } 582 // no signals, proceed at recorded speed 583 return speed; 584 } 585 586 /** 587 * Get entrance or exit speed set on signal head. 588 * 589 * @param signal signal head to query 590 * @param entrance true for EntranceSpeed, false for ExitSpeed 591 * @return permissible speed, Restricted if no speed set on signal 592 */ 593 private static @Nonnull String getPermissibleSignalSpeed(@Nonnull SignalHead signal, boolean entrance) { 594 int appearance = signal.getAppearance(); 595 String speed = InstanceManager.getDefault(SignalSpeedMap.class). 596 getAppearanceSpeed(signal.getAppearanceName(appearance)); 597 // on head, speed is the same for entry and exit 598 if (speed == null) { 599 log.error("SignalHead \"{}\" has no {} speed specified for appearance \"{}\"! - Restricting Movement!", 600 signal.getDisplayName(), (entrance ? ENTRANCE : "exit"), signal.getAppearanceName(appearance)); 601 speed = "Restricted"; 602 } 603 log.debug("SignalHead \"{}\" has {} speed notch= {} from appearance \"{}\"", 604 signal.getDisplayName(), (entrance ? ENTRANCE : "exit"), speed, signal.getAppearanceName(appearance)); 605 return speed; 606 } 607 608 /** 609 * Get entrance or exit speed set on signal mast. 610 * 611 * @param signal signal mast to query 612 * @param entrance true for EntranceSpeed, false for ExitSpeed 613 * @return permissible speed, Restricted if no speed set on signal 614 */ 615 private static @Nonnull String getPermissibleSignalSpeed(@Nonnull SignalMast signal, boolean entrance) { 616 String aspect = signal.getAspect(); 617 String signalAspect = ( aspect == null ? "" : aspect ); 618 String speed; 619 if (entrance) { 620 speed = InstanceManager.getDefault(SignalSpeedMap.class). 621 getAspectSpeed(signalAspect, signal.getSignalSystem()); 622 } else { 623 speed = InstanceManager.getDefault(SignalSpeedMap.class). 624 getAspectExitSpeed(signalAspect, signal.getSignalSystem()); 625 } 626 if (speed == null) { 627 log.error("SignalMast \"{}\" has no {} speed specified for aspect \"{}\"! - Restricting Movement!", 628 signal.getDisplayName(), (entrance ? ENTRANCE : "exit"), aspect); 629 speed = "Restricted"; 630 } 631 log.debug("SignalMast \"{}\" has {} speed notch= {} from aspect \"{}\"", 632 signal.getDisplayName(), (entrance ? ENTRANCE : "exit"), speed, aspect); 633 return speed; 634 } 635 636 /** 637 * Verify that each path has this potential block as its owning block. 638 * Block is a potential _toBlock and Paths are the current _toPaths 639 * or 640 * Block is a potential _fromBlock and Paths are the current _fromPaths 641 */ 642 private static boolean verify(@Nonnull List<OPath> paths, @CheckForNull OBlock block) { 643 if (block == null) { 644 return (paths.isEmpty()); 645 } 646 String name = block.getSystemName(); 647 for (OPath path : paths) { 648 Block blk = path.getBlock(); 649 if (blk == null) { 650 log.error("Path \"{}\" belongs to null block. Cannot verify set block to \"{}\"", 651 path.getName(), name); 652 return false; 653 } 654 String pathName = blk.getSystemName(); 655 if (!pathName.equals(name)) { 656 log.warn("Path \"{}\" belongs to block \"{}\". Cannot verify set block to \"{}\"", 657 path.getName(), pathName, name); 658 return false; 659 } 660 } 661 return true; 662 } 663 664 /** 665 * Check if path connects to Portal. 666 * 667 * @param path OPath to test 668 * @return true if valid 669 */ 670 public boolean isValidPath(@Nonnull OPath path) { 671 String name = path.getName(); 672 for (OPath toPath : _toPaths) { 673 if (toPath.getName().equals(name)) { 674 return true; 675 } 676 } 677 for (OPath fromPath : _fromPaths) { 678 if (fromPath.getName().equals(name)) { 679 return true; 680 } 681 } 682 return false; 683 } 684 685 /** 686 * Check portal has both blocks and they are different blocks. 687 * 688 * @return true if valid 689 */ 690 public boolean isValid() { 691 if (_toBlock == null || _fromBlock==null) { 692 return false; 693 } 694 return (!_toBlock.equals(_fromBlock)); 695 } 696 697 @OverridingMethodsMustInvokeSuper 698 public boolean dispose() { 699 if (!InstanceManager.getDefault(WarrantManager.class).okToRemovePortal(this)) { 700 return false; 701 } 702 if (_toBlock != null) { 703 _toBlock.removePortal(this); 704 } 705 if (_fromBlock != null) { 706 _fromBlock.removePortal(this); 707 } 708 pcs.firePropertyChange(PROPERTY_PORTAL_DELETE, true, false); 709 PropertyChangeListener[] listeners = pcs.getPropertyChangeListeners(); 710 for (PropertyChangeListener l : listeners) { 711 pcs.removePropertyChangeListener(l); 712 } 713 return true; 714 } 715 716 public String getDescription() { 717 return Bundle.getMessage("PortalDescription", 718 _name, getFromBlockName(), getToBlockName()); 719 } 720 721 @Override 722 @Nonnull 723 public String toString() { 724 StringBuilder sb = new StringBuilder("Portal \""); 725 sb.append(_name); 726 sb.append("\" from block \""); 727 sb.append(getFromBlockName()); 728 sb.append("\" to block \""); 729 sb.append(getToBlockName()); 730 sb.append("\""); 731 return sb.toString(); 732 } 733 734 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Portal.class); 735 736}