001package jmri.implementation; 002 003import java.beans.PropertyChangeEvent; 004import java.beans.PropertyVetoException; 005import java.util.ArrayList; 006import java.util.List; 007 008import jmri.Block; 009import jmri.EntryPoint; 010import jmri.JmriException; 011import jmri.NamedBean; 012import jmri.NamedBeanUsageReport; 013import jmri.Section; 014import jmri.Sensor; 015import jmri.Transit; 016import jmri.TransitSection; 017import jmri.TransitSectionAction; 018 019/** 020 * A Transit is a group of Sections representing a specified path through a 021 * layout. 022 * <p> 023 * A Transit may have the following states: 024 * <dl> 025 * <dt>IDLE</dt> 026 * <dd>available for assignment to a train</dd> 027 * <dt>ASSIGNED</dt> 028 * <dd>linked to a train in an {@link jmri.jmrit.dispatcher.ActiveTrain}</dd> 029 * </dl> 030 * <p> 031 * When assigned to a Transit, options may be set for the assigned Section. The 032 * Section and its options are kept in a {@link jmri.TransitSection} object. 033 * <p> 034 * To accommodate passing sidings and other track features, there may be 035 * multiple Sections connecting two other Sections in a Transit. If so, one 036 * Section is assigned as primary, and the other connecting Sections are 037 * assigned as alternates. 038 * <p> 039 * A Section may be in a Transit more than once, for example if a train is to 040 * make two or more loops around a layout before going elsewhere. 041 * <p> 042 * A Transit is normally traversed in the forward direction, that is, the 043 * direction of increasing Section Numbers. When a Transit traversal is started 044 * up, it is always started in the forward direction. However, to accommodate 045 * point-to-point (back and forth) route designs, the direction of travel in a 046 * Transit may be "reversed". While the Transit direction is "reversed", the 047 * direction of travel is the direction of decreasing Section numbers. Whether a 048 * Transit is in the "reversed" direction is kept in the ActiveTrain using the 049 * Transit. 050 * 051 * @author Dave Duchamp Copyright (C) 2008-2011 052 */ 053public class DefaultTransit extends AbstractNamedBean implements Transit { 054 055 /* 056 * Instance variables (not saved between runs) 057 */ 058 private static final NamedBean.DisplayOptions USERSYS = NamedBean.DisplayOptions.USERNAME_SYSTEMNAME; 059 private int mState = Transit.IDLE; 060 private final ArrayList<TransitSection> mTransitSectionList = new ArrayList<>(); 061 private int mMaxSequence = 0; 062 private final ArrayList<Integer> blockSecSeqList = new ArrayList<>(); 063 private final ArrayList<Integer> destBlocksSeqList = new ArrayList<>(); 064 065 public DefaultTransit(String systemName, String userName) { 066 super(systemName, userName); 067 } 068 069 public DefaultTransit(String systemName) { 070 super(systemName); 071 } 072 073 /** 074 * Query the state of this Transit. 075 * 076 * @return {@link #IDLE} or {@link #ASSIGNED} 077 */ 078 @Override 079 public int getState() { 080 return mState; 081 } 082 083 /** 084 * Set the state of this Transit. 085 * 086 * @param state {@link #IDLE} or {@link #ASSIGNED} 087 */ 088 @Override 089 public void setState(int state) { 090 if ((state == Transit.IDLE) || (state == Transit.ASSIGNED)) { 091 int old = mState; 092 mState = state; 093 firePropertyChange("state", old, mState); 094 } else { 095 log.error("Attempt to set Transit state to illegal value - {}", state); 096 } 097 } 098 099 /** 100 * Add a Section to this Transit. 101 * 102 * @param s the Section object to add 103 */ 104 @Override 105 public void addTransitSection(TransitSection s) { 106 mTransitSectionList.add(s); 107 mMaxSequence = s.getSequenceNumber(); 108 } 109 110 /** 111 * Get the list of TransitSections. 112 * 113 * @return a copy of the internal list of TransitSections or an empty list 114 */ 115 @Override 116 public ArrayList<TransitSection> getTransitSectionList() { 117 return new ArrayList<>(mTransitSectionList); 118 } 119 120 /** 121 * Get the maximum sequence number used in this Transit. 122 * 123 * @return the maximum sequence 124 */ 125 @Override 126 public int getMaxSequence() { 127 return mMaxSequence; 128 } 129 130 /** 131 * Remove all TransitSections in this Transit. 132 */ 133 @Override 134 public void removeAllSections() { 135 mTransitSectionList.clear(); 136 } 137 138 /** 139 * Check if a Section is in this Transit. 140 * 141 * @param s the section to check for 142 * @return true if the section is present; false otherwise 143 */ 144 @Override 145 public boolean containsSection(Section s) { 146 return mTransitSectionList.stream().anyMatch((ts) -> (ts.getSection() == s)); 147 } 148 149 /** 150 * Get a List of Sections with a given sequence number. 151 * 152 * @param seq the sequence number 153 * @return the list of of matching sections or an empty list if none 154 */ 155 @Override 156 public ArrayList<Section> getSectionListBySeq(int seq) { 157 ArrayList<Section> list = new ArrayList<>(); 158 for (TransitSection ts : mTransitSectionList) { 159 if (seq == ts.getSequenceNumber()) { 160 list.add(ts.getSection()); 161 } 162 } 163 return list; 164 } 165 166 /** 167 * Get a List of TransitSections with a given sequence number. 168 * 169 * @param seq the sequence number 170 * @return the list of of matching sections or an empty list if none 171 */ 172 @Override 173 public ArrayList<TransitSection> getTransitSectionListBySeq(int seq) { 174 ArrayList<TransitSection> list = new ArrayList<>(); 175 for (TransitSection ts : mTransitSectionList) { 176 if (seq == ts.getSequenceNumber()) { 177 list.add(ts); 178 } 179 } 180 return list; 181 } 182 183 /** 184 * Get a List of sequence numbers for a given Section. 185 * 186 * @param s the section to match 187 * @return the list of matching sequence numbers or an empty list if none 188 */ 189 @Override 190 public ArrayList<Integer> getSeqListBySection(Section s) { 191 ArrayList<Integer> list = new ArrayList<>(); 192 for (TransitSection ts : mTransitSectionList) { 193 if (s == ts.getSection()) { 194 list.add(ts.getSequenceNumber()); 195 } 196 } 197 return list; 198 } 199 200 /** 201 * Check if a Block is in this Transit. 202 * 203 * @param block the block to check for 204 * @return true if block is present; false otherwise 205 */ 206 @Override 207 public boolean containsBlock(Block block) { 208 for (Block b : getInternalBlocksList()) { 209 if (b == block) { 210 return true; 211 } 212 } 213 return false; 214 } 215 216 /** 217 * Get the number of times a Block is in this Transit. 218 * 219 * @param block the block to check for 220 * @return the number of times block is present; 0 if block is not present 221 */ 222 @Override 223 public int getBlockCount(Block block) { 224 int count = 0; 225 for (Block b : getInternalBlocksList()) { 226 if (b == block) { 227 count++; 228 } 229 } 230 return count; 231 } 232 233 /** 234 * Get a Section from one of its Blocks and its sequence number. 235 * 236 * @param b the block within the Section 237 * @param seq the sequence number of the Section 238 * @return the Section or null if no matching Section is present 239 */ 240 @Override 241 public Section getSectionFromBlockAndSeq(Block b, int seq) { 242 for (TransitSection ts : mTransitSectionList) { 243 if (ts.getSequenceNumber() == seq) { 244 Section s = ts.getSection(); 245 if (s.containsBlock(b)) { 246 return s; 247 } 248 } 249 } 250 return null; 251 } 252 253 /** 254 * Get Section from one of its EntryPoint Blocks and its sequence number. 255 * 256 * @param b the connecting block to the Section 257 * @param seq the sequence number of the Section 258 * @return the Section or null if no matching Section is present 259 */ 260 @Override 261 public Section getSectionFromConnectedBlockAndSeq(Block b, int seq) { 262 for (TransitSection ts : mTransitSectionList) { 263 if (ts.getSequenceNumber() == seq) { 264 Section s = ts.getSection(); 265 if (s.connectsToBlock(b)) { 266 return s; 267 } 268 } 269 } 270 return null; 271 } 272 273 /** 274 * Get the direction of a Section in the transit from its sequence number. 275 * 276 * @param s the Section to check 277 * @param seq the sequence number of the Section 278 * @return the direction of the Section (one of {@link jmri.Section#FORWARD} 279 * or {@link jmri.Section#REVERSE} or zero if s and seq are not in a 280 * TransitSection together 281 */ 282 @Override 283 public int getDirectionFromSectionAndSeq(Section s, int seq) { 284 for (TransitSection ts : mTransitSectionList) { 285 if ((ts.getSection() == s) && (ts.getSequenceNumber() == seq)) { 286 return ts.getDirection(); 287 } 288 } 289 return 0; 290 } 291 292 /** 293 * Get a TransitSection in the transit from its Section and sequence number. 294 * 295 * @param s the Section to check 296 * @param seq the sequence number of the Section 297 * @return the transit section or null if not found 298 */ 299 @Override 300 public TransitSection getTransitSectionFromSectionAndSeq(Section s, int seq) { 301 for (TransitSection ts : mTransitSectionList) { 302 if ((ts.getSection() == s) && (ts.getSequenceNumber() == seq)) { 303 return ts; 304 } 305 } 306 return null; 307 } 308 309 /** 310 * Get a list of all blocks internal to this Transit. Since Sections may be 311 * present more than once, blocks may be listed more than once. The sequence 312 * numbers of the Section the Block was found in are accumulated in a 313 * parallel list, which can be accessed by immediately calling 314 * {@link #getBlockSeqList()}. 315 * 316 * @return the list of all Blocks or an empty list if none are present 317 */ 318 @Override 319 public ArrayList<Block> getInternalBlocksList() { 320 ArrayList<Block> list = new ArrayList<>(); 321 blockSecSeqList.clear(); 322 mTransitSectionList.forEach((ts) -> { 323 ts.getSection().getBlockList().stream().forEach((b) -> { 324 list.add(b); 325 blockSecSeqList.add(ts.getSequenceNumber()); 326 }); 327 }); 328 return list; 329 } 330 331 /** 332 * Get a list of sequence numbers in this Transit. This list is generated by 333 * calling {@link #getInternalBlocksList()} or 334 * {@link #getEntryBlocksList()}. 335 * 336 * @return the list of all sequence numbers or an empty list if no Blocks 337 * are present 338 */ 339 @Override 340 public ArrayList<Integer> getBlockSeqList() { 341 return new ArrayList<>(blockSecSeqList); 342 } 343 344 /** 345 * Get a list of all entry Blocks to this Transit. These are Blocks that a 346 * Train might enter from and be going in the direction of this Transit. The 347 * sequence numbers of the Section the Block will enter are accumulated in a 348 * parallel list, which can be accessed by immediately calling 349 * {@link #getBlockSeqList()}. 350 * 351 * @return the list of all blocks or an empty list if none are present 352 */ 353 @Override 354 public ArrayList<Block> getEntryBlocksList() { 355 ArrayList<Block> list = new ArrayList<>(); 356 ArrayList<Block> internalBlocks = getInternalBlocksList(); 357 blockSecSeqList.clear(); 358 for (TransitSection ts : mTransitSectionList) { 359 List<EntryPoint> ePointList; 360 if (ts.getDirection() == Section.FORWARD) { 361 ePointList = ts.getSection().getForwardEntryPointList(); 362 } else { 363 ePointList = ts.getSection().getReverseEntryPointList(); 364 } 365 for (EntryPoint ep : ePointList) { 366 Block eb = ep.getFromBlock(); 367 boolean isInternal = false; 368 for (Block ib : internalBlocks) { 369 if (eb == ib) { 370 isInternal = true; 371 } 372 } 373 if (!isInternal) { 374 // not an internal Block, keep it 375 list.add(eb); 376 blockSecSeqList.add(ts.getSequenceNumber()); 377 } 378 } 379 } 380 return list; 381 } 382 383 /** 384 * Get a list of all destination blocks that can be reached from a specified 385 * starting block. The sequence numbers of the Sections destination blocks 386 * were found in are accumulated in a parallel list, which can be accessed 387 * by immediately calling {@link #getDestBlocksSeqList()}. 388 * <p> 389 * <strong>Note:</strong> A Train may not terminate in the same Section in 390 * which it starts. 391 * <p> 392 * <strong>Note:</strong> A Train must terminate in a Block within the 393 * Transit. 394 * 395 * @param startBlock the starting Block to find destinations for 396 * @param startInTransit true if startBlock is within this Transit; false 397 * otherwise 398 * @return a list of destination Blocks or an empty list if none exist 399 */ 400 @Override 401 public ArrayList<Block> getDestinationBlocksList(Block startBlock, boolean startInTransit) { 402 ArrayList<Block> list = new ArrayList<>(); 403 destBlocksSeqList.clear(); 404 if (startBlock == null) { 405 return list; 406 } 407 // get the sequence number of the Section of the starting Block 408 int startSeq = -1; 409 ArrayList<Block> startBlocks; 410 if (startInTransit) { 411 startBlocks = getInternalBlocksList(); 412 } else { 413 startBlocks = getEntryBlocksList(); 414 } 415 // programming note: the above calls initialize blockSecSeqList. 416 for (int k = 0; ((k < startBlocks.size()) && (startSeq == -1)); k++) { 417 if (startBlock == startBlocks.get(k)) { 418 startSeq = (blockSecSeqList.get(k)); 419 } 420 } 421 ArrayList<Block> internalBlocks = getInternalBlocksList(); 422 //allow for transits of length 1 423 if (startInTransit) { 424 for (int i = internalBlocks.size(); i > 0; i--) { 425 if (blockSecSeqList.get(i - 1) > startSeq) { 426 // could stop in this block, keep it 427 list.add(internalBlocks.get(i - 1)); 428 destBlocksSeqList.add(blockSecSeqList.get(i - 1)); 429 } 430 } 431 } else { 432 for (int i = internalBlocks.size(); i > 0; i--) { 433 if (blockSecSeqList.get(i - 1) >= startSeq) { 434 // could stop in this block, keep it 435 list.add(internalBlocks.get(i - 1)); 436 destBlocksSeqList.add(blockSecSeqList.get(i - 1)); 437 } 438 } 439 } 440 return list; 441 } 442 443 /** 444 * Get a list of destination Block sequence numbers in this Transit. This 445 * list is generated by calling 446 * {@link #getDestinationBlocksList(jmri.Block, boolean)}. 447 * 448 * @return the list of all destination Block sequence numbers or an empty 449 * list if no destination Blocks are present 450 */ 451 @Override 452 public ArrayList<Integer> getDestBlocksSeqList() { 453 ArrayList<Integer> list = new ArrayList<>(); 454 for (int i = 0; i < destBlocksSeqList.size(); i++) { 455 list.add(destBlocksSeqList.get(i)); 456 } 457 return list; 458 } 459 460 /** 461 * Check if this Transit is capable of continuous running. 462 * <p> 463 * A Transit is capable of continuous running if, after an Active Train 464 * completes the Transit, it can automatically be restarted. To be 465 * restartable, the first Section and the last Section must be the same 466 * Section, and the first and last Sections must be defined to run in the 467 * same direction. If the last Section is an alternate Section, the previous 468 * Section is tested. However, if the Active Train does not complete its 469 * Transit in the same Section it started in, the restart will not take 470 * place. 471 * 472 * @return true if continuous running is possible; otherwise false 473 */ 474 @Override 475 public boolean canBeResetWhenDone() { 476 TransitSection firstTS = mTransitSectionList.get(0); 477 int lastIndex = mTransitSectionList.size() - 1; 478 TransitSection lastTS = mTransitSectionList.get(lastIndex); 479 boolean OK = false; 480 while (!OK) { 481 if (firstTS.getSection() != lastTS.getSection()) { 482 if (lastTS.isAlternate() && (lastIndex > 1)) { 483 lastIndex--; 484 lastTS = mTransitSectionList.get(lastIndex); 485 } else { 486 log.warn("Section mismatch {} {}", (firstTS.getSection()).getDisplayName(USERSYS), (lastTS.getSection()).getDisplayName(USERSYS)); 487 return false; 488 } 489 } 490 OK = true; 491 } 492 // same Section, check direction 493 if (firstTS.getDirection() != lastTS.getDirection()) { 494 log.warn("Direction mismatch {} {}", (firstTS.getSection()).getDisplayName(USERSYS), (lastTS.getSection()).getDisplayName(USERSYS)); 495 return false; 496 } 497 return true; 498 } 499 500 /** 501 * Initialize blocking sensors for Sections in this Transit. This should be 502 * done before any Sections are allocated for this Transit. Only Sections 503 * that are {@link jmri.Section#FREE} are initialized, so as not to 504 * interfere with running active trains. If any Section does not have 505 * blocking sensors, warning messages are logged. 506 * 507 * @return 0 if no errors, number of errors otherwise. 508 */ 509 @Override 510 public int initializeBlockingSensors() { 511 int numErrors = 0; 512 for (int i = 0; i < mTransitSectionList.size(); i++) { 513 Section s = mTransitSectionList.get(i).getSection(); 514 try { 515 if (s.getForwardBlockingSensor() != null) { 516 if (s.getState() == Section.FREE) { 517 s.getForwardBlockingSensor().setState(Sensor.ACTIVE); 518 } 519 } else { 520 log.warn("Missing forward blocking sensor for section {}", s.getDisplayName(USERSYS)); 521 numErrors++; 522 } 523 } catch (JmriException reason) { 524 log.error("Exception when initializing forward blocking Sensor for Section {}", s.getDisplayName(USERSYS)); 525 numErrors++; 526 } 527 try { 528 if (s.getReverseBlockingSensor() != null) { 529 if (s.getState() == Section.FREE) { 530 s.getReverseBlockingSensor().setState(Sensor.ACTIVE); 531 } 532 } else { 533 log.warn("Missing reverse blocking sensor for section {}", s.getDisplayName(USERSYS)); 534 numErrors++; 535 } 536 } catch (JmriException reason) { 537 log.error("Exception when initializing reverse blocking Sensor for Section {}", s.getDisplayName(USERSYS)); 538 numErrors++; 539 } 540 } 541 return numErrors; 542 } 543 544 //@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "UC_USELESS_OBJECT", 545 // justification = "SpotBugs doesn't see that toBeRemoved is being read by the forEach clause") 546 @Override 547 public void removeTemporarySections() { 548 ArrayList<TransitSection> toBeRemoved = new ArrayList<>(); 549 for (TransitSection ts : mTransitSectionList) { 550 if (ts.isTemporary()) { 551 toBeRemoved.add(ts); 552 } 553 } 554 toBeRemoved.forEach((ts) -> { 555 mTransitSectionList.remove(ts); 556 }); 557 } 558 559 @Override 560 public boolean removeLastTemporarySection(Section s) { 561 TransitSection last = mTransitSectionList.get(mTransitSectionList.size() - 1); 562 if (last.getSection() != s) { 563 log.info("Section asked to be removed is not the last one"); 564 return false; 565 } 566 if (!last.isTemporary()) { 567 log.info("Section asked to be removed is not a temporary section"); 568 return false; 569 } 570 mTransitSectionList.remove(last); 571 return true; 572 573 } 574 575 @Override 576 public String getBeanType() { 577 return Bundle.getMessage("BeanNameTransit"); 578 } 579 580 @Override 581 public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException { 582 if ("CanDelete".equals(evt.getPropertyName())) { // NOI18N 583 NamedBean nb = (NamedBean) evt.getOldValue(); 584 if (nb instanceof Section) { 585 if (containsSection((Section) nb)) { 586 throw new PropertyVetoException(Bundle.getMessage("VetoTransitSection", getDisplayName()), evt); 587 } 588 } 589 } 590 // we ignore the property setConfigureManager 591 } 592 593 @Override 594 public List<NamedBeanUsageReport> getUsageReport(NamedBean bean) { 595 List<NamedBeanUsageReport> report = new ArrayList<>(); 596 jmri.SensorManager sm = jmri.InstanceManager.getDefault(jmri.SensorManager.class); 597 jmri.SignalHeadManager head = jmri.InstanceManager.getDefault(jmri.SignalHeadManager.class); 598 jmri.SignalMastManager mast = jmri.InstanceManager.getDefault(jmri.SignalMastManager.class); 599 if (bean != null) { 600 getTransitSectionList().forEach((transitSection) -> { 601 if (bean.equals(transitSection.getSection())) { 602 report.add(new NamedBeanUsageReport("TransitSection")); 603 } 604 if (bean.equals(sm.getSensor(transitSection.getStopAllocatingSensor()))) { 605 report.add(new NamedBeanUsageReport("TransitSensorStopAllocation")); 606 } 607 // Process actions 608 transitSection.getTransitSectionActionList().forEach((action) -> { 609 int whenCode = action.getWhenCode(); 610 int whatCode = action.getWhatCode(); 611 if (whenCode == TransitSectionAction.SENSORACTIVE || whenCode == TransitSectionAction.SENSORINACTIVE) { 612 if (bean.equals(sm.getSensor(action.getStringWhen()))) { 613 report.add(new NamedBeanUsageReport("TransitActionSensorWhen", transitSection.getSection())); 614 } 615 } 616 if (whatCode == TransitSectionAction.SETSENSORACTIVE || whatCode == TransitSectionAction.SETSENSORINACTIVE) { 617 if (bean.equals(sm.getSensor(action.getStringWhat()))) { 618 report.add(new NamedBeanUsageReport("TransitActionSensorWhat", transitSection.getSection())); 619 } 620 } 621 if (whatCode == TransitSectionAction.HOLDSIGNAL || whatCode == TransitSectionAction.RELEASESIGNAL) { 622 // Could be a signal head or a signal mast. 623 if (bean.equals(head.getSignalHead(action.getStringWhat()))) { 624 report.add(new NamedBeanUsageReport("TransitActionSignalHeadWhat", transitSection.getSection())); 625 } 626 if (bean.equals(mast.getSignalMast(action.getStringWhat()))) { 627 report.add(new NamedBeanUsageReport("TransitActionSignalMastWhat", transitSection.getSection())); 628 } 629 } 630 }); 631 }); 632 } 633 return report; 634 } 635 636 637 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultTransit.class); 638 639}