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