001package jmri.jmrit.dispatcher; 002 003import java.beans.PropertyChangeEvent; 004import java.beans.PropertyChangeListener; 005import java.beans.PropertyChangeSupport; 006import java.util.ArrayList; 007import java.util.List; 008import javax.annotation.Nonnull; 009import jmri.Block; 010import jmri.EntryPoint; 011import jmri.InstanceManager; 012import jmri.Section; 013import jmri.Sensor; 014import jmri.TransitSection; 015import jmri.jmrit.display.layoutEditor.LayoutTrackExpectedState; 016import jmri.jmrit.display.layoutEditor.LayoutTurnout; 017 018import org.slf4j.Logger; 019import org.slf4j.LoggerFactory; 020 021/** 022 * This class holds information and options for an AllocatedSection, a Section 023 * that is currently allocated to an ActiveTrain. 024 * <p> 025 * AllocatedSections are referenced via a list in DispatcherFrame, which serves 026 * as a manager for AllocatedSection objects. Each ActiveTrain also maintains a 027 * list of AllocatedSections currently assigned to it. 028 * <p> 029 * AllocatedSections are transient, and are not saved to disk. 030 * <p> 031 * AllocatedSections keep track of whether they have been entered and exited. 032 * <p> 033 * If the Active Train this Section is assigned to is being run automatically, 034 * support is provided for monitoring Section changes and changes for Blocks 035 * within the Section. 036 * <hr> 037 * This file is part of JMRI. 038 * <p> 039 * JMRI is open source software; you can redistribute it and/or modify it under 040 * the terms of version 2 of the GNU General Public License as published by the 041 * Free Software Foundation. See the "COPYING" file for a copy of this license. 042 * <p> 043 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY 044 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 045 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 046 * 047 * @author Dave Duchamp Copyright (C) 2008-2011 048 */ 049public class AllocatedSection { 050 051 private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); 052 053 /** 054 * Create an AllocatedSection. 055 * 056 * @param s the section to allocation 057 * @param at the train to allocate the section to 058 * @param seq the sequence location of the section in the route 059 * @param next the following section 060 * @param nextSeqNo the sequence location of the following section 061 */ 062 public AllocatedSection(@Nonnull Section s, ActiveTrain at, int seq, Section next, int nextSeqNo) { 063 mSection = s; 064 mActiveTrain = at; 065 mSequence = seq; 066 mNextSection = next; 067 mNextSectionSequence = nextSeqNo; 068 if (mSection.getOccupancy() == Section.OCCUPIED) { 069 mEntered = true; 070 } 071 // listen for changes in Section occupancy 072 mSection.addPropertyChangeListener(mSectionListener = (PropertyChangeEvent e) -> { 073 handleSectionChange(e); 074 }); 075 setStoppingSensors(); 076 if ((mActiveTrain.getAutoActiveTrain() == null) && !(InstanceManager.getDefault(DispatcherFrame.class).getSupportVSDecoder())) { 077 // for manual running, monitor block occupancy for selected Blocks only 078 if (mActiveTrain.getReverseAtEnd() 079 && ((mSequence == mActiveTrain.getEndBlockSectionSequenceNumber()) 080 || (mActiveTrain.getResetWhenDone() 081 && (mSequence == mActiveTrain.getStartBlockSectionSequenceNumber())))) { 082 initializeMonitorBlockOccupancy(); 083 } else if (mSequence == mActiveTrain.getEndBlockSectionSequenceNumber()) { 084 initializeMonitorBlockOccupancy(); 085 } 086 } else { 087 // monitor block occupancy for all Sections of automatially running trains 088 initializeMonitorBlockOccupancy(); 089 } 090 } 091 092 // instance variables 093 private Section mSection = null; 094 private ActiveTrain mActiveTrain = null; 095 private int mSequence = 0; 096 private Section mNextSection = null; 097 private int mNextSectionSequence = 0; 098 private PropertyChangeListener mSectionListener = null; 099 private boolean mEntered = false; 100 private boolean mExited = false; 101 private int mAllocationNumber = 0; // used to keep track of allocation order 102 private Sensor mForwardStoppingSensor = null; 103 private Sensor mReverseStoppingSensor = null; 104 // list of expected states of turnouts in allocated section 105 // used for delayed checking 106 private List<LayoutTrackExpectedState<LayoutTurnout>> autoTurnoutsResponse = null; 107 108 // 109 // Access methods 110 // 111 public void setAutoTurnoutsResponse(List<LayoutTrackExpectedState<LayoutTurnout>> atr) { 112 autoTurnoutsResponse = atr; 113 } 114 115 /** 116 * Get the length of the section remaining including current block 117 * @param block block to start totaling block lengths 118 * @return length in millimetres 119 */ 120 public float getLengthRemaining(Block block) { 121 float length = 0.0f; 122 if (mSection == null) { 123 return length; 124 } 125 int bStart = mSection.getBlockSequenceNumber(block); 126 if (mSection.getState() == Section.FORWARD) { 127 for (int ix = bStart;ix < mSection.getNumBlocks();ix++) { 128 Block b = (mSection.getBlockBySequenceNumber(ix)); 129 if (b != null) { 130 length += b.getLengthMm(); 131 } 132 } 133 } 134 else if (mSection.getState() == Section.REVERSE) { 135 for (int ix = 0 ;ix <= bStart ;ix++) { 136 Block b = (mSection.getBlockBySequenceNumber(ix)); 137 if (b != null) { 138 length += b.getLengthMm(); 139 } 140 } 141 } 142 log.info("Remaining length in section[{}] is [{}]",mSection.getDisplayName(), length); 143 return length; 144 } 145 146 public List<LayoutTrackExpectedState<LayoutTurnout>> getAutoTurnoutsResponse() { 147 return autoTurnoutsResponse; 148 } 149 150 public Section getSection() { 151 return mSection; 152 } 153 154 public String getSectionName() { 155 String s = mSection.getDisplayName(); 156 return s; 157 } 158 159 public ActiveTrain getActiveTrain() { 160 return mActiveTrain; 161 } 162 163 public String getActiveTrainName() { 164 return (mActiveTrain.getTrainName() + "/" + mActiveTrain.getTransitName()); 165 } 166 167 public int getSequence() { 168 return mSequence; 169 } 170 171 public Section getNextSection() { 172 return mNextSection; 173 } 174 175 public int getNextSectionSequence() { 176 return mNextSectionSequence; 177 } 178 179 protected boolean setNextSection(Section sec, int i) { 180 if (sec == null) { 181 mNextSection = null; 182 mNextSectionSequence = i; 183 return true; 184 } 185 if (mNextSection != null) { 186 log.error("Next section is already set"); 187 return false; 188 } 189 mNextSection = sec; 190 return true; 191 } 192 193 public void setNextSectionSequence(int i) { 194 mNextSectionSequence = i; 195 } 196 197 public boolean getEntered() { 198 return mEntered; 199 } 200 201 public boolean getExited() { 202 return mExited; 203 } 204 205 public int getAllocationNumber() { 206 return mAllocationNumber; 207 } 208 209 public void setAllocationNumber(int n) { 210 mAllocationNumber = n; 211 } 212 213 public Sensor getForwardStoppingSensor() { 214 return mForwardStoppingSensor; 215 } 216 217 public Sensor getReverseStoppingSensor() { 218 return mReverseStoppingSensor; 219 } 220 221 // instance variables used with automatic running of trains 222 private int mIndex = 0; 223 private PropertyChangeListener mExitSignalListener = null; 224 private final List<PropertyChangeListener> mBlockListeners = new ArrayList<>(); 225 private List<Block> mBlockList = null; 226 private final List<Block> mActiveBlockList = new ArrayList<>(); 227 228 // 229 // Access methods for automatic running instance variables 230 // 231 public void setIndex(int i) { 232 mIndex = i; 233 } 234 235 public int getIndex() { 236 return mIndex; 237 } 238 239 public void setExitSignalListener(PropertyChangeListener xSigListener) { 240 mExitSignalListener = xSigListener; 241 } 242 243 public PropertyChangeListener getExitSignalListener() { 244 return mExitSignalListener; 245 } 246 247 /** 248 * Methods 249 */ 250 final protected void setStoppingSensors() { 251 if (mSection.getState() == Section.FORWARD) { 252 mForwardStoppingSensor = mSection.getForwardStoppingSensor(); 253 mReverseStoppingSensor = mSection.getReverseStoppingSensor(); 254 } else { 255 mForwardStoppingSensor = mSection.getReverseStoppingSensor(); 256 mReverseStoppingSensor = mSection.getForwardStoppingSensor(); 257 } 258 } 259 260 protected TransitSection getTransitSection() { 261 return mActiveTrain.getTransit().getTransitSectionFromSectionAndSeq(mSection, mSequence); 262 } 263 264 public int getDirection() { 265 return mSection.getState(); 266 } 267 268 public int getActualLength() { 269 return mSection.getActualLength(); 270 } 271 272 public void reset() { 273 mExited = false; 274 mEntered = false; 275 if (mSection.getOccupancy() == Section.OCCUPIED) { 276 mEntered = true; 277 } 278 } 279 280 private synchronized void handleSectionChange(PropertyChangeEvent e) { 281 if (mSection.getOccupancy() == Section.OCCUPIED) { 282 mEntered = true; 283 } else if (mSection.getOccupancy() == Section.UNOCCUPIED) { 284 if (mEntered) { 285 mExited = true; 286 // set colour to still allocated. 287 // release will reset the colour to unoccupied. 288 if (InstanceManager.getDefault(DispatcherFrame.class).getExtraColorForAllocated()) { 289 mSection.setAlternateColorFromActiveBlock(true); 290 } 291 } 292 } 293 if (mActiveTrain.getAutoActiveTrain() != null) { 294 if (e.getPropertyName().equals("state")) { 295 mActiveTrain.getAutoActiveTrain().handleSectionStateChange(this); 296 } else if (e.getPropertyName().equals("occupancy")) { 297 mActiveTrain.getAutoActiveTrain().handleSectionOccupancyChange(this); 298 } 299 } 300 InstanceManager.getDefault(DispatcherFrame.class).sectionOccupancyChanged(); 301 } 302 303 public synchronized final void initializeMonitorBlockOccupancy() { 304 if (mBlockList != null) { 305 return; 306 } 307 mBlockList = mSection.getBlockList(); 308 for (int i = 0; i < mBlockList.size(); i++) { 309 Block b = mBlockList.get(i); 310 if (b != null) { 311 final int index = i; // block index 312 PropertyChangeListener listener = (PropertyChangeEvent e) -> { 313 handleBlockChange(index, e); 314 }; 315 b.addPropertyChangeListener(listener); 316 mBlockListeners.add(listener); 317 } 318 } 319 } 320 321 private synchronized void handleBlockChange(int index, PropertyChangeEvent e) { 322 if (e.getPropertyName().equals("state")) { 323 if (mBlockList == null) { 324 mBlockList = mSection.getBlockList(); 325 } 326 327 Block b = mBlockList.get(index); 328 if (!isInActiveBlockList(b)) { 329 int occ = b.getState(); 330 Runnable handleBlockChange = new RespondToBlockStateChange(b, occ, this); 331 Thread tBlockChange = jmri.util.ThreadingUtil.newThread(handleBlockChange, "Allocated Section Block Change on " + b.getDisplayName()); 332 tBlockChange.start(); 333 addToActiveBlockList(b); 334 if (InstanceManager.getDefault(DispatcherFrame.class).getSupportVSDecoder()) { 335 firePropertyChangeEvent("BlockStateChange", null, b.getSystemName()); // NOI18N 336 } 337 } 338 } 339 } 340 341 protected Block getExitBlock() { 342 if (mNextSection == null) { 343 return null; 344 } 345 EntryPoint ep = mSection.getExitPointToSection(mNextSection, mSection.getState()); 346 if (ep != null) { 347 return ep.getBlock(); 348 } 349 return null; 350 } 351 352 protected Block getEnterBlock(AllocatedSection previousAllocatedSection) { 353 if (previousAllocatedSection == null) { 354 return null; 355 } 356 Section sPrev = previousAllocatedSection.getSection(); 357 EntryPoint ep = mSection.getEntryPointFromSection(sPrev, mSection.getState()); 358 if (ep != null) { 359 return ep.getBlock(); 360 } 361 return null; 362 } 363 364 protected synchronized void addToActiveBlockList(Block b) { 365 if (b != null) { 366 mActiveBlockList.add(b); 367 } 368 } 369 370 protected synchronized void removeFromActiveBlockList(Block b) { 371 if (b != null) { 372 for (int i = 0; i < mActiveBlockList.size(); i++) { 373 if (b == mActiveBlockList.get(i)) { 374 mActiveBlockList.remove(i); 375 return; 376 } 377 } 378 } 379 } 380 381 protected synchronized boolean isInActiveBlockList(Block b) { 382 if (b != null) { 383 for (int i = 0; i < mActiveBlockList.size(); i++) { 384 if (b == mActiveBlockList.get(i)) { 385 return true; 386 } 387 } 388 } 389 return false; 390 } 391 392 public synchronized void dispose() { 393 if ((mSectionListener != null) && (mSection != null)) { 394 mSection.removePropertyChangeListener(mSectionListener); 395 } 396 mSectionListener = null; 397 for (int i = mBlockListeners.size(); i > 0; i--) { 398 Block b = mBlockList.get(i - 1); 399 b.removePropertyChangeListener(mBlockListeners.get(i - 1)); 400 } 401 } 402 403// _________________________________________________________________________________________ 404 // This class responds to Block state change in a separate thread 405 class RespondToBlockStateChange implements Runnable { 406 407 public RespondToBlockStateChange(Block b, int occ, AllocatedSection as) { 408 _block = b; 409 _aSection = as; 410 _occ = occ; 411 } 412 413 @Override 414 public void run() { 415 // delay to insure that change is not a short spike 416 // The forced delay has been removed. The delay can be controlled by the debounce 417 // values in the sensor table. The use of an additional fixed 250 milliseconds 418 // caused it to always fail when crossing small blocks at speed. 419 if (mActiveTrain.getAutoActiveTrain() != null) { 420 // automatically running train 421 mActiveTrain.getAutoActiveTrain().handleBlockStateChange(_aSection, _block); 422 } else if (_occ == Block.OCCUPIED) { 423 // manual running train - block newly occupied 424 if (!mActiveTrain.getAutoRun()) { 425 if ((_block == mActiveTrain.getEndBlock()) && mActiveTrain.getReverseAtEnd()) { 426 // reverse direction of Allocated Sections 427 mActiveTrain.reverseAllAllocatedSections(); 428 mActiveTrain.setRestart(mActiveTrain.getDelayReverseRestart(),mActiveTrain.getReverseRestartDelay(), 429 mActiveTrain.getReverseRestartSensor(),mActiveTrain.getResetReverseRestartSensor()); 430 } else if ((_block == mActiveTrain.getStartBlock()) && mActiveTrain.getResetWhenDone()) { 431 // reset the direction of Allocated Sections 432 mActiveTrain.resetAllAllocatedSections(); 433 mActiveTrain.setRestart(mActiveTrain.getDelayedRestart(),mActiveTrain.getRestartDelay(), 434 mActiveTrain.getRestartSensor(),mActiveTrain.getResetRestartSensor()); 435 } else if (_block == mActiveTrain.getEndBlock() || _block == mActiveTrain.getStartBlock() ) { 436 mActiveTrain.setStatus(ActiveTrain.DONE); 437 } 438 } 439 } 440 // remove from lists 441 removeFromActiveBlockList(_block); 442 } 443 444 private Block _block = null; 445 private int _occ = 0; 446 private AllocatedSection _aSection = null; 447 } 448 449 public void addPropertyChangeListener(PropertyChangeListener listener) { 450 pcs.addPropertyChangeListener(listener); 451 } 452 453 public void removePropertyChangeListener(PropertyChangeListener listener) { 454 pcs.removePropertyChangeListener(listener); 455 } 456 457 protected void firePropertyChangeEvent(PropertyChangeEvent evt) { 458 pcs.firePropertyChange(evt); 459 } 460 461 protected void firePropertyChangeEvent(String name, Object oldVal, Object newVal) { 462 pcs.firePropertyChange(name, oldVal, newVal); 463 } 464 465 private final static Logger log = LoggerFactory.getLogger(AllocatedSection.class); 466}