001package jmri.jmrit.logix; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004import java.awt.Color; 005import java.awt.Font; 006import java.beans.PropertyChangeListener; 007import java.util.*; 008import java.util.stream.Collectors; 009 010import javax.annotation.CheckForNull; 011import javax.annotation.Nonnull; 012 013import jmri.InstanceManager; 014import jmri.NamedBean; 015import jmri.NamedBeanHandle; 016import jmri.NamedBeanUsageReport; 017import jmri.Path; 018import jmri.Sensor; 019import jmri.Turnout; 020import jmri.util.ThreadingUtil; 021import org.slf4j.Logger; 022import org.slf4j.LoggerFactory; 023 024/** 025 * OBlock extends jmri.Block to be used in Logix Conditionals and Warrants. It 026 * is the smallest piece of track that can have occupancy detection. A better 027 * name would be Detection Circuit. However, an OBlock can be defined without an 028 * occupancy sensor and used to calculate routes. 029 * <p> 030 * Additional states are defined to indicate status of the track and trains to 031 * control panels. A jmri.Block has a PropertyChangeListener on the occupancy 032 * sensor and the OBlock will pass state changes of the occ.sensor on to its 033 * Warrant. 034 * <p> 035 * Entrances (exits when train moves in opposite direction) to OBlocks have 036 * Portals. A Portal object is a pair of OBlocks. Each OBlock has a list of its 037 * Portals. 038 * <p> 039 * When an OBlock (Detection Circuit) has a Portal whose entrance to the OBlock 040 * has a signal, then the OBlock and its chains of adjacent OBlocks up to the 041 * next OBlock having an entrance Portal with a signal, can be considered a 042 * "Block" in the sense of a prototypical railroad. Preferably all entrances to 043 * the "Block" should have entrance Portals with a signal. 044 * <p> 045 * A Portal has a list of paths (OPath objects) for each OBlock it separates. 046 * The paths are determined by the turnout settings of the turnouts contained in 047 * the block. Paths are contained within the Block boundaries. Names of OPath 048 * objects only need be unique within an OBlock. 049 * 050 * @author Pete Cressman (C) 2009 051 * @author Egbert Broerse (C) 2020 052 */ 053public class OBlock extends jmri.Block implements java.beans.PropertyChangeListener { 054 055 public enum OBlockStatus { 056 Unoccupied(UNOCCUPIED, "unoccupied", Bundle.getMessage("unoccupied")), 057 Occupied(OCCUPIED, "occupied", Bundle.getMessage("occupied")), 058 Allocated(ALLOCATED, "allocated", Bundle.getMessage("allocated")), 059 Running(RUNNING, "running", Bundle.getMessage("running")), 060 OutOfService(OUT_OF_SERVICE, "outOfService", Bundle.getMessage("outOfService")), 061 Dark(UNDETECTED, "dark", Bundle.getMessage("dark")), 062 TrackError(TRACK_ERROR, "powerError", Bundle.getMessage("powerError")); 063 064 private final int status; 065 private final String name; 066 private final String descr; 067 068 private static final Map<String, OBlockStatus> map = new HashMap<>(); 069 private static final Map<String, OBlockStatus> reverseMap = new HashMap<>(); 070 071 private OBlockStatus(int status, String name, String descr) { 072 this.status = status; 073 this.name = name; 074 this.descr = descr; 075 } 076 077 public int getStatus() { return status; } 078 079 public String getName() { return name; } 080 081 public String getDescr() { return descr; } 082 083 public static OBlockStatus getByName(String name) { return map.get(name); } 084 public static OBlockStatus getByDescr(String descr) { return reverseMap.get(descr); } 085 086 static { 087 for (OBlockStatus oblockStatus : OBlockStatus.values()) { 088 map.put(oblockStatus.getName(), oblockStatus); 089 reverseMap.put(oblockStatus.getDescr(), oblockStatus); 090 } 091 } 092 } 093 094 /* 095 * OBlock states: 096 * NamedBean.UNKNOWN = 0x01 097 * Block.OCCUPIED = Sensor.ACTIVE = 0x02 098 * Block.UNOCCUPIED = Sensor.INACTIVE= 0x04 099 * NamedBean.INCONSISTENT = 0x08 100 * Add the following to the 4 sensor states. 101 * States are OR'ed to show combination. e.g. ALLOCATED | OCCUPIED = allocated block is occupied 102 */ 103 public static final int ALLOCATED = 0x10; // reserve the block for subsequent use by a train 104 public static final int RUNNING = 0x20; // OBlock that running train has reached 105 public static final int OUT_OF_SERVICE = 0x40; // OBlock that should not be used 106 public static final int TRACK_ERROR = 0x80; // OBlock has Error 107 // UNDETECTED state bit is used for DARK blocks 108 // static final public int DARK = 0x01; // meaning: OBlock has no Sensor, same as UNKNOWN 109 110 private static final Color DEFAULT_FILL_COLOR = new Color(200, 0, 200); 111 112 public static String getLocalStatusName(String str) { 113 return OBlockStatus.getByName(str).getDescr(); 114 } 115 116 public static String getSystemStatusName(String str) { 117 return OBlockStatus.getByDescr(str).getName(); 118 } 119 private List<Portal> _portals = new ArrayList<>(); // portals to this block 120 121 private Warrant _warrant; // when not null, oblock is allocated to this warrant 122 private String _pathName; // when not null, this is the allocated path or last path used by a warrant 123 protected long _entryTime; // time when block became occupied 124 private boolean _metric = false; // desired display mode 125 private NamedBeanHandle<Sensor> _errNamedSensor; 126 private Color _markerForeground = Color.WHITE; 127 private Color _markerBackground = DEFAULT_FILL_COLOR; 128 private Font _markerFont; 129 130 public OBlock(@Nonnull String systemName) { 131 super(systemName); 132 OBlock.this.setState(UNDETECTED); 133 } 134 135 public OBlock(@Nonnull String systemName, String userName) { 136 super(systemName, userName); 137 OBlock.this.setState(UNDETECTED); 138 } 139 140 /* What super does currently is fine. 141 * FindBug wants us to duplicate and override anyway 142 */ 143 @Override 144 public boolean equals(Object obj) { 145 if (obj == this) { 146 return true; 147 } 148 if (obj == null) { 149 return false; 150 } 151 152 if (!getClass().equals(obj.getClass())) { 153 return false; 154 } else { 155 OBlock b = (OBlock) obj; 156 return b.getSystemName().equals(this.getSystemName()); 157 } 158 } 159 160 @Override 161 public int hashCode() { 162 return this.getSystemName().hashCode(); 163 } 164 165 /** 166 * {@inheritDoc} 167 * <p> 168 * Override to only set an existing sensor and to amend state with not 169 * UNDETECTED return true if an existing Sensor is set or sensor is to be 170 * removed from block. 171 */ 172 @Override 173 public boolean setSensor(String pName) { 174 Sensor oldSensor = getSensor(); 175 Sensor newSensor = null; 176 if (pName != null && pName.trim().length() > 0) { 177 newSensor = InstanceManager.sensorManagerInstance().getByUserName(pName); 178 if (newSensor == null) { 179 newSensor = InstanceManager.sensorManagerInstance().getBySystemName(pName); 180 } 181 if (newSensor == null) { 182 log.error("No sensor named '{}' exists.", pName); 183 return false; 184 } 185 } 186 if (oldSensor != null && oldSensor.equals(newSensor)) { 187 return true; 188 } 189 190 // save the non-sensor states 191 int saveState = getState() & ~(UNKNOWN | OCCUPIED | UNOCCUPIED | INCONSISTENT | UNDETECTED); 192 if (newSensor == null || pName == null) { 193 setNamedSensor(null); 194 } else { 195 setNamedSensor(InstanceManager.getDefault(jmri.NamedBeanHandleManager.class). 196 getNamedBeanHandle(pName, newSensor)); 197 } 198 setState(getState() | saveState); // add them back into new sensor 199 firePropertyChange("OccupancySensorChange", oldSensor, newSensor); 200 return true; 201 } 202 203 // override to determine if not UNDETECTED 204 @Override 205 public void setNamedSensor(@CheckForNull NamedBeanHandle<Sensor> namedSensor) { 206 super.setNamedSensor(namedSensor); 207 Sensor s = getSensor(); 208 if ( s != null) { 209 setState( s.getState() & ~UNDETECTED); 210 } 211 } 212 213 /** 214 * @param pName name of error sensor 215 * @return true if successful 216 */ 217 public boolean setErrorSensor(String pName) { 218 NamedBeanHandle<Sensor> newErrSensorHdl = null; 219 Sensor newErrSensor = null; 220 if (pName != null && pName.trim().length() > 0) { 221 newErrSensor = InstanceManager.sensorManagerInstance().getByUserName(pName); 222 if (newErrSensor == null) { 223 newErrSensor = InstanceManager.sensorManagerInstance().getBySystemName(pName); 224 } 225 if (newErrSensor != null) { 226 newErrSensorHdl = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class). 227 getNamedBeanHandle(pName, newErrSensor); 228 } 229 if (newErrSensor == null) { 230 log.error("No sensor named '{}' exists.", pName); 231 return false; 232 } 233 } 234 if (_errNamedSensor != null) { 235 if (_errNamedSensor.equals(newErrSensorHdl)) { 236 return true; 237 } else { 238 getErrorSensor().removePropertyChangeListener(this); 239 } 240 } 241 242 _errNamedSensor = newErrSensorHdl; 243 setState(getState() & ~TRACK_ERROR); 244 if (newErrSensor != null) { 245 newErrSensor.addPropertyChangeListener( this, 246 _errNamedSensor.getName(), "OBlock Error Sensor " + getDisplayName()); 247 if (newErrSensor.getState() == Sensor.ACTIVE) { 248 setState(getState() | TRACK_ERROR); 249 } else { 250 setState(getState() & ~TRACK_ERROR); 251 } 252 } 253 return true; 254 } 255 256 public Sensor getErrorSensor() { 257 if (_errNamedSensor == null) { 258 return null; 259 } 260 return _errNamedSensor.getBean(); 261 } 262 263 public NamedBeanHandle<Sensor> getNamedErrorSensor() { 264 return _errNamedSensor; 265 } 266 267 @Override 268 public void propertyChange(java.beans.PropertyChangeEvent evt) { 269 if (log.isDebugEnabled()) { 270 log.debug("property change: of \"{}\" property {} is now {} from {}", 271 getDisplayName(), evt.getPropertyName(), evt.getNewValue(), evt.getSource().getClass().getName()); 272 } 273 if ((getErrorSensor() != null) && (evt.getSource().equals(getErrorSensor())) 274 && evt.getPropertyName().equals("KnownState")) { 275 int errState = ((Integer) evt.getNewValue()); 276 int oldState = getState(); 277 if (errState == Sensor.ACTIVE) { 278 setState(oldState | TRACK_ERROR); 279 } else { 280 setState(oldState & ~TRACK_ERROR); 281 } 282 firePropertyChange("pathState", oldState, getState()); 283 } 284 } 285 286 /** 287 * Another block sharing a turnout with this block queries whether turnout 288 * is in use. 289 * 290 * @param path that uses a common shared turnout 291 * @return If warrant exists and path==pathname, return warrant display 292 * name, else null. 293 */ 294 protected String isPathSet(@Nonnull String path) { 295 String msg = null; 296 if (_warrant != null && path.equals(_pathName)) { 297 msg = _warrant.getDisplayName(); 298 } 299 log.trace("Path \"{}\" in oblock \"{}\" {}", path, getDisplayName(), 300 (msg == null ? "not set" : " set in warrant " + msg)); 301 return msg; 302 } 303 304 public Warrant getWarrant() { 305 return _warrant; 306 } 307 308 public boolean isAllocatedTo(Warrant warrant) { 309 if (warrant == null) { 310 return false; 311 } 312 return warrant.equals(_warrant); 313 } 314 315 public String getAllocatedPathName() { 316 return _pathName; 317 } 318 319 public void setMetricUnits(boolean type) { 320 _metric = type; 321 } 322 323 public boolean isMetric() { 324 return _metric; 325 } 326 327 public void setMarkerForeground(Color c) { 328 _markerForeground = c; 329 } 330 331 public Color getMarkerForeground() { 332 return _markerForeground; 333 } 334 335 public void setMarkerBackground(Color c) { 336 _markerBackground = c; 337 } 338 339 public Color getMarkerBackground() { 340 return _markerBackground; 341 } 342 343 public void setMarkerFont(Font f) { 344 _markerFont = f; 345 } 346 347 public Font getMarkerFont() { 348 return _markerFont; 349 } 350 351 /** 352 * Update the OBlock status. 353 * Override Block because change must come from an OBlock for Web Server to receive it 354 * 355 * @param v the new state, from OBlock.ALLOCATED etc, named 'status' in JSON Servlet and Web Server 356 */ 357 @Override 358 public void setState(int v) { 359 int old = getState(); 360 super.setState(v); 361 // override Block to get proper source to be recognized by listener in Web Server 362 log.debug("\"{}\" setState({})", getDisplayName(), getState()); 363 firePropertyChange("state", old, getState()); // used by CPE indicator track icons 364 } 365 366 /** 367 * {@inheritDoc} 368 */ 369 @Override 370 public void setValue(Object o) { 371 super.setValue(o); 372 if (o == null) { 373 _markerForeground = Color.WHITE; 374 _markerBackground = DEFAULT_FILL_COLOR; 375 _markerFont = null; 376 } 377 } 378 379 /*_ 380 * From the universal name for block status, check if it is the current status 381 */ 382 public boolean statusIs(String statusName) { 383 OBlockStatus oblockStatus = OBlockStatus.getByName(statusName); 384 if (oblockStatus != null) { 385 return ((getState() & oblockStatus.getStatus()) != 0); 386 } 387 log.error("\"{}\" type not found. Update Conditional State Variable testing OBlock \"{}\" status", 388 getDisplayName(), statusName); 389 return false; 390 } 391 392 public boolean isDark() { 393 return (getState() & OBlock.UNDETECTED) != 0; 394 } 395 396 public boolean isOccupied() { 397 return (getState() & OBlock.OCCUPIED) != 0; 398 } 399 400 public String occupiedBy() { 401 Warrant w = _warrant; 402 if (isOccupied()) { 403 if (w != null) { 404 return w.getTrainName(); 405 } else { 406 return Bundle.getMessage("unknownTrain"); 407 } 408 } else { 409 return null; 410 } 411 } 412 413 /** 414 * Test that block is not occupied and not allocated 415 * 416 * @return true if not occupied and not allocated 417 */ 418 public boolean isFree() { 419 int state = getState(); 420 return ((state & ALLOCATED) == 0 && (state & OCCUPIED) == 0); 421 } 422 423 /** 424 * Allocate (reserves) the block for the Warrant Note the block may be 425 * OCCUPIED by a non-warranted train, but the allocation is permitted. 426 * 427 * @param warrant the Warrant 428 * @return message with if block is already allocated to another warrant or 429 * block is OUT_OF_SERVICE 430 */ 431 @CheckForNull 432 public String allocate(Warrant warrant) { 433 if (warrant == null) { 434 log.error("allocate(warrant) called with null warrant in block \"{}\"!", getDisplayName()); 435 return "ERROR! allocate called with null warrant in block \"" + getDisplayName() + "\"!"; 436 } 437 if (_warrant != null) { 438 if (!warrant.equals(_warrant)) { 439 return Bundle.getMessage("AllocatedToWarrant", 440 _warrant.getDisplayName(), getDisplayName(), _warrant.getTrainName()); 441 } else { 442 return null; 443 } 444 } 445 /* 446 int state = getState(); 447 if ((state & OUT_OF_SERVICE) != 0) { 448 return Bundle.getMessage("BlockOutOfService", getDisplayName()); 449 }*/ 450 451 _warrant = warrant; 452 if (log.isDebugEnabled()) { 453 log.debug("Allocate OBlock \"{}\" to warrant \"{}\".", 454 getDisplayName(), warrant.getDisplayName()); 455 } 456 int old = getState(); 457 int newState = old | ALLOCATED; 458 super.setState(newState); 459 firePropertyChange("state", old, newState); 460 return null; 461 } 462 463 // Highlights track icons to show that block is allocated. 464 protected void showAllocated(Warrant warrant, String pathName) { 465 if (_warrant != null && !_warrant.equals(warrant)) { 466 return; 467 } 468 if (_pathName == null) { 469 _pathName = pathName; 470 } 471 firePropertyChange("pathState", 0, getState()); 472// super.setState(getState()); 473 } 474 475 /** 476 * Note path name may be set if block is not allocated to a warrant. For use 477 * by CircuitBuilder Only. (test paths for editCircuitPaths) 478 * 479 * @param pathName name of a path 480 * @return error message, otherwise null 481 */ 482 @CheckForNull 483 public String allocatePath(String pathName) { 484 log.debug("Allocate OBlock path \"{}\" in block \"{}\", state= {}", 485 pathName, getSystemName(), getState()); 486 if (pathName == null) { 487 log.error("allocate called with null pathName in block \"{}\"!", getDisplayName()); 488 return null; 489 } else if (_warrant != null) { 490 // allocated to another warrant 491 return Bundle.getMessage("AllocatedToWarrant", 492 _warrant.getDisplayName(), getDisplayName(), _warrant.getTrainName()); 493 } 494 _pathName = pathName; 495 // DO NOT ALLOCATE block 496 return null; 497 } 498 499 public String getAllocatingWarrantName() { 500 if (_warrant == null) { 501 return ("no warrant"); 502 } else { 503 return _warrant.getDisplayName(); 504 } 505 } 506 507 /** 508 * Remove allocation state // maybe restore this? Remove listener regardless of ownership 509 * 510 * @param warrant warrant that has reserved this block. null is allowed for 511 * Conditionals and CircuitBuilder to reset the block. 512 * Otherwise, null should not be used. 513 * @return true if warrant deallocated. 514 */ 515 public boolean deAllocate(Warrant warrant) { 516 if (warrant == null) { 517 return true; 518 } 519 if (_warrant != null) { 520 if (!_warrant.equals(warrant)) { 521 log.warn("{} cannot deallocate. {}", warrant.getDisplayName(), Bundle.getMessage("AllocatedToWarrant", 522 _warrant.getDisplayName(), getDisplayName(), _warrant.getTrainName())); 523 return false; 524 } 525 Warrant curWarrant = _warrant; 526 _warrant = null; // At times, removePropertyChangeListener may be run on a delayed thread. 527 try { 528 if (log.isDebugEnabled()) { 529 log.debug("deAllocate block \"{}\" from warrant \"{}\"", 530 getDisplayName(), warrant.getDisplayName()); 531 } 532 removePropertyChangeListener(curWarrant); 533 } catch (Exception ex) { 534 // disposed warrant may throw null pointer - continue deallocation 535 log.trace("Warrant {} unregistered.", curWarrant.getDisplayName(), ex); 536 } 537 } 538 _warrant = null; 539 if (_pathName != null) { 540 OPath path = getPathByName(_pathName); 541 if (path != null) { 542 int lockState = Turnout.CABLOCKOUT & Turnout.PUSHBUTTONLOCKOUT; 543 path.setTurnouts(0, false, lockState, false); 544 Portal portal = path.getFromPortal(); 545 if (portal != null) { 546 portal.setState(Portal.UNKNOWN); 547 } 548 portal = path.getToPortal(); 549 if (portal != null) { 550 portal.setState(Portal.UNKNOWN); 551 } 552 } 553 } 554 int old = getState(); 555 super.setState(old & ~(ALLOCATED | RUNNING)); // unset allocated and running bits 556 firePropertyChange("state", old, getState()); 557 return true; 558 } 559 560 public void setOutOfService(boolean set) { 561 if (set) { 562 setState(getState() | OUT_OF_SERVICE); // set OoS bit 563 } else { 564 setState(getState() & ~OUT_OF_SERVICE); // unset OoS bit 565 } 566 } 567 568 public void setError(boolean set) { 569 if (set) { 570 setState(getState() | TRACK_ERROR); // set err bit 571 } else { 572 setState(getState() & ~TRACK_ERROR); // unset err bit 573 } 574 } 575 576 /** 577 * Enforce unique portal names. Portals are now managed beans since 2014. 578 * This enforces unique names. 579 * 580 * @param portal the Portal to add 581 */ 582 public void addPortal(Portal portal) { 583 String name = getDisplayName(); 584 if (!name.equals(portal.getFromBlockName()) && !name.equals(portal.getToBlockName())) { 585 log.warn("{} not in block {}", portal.getDescription(), getDisplayName()); 586 return; 587 } 588 String pName = portal.getName(); 589 if (pName != null) { // pName may be null if called from Portal ctor 590 for (Portal value : _portals) { 591 if (pName.equals(value.getName())) { 592 return; 593 } 594 } 595 } 596 int oldSize = _portals.size(); 597 _portals.add(portal); 598 log.trace("add portal \"{}\" to Block \"{}\"", portal.getName(), getDisplayName()); 599 firePropertyChange("portalCount", oldSize, _portals.size()); 600 } 601 602 /** 603 * Remove portal from OBlock and stub all paths using this portal to be dead 604 * end spurs. 605 * 606 * @param portal the Portal to remove 607 */ 608 @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST_OF_RETURN_VALUE", justification = "OPath extends Path") 609 protected void removePortal(@CheckForNull Portal portal) { 610 if (portal != null) { 611 Iterator<Path> iter = getPaths().iterator(); 612 while (iter.hasNext()) { 613 OPath path = (OPath) iter.next(); 614 if (portal.equals(path.getFromPortal())) { 615 path.setFromPortal(null); 616 log.trace("removed Portal {} from Path \"{}\" in oblock {}", 617 portal.getName(), path.getName(), getDisplayName()); 618 } 619 if (portal.equals(path.getToPortal())) { 620 path.setToPortal(null); 621 log.trace("removed Portal {} from Path \"{}\" in oblock {}", 622 portal.getName(), path.getName(), getDisplayName()); 623 } 624 } 625 iter = getPaths().iterator(); 626 while (iter.hasNext()) { 627 OPath path = (OPath) iter.next(); 628 if (path.getFromPortal() == null && path.getToPortal() == null) { 629 removeOPath(path); 630 log.trace("removed Path \"{}\" from oblock {}", path.getName(), getDisplayName()); 631 } 632 } 633 int oldSize = _portals.size(); 634 _portals = _portals.stream().filter(p -> !Objects.equals(p,portal)).collect(Collectors.toList()); 635 firePropertyChange("portalCount", oldSize, _portals.size()); 636 } 637 } 638 639 public Portal getPortalByName(String name) { 640 for (Portal po : _portals) { 641 if (po.getName().equals(name)) { 642 return po; 643 } 644 } 645 return null; 646 } 647 648 @Nonnull 649 public List<Portal> getPortals() { 650 return new ArrayList<>(_portals); 651 } 652 653 public void setPortals(ArrayList<Portal> portals) { 654 _portals = portals; 655 } 656 657 @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST_OF_RETURN_VALUE", justification = "OPath extends Path") 658 public OPath getPathByName(String name) { 659 for (Path opa : getPaths()) { 660 OPath path = (OPath) opa; 661 if (path.getName().equals(name)) { 662 return path; 663 } 664 } 665 return null; 666 } 667 668 @Override 669 public void setLength(float len) { 670 // Only shorten paths longer than 'len' 671 getPaths().stream().forEach(p -> { 672 if (p.getLength() > len) { 673 p.setLength(len); // set to default 674 } 675 }); 676 super.setLength(len); 677 } 678 679 /** 680 * Enforce unique path names within OBlock, but allow a duplicate name of an 681 * OPath from another OBlock to be checked if it is in one of the OBlock's 682 * Portals. 683 * 684 * @param path the OPath to add 685 * @return true if path was added to OBlock 686 */ 687 @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST_OF_RETURN_VALUE", justification = "OPath extends Path") 688 public boolean addPath(OPath path) { 689 String pName = path.getName(); 690 log.trace("addPath \"{}\" to OBlock {}", pName, getSystemName()); 691 List<Path> list = getPaths(); 692 for (Path p : list) { 693 if (((OPath) p).equals(path)) { 694 log.trace("Path \"{}\" duplicated in OBlock {}", pName, getSystemName()); 695 return false; 696 } 697 if (pName.equals(((OPath) p).getName())) { 698 log.trace("Path named \"{}\" already exists in OBlock {}", pName, getSystemName()); 699 return false; 700 } 701 } 702 OBlock pathBlock = (OBlock) path.getBlock(); 703 if (pathBlock != null && !this.equals(pathBlock)) { 704 log.warn("Path \"{}\" already in block {}, cannot be added to block {}", 705 pName, pathBlock.getDisplayName(), getDisplayName()); 706 return false; 707 } 708 path.setBlock(this); 709 Portal portal = path.getFromPortal(); 710 if (portal != null) { 711 if (!portal.addPath(path)) { 712 log.trace("Path \"{}\" rejected by portal {}", pName, portal.getName()); 713 return false; 714 } 715 } 716 portal = path.getToPortal(); 717 if (portal != null) { 718 if (!portal.addPath(path)) { 719 log.debug("Path \"{}\" rejected by portal {}", pName, portal.getName()); 720 return false; 721 } 722 } 723 super.addPath(path); 724 firePropertyChange("pathCount", null, getPaths().size()); 725 return true; 726 } 727 728 public boolean removeOPath(OPath path) { 729 jmri.Block block = path.getBlock(); 730 if (block != null && !getSystemName().equals(block.getSystemName())) { 731 return false; 732 } 733 if (!InstanceManager.getDefault(jmri.jmrit.logix.WarrantManager.class).okToRemoveBlockPath(this, path)) { 734 return false; 735 } 736 path.clearSettings(); 737 super.removePath(path); 738 // remove path from its portals 739 Portal portal = path.getToPortal(); 740 if (portal != null) { 741 portal.removePath(path); 742 } 743 portal = path.getFromPortal(); 744 if (portal != null) { 745 portal.removePath(path); 746 } 747 path.dispose(); 748 firePropertyChange("pathCount", path, getPaths().size()); 749 return true; 750 } 751 752 /** 753 * Set Turnouts for the path. 754 * <p> 755 * Called by warrants to set turnouts for a train it is able to run. 756 * The warrant parameter verifies that the block is 757 * indeed allocated to the warrant. If the block is unwarranted then the 758 * block is allocated to the calling warrant. A logix conditional may also 759 * call this method with a null warrant parameter for manual logix control. 760 * If the block is under a different warrant the call will be rejected. 761 * 762 * @param pathName name of the path 763 * @param warrant warrant the block is allocated to 764 * @return error message if the call fails. null if the call succeeds 765 */ 766 protected String setPath(String pathName, Warrant warrant) { 767 OPath path = getPathByName(pathName); 768 if (path == null) { 769 return Bundle.getMessage("PathNotFound", pathName, getDisplayName()); 770 } 771 if (warrant == null || !warrant.equals(_warrant)) { 772 String name; 773 if (_warrant != null) { 774 name = _warrant.getDisplayName(); 775 } else { 776 name = Bundle.getMessage("Warrant"); 777 } 778 return Bundle.getMessage("PathNotSet", pathName, getDisplayName(), name); 779 } 780 _pathName = pathName; 781 int lockState = Turnout.CABLOCKOUT & Turnout.PUSHBUTTONLOCKOUT; 782 path.setTurnouts(0, true, lockState, true); 783 firePropertyChange("pathState", 0, getState()); 784 if (log.isTraceEnabled()) { 785 log.debug("setPath: Path \"{}\" in path \"{}\" {} set for warrant {}", 786 pathName, getDisplayName(), _pathName, warrant.getDisplayName()); 787 } 788 return null; 789 } 790 791 protected OPath getPath() { 792 if (_pathName == null) { 793 return null; 794 } 795 return getPathByName(_pathName); 796 } 797 798 /* 799 * Call for Circuit Builder to make icon color changes for its GUI 800 */ 801 public void pseudoPropertyChange(String propName, Object old, Object n) { 802 log.trace("pseudoPropertyChange: Block \"{}\" property \"{}\" new value= {}", 803 getSystemName(), propName, n); 804 firePropertyChange(propName, old, n); 805 } 806 807 /** 808 * (Override) Handles Block sensor going INACTIVE: this block is empty. 809 * Called by handleSensorChange 810 */ 811 @Override 812 public void goingInactive() { 813 //log.debug("OBlock \"{}\" going UNOCCUPIED from state= {}", getDisplayName(), getState()); 814 // preserve the non-sensor states 815 // non-UNOCCUPIED sensor states are removed (also cannot be RUNNING there if being UNOCCUPIED) 816 setState((getState() & ~(UNKNOWN | OCCUPIED | INCONSISTENT | RUNNING)) | UNOCCUPIED); 817 setValue(null); 818 if (_warrant != null) { 819 ThreadingUtil.runOnLayout(() -> _warrant.goingInactive(this)); 820 } 821 } 822 823 /** 824 * (Override) Handles Block sensor going ACTIVE: this block is now occupied, 825 * figure out from who and copy their value. Called by handleSensorChange 826 */ 827 @Override 828 public void goingActive() { 829 // preserve the non-sensor states when being OCCUPIED and remove non-OCCUPIED sensor states 830 setState((getState() & ~(UNKNOWN | UNOCCUPIED | INCONSISTENT)) | OCCUPIED); 831 _entryTime = System.currentTimeMillis(); 832 if (_warrant != null) { 833 ThreadingUtil.runOnLayout(() -> _warrant.goingActive(this)); 834 } 835 } 836 837 @Override 838 public void goingUnknown() { 839 setState((getState() & ~(UNOCCUPIED | OCCUPIED | INCONSISTENT)) | UNKNOWN); 840 } 841 842 @Override 843 public void goingInconsistent() { 844 setState((getState() & ~(UNKNOWN | UNOCCUPIED | OCCUPIED)) | INCONSISTENT); 845 } 846 847 @Override 848 @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST_OF_RETURN_VALUE", justification = "OPath extends Path") 849 public void dispose() { 850 if (!InstanceManager.getDefault(WarrantManager.class).okToRemoveBlock(this)) { 851 return; 852 } 853 firePropertyChange("deleted", null, null); 854 // remove paths first 855 for (Path pa : getPaths()) { 856 removeOPath((OPath)pa); 857 } 858 for (Portal portal : getPortals()) { 859 if (log.isTraceEnabled()) { 860 log.debug("this = {}, toBlock = {}, fromblock= {}", getDisplayName(), 861 portal.getToBlock().getDisplayName(), portal.getFromBlock().getDisplayName()); 862 } 863 if (this.equals(portal.getToBlock())) { 864 portal.setToBlock(null, false); 865 } 866 if (this.equals(portal.getFromBlock())) { 867 portal.setFromBlock(null, false); 868 } 869 } 870 _portals.clear(); 871 for (PropertyChangeListener listener : getPropertyChangeListeners()) { 872 removePropertyChangeListener(listener); 873 } 874 jmri.InstanceManager.getDefault(OBlockManager.class).deregister(this); 875 super.dispose(); 876 } 877 878 public String getDescription() { 879 return java.text.MessageFormat.format( 880 Bundle.getMessage("BlockDescription"), getDisplayName()); 881 } 882 883 @Override 884 public List<NamedBeanUsageReport> getUsageReport(NamedBean bean) { 885 List<NamedBeanUsageReport> report = new ArrayList<>(); 886 List<NamedBean> duplicateCheck = new ArrayList<>(); 887 if (bean != null) { 888 if (log.isDebugEnabled()) { 889 Sensor s = getSensor(); 890 log.debug("oblock: {}, sensor = {}", getDisplayName(), (s==null?"Dark OBlock":s.getDisplayName())); // NOI18N 891 } 892 if (bean.equals(getSensor())) { 893 report.add(new NamedBeanUsageReport("OBlockSensor")); // NOI18N 894 } 895 if (bean.equals(getErrorSensor())) { 896 report.add(new NamedBeanUsageReport("OBlockSensorError")); // NOI18N 897 } 898 if (bean.equals(getWarrant())) { 899 report.add(new NamedBeanUsageReport("OBlockWarant")); // NOI18N 900 } 901 902 getPortals().forEach((portal) -> { 903 if (log.isDebugEnabled()) { 904 log.debug(" portal: {}, fb = {}, tb = {}, fs = {}, ts = {}", // NOI18N 905 portal.getName(), portal.getFromBlockName(), portal.getToBlockName(), 906 portal.getFromSignalName(), portal.getToSignalName()); 907 } 908 if (bean.equals(portal.getFromBlock()) || bean.equals(portal.getToBlock())) { 909 report.add(new NamedBeanUsageReport("OBlockPortalNeighborOBlock", portal.getName())); // NOI18N 910 } 911 if (bean.equals(portal.getFromSignal()) || bean.equals(portal.getToSignal())) { 912 report.add(new NamedBeanUsageReport("OBlockPortalSignal", portal.getName())); // NOI18N 913 } 914 915 portal.getFromPaths().forEach((path) -> { 916 log.debug(" from path = {}", path.getName()); // NOI18N 917 path.getSettings().forEach((setting) -> { 918 log.debug(" turnout = {}", setting.getBean().getDisplayName()); // NOI18N 919 if (bean.equals(setting.getBean())) { 920 if (!duplicateCheck.contains(bean)) { 921 report.add(new NamedBeanUsageReport("OBlockPortalPathTurnout", portal.getName())); // NOI18N 922 duplicateCheck.add(bean); 923 } 924 } 925 }); 926 }); 927 portal.getToPaths().forEach((path) -> { 928 log.debug(" to path = {}", path.getName()); // NOI18N 929 path.getSettings().forEach((setting) -> { 930 log.debug(" turnout = {}", setting.getBean().getDisplayName()); // NOI18N 931 if (bean.equals(setting.getBean())) { 932 if (!duplicateCheck.contains(bean)) { 933 report.add(new NamedBeanUsageReport("OBlockPortalPathTurnout", portal.getName())); // NOI18N 934 duplicateCheck.add(bean); 935 } 936 } 937 }); 938 }); 939 }); 940 } 941 return report; 942 } 943 944 @Override 945 @Nonnull 946 public String getBeanType() { 947 return Bundle.getMessage("BeanNameOBlock"); 948 } 949 950 private static final Logger log = LoggerFactory.getLogger(OBlock.class); 951 952}