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 if (mSection.getState() == Section.FORWARD) { 126 for (int ix = 0;ix < mSection.getNumBlocks();ix++) { 127 Block b = (mSection.getBlockBySequenceNumber(ix)); 128 if (b != null) { 129 if (length > 0.0f || b == block) { 130 length += b.getLengthMm(); 131 } 132 } 133 } 134 } 135 else if (mSection.getState() == Section.REVERSE) { 136 for (int ix = mSection.getNumBlocks()-1;ix > -1 ;ix--) { 137 Block b = (mSection.getBlockBySequenceNumber(ix)); 138 if (b != null) { 139 if (length > 0.0f || b == block) { 140 length += b.getLengthMm(); 141 } 142 } 143 } 144 } 145 log.debug("Remaining length in section[{}] is [{}]",mSection.getDisplayName(), length); 146 return length; 147 } 148 149 public List<LayoutTrackExpectedState<LayoutTurnout>> getAutoTurnoutsResponse() { 150 return autoTurnoutsResponse; 151 } 152 153 public Section getSection() { 154 return mSection; 155 } 156 157 public String getSectionName() { 158 String s = mSection.getDisplayName(); 159 return s; 160 } 161 162 public ActiveTrain getActiveTrain() { 163 return mActiveTrain; 164 } 165 166 public String getActiveTrainName() { 167 return (mActiveTrain.getTrainName() + "/" + mActiveTrain.getTransitName()); 168 } 169 170 public int getSequence() { 171 return mSequence; 172 } 173 174 public Section getNextSection() { 175 return mNextSection; 176 } 177 178 public int getNextSectionSequence() { 179 return mNextSectionSequence; 180 } 181 182 protected boolean setNextSection(Section sec, int i) { 183 if (sec == null) { 184 mNextSection = null; 185 mNextSectionSequence = i; 186 return true; 187 } 188 if (mNextSection != null) { 189 log.error("Next section is already set"); 190 return false; 191 } 192 mNextSection = sec; 193 return true; 194 } 195 196 public void setNextSectionSequence(int i) { 197 mNextSectionSequence = i; 198 } 199 200 public boolean getEntered() { 201 return mEntered; 202 } 203 204 public boolean getExited() { 205 return mExited; 206 } 207 208 public int getAllocationNumber() { 209 return mAllocationNumber; 210 } 211 212 public void setAllocationNumber(int n) { 213 mAllocationNumber = n; 214 } 215 216 public Sensor getForwardStoppingSensor() { 217 return mForwardStoppingSensor; 218 } 219 220 public Sensor getReverseStoppingSensor() { 221 return mReverseStoppingSensor; 222 } 223 224 // instance variables used with automatic running of trains 225 private int mIndex = 0; 226 private PropertyChangeListener mExitSignalListener = null; 227 private final List<PropertyChangeListener> mBlockListeners = new ArrayList<>(); 228 private List<Block> mBlockList = null; 229 private final List<Block> mActiveBlockList = new ArrayList<>(); 230 231 // 232 // Access methods for automatic running instance variables 233 // 234 public void setIndex(int i) { 235 mIndex = i; 236 } 237 238 public int getIndex() { 239 return mIndex; 240 } 241 242 public void setExitSignalListener(PropertyChangeListener xSigListener) { 243 mExitSignalListener = xSigListener; 244 } 245 246 public PropertyChangeListener getExitSignalListener() { 247 return mExitSignalListener; 248 } 249 250 /** 251 * Methods 252 */ 253 final protected void setStoppingSensors() { 254 if (mSection.getState() == Section.FORWARD) { 255 mForwardStoppingSensor = mSection.getForwardStoppingSensor(); 256 mReverseStoppingSensor = mSection.getReverseStoppingSensor(); 257 } else { 258 mForwardStoppingSensor = mSection.getReverseStoppingSensor(); 259 mReverseStoppingSensor = mSection.getForwardStoppingSensor(); 260 } 261 } 262 263 protected TransitSection getTransitSection() { 264 return mActiveTrain.getTransit().getTransitSectionFromSectionAndSeq(mSection, mSequence); 265 } 266 267 public int getDirection() { 268 return mSection.getState(); 269 } 270 271 public int getActualLength() { 272 return mSection.getActualLength(); 273 } 274 275 public void reset() { 276 mExited = false; 277 mEntered = false; 278 if (mSection.getOccupancy() == Section.OCCUPIED) { 279 mEntered = true; 280 } 281 } 282 283 private synchronized void handleSectionChange(PropertyChangeEvent e) { 284 if (mSection.getOccupancy() == Section.OCCUPIED) { 285 mEntered = true; 286 } else if (mSection.getOccupancy() == Section.UNOCCUPIED) { 287 if (mEntered) { 288 mExited = true; 289 // set colour to still allocated. 290 // release will reset the colour to unoccupied. 291 if (InstanceManager.getDefault(DispatcherFrame.class).getExtraColorForAllocated()) { 292 mSection.setAlternateColorFromActiveBlock(true); 293 } 294 } 295 } 296 if (mActiveTrain.getAutoActiveTrain() != null) { 297 if (e.getPropertyName().equals("state")) { 298 mActiveTrain.getAutoActiveTrain().handleSectionStateChange(this); 299 } else if (e.getPropertyName().equals("occupancy")) { 300 mActiveTrain.getAutoActiveTrain().handleSectionOccupancyChange(this); 301 } 302 } 303 InstanceManager.getDefault(DispatcherFrame.class).sectionOccupancyChanged(); 304 } 305 306 public synchronized final void initializeMonitorBlockOccupancy() { 307 if (mBlockList != null) { 308 return; 309 } 310 mBlockList = mSection.getBlockList(); 311 for (int i = 0; i < mBlockList.size(); i++) { 312 Block b = mBlockList.get(i); 313 if (b != null) { 314 final int index = i; // block index 315 PropertyChangeListener listener = (PropertyChangeEvent e) -> { 316 handleBlockChange(index, e); 317 }; 318 b.addPropertyChangeListener(listener); 319 mBlockListeners.add(listener); 320 } 321 } 322 } 323 324 private synchronized void handleBlockChange(int index, PropertyChangeEvent e) { 325 if (e.getPropertyName().equals("state")) { 326 if (mBlockList == null) { 327 mBlockList = mSection.getBlockList(); 328 } 329 330 Block b = mBlockList.get(index); 331 if (!isInActiveBlockList(b)) { 332 int occ = b.getState(); 333 Runnable handleBlockChange = new RespondToBlockStateChange(b, occ, this); 334 Thread tBlockChange = jmri.util.ThreadingUtil.newThread(handleBlockChange, "Allocated Section Block Change on " + b.getDisplayName()); 335 tBlockChange.start(); 336 addToActiveBlockList(b); 337 if (InstanceManager.getDefault(DispatcherFrame.class).getSupportVSDecoder()) { 338 firePropertyChangeEvent("BlockStateChange", null, b.getSystemName()); // NOI18N 339 } 340 } 341 } 342 } 343 344 protected Block getExitBlock() { 345 if (mNextSection == null) { 346 return null; 347 } 348 EntryPoint ep = mSection.getExitPointToSection(mNextSection, mSection.getState()); 349 if (ep != null) { 350 return ep.getBlock(); 351 } 352 return null; 353 } 354 355 protected Block getEnterBlock(AllocatedSection previousAllocatedSection) { 356 if (previousAllocatedSection == null) { 357 return null; 358 } 359 Section sPrev = previousAllocatedSection.getSection(); 360 EntryPoint ep = mSection.getEntryPointFromSection(sPrev, mSection.getState()); 361 if (ep != null) { 362 return ep.getBlock(); 363 } 364 return null; 365 } 366 367 protected synchronized void addToActiveBlockList(Block b) { 368 if (b != null) { 369 mActiveBlockList.add(b); 370 } 371 } 372 373 protected synchronized void removeFromActiveBlockList(Block b) { 374 if (b != null) { 375 for (int i = 0; i < mActiveBlockList.size(); i++) { 376 if (b == mActiveBlockList.get(i)) { 377 mActiveBlockList.remove(i); 378 return; 379 } 380 } 381 } 382 } 383 384 protected synchronized boolean isInActiveBlockList(Block b) { 385 if (b != null) { 386 for (int i = 0; i < mActiveBlockList.size(); i++) { 387 if (b == mActiveBlockList.get(i)) { 388 return true; 389 } 390 } 391 } 392 return false; 393 } 394 395 public synchronized void dispose() { 396 if ((mSectionListener != null) && (mSection != null)) { 397 mSection.removePropertyChangeListener(mSectionListener); 398 } 399 mSectionListener = null; 400 for (int i = mBlockListeners.size(); i > 0; i--) { 401 Block b = mBlockList.get(i - 1); 402 b.removePropertyChangeListener(mBlockListeners.get(i - 1)); 403 } 404 } 405 406// _________________________________________________________________________________________ 407 // This class responds to Block state change in a separate thread 408 class RespondToBlockStateChange implements Runnable { 409 410 public RespondToBlockStateChange(Block b, int occ, AllocatedSection as) { 411 _block = b; 412 _aSection = as; 413 _occ = occ; 414 } 415 416 @Override 417 public void run() { 418 // delay to insure that change is not a short spike 419 // The forced delay has been removed. The delay can be controlled by the debounce 420 // values in the sensor table. The use of an additional fixed 250 milliseconds 421 // caused it to always fail when crossing small blocks at speed. 422 if (mActiveTrain.getAutoActiveTrain() != null) { 423 // automatically running train 424 mActiveTrain.getAutoActiveTrain().handleBlockStateChange(_aSection, _block); 425 } else if (_occ == Block.OCCUPIED) { 426 // manual running train - block newly occupied 427 if (!mActiveTrain.getAutoRun()) { 428 if ((_block == mActiveTrain.getEndBlock()) && mActiveTrain.getReverseAtEnd()) { 429 // reverse direction of Allocated Sections 430 mActiveTrain.reverseAllAllocatedSections(); 431 mActiveTrain.setRestart(mActiveTrain.getDelayReverseRestart(),mActiveTrain.getReverseRestartDelay(), 432 mActiveTrain.getReverseRestartSensor(),mActiveTrain.getResetReverseRestartSensor()); 433 } else if ((_block == mActiveTrain.getStartBlock()) && mActiveTrain.getResetWhenDone()) { 434 // reset the direction of Allocated Sections 435 mActiveTrain.resetAllAllocatedSections(); 436 mActiveTrain.setRestart(mActiveTrain.getDelayedRestart(),mActiveTrain.getRestartDelay(), 437 mActiveTrain.getRestartSensor(),mActiveTrain.getResetRestartSensor()); 438 } else if (_block == mActiveTrain.getEndBlock() || _block == mActiveTrain.getStartBlock() ) { 439 mActiveTrain.setStatus(ActiveTrain.DONE); 440 } 441 } 442 } 443 // remove from lists 444 removeFromActiveBlockList(_block); 445 } 446 447 private Block _block = null; 448 private int _occ = 0; 449 private AllocatedSection _aSection = null; 450 } 451 452 public void addPropertyChangeListener(PropertyChangeListener listener) { 453 pcs.addPropertyChangeListener(listener); 454 } 455 456 public void removePropertyChangeListener(PropertyChangeListener listener) { 457 pcs.removePropertyChangeListener(listener); 458 } 459 460 protected void firePropertyChangeEvent(PropertyChangeEvent evt) { 461 pcs.firePropertyChange(evt); 462 } 463 464 protected void firePropertyChangeEvent(String name, Object oldVal, Object newVal) { 465 pcs.firePropertyChange(name, oldVal, newVal); 466 } 467 468 private final static Logger log = LoggerFactory.getLogger(AllocatedSection.class); 469}