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