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(NamedBeanHandle<Sensor> namedSensor) { 206 super.setNamedSensor(namedSensor); 207 if (namedSensor != null) { 208 setState(getSensor().getState() & ~UNDETECTED); 209 } 210 } 211 212 /** 213 * @param pName name of error sensor 214 * @return true if successful 215 */ 216 public boolean setErrorSensor(String pName) { 217 NamedBeanHandle<Sensor> newErrSensorHdl = null; 218 Sensor newErrSensor = null; 219 if (pName != null && pName.trim().length() > 0) { 220 newErrSensor = InstanceManager.sensorManagerInstance().getByUserName(pName); 221 if (newErrSensor == null) { 222 newErrSensor = InstanceManager.sensorManagerInstance().getBySystemName(pName); 223 } 224 if (newErrSensor != null) { 225 newErrSensorHdl = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class). 226 getNamedBeanHandle(pName, newErrSensor); 227 } 228 if (newErrSensor == null) { 229 log.error("No sensor named '{}' exists.", pName); 230 return false; 231 } 232 } 233 if (_errNamedSensor != null) { 234 if (_errNamedSensor.equals(newErrSensorHdl)) { 235 return true; 236 } else { 237 getErrorSensor().removePropertyChangeListener(this); 238 } 239 } 240 241 _errNamedSensor = newErrSensorHdl; 242 setState(getState() & ~TRACK_ERROR); 243 if (newErrSensor != null) { 244 newErrSensor.addPropertyChangeListener( this, 245 _errNamedSensor.getName(), "OBlock Error Sensor " + getDisplayName()); 246 if (newErrSensor.getState() == Sensor.ACTIVE) { 247 setState(getState() | TRACK_ERROR); 248 } else { 249 setState(getState() & ~TRACK_ERROR); 250 } 251 } 252 return true; 253 } 254 255 public Sensor getErrorSensor() { 256 if (_errNamedSensor == null) { 257 return null; 258 } 259 return _errNamedSensor.getBean(); 260 } 261 262 public NamedBeanHandle<Sensor> getNamedErrorSensor() { 263 return _errNamedSensor; 264 } 265 266 @Override 267 public void propertyChange(java.beans.PropertyChangeEvent evt) { 268 if (log.isDebugEnabled()) { 269 log.debug("property change: of \"{}\" property {} is now {} from {}", 270 getDisplayName(), evt.getPropertyName(), evt.getNewValue(), evt.getSource().getClass().getName()); 271 } 272 if ((getErrorSensor() != null) && (evt.getSource().equals(getErrorSensor())) 273 && evt.getPropertyName().equals("KnownState")) { 274 int errState = ((Integer) evt.getNewValue()); 275 int oldState = getState(); 276 if (errState == Sensor.ACTIVE) { 277 setState(oldState | TRACK_ERROR); 278 } else { 279 setState(oldState & ~TRACK_ERROR); 280 } 281 firePropertyChange("pathState", oldState, getState()); 282 } 283 } 284 285 /** 286 * Another block sharing a turnout with this block queries whether turnout 287 * is in use. 288 * 289 * @param path that uses a common shared turnout 290 * @return If warrant exists and path==pathname, return warrant display 291 * name, else null. 292 */ 293 protected String isPathSet(@Nonnull String path) { 294 String msg = null; 295 if (_warrant != null && path.equals(_pathName)) { 296 msg = _warrant.getDisplayName(); 297 } 298 log.trace("Path \"{}\" in oblock \"{}\" {}", path, getDisplayName(), 299 (msg == null ? "not set" : " set in warrant " + msg)); 300 return msg; 301 } 302 303 public Warrant getWarrant() { 304 return _warrant; 305 } 306 307 public boolean isAllocatedTo(Warrant warrant) { 308 if (warrant == null) { 309 return false; 310 } 311 return warrant.equals(_warrant); 312 } 313 314 public String getAllocatedPathName() { 315 return _pathName; 316 } 317 318 public void setMetricUnits(boolean type) { 319 _metric = type; 320 } 321 322 public boolean isMetric() { 323 return _metric; 324 } 325 326 public void setMarkerForeground(Color c) { 327 _markerForeground = c; 328 } 329 330 public Color getMarkerForeground() { 331 return _markerForeground; 332 } 333 334 public void setMarkerBackground(Color c) { 335 _markerBackground = c; 336 } 337 338 public Color getMarkerBackground() { 339 return _markerBackground; 340 } 341 342 public void setMarkerFont(Font f) { 343 _markerFont = f; 344 } 345 346 public Font getMarkerFont() { 347 return _markerFont; 348 } 349 350 /** 351 * Update the OBlock status. 352 * Override Block because change must come from an OBlock for Web Server to receive it 353 * 354 * @param v the new state, from OBlock.ALLOCATED etc, named 'status' in JSON Servlet and Web Server 355 */ 356 @Override 357 public void setState(int v) { 358 int old = getState(); 359 super.setState(v); 360 // override Block to get proper source to be recognized by listener in Web Server 361 log.debug("\"{}\" setState({})", getDisplayName(), getState()); 362 firePropertyChange("state", old, getState()); // used by CPE indicator track icons 363 } 364 365 /** 366 * {@inheritDoc} 367 */ 368 @Override 369 public void setValue(Object o) { 370 super.setValue(o); 371 if (o == null) { 372 _markerForeground = Color.WHITE; 373 _markerBackground = DEFAULT_FILL_COLOR; 374 _markerFont = null; 375 } 376 } 377 378 /*_ 379 * From the universal name for block status, check if it is the current status 380 */ 381 public boolean statusIs(String statusName) { 382 OBlockStatus oblockStatus = OBlockStatus.getByName(statusName); 383 if (oblockStatus != null) { 384 return ((getState() & oblockStatus.getStatus()) != 0); 385 } 386 log.error("\"{}\" type not found. Update Conditional State Variable testing OBlock \"{}\" status", 387 getDisplayName(), statusName); 388 return false; 389 } 390 391 public boolean isDark() { 392 return (getState() & OBlock.UNDETECTED) != 0; 393 } 394 395 public boolean isOccupied() { 396 return (getState() & OBlock.OCCUPIED) != 0; 397 } 398 399 public String occupiedBy() { 400 Warrant w = _warrant; 401 if (isOccupied()) { 402 if (w != null) { 403 return w.getTrainName(); 404 } else { 405 return Bundle.getMessage("unknownTrain"); 406 } 407 } else { 408 return null; 409 } 410 } 411 412 /** 413 * Test that block is not occupied and not allocated 414 * 415 * @return true if not occupied and not allocated 416 */ 417 public boolean isFree() { 418 int state = getState(); 419 return ((state & ALLOCATED) == 0 && (state & OCCUPIED) == 0); 420 } 421 422 /** 423 * Allocate (reserves) the block for the Warrant Note the block may be 424 * OCCUPIED by a non-warranted train, but the allocation is permitted. 425 * 426 * @param warrant the Warrant 427 * @return message with if block is already allocated to another warrant or 428 * block is OUT_OF_SERVICE 429 */ 430 @CheckForNull 431 public String allocate(Warrant warrant) { 432 if (warrant == null) { 433 log.error("allocate(warrant) called with null warrant in block \"{}\"!", getDisplayName()); 434 return "ERROR! allocate called with null warrant in block \"" + getDisplayName() + "\"!"; 435 } 436 if (_warrant != null) { 437 if (!warrant.equals(_warrant)) { 438 return Bundle.getMessage("AllocatedToWarrant", 439 _warrant.getDisplayName(), getDisplayName(), _warrant.getTrainName()); 440 } else { 441 return null; 442 } 443 } 444 /* 445 int state = getState(); 446 if ((state & OUT_OF_SERVICE) != 0) { 447 return Bundle.getMessage("BlockOutOfService", getDisplayName()); 448 }*/ 449 450 _warrant = warrant; 451 if (log.isDebugEnabled()) { 452 log.debug("Allocate OBlock \"{}\" to warrant \"{}\".", 453 getDisplayName(), warrant.getDisplayName()); 454 } 455 int old = getState(); 456 int newState = old | ALLOCATED; 457 super.setState(newState); 458 firePropertyChange("state", old, newState); 459 return null; 460 } 461 462 // Highlights track icons to show that block is allocated. 463 protected void showAllocated(Warrant warrant, String pathName) { 464 if (_warrant != null && !_warrant.equals(warrant)) { 465 return; 466 } 467 if (_pathName == null) { 468 _pathName = pathName; 469 } 470 firePropertyChange("pathState", 0, getState()); 471// super.setState(getState()); 472 } 473 474 /** 475 * Note path name may be set if block is not allocated to a warrant. For use 476 * by CircuitBuilder Only. (test paths for editCircuitPaths) 477 * 478 * @param pathName name of a path 479 * @return error message, otherwise null 480 */ 481 @CheckForNull 482 public String allocatePath(String pathName) { 483 log.debug("Allocate OBlock path \"{}\" in block \"{}\", state= {}", 484 pathName, getSystemName(), getState()); 485 if (pathName == null) { 486 log.error("allocate called with null pathName in block \"{}\"!", getDisplayName()); 487 return null; 488 } else if (_warrant != null) { 489 // allocated to another warrant 490 return Bundle.getMessage("AllocatedToWarrant", 491 _warrant.getDisplayName(), getDisplayName(), _warrant.getTrainName()); 492 } 493 _pathName = pathName; 494 // DO NOT ALLOCATE block 495 return null; 496 } 497 498 public String getAllocatingWarrantName() { 499 if (_warrant == null) { 500 return ("no warrant"); 501 } else { 502 return _warrant.getDisplayName(); 503 } 504 } 505 506 /** 507 * Remove allocation state // maybe restore this? Remove listener regardless of ownership 508 * 509 * @param warrant warrant that has reserved this block. null is allowed for 510 * Conditionals and CircuitBuilder to reset the block. 511 * Otherwise, null should not be used. 512 * @return true if warrant deallocated. 513 */ 514 public boolean deAllocate(Warrant warrant) { 515 if (warrant == null) { 516 return true; 517 } 518 if (_warrant != null) { 519 if (!_warrant.equals(warrant)) { 520 log.warn("{} cannot deallocate. {}", warrant.getDisplayName(), Bundle.getMessage("AllocatedToWarrant", 521 _warrant.getDisplayName(), getDisplayName(), _warrant.getTrainName())); 522 return false; 523 } 524 Warrant curWarrant = _warrant; 525 _warrant = null; // At times, removePropertyChangeListener may be run on a delayed thread. 526 try { 527 if (log.isDebugEnabled()) { 528 log.debug("deAllocate block \"{}\" from warrant \"{}\"", 529 getDisplayName(), warrant.getDisplayName()); 530 } 531 removePropertyChangeListener(curWarrant); 532 } catch (Exception ex) { 533 // disposed warrant may throw null pointer - continue deallocation 534 log.trace("Warrant {} unregistered.", curWarrant.getDisplayName(), ex); 535 } 536 } 537 _warrant = null; 538 if (_pathName != null) { 539 OPath path = getPathByName(_pathName); 540 if (path != null) { 541 int lockState = Turnout.CABLOCKOUT & Turnout.PUSHBUTTONLOCKOUT; 542 path.setTurnouts(0, false, lockState, false); 543 Portal portal = path.getFromPortal(); 544 if (portal != null) { 545 portal.setState(Portal.UNKNOWN); 546 } 547 portal = path.getToPortal(); 548 if (portal != null) { 549 portal.setState(Portal.UNKNOWN); 550 } 551 } 552 } 553 int old = getState(); 554 super.setState(old & ~(ALLOCATED | RUNNING)); // unset allocated and running bits 555 firePropertyChange("state", old, getState()); 556 return true; 557 } 558 559 public void setOutOfService(boolean set) { 560 if (set) { 561 setState(getState() | OUT_OF_SERVICE); // set OoS bit 562 } else { 563 setState(getState() & ~OUT_OF_SERVICE); // unset OoS bit 564 } 565 } 566 567 public void setError(boolean set) { 568 if (set) { 569 setState(getState() | TRACK_ERROR); // set err bit 570 } else { 571 setState(getState() & ~TRACK_ERROR); // unset err bit 572 } 573 } 574 575 /** 576 * Enforce unique portal names. Portals are now managed beans since 2014. 577 * This enforces unique names. 578 * 579 * @param portal the Portal to add 580 */ 581 public void addPortal(Portal portal) { 582 String name = getDisplayName(); 583 if (!name.equals(portal.getFromBlockName()) && !name.equals(portal.getToBlockName())) { 584 log.warn("{} not in block {}", portal.getDescription(), getDisplayName()); 585 return; 586 } 587 String pName = portal.getName(); 588 if (pName != null) { // pName may be null if called from Portal ctor 589 for (Portal value : _portals) { 590 if (pName.equals(value.getName())) { 591 return; 592 } 593 } 594 } 595 int oldSize = _portals.size(); 596 _portals.add(portal); 597 log.trace("add portal \"{}\" to Block \"{}\"", portal.getName(), getDisplayName()); 598 firePropertyChange("portalCount", oldSize, _portals.size()); 599 } 600 601 /** 602 * Remove portal from OBlock and stub all paths using this portal to be dead 603 * end spurs. 604 * 605 * @param portal the Portal to remove 606 */ 607 @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST_OF_RETURN_VALUE", justification = "OPath extends Path") 608 protected void removePortal(@CheckForNull Portal portal) { 609 if (portal != null) { 610 Iterator<Path> iter = getPaths().iterator(); 611 while (iter.hasNext()) { 612 OPath path = (OPath) iter.next(); 613 if (portal.equals(path.getFromPortal())) { 614 path.setFromPortal(null); 615 log.trace("removed Portal {} from Path \"{}\" in oblock {}", 616 portal.getName(), path.getName(), getDisplayName()); 617 } 618 if (portal.equals(path.getToPortal())) { 619 path.setToPortal(null); 620 log.trace("removed Portal {} from Path \"{}\" in oblock {}", 621 portal.getName(), path.getName(), getDisplayName()); 622 } 623 } 624 iter = getPaths().iterator(); 625 while (iter.hasNext()) { 626 OPath path = (OPath) iter.next(); 627 if (path.getFromPortal() == null && path.getToPortal() == null) { 628 removeOPath(path); 629 log.trace("removed Path \"{}\" from oblock {}", path.getName(), getDisplayName()); 630 } 631 } 632 int oldSize = _portals.size(); 633 _portals = _portals.stream().filter(p -> !Objects.equals(p,portal)).collect(Collectors.toList()); 634 firePropertyChange("portalCount", oldSize, _portals.size()); 635 } 636 } 637 638 public Portal getPortalByName(String name) { 639 for (Portal po : _portals) { 640 if (po.getName().equals(name)) { 641 return po; 642 } 643 } 644 return null; 645 } 646 647 @Nonnull 648 public List<Portal> getPortals() { 649 return new ArrayList<>(_portals); 650 } 651 652 public void setPortals(ArrayList<Portal> portals) { 653 _portals = portals; 654 } 655 656 @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST_OF_RETURN_VALUE", justification = "OPath extends Path") 657 public OPath getPathByName(String name) { 658 for (Path opa : getPaths()) { 659 OPath path = (OPath) opa; 660 if (path.getName().equals(name)) { 661 return path; 662 } 663 } 664 return null; 665 } 666 667 @Override 668 public void setLength(float len) { 669 // Only shorten paths longer than 'len' 670 getPaths().stream().forEach(p -> { 671 if (p.getLength() > len) { 672 p.setLength(len); // set to default 673 } 674 }); 675 super.setLength(len); 676 } 677 678 /** 679 * Enforce unique path names within OBlock, but allow a duplicate name of an 680 * OPath from another OBlock to be checked if it is in one of the OBlock's 681 * Portals. 682 * 683 * @param path the OPath to add 684 * @return true if path was added to OBlock 685 */ 686 @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST_OF_RETURN_VALUE", justification = "OPath extends Path") 687 public boolean addPath(OPath path) { 688 String pName = path.getName(); 689 log.trace("addPath \"{}\" to OBlock {}", pName, getSystemName()); 690 List<Path> list = getPaths(); 691 for (Path p : list) { 692 if (((OPath) p).equals(path)) { 693 log.trace("Path \"{}\" duplicated in OBlock {}", pName, getSystemName()); 694 return false; 695 } 696 if (pName.equals(((OPath) p).getName())) { 697 log.trace("Path named \"{}\" already exists in OBlock {}", pName, getSystemName()); 698 return false; 699 } 700 } 701 OBlock pathBlock = (OBlock) path.getBlock(); 702 if (pathBlock != null && !this.equals(pathBlock)) { 703 log.warn("Path \"{}\" already in block {}, cannot be added to block {}", 704 pName, pathBlock.getDisplayName(), getDisplayName()); 705 return false; 706 } 707 path.setBlock(this); 708 Portal portal = path.getFromPortal(); 709 if (portal != null) { 710 if (!portal.addPath(path)) { 711 log.trace("Path \"{}\" rejected by portal {}", pName, portal.getName()); 712 return false; 713 } 714 } 715 portal = path.getToPortal(); 716 if (portal != null) { 717 if (!portal.addPath(path)) { 718 log.debug("Path \"{}\" rejected by portal {}", pName, portal.getName()); 719 return false; 720 } 721 } 722 super.addPath(path); 723 firePropertyChange("pathCount", null, getPaths().size()); 724 return true; 725 } 726 727 public boolean removeOPath(OPath path) { 728 jmri.Block block = path.getBlock(); 729 if (block != null && !getSystemName().equals(block.getSystemName())) { 730 return false; 731 } 732 if (!InstanceManager.getDefault(jmri.jmrit.logix.WarrantManager.class).okToRemoveBlockPath(this, path)) { 733 return false; 734 } 735 path.clearSettings(); 736 super.removePath(path); 737 // remove path from its portals 738 Portal portal = path.getToPortal(); 739 if (portal != null) { 740 portal.removePath(path); 741 } 742 portal = path.getFromPortal(); 743 if (portal != null) { 744 portal.removePath(path); 745 } 746 path.dispose(); 747 firePropertyChange("pathCount", path, getPaths().size()); 748 return true; 749 } 750 751 /** 752 * Set Turnouts for the path. 753 * <p> 754 * Called by warrants to set turnouts for a train it is able to run. 755 * The warrant parameter verifies that the block is 756 * indeed allocated to the warrant. If the block is unwarranted then the 757 * block is allocated to the calling warrant. A logix conditional may also 758 * call this method with a null warrant parameter for manual logix control. 759 * If the block is under a different warrant the call will be rejected. 760 * 761 * @param pathName name of the path 762 * @param warrant warrant the block is allocated to 763 * @return error message if the call fails. null if the call succeeds 764 */ 765 protected String setPath(String pathName, Warrant warrant) { 766 OPath path = getPathByName(pathName); 767 if (path == null) { 768 return Bundle.getMessage("PathNotFound", pathName, getDisplayName()); 769 } 770 if (warrant == null || !warrant.equals(_warrant)) { 771 String name; 772 if (_warrant != null) { 773 name = _warrant.getDisplayName(); 774 } else { 775 name = Bundle.getMessage("Warrant"); 776 } 777 return Bundle.getMessage("PathNotSet", pathName, getDisplayName(), name); 778 } 779 _pathName = pathName; 780 int lockState = Turnout.CABLOCKOUT & Turnout.PUSHBUTTONLOCKOUT; 781 path.setTurnouts(0, true, lockState, true); 782 firePropertyChange("pathState", 0, getState()); 783 if (log.isTraceEnabled()) { 784 log.debug("setPath: Path \"{}\" in path \"{}\" {} set for warrant {}", 785 pathName, getDisplayName(), _pathName, warrant.getDisplayName()); 786 } 787 return null; 788 } 789 790 protected OPath getPath() { 791 if (_pathName == null) { 792 return null; 793 } 794 return getPathByName(_pathName); 795 } 796 797 /* 798 * Call for Circuit Builder to make icon color changes for its GUI 799 */ 800 public void pseudoPropertyChange(String propName, Object old, Object n) { 801 log.trace("pseudoPropertyChange: Block \"{}\" property \"{}\" new value= {}", 802 getSystemName(), propName, n); 803 firePropertyChange(propName, old, n); 804 } 805 806 /** 807 * (Override) Handles Block sensor going INACTIVE: this block is empty. 808 * Called by handleSensorChange 809 */ 810 @Override 811 public void goingInactive() { 812 //log.debug("OBlock \"{}\" going UNOCCUPIED from state= {}", getDisplayName(), getState()); 813 // preserve the non-sensor states 814 // non-UNOCCUPIED sensor states are removed (also cannot be RUNNING there if being UNOCCUPIED) 815 setState((getState() & ~(UNKNOWN | OCCUPIED | INCONSISTENT | RUNNING)) | UNOCCUPIED); 816 setValue(null); 817 if (_warrant != null) { 818 ThreadingUtil.runOnLayout(() -> _warrant.goingInactive(this)); 819 } 820 } 821 822 /** 823 * (Override) Handles Block sensor going ACTIVE: this block is now occupied, 824 * figure out from who and copy their value. Called by handleSensorChange 825 */ 826 @Override 827 public void goingActive() { 828 // preserve the non-sensor states when being OCCUPIED and remove non-OCCUPIED sensor states 829 setState((getState() & ~(UNKNOWN | UNOCCUPIED | INCONSISTENT)) | OCCUPIED); 830 _entryTime = System.currentTimeMillis(); 831 if (_warrant != null) { 832 ThreadingUtil.runOnLayout(() -> _warrant.goingActive(this)); 833 } 834 } 835 836 @Override 837 public void goingUnknown() { 838 setState((getState() & ~(UNOCCUPIED | OCCUPIED | INCONSISTENT)) | UNKNOWN); 839 } 840 841 @Override 842 public void goingInconsistent() { 843 setState((getState() & ~(UNKNOWN | UNOCCUPIED | OCCUPIED)) | INCONSISTENT); 844 } 845 846 @Override 847 @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST_OF_RETURN_VALUE", justification = "OPath extends Path") 848 public void dispose() { 849 if (!InstanceManager.getDefault(WarrantManager.class).okToRemoveBlock(this)) { 850 return; 851 } 852 firePropertyChange("deleted", null, null); 853 // remove paths first 854 for (Path pa : getPaths()) { 855 removeOPath((OPath)pa); 856 } 857 for (Portal portal : getPortals()) { 858 if (log.isTraceEnabled()) { 859 log.debug("this = {}, toBlock = {}, fromblock= {}", getDisplayName(), 860 portal.getToBlock().getDisplayName(), portal.getFromBlock().getDisplayName()); 861 } 862 if (this.equals(portal.getToBlock())) { 863 portal.setToBlock(null, false); 864 } 865 if (this.equals(portal.getFromBlock())) { 866 portal.setFromBlock(null, false); 867 } 868 } 869 _portals.clear(); 870 for (PropertyChangeListener listener : getPropertyChangeListeners()) { 871 removePropertyChangeListener(listener); 872 } 873 jmri.InstanceManager.getDefault(OBlockManager.class).deregister(this); 874 super.dispose(); 875 } 876 877 public String getDescription() { 878 return java.text.MessageFormat.format( 879 Bundle.getMessage("BlockDescription"), getDisplayName()); 880 } 881 882 @Override 883 public List<NamedBeanUsageReport> getUsageReport(NamedBean bean) { 884 List<NamedBeanUsageReport> report = new ArrayList<>(); 885 List<NamedBean> duplicateCheck = new ArrayList<>(); 886 if (bean != null) { 887 if (log.isDebugEnabled()) { 888 Sensor s = getSensor(); 889 log.debug("oblock: {}, sensor = {}", getDisplayName(), (s==null?"Dark OBlock":s.getDisplayName())); // NOI18N 890 } 891 if (bean.equals(getSensor())) { 892 report.add(new NamedBeanUsageReport("OBlockSensor")); // NOI18N 893 } 894 if (bean.equals(getErrorSensor())) { 895 report.add(new NamedBeanUsageReport("OBlockSensorError")); // NOI18N 896 } 897 if (bean.equals(getWarrant())) { 898 report.add(new NamedBeanUsageReport("OBlockWarant")); // NOI18N 899 } 900 901 getPortals().forEach((portal) -> { 902 if (log.isDebugEnabled()) { 903 log.debug(" portal: {}, fb = {}, tb = {}, fs = {}, ts = {}", // NOI18N 904 portal.getName(), portal.getFromBlockName(), portal.getToBlockName(), 905 portal.getFromSignalName(), portal.getToSignalName()); 906 } 907 if (bean.equals(portal.getFromBlock()) || bean.equals(portal.getToBlock())) { 908 report.add(new NamedBeanUsageReport("OBlockPortalNeighborOBlock", portal.getName())); // NOI18N 909 } 910 if (bean.equals(portal.getFromSignal()) || bean.equals(portal.getToSignal())) { 911 report.add(new NamedBeanUsageReport("OBlockPortalSignal", portal.getName())); // NOI18N 912 } 913 914 portal.getFromPaths().forEach((path) -> { 915 log.debug(" from path = {}", path.getName()); // NOI18N 916 path.getSettings().forEach((setting) -> { 917 log.debug(" turnout = {}", setting.getBean().getDisplayName()); // NOI18N 918 if (bean.equals(setting.getBean())) { 919 if (!duplicateCheck.contains(bean)) { 920 report.add(new NamedBeanUsageReport("OBlockPortalPathTurnout", portal.getName())); // NOI18N 921 duplicateCheck.add(bean); 922 } 923 } 924 }); 925 }); 926 portal.getToPaths().forEach((path) -> { 927 log.debug(" to path = {}", path.getName()); // NOI18N 928 path.getSettings().forEach((setting) -> { 929 log.debug(" turnout = {}", setting.getBean().getDisplayName()); // NOI18N 930 if (bean.equals(setting.getBean())) { 931 if (!duplicateCheck.contains(bean)) { 932 report.add(new NamedBeanUsageReport("OBlockPortalPathTurnout", portal.getName())); // NOI18N 933 duplicateCheck.add(bean); 934 } 935 } 936 }); 937 }); 938 }); 939 } 940 return report; 941 } 942 943 @Override 944 @Nonnull 945 public String getBeanType() { 946 return Bundle.getMessage("BeanNameOBlock"); 947 } 948 949 private static final Logger log = LoggerFactory.getLogger(OBlock.class); 950 951}