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