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