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 getLength() { 272 return mSection.getLengthI(InstanceManager.getDefault(DispatcherFrame.class).getUseScaleMeters(), 273 InstanceManager.getDefault(DispatcherFrame.class).getScale()); 274 } 275 276 public void reset() { 277 mExited = false; 278 mEntered = false; 279 if (mSection.getOccupancy() == Section.OCCUPIED) { 280 mEntered = true; 281 } 282 } 283 284 private synchronized void handleSectionChange(PropertyChangeEvent e) { 285 if (mSection.getOccupancy() == Section.OCCUPIED) { 286 mEntered = true; 287 } else if (mSection.getOccupancy() == Section.UNOCCUPIED) { 288 if (mEntered) { 289 mExited = true; 290 } 291 } 292 if (mActiveTrain.getAutoActiveTrain() != null) { 293 if (e.getPropertyName().equals("state")) { 294 mActiveTrain.getAutoActiveTrain().handleSectionStateChange(this); 295 } else if (e.getPropertyName().equals("occupancy")) { 296 mActiveTrain.getAutoActiveTrain().handleSectionOccupancyChange(this); 297 } 298 } 299 InstanceManager.getDefault(DispatcherFrame.class).sectionOccupancyChanged(); 300 } 301 302 public synchronized final void initializeMonitorBlockOccupancy() { 303 if (mBlockList != null) { 304 return; 305 } 306 mBlockList = mSection.getBlockList(); 307 for (int i = 0; i < mBlockList.size(); i++) { 308 Block b = mBlockList.get(i); 309 if (b != null) { 310 final int index = i; // block index 311 PropertyChangeListener listener = (PropertyChangeEvent e) -> { 312 handleBlockChange(index, e); 313 }; 314 b.addPropertyChangeListener(listener); 315 mBlockListeners.add(listener); 316 } 317 } 318 } 319 320 private synchronized void handleBlockChange(int index, PropertyChangeEvent e) { 321 if (e.getPropertyName().equals("state")) { 322 if (mBlockList == null) { 323 mBlockList = mSection.getBlockList(); 324 } 325 326 Block b = mBlockList.get(index); 327 if (!isInActiveBlockList(b)) { 328 int occ = b.getState(); 329 Runnable handleBlockChange = new RespondToBlockStateChange(b, occ, this); 330 Thread tBlockChange = jmri.util.ThreadingUtil.newThread(handleBlockChange, "Allocated Section Block Change on " + b.getDisplayName()); 331 tBlockChange.start(); 332 addToActiveBlockList(b); 333 if (InstanceManager.getDefault(DispatcherFrame.class).getSupportVSDecoder()) { 334 firePropertyChangeEvent("BlockStateChange", null, b.getSystemName()); // NOI18N 335 } 336 } 337 } 338 } 339 340 protected Block getExitBlock() { 341 if (mNextSection == null) { 342 return null; 343 } 344 EntryPoint ep = mSection.getExitPointToSection(mNextSection, mSection.getState()); 345 if (ep != null) { 346 return ep.getBlock(); 347 } 348 return null; 349 } 350 351 protected Block getEnterBlock(AllocatedSection previousAllocatedSection) { 352 if (previousAllocatedSection == null) { 353 return null; 354 } 355 Section sPrev = previousAllocatedSection.getSection(); 356 EntryPoint ep = mSection.getEntryPointFromSection(sPrev, mSection.getState()); 357 if (ep != null) { 358 return ep.getBlock(); 359 } 360 return null; 361 } 362 363 protected synchronized void addToActiveBlockList(Block b) { 364 if (b != null) { 365 mActiveBlockList.add(b); 366 } 367 } 368 369 protected synchronized void removeFromActiveBlockList(Block b) { 370 if (b != null) { 371 for (int i = 0; i < mActiveBlockList.size(); i++) { 372 if (b == mActiveBlockList.get(i)) { 373 mActiveBlockList.remove(i); 374 return; 375 } 376 } 377 } 378 } 379 380 protected synchronized boolean isInActiveBlockList(Block b) { 381 if (b != null) { 382 for (int i = 0; i < mActiveBlockList.size(); i++) { 383 if (b == mActiveBlockList.get(i)) { 384 return true; 385 } 386 } 387 } 388 return false; 389 } 390 391 public synchronized void dispose() { 392 if ((mSectionListener != null) && (mSection != null)) { 393 mSection.removePropertyChangeListener(mSectionListener); 394 } 395 mSectionListener = null; 396 for (int i = mBlockListeners.size(); i > 0; i--) { 397 Block b = mBlockList.get(i - 1); 398 b.removePropertyChangeListener(mBlockListeners.get(i - 1)); 399 } 400 } 401 402// _________________________________________________________________________________________ 403 // This class responds to Block state change in a separate thread 404 class RespondToBlockStateChange implements Runnable { 405 406 public RespondToBlockStateChange(Block b, int occ, AllocatedSection as) { 407 _block = b; 408 _aSection = as; 409 _occ = occ; 410 } 411 412 @Override 413 public void run() { 414 // delay to insure that change is not a short spike 415 // The forced delay has been removed. The delay can be controlled by the debounce 416 // values in the sensor table. The use of an additional fixed 250 milliseconds 417 // caused it to always fail when crossing small blocks at speed. 418 if (mActiveTrain.getAutoActiveTrain() != null) { 419 // automatically running train 420 mActiveTrain.getAutoActiveTrain().handleBlockStateChange(_aSection, _block); 421 } else if (_occ == Block.OCCUPIED) { 422 // manual running train - block newly occupied 423 if (!mActiveTrain.getAutoRun()) { 424 if ((_block == mActiveTrain.getEndBlock()) && mActiveTrain.getReverseAtEnd()) { 425 // reverse direction of Allocated Sections 426 mActiveTrain.reverseAllAllocatedSections(); 427 mActiveTrain.setRestart(mActiveTrain.getDelayReverseRestart(),mActiveTrain.getReverseRestartDelay(), 428 mActiveTrain.getReverseRestartSensor(),mActiveTrain.getResetReverseRestartSensor()); 429 } else if ((_block == mActiveTrain.getStartBlock()) && mActiveTrain.getResetWhenDone()) { 430 // reset the direction of Allocated Sections 431 mActiveTrain.resetAllAllocatedSections(); 432 mActiveTrain.setRestart(mActiveTrain.getDelayedRestart(),mActiveTrain.getRestartDelay(), 433 mActiveTrain.getRestartSensor(),mActiveTrain.getResetRestartSensor()); 434 } else if (_block == mActiveTrain.getEndBlock() || _block == mActiveTrain.getStartBlock() ) { 435 mActiveTrain.setStatus(ActiveTrain.DONE); 436 } 437 } 438 } 439 // remove from lists 440 removeFromActiveBlockList(_block); 441 } 442 443 private Block _block = null; 444 private int _occ = 0; 445 private AllocatedSection _aSection = null; 446 } 447 448 public void addPropertyChangeListener(PropertyChangeListener listener) { 449 pcs.addPropertyChangeListener(listener); 450 } 451 452 public void removePropertyChangeListener(PropertyChangeListener listener) { 453 pcs.removePropertyChangeListener(listener); 454 } 455 456 protected void firePropertyChangeEvent(PropertyChangeEvent evt) { 457 pcs.firePropertyChange(evt); 458 } 459 460 protected void firePropertyChangeEvent(String name, Object oldVal, Object newVal) { 461 pcs.firePropertyChange(name, oldVal, newVal); 462 } 463 464 private final static Logger log = LoggerFactory.getLogger(AllocatedSection.class); 465}