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 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 // public static final 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(@CheckForNull 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 _errNamedSensor.getBean().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 @CheckForNull 275 public Sensor getErrorSensor() { 276 if (_errNamedSensor == null) { 277 return null; 278 } 279 return _errNamedSensor.getBean(); 280 } 281 282 @CheckForNull 283 public NamedBeanHandle<Sensor> getNamedErrorSensor() { 284 return _errNamedSensor; 285 } 286 287 @Override 288 public void propertyChange(java.beans.PropertyChangeEvent evt) { 289 if (log.isDebugEnabled()) { 290 log.debug("property change: of \"{}\" property {} is now {} from {}", 291 getDisplayName(), evt.getPropertyName(), evt.getNewValue(), evt.getSource().getClass().getName()); 292 } 293 if ((getErrorSensor() != null) && (evt.getSource().equals(getErrorSensor())) 294 && Sensor.PROPERTY_KNOWN_STATE.equals(evt.getPropertyName())) { 295 int errState = ((Integer) evt.getNewValue()); 296 int oldState = getState(); 297 if (errState == Sensor.ACTIVE) { 298 setState(oldState | TRACK_ERROR); 299 } else { 300 setState(oldState & ~TRACK_ERROR); 301 } 302 firePropertyChange(PROPERTY_PATH_STATE, oldState, getState()); 303 } 304 } 305 306 /** 307 * Another block sharing a turnout with this block queries whether turnout 308 * is in use. 309 * 310 * @param path that uses a common shared turnout 311 * @return If warrant exists and path==pathname, return warrant display 312 * name, else null. 313 */ 314 @CheckForNull 315 protected String isPathSet(@Nonnull String path) { 316 String msg = null; 317 if (_warrant != null && path.equals(_pathName)) { 318 msg = _warrant.getDisplayName(); 319 } 320 log.trace("Path \"{}\" in oblock \"{}\" {}", path, getDisplayName(), 321 (msg == null ? "not set" : " set in warrant " + msg)); 322 return msg; 323 } 324 325 @CheckForNull 326 public Warrant getWarrant() { 327 return _warrant; 328 } 329 330 public boolean isAllocatedTo( @CheckForNull Warrant warrant) { 331 if (warrant == null) { 332 return false; 333 } 334 return warrant.equals(_warrant); 335 } 336 337 public String getAllocatedPathName() { 338 return _pathName; 339 } 340 341 public void setMetricUnits(boolean type) { 342 _metric = type; 343 } 344 345 public boolean isMetric() { 346 return _metric; 347 } 348 349 public void setMarkerForeground(Color c) { 350 _markerForeground = c; 351 } 352 353 public Color getMarkerForeground() { 354 return _markerForeground; 355 } 356 357 public void setMarkerBackground(Color c) { 358 _markerBackground = c; 359 } 360 361 public Color getMarkerBackground() { 362 return _markerBackground; 363 } 364 365 public void setMarkerFont(Font f) { 366 _markerFont = f; 367 } 368 369 public Font getMarkerFont() { 370 return _markerFont; 371 } 372 373 /** 374 * Update the OBlock status. 375 * Block.setState fires the "state" property change with this OBlock as 376 * its source, as used by the Web Server and CPE indicator track icons. 377 * 378 * @param v the new state, from OBlock.ALLOCATED etc, named 'status' in JSON Servlet and Web Server 379 */ 380 @Override 381 public void setState(int v) { 382 super.setState(v); 383 log.debug("\"{}\" setState({})", getDisplayName(), getState()); 384 } 385 386 /** 387 * {@inheritDoc} 388 */ 389 @Override 390 public void setValue(Object o) { 391 super.setValue(o); 392 if (o == null) { 393 _markerForeground = Color.WHITE; 394 _markerBackground = DEFAULT_FILL_COLOR; 395 _markerFont = null; 396 } 397 } 398 399 /*_ 400 * From the universal name for block status, check if it is the current status 401 */ 402 public boolean statusIs(String statusName) { 403 OBlockStatus oblockStatus = OBlockStatus.getByName(statusName); 404 if (oblockStatus != null) { 405 return ((getState() & oblockStatus.getStatus()) != 0); 406 } 407 log.error("\"{}\" type not found. Update Conditional State Variable testing OBlock \"{}\" status", 408 getDisplayName(), statusName); 409 return false; 410 } 411 412 public boolean isDark() { 413 return (getState() & OBlock.UNDETECTED) != 0; 414 } 415 416 public boolean isOccupied() { 417 return (getState() & OBlock.OCCUPIED) != 0; 418 } 419 420 @CheckForNull 421 public String occupiedBy() { 422 Warrant w = _warrant; 423 if (isOccupied()) { 424 if (w != null) { 425 return w.getTrainName(); 426 } else { 427 return Bundle.getMessage("unknownTrain"); 428 } 429 } else { 430 return null; 431 } 432 } 433 434 /** 435 * Test that block is not occupied and not allocated 436 * 437 * @return true if not occupied and not allocated 438 */ 439 public boolean isFree() { 440 int state = getState(); 441 return ((state & ALLOCATED) == 0 && (state & OCCUPIED) == 0); 442 } 443 444 /** 445 * Allocate (reserves) the block for the Warrant Note the block may be 446 * OCCUPIED by a non-warranted train, but the allocation is permitted. 447 * 448 * @param warrant the Warrant 449 * @return message with if block is already allocated to another warrant or 450 * block is OUT_OF_SERVICE 451 */ 452 @CheckForNull 453 public String allocate(Warrant warrant) { 454 if (warrant == null) { 455 log.error("allocate(warrant) called with null warrant in block \"{}\"!", getDisplayName()); 456 return "ERROR! allocate called with null warrant in block \"" + getDisplayName() + "\"!"; 457 } 458 if (_warrant != null) { 459 if (!warrant.equals(_warrant)) { 460 return Bundle.getMessage("AllocatedToWarrant", 461 _warrant.getDisplayName(), getDisplayName(), _warrant.getTrainName()); 462 } else { 463 return null; 464 } 465 } 466 /* 467 int state = getState(); 468 if ((state & OUT_OF_SERVICE) != 0) { 469 return Bundle.getMessage("BlockOutOfService", getDisplayName()); 470 }*/ 471 472 _warrant = warrant; 473 if (log.isDebugEnabled()) { 474 log.debug("Allocate OBlock \"{}\" to warrant \"{}\".", 475 getDisplayName(), warrant.getDisplayName()); 476 } 477 super.setState(getState() | ALLOCATED); 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 super.setState(getState() & ~(ALLOCATED | RUNNING)); // unset allocated and running bits 573 return true; 574 } 575 576 public void setOutOfService(boolean set) { 577 if (set) { 578 setState(getState() | OUT_OF_SERVICE); // set OoS bit 579 } else { 580 setState(getState() & ~OUT_OF_SERVICE); // unset OoS bit 581 } 582 } 583 584 public void setError(boolean set) { 585 if (set) { 586 setState(getState() | TRACK_ERROR); // set err bit 587 } else { 588 setState(getState() & ~TRACK_ERROR); // unset err bit 589 } 590 } 591 592 /** 593 * Enforce unique portal names. Portals are now managed beans since 2014. 594 * This enforces unique names. 595 * 596 * @param portal the Portal to add 597 */ 598 public void addPortal(Portal portal) { 599 String name = getDisplayName(); 600 if (!name.equals(portal.getFromBlockName()) && !name.equals(portal.getToBlockName())) { 601 log.warn("{} not in block {}", portal.getDescription(), getDisplayName()); 602 return; 603 } 604 String pName = portal.getName(); 605 if (pName != null) { // pName may be null if called from Portal ctor 606 for (Portal value : _portals) { 607 if (pName.equals(value.getName())) { 608 return; 609 } 610 } 611 } 612 int oldSize = _portals.size(); 613 _portals.add(portal); 614 log.trace("add portal \"{}\" to Block \"{}\"", portal.getName(), getDisplayName()); 615 firePropertyChange(PROPERTY_PORTAL_COUNT, oldSize, _portals.size()); 616 } 617 618 /** 619 * Remove portal from OBlock and stub all paths using this portal to be dead 620 * end spurs. 621 * 622 * @param portal the Portal to remove 623 */ 624 @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST_OF_RETURN_VALUE", justification = "OPath extends Path") 625 protected void removePortal(@CheckForNull Portal portal) { 626 if (portal != null) { 627 Iterator<Path> iter = getPaths().iterator(); 628 while (iter.hasNext()) { 629 OPath path = (OPath) iter.next(); 630 if (portal.equals(path.getFromPortal())) { 631 path.setFromPortal(null); 632 log.trace("removed Portal {} from Path \"{}\" in oblock {}", 633 portal.getName(), path.getName(), getDisplayName()); 634 } 635 if (portal.equals(path.getToPortal())) { 636 path.setToPortal(null); 637 log.trace("removed Portal {} from Path \"{}\" in oblock {}", 638 portal.getName(), path.getName(), getDisplayName()); 639 } 640 } 641 iter = getPaths().iterator(); 642 while (iter.hasNext()) { 643 OPath path = (OPath) iter.next(); 644 if (path.getFromPortal() == null && path.getToPortal() == null) { 645 removeOPath(path); 646 log.trace("removed Path \"{}\" from oblock {}", path.getName(), getDisplayName()); 647 } 648 } 649 int oldSize = _portals.size(); 650 _portals = _portals.stream().filter(p -> !Objects.equals(p,portal)).collect(Collectors.toList()); 651 firePropertyChange(PROPERTY_PORTAL_COUNT, oldSize, _portals.size()); 652 } 653 } 654 655 public Portal getPortalByName(String name) { 656 for (Portal po : _portals) { 657 if (po.getName().equals(name)) { 658 return po; 659 } 660 } 661 return null; 662 } 663 664 @Nonnull 665 public List<Portal> getPortals() { 666 return new ArrayList<>(_portals); 667 } 668 669 public void setPortals(ArrayList<Portal> portals) { 670 _portals = portals; 671 } 672 673 @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST_OF_RETURN_VALUE", justification = "OPath extends Path") 674 public OPath getPathByName(String name) { 675 for (Path opa : getPaths()) { 676 OPath path = (OPath) opa; 677 if (path.getName().equals(name)) { 678 return path; 679 } 680 } 681 return null; 682 } 683 684 @Override 685 public void setLength(float len) { 686 // Only shorten paths longer than 'len' 687 getPaths().stream().forEach(p -> { 688 if (p.getLength() > len) { 689 p.setLength(len); // set to default 690 } 691 }); 692 super.setLength(len); 693 } 694 695 /** 696 * Enforce unique path names within OBlock, but allow a duplicate name of an 697 * OPath from another OBlock to be checked if it is in one of the OBlock's 698 * Portals. 699 * 700 * @param path the OPath to add 701 * @return true if path was added to OBlock 702 */ 703 @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST_OF_RETURN_VALUE", justification = "OPath extends Path") 704 public boolean addPath(OPath path) { 705 String pName = path.getName(); 706 log.trace("addPath \"{}\" to OBlock {}", pName, getSystemName()); 707 List<Path> list = getPaths(); 708 for (Path p : list) { 709 if (((OPath) p).equals(path)) { 710 log.trace("Path \"{}\" duplicated in OBlock {}", pName, getSystemName()); 711 return false; 712 } 713 if (pName.equals(((OPath) p).getName())) { 714 log.trace("Path named \"{}\" already exists in OBlock {}", pName, getSystemName()); 715 return false; 716 } 717 } 718 OBlock pathBlock = (OBlock) path.getBlock(); 719 if (pathBlock != null && !this.equals(pathBlock)) { 720 log.warn("Path \"{}\" already in block {}, cannot be added to block {}", 721 pName, pathBlock.getDisplayName(), getDisplayName()); 722 return false; 723 } 724 path.setBlock(this); 725 Portal portal = path.getFromPortal(); 726 if (portal != null) { 727 if (!portal.addPath(path)) { 728 log.trace("Path \"{}\" rejected by portal {}", pName, portal.getName()); 729 return false; 730 } 731 } 732 portal = path.getToPortal(); 733 if (portal != null) { 734 if (!portal.addPath(path)) { 735 log.debug("Path \"{}\" rejected by portal {}", pName, portal.getName()); 736 return false; 737 } 738 } 739 super.addPath(path); 740 firePropertyChange(PROPERTY_PATH_COUNT, null, getPaths().size()); 741 return true; 742 } 743 744 public boolean removeOPath(OPath path) { 745 jmri.Block block = path.getBlock(); 746 if (block != null && !getSystemName().equals(block.getSystemName())) { 747 return false; 748 } 749 if (!InstanceManager.getDefault(jmri.jmrit.logix.WarrantManager.class).okToRemoveBlockPath(this, path)) { 750 return false; 751 } 752 path.clearSettings(); 753 super.removePath(path); 754 // remove path from its portals 755 Portal portal = path.getToPortal(); 756 if (portal != null) { 757 portal.removePath(path); 758 } 759 portal = path.getFromPortal(); 760 if (portal != null) { 761 portal.removePath(path); 762 } 763 path.dispose(); 764 firePropertyChange(PROPERTY_PATH_COUNT, path, getPaths().size()); 765 return true; 766 } 767 768 /** 769 * Set Turnouts for the path. 770 * <p> 771 * Called by warrants to set turnouts for a train it is able to run. 772 * The warrant parameter verifies that the block is 773 * indeed allocated to the warrant. If the block is unwarranted then the 774 * block is allocated to the calling warrant. A logix conditional may also 775 * call this method with a null warrant parameter for manual logix control. 776 * If the block is under a different warrant the call will be rejected. 777 * 778 * @param pathName name of the path 779 * @param warrant warrant the block is allocated to 780 * @return error message if the call fails. null if the call succeeds 781 */ 782 protected String setPath(String pathName, Warrant warrant) { 783 OPath path = getPathByName(pathName); 784 if (path == null) { 785 return Bundle.getMessage("PathNotFound", pathName, getDisplayName()); 786 } 787 if (warrant == null || !warrant.equals(_warrant)) { 788 String name; 789 if (_warrant != null) { 790 name = _warrant.getDisplayName(); 791 } else { 792 name = Bundle.getMessage("Warrant"); 793 } 794 return Bundle.getMessage("PathNotSet", pathName, getDisplayName(), name); 795 } 796 _pathName = pathName; 797 int lockState = Turnout.CABLOCKOUT & Turnout.PUSHBUTTONLOCKOUT; 798 path.setTurnouts(0, true, lockState, true); 799 firePropertyChange(PROPERTY_PATH_STATE, 0, getState()); 800 if (log.isTraceEnabled()) { 801 log.debug("setPath: Path \"{}\" in path \"{}\" {} set for warrant {}", 802 pathName, getDisplayName(), _pathName, warrant.getDisplayName()); 803 } 804 return null; 805 } 806 807 protected OPath getPath() { 808 if (_pathName == null) { 809 return null; 810 } 811 return getPathByName(_pathName); 812 } 813 814 /* 815 * Call for Circuit Builder to make icon color changes for its GUI 816 */ 817 public void pseudoPropertyChange(String propName, Object old, Object n) { 818 log.trace("pseudoPropertyChange: Block \"{}\" property \"{}\" new value= {}", 819 getSystemName(), propName, n); 820 firePropertyChange(propName, old, n); 821 } 822 823 /** 824 * (Override) Handles Block sensor going INACTIVE: this block is empty. 825 * Called by handleSensorChange 826 */ 827 @Override 828 public void goingInactive() { 829 //log.debug("OBlock \"{}\" going UNOCCUPIED from state= {}", getDisplayName(), getState()); 830 // preserve the non-sensor states 831 // non-UNOCCUPIED sensor states are removed (also cannot be RUNNING there if being UNOCCUPIED) 832 setState((getState() & ~(UNKNOWN | OCCUPIED | INCONSISTENT | RUNNING)) | UNOCCUPIED); 833 setValue(null); 834 if (_warrant != null) { 835 ThreadingUtil.runOnLayout(() -> _warrant.goingInactive(this)); 836 } 837 } 838 839 /** 840 * (Override) Handles Block sensor going ACTIVE: this block is now occupied, 841 * figure out from who and copy their value. Called by handleSensorChange 842 */ 843 @Override 844 public void goingActive() { 845 // preserve the non-sensor states when being OCCUPIED and remove non-OCCUPIED sensor states 846 setState((getState() & ~(UNKNOWN | UNOCCUPIED | INCONSISTENT)) | OCCUPIED); 847 _entryTime = System.currentTimeMillis(); 848 if (_warrant != null) { 849 ThreadingUtil.runOnLayout(() -> _warrant.goingActive(this)); 850 } 851 } 852 853 @Override 854 public void goingUnknown() { 855 setState((getState() & ~(UNOCCUPIED | OCCUPIED | INCONSISTENT)) | UNKNOWN); 856 } 857 858 @Override 859 public void goingInconsistent() { 860 setState((getState() & ~(UNKNOWN | UNOCCUPIED | OCCUPIED)) | INCONSISTENT); 861 } 862 863 @Override 864 @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST_OF_RETURN_VALUE", justification = "OPath extends Path") 865 public void dispose() { 866 if (!InstanceManager.getDefault(WarrantManager.class).okToRemoveBlock(this)) { 867 return; 868 } 869 firePropertyChange(PROPERTY_DELETED, null, null); 870 // remove paths first 871 for (Path pa : getPaths()) { 872 removeOPath((OPath)pa); 873 } 874 for (Portal portal : getPortals()) { 875 if (log.isTraceEnabled()) { 876 log.debug("this = {}, toBlock = {}, fromblock= {}", getDisplayName(), 877 portal.getToBlock().getDisplayName(), portal.getFromBlock().getDisplayName()); 878 } 879 if (this.equals(portal.getToBlock())) { 880 portal.setToBlock(null, false); 881 } 882 if (this.equals(portal.getFromBlock())) { 883 portal.setFromBlock(null, false); 884 } 885 } 886 _portals.clear(); 887 for (PropertyChangeListener listener : getPropertyChangeListeners()) { 888 removePropertyChangeListener(listener); 889 } 890 jmri.InstanceManager.getDefault(OBlockManager.class).deregister(this); 891 super.dispose(); 892 } 893 894 public String getDescription() { 895 return java.text.MessageFormat.format( 896 Bundle.getMessage("BlockDescription"), getDisplayName()); 897 } 898 899 @Override 900 public List<NamedBeanUsageReport> getUsageReport(NamedBean bean) { 901 List<NamedBeanUsageReport> report = new ArrayList<>(); 902 List<NamedBean> duplicateCheck = new ArrayList<>(); 903 if (bean != null) { 904 if (log.isDebugEnabled()) { 905 Sensor s = getSensor(); 906 log.debug("oblock: {}, sensor = {}", getDisplayName(), (s==null?"Dark OBlock":s.getDisplayName())); // NOI18N 907 } 908 if (bean.equals(getSensor())) { 909 report.add(new NamedBeanUsageReport("OBlockSensor")); // NOI18N 910 } 911 if (bean.equals(getErrorSensor())) { 912 report.add(new NamedBeanUsageReport("OBlockSensorError")); // NOI18N 913 } 914 if (bean.equals(getWarrant())) { 915 report.add(new NamedBeanUsageReport("OBlockWarant")); // NOI18N 916 } 917 918 getPortals().forEach((portal) -> { 919 if (log.isDebugEnabled()) { 920 log.debug(" portal: {}, fb = {}, tb = {}, fs = {}, ts = {}", // NOI18N 921 portal.getName(), portal.getFromBlockName(), portal.getToBlockName(), 922 portal.getFromSignalName(), portal.getToSignalName()); 923 } 924 if (bean.equals(portal.getFromBlock()) || bean.equals(portal.getToBlock())) { 925 report.add(new NamedBeanUsageReport("OBlockPortalNeighborOBlock", portal.getName())); // NOI18N 926 } 927 if (bean.equals(portal.getFromSignal()) || bean.equals(portal.getToSignal())) { 928 report.add(new NamedBeanUsageReport("OBlockPortalSignal", portal.getName())); // NOI18N 929 } 930 931 portal.getFromPaths().forEach((path) -> { 932 log.debug(" from path = {}", path.getName()); // NOI18N 933 path.getSettings().forEach((setting) -> { 934 log.debug(" turnout = {}", setting.getBean().getDisplayName()); // NOI18N 935 if (bean.equals(setting.getBean())) { 936 if (!duplicateCheck.contains(bean)) { 937 report.add(new NamedBeanUsageReport("OBlockPortalPathTurnout", portal.getName())); // NOI18N 938 duplicateCheck.add(bean); 939 } 940 } 941 }); 942 }); 943 portal.getToPaths().forEach((path) -> { 944 log.debug(" to path = {}", path.getName()); // NOI18N 945 path.getSettings().forEach((setting) -> { 946 log.debug(" turnout = {}", setting.getBean().getDisplayName()); // NOI18N 947 if (bean.equals(setting.getBean())) { 948 if (!duplicateCheck.contains(bean)) { 949 report.add(new NamedBeanUsageReport("OBlockPortalPathTurnout", portal.getName())); // NOI18N 950 duplicateCheck.add(bean); 951 } 952 } 953 }); 954 }); 955 }); 956 } 957 return report; 958 } 959 960 @Override 961 @Nonnull 962 public String getBeanType() { 963 return Bundle.getMessage("BeanNameOBlock"); 964 } 965 966 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(OBlock.class); 967 968}