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        int bStart = mSection.getBlockSequenceNumber(block);
126        if (mSection.getState() == Section.FORWARD) {
127            for (int ix = bStart;ix < mSection.getNumBlocks();ix++) {
128                Block b = (mSection.getBlockBySequenceNumber(ix));
129                if (b != null) {
130                    length += b.getLengthMm();
131                }
132            }
133        }
134        else if (mSection.getState() == Section.REVERSE) {
135            for (int ix =  0 ;ix <= bStart ;ix++) {
136                Block b = (mSection.getBlockBySequenceNumber(ix));
137                if (b != null) {
138                    length += b.getLengthMm();
139                }
140            }
141        }
142        log.trace("Remaining length in section[{}] is [{}]",mSection.getDisplayName(), length);
143        return length;
144    }
145
146    public List<LayoutTrackExpectedState<LayoutTurnout>> getAutoTurnoutsResponse() {
147        return autoTurnoutsResponse;
148    }
149
150    public Section getSection() {
151        return mSection;
152    }
153
154    public String getSectionName() {
155        String s = mSection.getDisplayName();
156        return s;
157    }
158
159    public ActiveTrain getActiveTrain() {
160        return mActiveTrain;
161    }
162
163    public String getActiveTrainName() {
164        return (mActiveTrain.getTrainName() + "/" + mActiveTrain.getTransitName());
165    }
166
167    public int getSequence() {
168        return mSequence;
169    }
170
171    public Section getNextSection() {
172        return mNextSection;
173    }
174
175    public int getNextSectionSequence() {
176        return mNextSectionSequence;
177    }
178
179    protected boolean setNextSection(Section sec, int i) {
180        if (sec == null) {
181            mNextSection = null;
182            mNextSectionSequence = i;
183            return true;
184        }
185        if (mNextSection != null) {
186            log.error("Next section is already set");
187            return false;
188        }
189        mNextSection = sec;
190        return true;
191    }
192
193    public void setNextSectionSequence(int i) {
194        mNextSectionSequence = i;
195    }
196
197    public boolean getEntered() {
198        return mEntered;
199    }
200
201    public boolean getExited() {
202        return mExited;
203    }
204
205    public int getAllocationNumber() {
206        return mAllocationNumber;
207    }
208
209    public void setAllocationNumber(int n) {
210        mAllocationNumber = n;
211    }
212
213    public Sensor getForwardStoppingSensor() {
214        return mForwardStoppingSensor;
215    }
216
217    public Sensor getReverseStoppingSensor() {
218        return mReverseStoppingSensor;
219    }
220
221    // instance variables used with automatic running of trains
222    private int mIndex = 0;
223    private PropertyChangeListener mExitSignalListener = null;
224    private final List<PropertyChangeListener> mBlockListeners = new ArrayList<>();
225    private List<Block> mBlockList = null;
226    private final List<Block> mActiveBlockList = new ArrayList<>();
227
228    //
229    // Access methods for automatic running instance variables
230    //
231    public void setIndex(int i) {
232        mIndex = i;
233    }
234
235    public int getIndex() {
236        return mIndex;
237    }
238
239    public void setExitSignalListener(PropertyChangeListener xSigListener) {
240        mExitSignalListener = xSigListener;
241    }
242
243    public PropertyChangeListener getExitSignalListener() {
244        return mExitSignalListener;
245    }
246
247    /**
248     * Methods
249     */
250    final protected void setStoppingSensors() {
251        if (mSection.getState() == Section.FORWARD) {
252            mForwardStoppingSensor = mSection.getForwardStoppingSensor();
253            mReverseStoppingSensor = mSection.getReverseStoppingSensor();
254        } else {
255            mForwardStoppingSensor = mSection.getReverseStoppingSensor();
256            mReverseStoppingSensor = mSection.getForwardStoppingSensor();
257        }
258    }
259
260    protected TransitSection getTransitSection() {
261        return mActiveTrain.getTransit().getTransitSectionFromSectionAndSeq(mSection, mSequence);
262    }
263
264    public int getDirection() {
265        return mSection.getState();
266    }
267
268    public int getActualLength() {
269        return mSection.getActualLength();
270    }
271
272    public void reset() {
273        mExited = false;
274        mEntered = false;
275        if (mSection.getOccupancy() == Section.OCCUPIED) {
276            mEntered = true;
277        }
278    }
279
280    private synchronized void handleSectionChange(PropertyChangeEvent e) {
281        log.trace("{}:AllocatedSection[{}]:handleSectionChange Property Name[{}] old[{}] New[{}]",
282                mActiveTrain.getActiveTrainName(), mSection.getDisplayName(),
283                e.getPropertyName(), e.getOldValue(), e.getNewValue());
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}