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}