001package jmri.jmrit.dispatcher;
002
003import java.beans.PropertyChangeListener;
004import java.beans.PropertyChangeSupport;
005import java.util.ArrayList;
006import java.util.Date;
007import java.util.List;
008
009import javax.annotation.OverridingMethodsMustInvokeSuper;
010import javax.annotation.Nonnull;
011
012import jmri.Block;
013import jmri.InstanceManager;
014import jmri.NamedBeanHandle;
015import jmri.Path;
016import jmri.Section;
017import jmri.Sensor;
018import jmri.Transit;
019import jmri.Transit.TransitType;
020import jmri.TransitSection;
021import jmri.Section.SectionType;
022import jmri.beans.PropertyChangeProvider;
023import jmri.jmrit.display.layoutEditor.LayoutBlock;
024import jmri.jmrit.display.layoutEditor.LayoutBlockManager;
025
026/**
027 * This class holds information and options for an ActiveTrain, that is a train
028 * that has been linked to a Transit and activated for transit around the
029 * layout.
030 * <p>
031 * An ActiveTrain may be assigned one of the following modes, which specify how
032 * the active train will be run through its transit: AUTOMATIC - indicates the
033 * ActiveTrain will be run under automatic control of the computer. (Automatic
034 * Running) MANUAL - indicates an ActiveTrain running in AUTOMATIC mode has
035 * reached a Special Action in its Transit that requires MANUAL operation. When
036 * this happens, the status changes to WORKING, and the mode changes to MANUAL.
037 * The ActiveTrain will be run by an operator using a throttle. AUTOMATIC
038 * running is resumed when the work has been completed. DISPATCHED - indicates
039 * the ActiveTrain will be run by an operator using a throttle. A dispatcher
040 * will allocate Sections to the ActiveTrain as needed, control optional signals
041 * using a CTC panel or computer logic, and arbitrate any conflicts between
042 * ActiveTrains. (Human Dispatcher).
043 * <p>
044 * An ActiveTrain will have one of the following statuses:
045 * <dl>
046 * <dt>RUNNING</dt><dd>Actively running on the layout, according to its mode of
047 * operation.</dd>
048 * <dt>PAUSED</dt><dd>Paused waiting for a user-specified number of fast clock
049 * minutes. The Active Train is expected to move to either RUNNING or WAITING
050 * once the specified number of minutes has elapsed. This is intended for
051 * automatic station stops. (automatic trains only)</dd>
052 * <dt>WAITING</dt><dd>Stopped waiting for a Section allocation. This is the
053 * state the Active Train is in when it is created in Dispatcher.</dd>
054 * <dt>WORKING</dt><dd>Performing work under control of a human engineer. This is
055 * the state an Active Train assumes when an engineer is picking up or setting
056 * out cars at industries. (automatic trains only)</dd>
057 * <dt>READY</dt><dd>Train has completed WORKING, and is awaiting a restart -
058 * dispatcher clearance to resume running. (automatic trains only)</dd>
059 * <dt>STOPPED</dt><dd>Train was stopped by the dispatcher. Dispatcher must
060 * resume. (automatic trains only)</dd>
061 * <dt>DONE</dt><dd>Train has completed its transit of the layout and is ready to
062 * be terminated by the dispatcher, or Restart pressed to repeat the automated
063 * run.</dd>
064 * </dl>
065 * Status is a bound property.
066 * <p>
067 * The ActiveTrain status should maintained (setStatus) by the running class, or
068 * if running in DISPATCHED mode, by Dispatcher. When an ActiveTrain is WAITING,
069 * and the dispatcher allocates a section to it, the status of the ActiveTrain
070 * is automatically set to RUNNING. So an autoRun class can listen to the status
071 * of the ActiveTrain to trigger start up if the train has been waiting for the
072 * dispatcher. Note: There is still more to be programmed here.
073 * <p>
074 * Train information supplied when the ActiveTrain is created can come from any
075 * of the following:
076 * <dl>
077 * <dt>ROSTER</dt><dd>The train was selected from the JMRI roster menu</dd>
078 * <dt>OPERATIONS</dt><dd>The train was selected from trains available from JMRI
079 * operations</dd>
080 * <dt>USER</dt><dd>Neither menu was used--the user entered a name and DCC
081 * address.</dd>
082 * </dl>
083 * Train source information is recorded when an ActiveTrain is created,
084 * and may be referenced by getTrainSource if it is needed by other objects. The
085 * train source should be specified in the Dispatcher Options window prior to
086 * creating an ActiveTrain.
087 * <p>
088 * ActiveTrains are referenced via a list in DispatcherFrame, which serves as a
089 * manager for ActiveTrain objects.
090 * <p>
091 * ActiveTrains are transient, and are not saved to disk. Active Train
092 * information can be saved to disk, making set up with the same options, etc
093 * very easy.
094 * <p>
095 * An ActiveTrain runs through its Transit in the FORWARD direction, until a
096 * Transit Action reverses the direction of travel in the Transit. When running
097 * with its Transit reversed, the Active Train returns to its starting Section.
098 * Upon reaching and stopping in its starting Section, the Transit is
099 * automatically set back to the forward direction. If AutoRestart is set, the
100 * run is repeated. The direction of travel in the Transit is maintained here.
101 *
102 * <p>
103 * This file is part of JMRI.
104 * <p>
105 * JMRI is open source software; you can redistribute it and/or modify it under
106 * the terms of version 2 of the GNU General Public License as published by the
107 * Free Software Foundation. See the "COPYING" file for a copy of this license.
108 * <p>
109 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
110 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
111 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
112 *
113 * @author Dave Duchamp Copyright (C) 2008-2011
114 */
115public class ActiveTrain implements PropertyChangeProvider {
116
117    private static final jmri.NamedBean.DisplayOptions USERSYS = jmri.NamedBean.DisplayOptions.USERNAME_SYSTEMNAME;
118
119    /**
120     * Create an ActiveTrain.
121     *
122     * @param t           the transit linked to this ActiveTrain
123     * @param name        the train name
124     * @param trainSource the source for this ActiveTrain
125     */
126    public ActiveTrain(Transit t, String name, int trainSource) {
127        mTransit = t;
128        mTrainName = name;
129        mTrainSource = trainSource;
130    }
131
132    /**
133     * Constants representing the Status of this ActiveTrain When created, the
134     * Status of an Active Train is always WAITING,
135     */
136    public static final int RUNNING = 0x01;   // running on the layout
137    public static final int PAUSED = 0x02;    // paused for a number of fast minutes
138    public static final int WAITING = 0x04;   // waiting for a section allocation
139    public static final int WORKING = 0x08;   // actively working
140    public static final int READY = 0x10;   // completed work, waiting for restart
141    public static final int STOPPED = 0x20;   // stopped by the dispatcher (auto trains only)
142    public static final int DONE = 0x40;   // completed its transit
143
144    /**
145     * Constants representing Type of ActiveTrains.
146     */
147    public static final int NONE = 0x00;               // no train type defined
148    public static final int LOCAL_PASSENGER = 0x01;    // low priority local passenger train
149    public static final int LOCAL_FREIGHT = 0x02;      // low priority freight train performing local tasks
150    public static final int THROUGH_PASSENGER = 0x03;  // normal priority through passenger train
151    public static final int THROUGH_FREIGHT = 0x04;    // normal priority through freight train
152    public static final int EXPRESS_PASSENGER = 0x05;  // high priority passenger train
153    public static final int EXPRESS_FREIGHT = 0x06;    // high priority freight train
154    public static final int MOW = 0x07;          // low priority maintenance of way train
155
156    /**
157     * Constants representing the mode of running of the Active Train The mode
158     * is set when the Active Train is created. The mode may be switched during
159     * a run.
160     */
161    public static final int AUTOMATIC = 0x02;   // requires mAutoRun to be "true" (auto trains only)
162    public static final int MANUAL = 0x04;    // requires mAutoRun to be "true" (auto trains only)
163    public static final int DISPATCHED = 0x08;
164    public static final int TERMINATED = 0x10; //terminated
165
166    /**
167     * Constants representing the source of the train information
168     */
169    public static final int ROSTER = 0x01;
170    public static final int OPERATIONS = 0x02;
171    public static final int USER = 0x04;
172
173    /**
174     * The value of {@link #getAllocateMethod()} if allocating as many sections as are clear.
175     */
176    public static final int ALLOCATE_AS_FAR_AS_IT_CAN = -1;
177    /**
178     * The value of {@link #getAllocateMethod()} if allocating up until the next safe section
179     */
180    public static final int ALLOCATE_BY_SAFE_SECTIONS = 0;
181
182    /**
183     * String property constant for status.
184     */
185    public static final String PROPERTY_STATUS = "status";
186
187    /**
188     * String property constant for mode.
189     */
190    public static final String PROPERTY_MODE = "mode";
191
192    /**
193     * String property constant for signal.
194     */
195    public static final String PROPERTY_SIGNAL = "signal";
196
197    /**
198     * String property constant for signal.
199     */
200    public static final String PROPERTY_CURRENT_BLOCK = "currentblock";
201
202    /**
203     * String property constant for signal.
204     */
205    public static final String PROPERTY_NEXT_BLOCK = "nextblock";
206
207    /**
208     * String property constant for section allocated.
209     */
210    public static final String PROPERTY_SECTION_ALLOCATED = "sectionallocated";
211
212    /**
213     * String property constant for section de-allocated.
214     */
215    public static final String PROPERTY_SECTION_DEALLOCATED = "sectiondeallocated";
216
217    /**
218     * How much of the train can be detected
219     */
220    public enum TrainDetection {
221        TRAINDETECTION_WHOLETRAIN,
222        TRAINDETECTION_HEADONLY,
223        TRAINDETECTION_HEADANDTAIL
224    }
225
226    /**
227     * Scale Length type
228     */
229    public enum TrainLengthUnits {
230        TRAINLENGTH_SCALEFEET,
231        TRAINLENGTH_SCALEMETERS,
232        TRAINLENGTH_ACTUALINCHS,
233        TRAINLENGTH_ACTUALCM
234    }
235
236    // instance variables
237    private DispatcherFrame mDispatcher = null;
238    private Transit mTransit = null;
239    private String mTrainName = "";
240    private int mTrainSource = ROSTER;
241    private jmri.jmrit.roster.RosterEntry mRoster = null;
242    private int mStatus = WAITING;
243    private int mMode = DISPATCHED;
244    private boolean mTransitReversed = false;  // true if Transit is running in reverse
245    private boolean mAllocationReversed = false;  // true if allocating Sections in reverse
246    private AutoActiveTrain mAutoActiveTrain = null;
247    private final List<AllocatedSection> mAllocatedSections = new ArrayList<>();
248    private Section mLastAllocatedSection = null;
249    private Section mLastAllocOverrideSafe = null;
250    private int mLastAllocatedSectionSeqNumber = 0;
251    private Section mSecondAllocatedSection = null;
252    private int mNextAllocationNumber = 1;
253    private Section mNextSectionToAllocate = null;
254    private int mNextSectionSeqNumber = 0;
255    private int mNextSectionDirection = 0;
256    private Block mStartBlock = null;
257    private int mStartBlockSectionSequenceNumber = 0;
258    private Block mEndBlock = null;
259    private Section mEndBlockSection = null;
260    private int mEndBlockSectionSequenceNumber = 0;
261    private int mPriority = 0;
262    private boolean mAutoRun = false;
263    private String mDccAddress = "";
264    private boolean mResetWhenDone = true;
265    private boolean mReverseAtEnd = false;
266    private int mAllocateMethod = 3;
267    public static final int NODELAY = 0x00;
268    public static final int TIMEDDELAY = 0x01;
269    public static final int SENSORDELAY = 0x02;
270    private TrainDetection trainDetection = TrainDetection.TRAINDETECTION_HEADONLY;
271
272    private int mDelayedRestart = NODELAY;
273    private int mDelayedStart = NODELAY;
274    private int mDepartureTimeHr = 8;
275    private int mDepartureTimeMin = 0;
276    private int mRestartDelay = 0;
277    private NamedBeanHandle<Sensor> mStartSensor = null; // A Sensor that when changes state to active will trigger the trains start.
278    private boolean resetStartSensor = true;
279    private NamedBeanHandle<Sensor> mRestartSensor = null; // A Sensor that when changes state to active will trigger the trains restart.
280    private boolean resetRestartSensor = true;
281    private NamedBeanHandle<Sensor> mReverseRestartSensor = null; // A Sensor that when changes state to active will trigger the trains restart.
282    private boolean resetReverseRestartSensor = true;
283    private int mDelayReverseRestart = NODELAY;
284    private int mTrainType = LOCAL_FREIGHT;
285    private boolean terminateWhenFinished = false;
286    private String mNextTrain = "";
287    private int mSignalType;
288    // Runtime/config flag: whether to honour section stop sensors
289    private boolean useStopSensor = true;
290
291    // start up instance variables
292    private boolean mStarted = false;
293
294    //
295    // Access methods
296    //
297    public boolean getStarted() {
298        return mStarted;
299    }
300
301    public void setDispatcher(DispatcherFrame df) {
302        mDispatcher = df;
303        mSignalType = df.getSignalType();
304        if (mTransit.getTransitType() == TransitType.DYNAMICADHOC) {
305            mSignalType = DispatcherFrame.SECTIONSALLOCATED;
306        }
307    }
308
309    public void setStarted() {
310        mStarted = true;
311        mStatus = RUNNING;
312        holdAllocation(false);
313        setStatus(WAITING);
314        if (mAutoActiveTrain != null) {
315            mAutoActiveTrain.setupNewCurrentSignal(null,false);
316        }
317    }
318
319    public Transit getTransit() {
320        return mTransit;
321    }
322
323    public String getTransitName() {
324        return mTransit.getDisplayName();
325    }
326
327    public String getActiveTrainName() {
328        return (mTrainName + " / " + getTransitName());
329    }
330
331    // Note: Transit and Train may not be changed once an ActiveTrain is created.
332    public String getTrainName() {
333        return mTrainName;
334    }
335
336    public int getTrainSource() {
337        return mTrainSource;
338    }
339
340    public void setRosterEntry(jmri.jmrit.roster.RosterEntry re) {
341        mRoster = re;
342    }
343    
344    public boolean getUseStopSensor() { 
345        return useStopSensor;
346    }
347    
348    public void setUseStopSensor(boolean value) {
349        useStopSensor = value; 
350    }
351
352    public jmri.jmrit.roster.RosterEntry getRosterEntry() {
353        if (mRoster == null && getTrainSource() == ROSTER) {
354            //Try to resolve the roster based upon the train name
355            mRoster = jmri.jmrit.roster.Roster.getDefault().getEntryForId(getTrainName());
356        } else if (getTrainSource() != ROSTER) {
357            mRoster = null;
358        }
359        return mRoster;
360    }
361
362    public int getStatus() {
363        return mStatus;
364    }
365
366    public void setStatus(int status) {
367        if (restartPoint) {
368            return;
369        }
370        if ((status == RUNNING) || (status == PAUSED) || (status == WAITING) || (status == WORKING)
371                || (status == READY) || (status == STOPPED) || (status == DONE)) {
372            if (mStatus != status) {
373                int old = mStatus;
374                mStatus = status;
375                firePropertyChange(PROPERTY_STATUS, old, mStatus);
376                if (mStatus == DONE) {
377                    mDispatcher.terminateActiveTrain(this,terminateWhenFinished,true);
378                }
379            }
380        } else {
381            log.error("Invalid ActiveTrain status - {}", status);
382        }
383    }
384
385    public void setControlingSignal(Object oldSignal, Object newSignal) {
386        firePropertyChange(PROPERTY_SIGNAL, oldSignal, newSignal);
387    }
388
389    public void setNextBlock(Object oldSignal, Object newSignal) {
390        firePropertyChange(PROPERTY_NEXT_BLOCK, oldSignal, newSignal);
391    }
392
393    public void setCurrentBlock(Object oldSignal, Object newSignal) {
394        firePropertyChange(PROPERTY_CURRENT_BLOCK, oldSignal, newSignal);
395    }
396
397    public String getStatusText() {
398        if (mStatus == RUNNING) {
399            return Bundle.getMessage("RUNNING");
400        } else if (mStatus == PAUSED) {
401            return Bundle.getMessage("PAUSED");
402        } else if (mStatus == WAITING) {
403            if (!mStarted) {
404                if (mDelayedStart == TIMEDDELAY) {
405                    return jmri.jmrit.beantable.LogixTableAction.formatTime(mDepartureTimeHr,
406                            mDepartureTimeMin) + " " + Bundle.getMessage("START");
407                } else if (mDelayedStart == SENSORDELAY) {
408                    return (Bundle.getMessage("BeanNameSensor") + " " + getDelaySensorName());
409                }
410            }
411            return Bundle.getMessage("WAITING");
412        } else if (mStatus == WORKING) {
413            return Bundle.getMessage("WORKING");
414        } else if (mStatus == READY) {
415            if (restartPoint && getDelayedRestart() == TIMEDDELAY) {
416                return jmri.jmrit.beantable.LogixTableAction.formatTime(restartHr,
417                        restartMin) + " " + Bundle.getMessage("START");
418            } else if (restartPoint && getDelayedRestart() == SENSORDELAY) {
419                return (Bundle.getMessage("BeanNameSensor") + " " + getRestartSensorName());
420            }
421            return Bundle.getMessage("READY");
422        } else if (mStatus == STOPPED) {
423            return Bundle.getMessage("STOPPED");
424        } else if (mStatus == DONE) {
425            return Bundle.getMessage("DONE");
426        }
427        return ("");
428    }
429
430    /**
431     * sets the train detection type
432     * @param value {@link ActiveTrain.TrainDetection}
433     */
434    public void setTrainDetection(TrainDetection value) {
435        trainDetection = value;
436    }
437
438    /**
439     * Gets the train detection type
440     * @return {@link ActiveTrain.TrainDetection}
441     */
442    public TrainDetection getTrainDetection() {
443        return trainDetection;
444    }
445
446    private long maxTrainLengthMM = 0;
447    private boolean noLengthWarningGiven = false;
448
449    /**
450     * Set the train length, used for any train whose tail is not always
451     * detected.
452     *
453     * @param lengthMM length in layout mm, 0 if not known
454     */
455    public void setMaxTrainLengthMM(long lengthMM) {
456        maxTrainLengthMM = Math.max(0, lengthMM);
457    }
458
459    /**
460     * Get the train length.
461     *
462     * @return length in layout mm, 0 if not known
463     */
464    public long getMaxTrainLengthMM() {
465        return maxTrainLengthMM;
466    }
467
468    /**
469     * Report, once only, that this train has no length.
470     *
471     * @return true the first time it is called
472     */
473    boolean firstNoLengthWarning() {
474        boolean first = !noLengthWarningGiven;
475        noLengthWarningGiven = true;
476        return first;
477    }
478
479    public boolean isTransitReversed() {
480        return mTransitReversed;
481    }
482
483    public void setTransitReversed(boolean set) {
484        mTransitReversed = set;
485    }
486
487    public boolean isAllocationReversed() {
488        return mAllocationReversed;
489    }
490
491    public void setAllocationReversed(boolean set) {
492        mAllocationReversed = set;
493    }
494
495    public int getDelayedStart() {
496        return mDelayedStart;
497    }
498
499    public void setNextTrain(String nextTrain) {
500        mNextTrain = nextTrain;
501    }
502
503    public String getNextTrain() {
504        return mNextTrain;
505    }
506
507    public void setDelayedStart(int delay) {
508        mDelayedStart = delay;
509    }
510
511    public int getDelayedRestart() {
512        return mDelayedRestart;
513    }
514
515    public void setDelayedRestart(int delay) {
516        mDelayedRestart = delay;
517    }
518
519    public int getDelayReverseRestart() {
520        return mDelayReverseRestart;
521    }
522
523    public void setReverseDelayRestart(int delay) {
524        mDelayReverseRestart = delay;
525    }
526
527    public int getDepartureTimeHr() {
528        return mDepartureTimeHr;
529    }
530
531    public void setDepartureTimeHr(int hr) {
532        mDepartureTimeHr = hr;
533    }
534
535    public int getDepartureTimeMin() {
536        return mDepartureTimeMin;
537    }
538
539    public void setDepartureTimeMin(int min) {
540        mDepartureTimeMin = min;
541    }
542
543    public void setRestartDelay(int min) {
544        mRestartDelay = min;
545    }
546
547    public int getRestartDelay() {
548        return mRestartDelay;
549    }
550
551    int mReverseRestartDelay;
552    public int getReverseRestartDelay() {
553        return mReverseRestartDelay;
554    }
555    public void setReverseRestartDelay(int min) {
556        mReverseRestartDelay = min;
557    }
558
559    int restartHr = 0;
560    int restartMin = 0;
561
562    public int getRestartDepartHr() {
563        return restartHr;
564    }
565
566    public int getRestartDepartMin() {
567        return restartMin;
568    }
569
570    public void setTerminateWhenDone(boolean boo) {
571        terminateWhenFinished = boo;
572    }
573
574    public Sensor getDelaySensor() {
575        if (mStartSensor == null) {
576            return null;
577        }
578        return mStartSensor.getBean();
579    }
580
581    public String getDelaySensorName() {
582        if (mStartSensor == null) {
583            return null;
584        }
585        return mStartSensor.getName();
586    }
587
588    public void setDelaySensor(Sensor s) {
589        if (s == null) {
590            mStartSensor = null;
591            return;
592        }
593        mStartSensor = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(s.getDisplayName(), s);
594    }
595
596    public void setResetStartSensor(boolean b) {
597        resetStartSensor = b;
598    }
599
600    public boolean getResetStartSensor() {
601        return resetStartSensor;
602    }
603
604    public Sensor getReverseRestartSensor() {
605        if (mReverseRestartSensor == null) {
606            return null;
607        }
608        return mReverseRestartSensor.getBean();
609    }
610
611    public String getReverseRestartSensorName() {
612        if (mReverseRestartSensor == null) {
613            return null;
614        }
615        return mReverseRestartSensor.getName();
616    }
617
618    public void setReverseDelaySensor(Sensor s) {
619        if (s == null) {
620            mReverseRestartSensor = null;
621            return;
622        }
623        mReverseRestartSensor = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(s.getDisplayName(), s);
624    }
625
626    public void setReverseResetRestartSensor(boolean b) {
627        resetReverseRestartSensor = b;
628    }
629
630    public boolean getResetReverseRestartSensor() {
631        return resetReverseRestartSensor;
632    }
633
634    public Sensor getRestartSensor() {
635        if (mRestartSensor == null) {
636            return null;
637        }
638        return mRestartSensor.getBean();
639    }
640
641    public String getRestartSensorName() {
642        if (mRestartSensor == null) {
643            return null;
644        }
645        return mRestartSensor.getName();
646    }
647
648    public void setRestartSensor(Sensor s) {
649        if (s == null) {
650            mRestartSensor = null;
651            return;
652        }
653        mRestartSensor = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(s.getDisplayName(), s);
654    }
655
656    public void setResetRestartSensor(boolean b) {
657        resetRestartSensor = b;
658    }
659
660    public boolean getResetRestartSensor() {
661        return resetRestartSensor;
662    }
663
664    public int getSignalType() {
665        return mSignalType;
666    }
667
668    private java.beans.PropertyChangeListener delaySensorListener = null;
669    private java.beans.PropertyChangeListener restartSensorListener = null;
670    private java.beans.PropertyChangeListener restartAllocationSensorListener = null;
671
672    public void initializeDelaySensor() {
673        if (mStartSensor == null) {
674            log.error("Call to initialise delay on start sensor, but none specified");
675            return;
676        }
677        if (delaySensorListener == null) {
678            final ActiveTrain at = this;
679            delaySensorListener = e -> {
680                if (Sensor.PROPERTY_KNOWN_STATE.equals(e.getPropertyName())
681                        && ((Integer) e.getNewValue()) == Sensor.ACTIVE) {
682                    getDelaySensor().removePropertyChangeListener(delaySensorListener);
683                    mDispatcher.removeDelayedTrain(at);
684                    setStarted();
685                    mDispatcher.queueScanOfAllocationRequests();
686                    if (resetStartSensor) {
687                        try {
688                            getDelaySensor().setKnownState(Sensor.INACTIVE);
689                            log.debug("Start sensor {} set back to inActive", getDelaySensor().getDisplayName(USERSYS));
690                        } catch (jmri.JmriException ex) {
691                            log.error("Error resetting start sensor {} back to inActive",
692                                getDelaySensor().getDisplayName(USERSYS));
693                        }
694                    }
695                }
696            };
697        }
698        getDelaySensor().addPropertyChangeListener(delaySensorListener);
699    }
700
701    public void initializeRestartSensor(Sensor restartSensor, boolean resetSensor) {
702        if (restartSensor == null) {
703            log.error("Call to initialise delay on restart sensor, but none specified");
704            return;
705        }
706        if (restartSensorListener == null) {
707            final ActiveTrain at = this;
708            restartSensorListener = e -> {
709                if (Sensor.PROPERTY_KNOWN_STATE.equals(e.getPropertyName())
710                        && ((Integer) e.getNewValue()) == Sensor.ACTIVE) {
711                    restartSensor.removePropertyChangeListener(restartSensorListener);
712                    restartSensorListener = null;
713                    mDispatcher.removeDelayedTrain(at);
714                    restart();
715                    mDispatcher.queueScanOfAllocationRequests();
716                    if (resetSensor) {
717                        try {
718                            restartSensor.setKnownState(Sensor.INACTIVE);
719                            log.debug("Restart sensor {} set back to inActive",
720                                getRestartSensor().getDisplayName(USERSYS));
721                        } catch (jmri.JmriException ex) {
722                            log.error("Error resetting restart sensor back to inActive");
723                        }
724                    }
725                }
726            };
727        }
728        restartSensor.addPropertyChangeListener(restartSensorListener);
729    }
730
731    public void initializeRestartAllocationSensor(NamedBeanHandle<Sensor> restartAllocationSensor) {
732        if (restartAllocationSensor == null) {
733            log.error("Call to initialise delay on restart allocation sensor, but none specified");
734            return;
735        }
736        if (restartAllocationSensorListener == null) {
737            restartAllocationSensorListener = e -> {
738                if (Sensor.PROPERTY_KNOWN_STATE.equals(e.getPropertyName())
739                        && (((Integer) e.getNewValue()) == Sensor.INACTIVE)) {
740                    restartAllocationSensor.getBean().removePropertyChangeListener(restartAllocationSensorListener);
741                    restartAllocationSensorListener = null;
742                    mDispatcher.queueScanOfAllocationRequests();
743                }
744            };
745        }
746        restartAllocationSensor.getBean().addPropertyChangeListener(restartAllocationSensorListener);
747    }
748
749    public void setTrainType(int type) {
750        mTrainType = type;
751    }
752
753    /**
754     * set train type using localized string name as stored
755     *
756     * @param sType  name, such as "LOCAL_PASSENGER"
757     */
758    public void setTrainType(String sType) {
759        if (sType.equals(Bundle.getMessage("LOCAL_FREIGHT"))) {
760            setTrainType(LOCAL_FREIGHT);
761        } else if (sType.equals(Bundle.getMessage("LOCAL_PASSENGER"))) {
762            setTrainType(LOCAL_PASSENGER);
763        } else if (sType.equals(Bundle.getMessage("THROUGH_FREIGHT"))) {
764            setTrainType(THROUGH_FREIGHT);
765        } else if (sType.equals(Bundle.getMessage("THROUGH_PASSENGER"))) {
766            setTrainType(THROUGH_PASSENGER);
767        } else if (sType.equals(Bundle.getMessage("EXPRESS_FREIGHT"))) {
768            setTrainType(EXPRESS_FREIGHT);
769        } else if (sType.equals(Bundle.getMessage("EXPRESS_PASSENGER"))) {
770            setTrainType(EXPRESS_PASSENGER);
771        } else if (sType.equals(Bundle.getMessage("MOW"))) {
772            setTrainType(MOW);
773        }
774    }
775
776    public int getTrainType() {
777        return mTrainType;
778    }
779
780    public String getTrainTypeText() {
781        if (mTrainType == LOCAL_FREIGHT) {
782            return Bundle.getMessage("LOCAL_FREIGHT");
783        } else if (mTrainType == LOCAL_PASSENGER) {
784            return Bundle.getMessage("LOCAL_PASSENGER");
785        } else if (mTrainType == THROUGH_FREIGHT) {
786            return Bundle.getMessage("THROUGH_FREIGHT");
787        } else if (mTrainType == THROUGH_PASSENGER) {
788            return Bundle.getMessage("THROUGH_PASSENGER");
789        } else if (mTrainType == EXPRESS_FREIGHT) {
790            return Bundle.getMessage("EXPRESS_FREIGHT");
791        } else if (mTrainType == EXPRESS_PASSENGER) {
792            return Bundle.getMessage("EXPRESS_PASSENGER");
793        } else if (mTrainType == MOW) {
794            return Bundle.getMessage("MOW");
795        }
796        return ("");
797    }
798
799    public int getMode() {
800        return mMode;
801    }
802
803    public void forcePassNextSafeSection() {
804        for (AllocatedSection as: mAllocatedSections) {
805            if (as.getTransitSection().getSection() == mLastAllocatedSection
806                    && as.getTransitSection().isSafe()
807                    && as.getNextSection().getOccupancy() == Section.UNOCCUPIED) {
808                mLastAllocOverrideSafe = mLastAllocatedSection;
809            }
810        }
811    }
812
813    public void setMode(int mode) {
814        if ((mode == AUTOMATIC) || (mode == MANUAL)
815                || (mode == DISPATCHED || mode == TERMINATED)) {
816            int old = mMode;
817            mMode = mode;
818            firePropertyChange(PROPERTY_MODE, old, mMode);
819        } else {
820            log.error("Attempt to set ActiveTrain mode to illegal value - {}", mode);
821        }
822    }
823
824    @Nonnull
825    public String getModeText() {
826        switch (mMode) {
827            case AUTOMATIC:
828                return Bundle.getMessage("AUTOMATIC");
829            case MANUAL:
830                return Bundle.getMessage("MANUAL");
831            case DISPATCHED:
832                return Bundle.getMessage("DISPATCHED");
833            case TERMINATED:
834                return Bundle.getMessage("TERMINATED");
835            default:
836                return "";
837        }
838    }
839
840    public void setAutoActiveTrain(AutoActiveTrain aat) {
841        mAutoActiveTrain = aat;
842    }
843
844    public AutoActiveTrain getAutoActiveTrain() {
845        return mAutoActiveTrain;
846    }
847
848    public int getRunningDirectionFromSectionAndSeq(Section s, int seqNo) {
849        int dir = mTransit.getDirectionFromSectionAndSeq(s, seqNo);
850        if (mTransitReversed) {
851            if (dir == Section.FORWARD) {
852                dir = Section.REVERSE;
853            } else {
854                dir = Section.FORWARD;
855            }
856        }
857        return dir;
858    }
859
860    public int getAllocationDirectionFromSectionAndSeq(Section s, int seqNo) {
861        int dir = mTransit.getDirectionFromSectionAndSeq(s, seqNo);
862        if (mAllocationReversed) {
863            if (dir == Section.FORWARD) {
864                dir = Section.REVERSE;
865            } else {
866                dir = Section.FORWARD;
867            }
868        }
869        return dir;
870    }
871
872    public void addAllocatedSection(AllocatedSection as) {
873        if (as != null) {
874            mAllocatedSections.add(as);
875            if (as.getSection() == mNextSectionToAllocate) {
876                // this  is the next Section in the Transit, update pointers
877                mLastAllocatedSection = as.getSection();
878                mLastAllocOverrideSafe = null;
879                mLastAllocatedSectionSeqNumber = mNextSectionSeqNumber;
880                mNextSectionToAllocate = as.getNextSection();
881                mNextSectionSeqNumber = as.getNextSectionSequence();
882                mNextSectionDirection = getAllocationDirectionFromSectionAndSeq(
883                        mNextSectionToAllocate, mNextSectionSeqNumber);
884                as.setAllocationNumber(mNextAllocationNumber);
885                mNextAllocationNumber++;
886            } else {
887                // this is an extra allocated Section
888                as.setAllocationNumber(-1);
889            }
890            if ((mStatus == WAITING) && mStarted) {
891                setStatus(RUNNING);
892            }
893            if (as.getSequence() == 2) {
894                mSecondAllocatedSection = as.getSection();
895            }
896            if (mDispatcher.getNameInAllocatedBlock()) {
897                if (mDispatcher.getRosterEntryInBlock() && getRosterEntry() != null) {
898                    as.getSection().setNameFromActiveBlock(getRosterEntry());
899                } else {
900                    as.getSection().setNameInBlocks(mTrainName);
901                }
902                as.getSection().suppressNameUpdate(true);
903            }
904            if (mDispatcher.getExtraColorForAllocated()) {
905                as.getSection().setAlternateColorFromActiveBlock(true);
906            }
907            // notify anyone interested
908            firePropertyChange(PROPERTY_SECTION_ALLOCATED,as , null);
909            refreshPanel();
910        } else {
911            log.error("Null Allocated Section reference in addAllocatedSection of ActiveTrain");
912        }
913    }
914
915    private void refreshPanel() {
916        var editorManager = InstanceManager.getDefault(jmri.jmrit.display.EditorManager.class);
917        for (var panel : editorManager.getAll(jmri.jmrit.display.layoutEditor.LayoutEditor.class)) {
918            panel.redrawPanel();
919        }
920    }
921
922    public void removeAllocatedSection(AllocatedSection as) {
923        if (as == null) {
924            log.error("Null AllocatedSection reference in removeAllocatedSection of ActiveTrain");
925            return;
926        }
927        int index = -1;
928        for (int i = 0; i < mAllocatedSections.size(); i++) {
929            if (as == mAllocatedSections.get(i)) {
930                index = i;
931            }
932        }
933        if (index < 0) {
934            log.error("Attempt to remove an unallocated Section {}", as.getSection().getDisplayName(USERSYS));
935            return;
936        }
937        mAllocatedSections.remove(index);
938        for (Block b: as.getSection().getBlockList()) {
939            // A Block that is also in another allocated Section keeps its
940            // name and colour until that Section is released too.
941            if (!mDispatcher.checkForBlockInAllocatedSection(b, as.getSection())) {
942                releaseBlockDisplay(b);
943            }
944        }
945        // notify anyone interested
946        firePropertyChange(PROPERTY_SECTION_DEALLOCATED,as , null);
947        refreshPanel();
948        if (as.getSection() == mLastAllocatedSection) {
949            mLastAllocatedSection = null;
950            mLastAllocOverrideSafe = null;
951            if (!mAllocatedSections.isEmpty()) {
952                mLastAllocatedSection = mAllocatedSections.get(
953                        mAllocatedSections.size() - 1).getSection();
954                mLastAllocatedSectionSeqNumber = mAllocatedSections.size() - 1;
955            }
956        }
957    }
958
959    /**
960     * Remove what was shown for this train on a Block that is no longer
961     * allocated: the train name in an unoccupied Block, and the extra colour.
962     * The memory of the Layout Block follows the Block again.
963     *
964     * @param b the Block that is no longer allocated
965     */
966    private void releaseBlockDisplay(Block b) {
967        boolean nameInBlock = mDispatcher.getNameInAllocatedBlock();
968        if (nameInBlock && b.getState() == Block.UNOCCUPIED) {
969            b.setValue(null);
970        }
971        String userName = b.getUserName();
972        if (userName != null) {
973            LayoutBlock lb = InstanceManager.getDefault(LayoutBlockManager.class).getByUserName(userName);
974            if (lb != null) {
975                if (nameInBlock) {
976                    lb.setSuppressNameUpdate(false);
977                }
978                lb.setUseExtraColor(false);
979            }
980        }
981    }
982
983    private boolean allocatingAFresh = false;
984
985    /**
986     * This resets the state of the ActiveTrain so that it can be reallocated.
987     * A request made while the train is already being reset is ignored.
988     */
989    public void allocateAFresh() {
990        if (allocatingAFresh) {
991            log.debug("{}: already being reset, request ignored", getTrainName());
992            return;
993        }
994        allocatingAFresh = true;
995        try {
996            doAllocateAFresh();
997        } finally {
998            allocatingAFresh = false;
999        }
1000    }
1001
1002    /*
1003     * While this waits for the auto allocator on the GUI thread, other GUI
1004     * events, such as another Restart, are handled.
1005     */
1006    private void doAllocateAFresh() {
1007        setStatus(WAITING);
1008        holdAllocation = false;
1009        setTransitReversed(false);
1010        List<AllocatedSection> sectionsToRelease = new ArrayList<>();
1011        for (AllocatedSection as : mDispatcher.getAllocatedSectionsList()) {
1012            if (as.getActiveTrain() == this) {
1013                sectionsToRelease.add(as);
1014            }
1015        }
1016        for (AllocatedSection as : sectionsToRelease) {
1017            mDispatcher.releaseAllocatedSection(as, true); // need to find Allocated Section
1018            mDispatcher.queueWaitForEmpty(); //ensure release processed before proceding.
1019            as.getSection().setState(Section.FREE);
1020        }
1021        if (mLastAllocatedSection != null) {
1022            mLastAllocatedSection.setState(Section.FREE);
1023        }
1024        resetAllAllocatedSections();
1025        clearAllocations();
1026        setAllocationReversed(false);
1027        // wait for AutoAllocate to do complete.
1028        mDispatcher.queueWaitForEmpty();
1029        if (mAutoRun) {
1030            mAutoActiveTrain.allocateAFresh();
1031        }
1032        mDispatcher.allocateNewActiveTrain(this);
1033    }
1034
1035    public void clearAllocations() {
1036        for (AllocatedSection as : getAllocatedSectionList()) {
1037            removeAllocatedSection(as);
1038        }
1039    }
1040
1041    public List<AllocatedSection> getAllocatedSectionList() {
1042        List<AllocatedSection> list = new ArrayList<>();
1043        for (int i = 0; i < mAllocatedSections.size(); i++) {
1044            list.add(mAllocatedSections.get(i));
1045        }
1046        return list;
1047    }
1048
1049    /**
1050     * Returns list of all Blocks occupied by or allocated to this train. They
1051     * are in order from the tail of the train to the head of the train then on
1052     * to the forward-most allocated block. Note that unoccupied blocks can
1053     * exist before and after the occupied blocks.
1054     *
1055     * TODO: doesn't handle reversing of adjacent multi-block sections well
1056     *
1057     * @return the list of blocks order of occupation
1058     */
1059    public List<Block> getBlockList() {
1060        List<Block> list = new ArrayList<>();
1061        for (int i = 0; i < mAllocatedSections.size(); i++) { // loop thru allocated sections, then all blocks for each section
1062            Section s = mAllocatedSections.get(i).getSection();
1063            List<Block> bl = s.getBlockList();
1064            if (bl.size() > 1) { //sections with multiple blocks need extra logic
1065
1066                boolean blocksConnected = true;
1067                //determine if blocks should be added in forward or reverse order based on connectivity
1068                if (i == 0) { //for first section, compare last block to first of next section
1069                    if (mAllocatedSections.size() > 1
1070                            && //only one section, assume forward
1071                            !connected(bl.get(bl.size() - 1), mAllocatedSections.get(i + 1).getSection().getBlockList().get(0))) {
1072                        blocksConnected = false;
1073                    }
1074                } else { //not first section, check for connectivity between last block in list, and first block in this section
1075                    if (!connected(list.get(list.size() - 1), bl.get(0))) { //last block is not connected to first block, add reverse
1076                        blocksConnected = false;
1077                    }
1078                }
1079                if (blocksConnected) { //blocks were connected, so add to outgoing in forward order
1080                    for (int j = 0; j < bl.size(); j++) {
1081                        Block b = bl.get(j);
1082                        list.add(b);
1083                        log.trace("block {} ({}) added to list for Section {} (fwd)", b.getDisplayName(USERSYS),
1084                                (b.getState() == Block.OCCUPIED ? "OCCUPIED" : "UNOCCUPIED"),
1085                                s.getDisplayName(USERSYS));
1086                    }
1087                } else { //not connected, add in reverse order
1088                    for (int j = bl.size() - 1; j >= 0; j--) {
1089                        Block b = bl.get(j);
1090                        list.add(b);
1091                        log.trace("block {} ({}) added to list for Section {} (rev)", b.getDisplayName(USERSYS),
1092                                (b.getState() == Block.OCCUPIED ? "OCCUPIED" : "UNOCCUPIED"),
1093                                s.getDisplayName(USERSYS));
1094                    }
1095                }
1096
1097            } else { //single block sections are simply added to the outgoing list
1098                Block b = bl.get(0);
1099                list.add(b);
1100                log.trace("block {} ({}) added to list for Section {} (one)", b.getDisplayName(USERSYS),
1101                        (b.getState() == Block.OCCUPIED ? "OCCUPIED" : "UNOCCUPIED"),
1102                        s.getDisplayName(USERSYS));
1103            }
1104        }
1105        return list;
1106    }
1107
1108    /* copied from Section.java */
1109    private boolean connected(Block b1, Block b2) {
1110        if ((b1 != null) && (b2 != null)) {
1111            List<Path> paths = b1.getPaths();
1112            for (int i = 0; i < paths.size(); i++) {
1113                if (paths.get(i).getBlock() == b2) {
1114                    return true;
1115                }
1116            }
1117        }
1118        return false;
1119    }
1120
1121    public Section getLastAllocatedSection() {
1122        return mLastAllocatedSection;
1123    }
1124
1125    public Section getLastAllocOverrideSafe() {
1126        return mLastAllocOverrideSafe;
1127    }
1128
1129    public int getLastAllocatedSectionSeqNumber() {
1130        return mLastAllocatedSectionSeqNumber;
1131    }
1132
1133    public String getLastAllocatedSectionName() {
1134        if (mLastAllocatedSection == null) {
1135            return "<" + Bundle.getMessage("None").toLowerCase() + ">"; // <none>
1136        }
1137        return getSectionName(mLastAllocatedSection);
1138    }
1139
1140    public Section getNextSectionToAllocate() {
1141        return mNextSectionToAllocate;
1142    }
1143
1144    public int getNextSectionSeqNumber() {
1145        return mNextSectionSeqNumber;
1146    }
1147
1148    public String getNextSectionToAllocateName() {
1149        if (mNextSectionToAllocate == null) {
1150            return "<" + Bundle.getMessage("None").toLowerCase() + ">"; // <none>
1151        }
1152        return getSectionName(mNextSectionToAllocate);
1153    }
1154
1155    private String getSectionName(@Nonnull Section sc) {
1156        return sc.getDisplayName();
1157    }
1158
1159    public Block getStartBlock() {
1160        return mStartBlock;
1161    }
1162
1163    public void setStartBlock(Block sBlock) {
1164        mStartBlock = sBlock;
1165    }
1166
1167    public int getStartBlockSectionSequenceNumber() {
1168        return mStartBlockSectionSequenceNumber;
1169    }
1170
1171    public void setStartBlockSectionSequenceNumber(int sBlockSeqNum) {
1172        mStartBlockSectionSequenceNumber = sBlockSeqNum;
1173    }
1174
1175    public Block getEndBlock() {
1176        return mEndBlock;
1177    }
1178
1179    public void setEndBlock(Block eBlock) {
1180        mEndBlock = eBlock;
1181    }
1182
1183    public Section getEndBlockSection() {
1184        return mEndBlockSection;
1185    }
1186
1187    public void setEndBlockSection(Section eSection) {
1188        mEndBlockSection = eSection;
1189    }
1190
1191    public int getEndBlockSectionSequenceNumber() {
1192        return mEndBlockSectionSequenceNumber;
1193    }
1194
1195    public void setEndBlockSectionSequenceNumber(int eBlockSeqNum) {
1196        mEndBlockSectionSequenceNumber = eBlockSeqNum;
1197    }
1198
1199    public int getPriority() {
1200        return mPriority;
1201    }
1202
1203    public void setPriority(int priority) {
1204        mPriority = priority;
1205    }
1206
1207    public boolean getAutoRun() {
1208        return mAutoRun;
1209    }
1210
1211    public void setAutoRun(boolean autoRun) {
1212        mAutoRun = autoRun;
1213    }
1214
1215    public String getDccAddress() {
1216        return mDccAddress;
1217    }
1218
1219    public void setDccAddress(String dccAddress) {
1220        mDccAddress = dccAddress;
1221    }
1222
1223    public boolean getResetWhenDone() {
1224        return mResetWhenDone;
1225    }
1226
1227    public void setResetWhenDone(boolean s) {
1228        mResetWhenDone = s;
1229    }
1230
1231    public boolean getReverseAtEnd() {
1232        return mReverseAtEnd;
1233    }
1234
1235    public void setReverseAtEnd(boolean s) {
1236        mReverseAtEnd = s;
1237    }
1238
1239    protected Section getSecondAllocatedSection() {
1240        return mSecondAllocatedSection;
1241    }
1242
1243    /**
1244     * Returns the AllocateM Method to be used by autoAllocate
1245     *
1246     * @return The number of Blocks ahead to be allocated or 0 = Allocate By Safe
1247     *         sections or -1 - Allocate All The Way.
1248     */
1249    public int getAllocateMethod() {
1250        return mAllocateMethod;
1251    }
1252
1253    /**
1254     * Sets the Allocation Method to be used bu autoAllocate
1255     * @param i The number of Blocks ahead to be allocated or 0 = Allocate By Safe
1256     *          sections or -1 - Allocate All The Way.
1257     */
1258    public void setAllocateMethod(int i) {
1259        mAllocateMethod = i;
1260    }
1261
1262    //
1263    // Operating methods
1264    //
1265    public AllocationRequest initializeFirstAllocation() {
1266        if (!mAllocatedSections.isEmpty()) {
1267            log.error("ERROR - Request to initialize first allocation, when allocations already present");
1268            return null;
1269        }
1270        if ((mStartBlockSectionSequenceNumber > 0) && (mStartBlock != null)) {
1271            mNextSectionToAllocate = mTransit.getSectionFromBlockAndSeq(mStartBlock,
1272                    mStartBlockSectionSequenceNumber);
1273            if (mNextSectionToAllocate == null) {
1274                mNextSectionToAllocate = mTransit.getSectionFromConnectedBlockAndSeq(mStartBlock,
1275                        mStartBlockSectionSequenceNumber);
1276                if (mNextSectionToAllocate == null) {
1277                    log.error("ERROR - Cannot find Section for first allocation of ActiveTrain{}", getActiveTrainName());
1278                    return null;
1279                }
1280            }
1281            mNextSectionSeqNumber = mStartBlockSectionSequenceNumber;
1282            mNextSectionDirection = getAllocationDirectionFromSectionAndSeq(mNextSectionToAllocate,
1283                    mNextSectionSeqNumber);
1284        } else {
1285            log.error("ERROR - Insufficient information to initialize first allocation");
1286            return null;
1287        }
1288        if (!mDispatcher.requestAllocation(this,
1289                mNextSectionToAllocate, mNextSectionDirection, mNextSectionSeqNumber, true, null, true)) {
1290            log.error("Allocation request failed for first allocation of {}", getActiveTrainName());
1291        }
1292        if (mDispatcher.getRosterEntryInBlock() && getRosterEntry() != null) {
1293            mStartBlock.setValue(getRosterEntry());
1294        } else if (mDispatcher.getShortNameInBlock()) {
1295            mStartBlock.setValue(mTrainName);
1296        }
1297        AllocationRequest ar = mDispatcher.findAllocationRequestInQueue(mNextSectionToAllocate,
1298                mNextSectionSeqNumber, mNextSectionDirection, this);
1299        return ar;
1300    }
1301
1302    protected boolean addEndSection(Section s, int seq) {
1303        AllocatedSection as = mAllocatedSections.get(mAllocatedSections.size() - 1);
1304        if (!as.setNextSection(s, seq)) {
1305            return false;
1306        }
1307        setEndBlockSection(s);
1308        setEndBlockSectionSequenceNumber(seq);
1309        //At this stage the section direction hasn't been set, by default the exit block returned is the reverse if the section is free
1310        setEndBlock(s.getExitBlock());
1311        mNextSectionSeqNumber = seq;
1312        mNextSectionToAllocate = s;
1313        return true;
1314    }
1315
1316    /*This is for use where the transit has been extended, then the last section has been cancelled no
1317     checks are performed, these should be done by a higher level code*/
1318    protected void removeLastAllocatedSection() {
1319        AllocatedSection as = mAllocatedSections.get(mAllocatedSections.size() - 1);
1320        //Set the end block using the AllocatedSections exit block before clearing the next section in the allocatedsection
1321        setEndBlock(as.getExitBlock());
1322
1323        as.setNextSection(null, 0);
1324        setEndBlockSection(as.getSection());
1325
1326        setEndBlockSectionSequenceNumber(getEndBlockSectionSequenceNumber() - 1);
1327        // In theory the following values should have already been set if there are no more sections to allocate.
1328        mNextSectionSeqNumber = 0;
1329        mNextSectionToAllocate = null;
1330    }
1331
1332    protected AllocatedSection reverseAllAllocatedSections() {
1333        AllocatedSection aSec = null;
1334        for (int i = 0; i < mAllocatedSections.size(); i++) {
1335            aSec = mAllocatedSections.get(i);
1336            int dir = mTransit.getDirectionFromSectionAndSeq(aSec.getSection(), aSec.getSequence());
1337            if (dir == Section.FORWARD) {
1338                aSec.getSection().setState(Section.REVERSE);
1339            } else {
1340                aSec.getSection().setState(Section.FORWARD);
1341            }
1342            aSec.setStoppingSensors();
1343        }
1344        return aSec;
1345    }
1346
1347    protected void resetAllAllocatedSections() {
1348        for (int i = 0; i < mAllocatedSections.size(); i++) {
1349            AllocatedSection aSec = mAllocatedSections.get(i);
1350            int dir = mTransit.getDirectionFromSectionAndSeq(aSec.getSection(), aSec.getSequence());
1351            aSec.getSection().setState(dir);
1352            aSec.setStoppingSensors();
1353        }
1354    }
1355
1356    protected void setRestart(int delayType, int restartDelay, Sensor delaySensor, boolean resetSensorAfter) {
1357        if (delayType == NODELAY) {
1358            holdAllocation(false);
1359            return;
1360        }
1361
1362        setStatus(READY);
1363        restartPoint = true;
1364        if (delayType == TIMEDDELAY) {
1365            Date now = InstanceManager.getDefault(jmri.Timebase.class).getTime();
1366            @SuppressWarnings("deprecation") // Date.getHours
1367            int nowHours = now.getHours();
1368            @SuppressWarnings("deprecation") // Date.getMinutes
1369            int nowMinutes = now.getMinutes();
1370            int hours = restartDelay / 60;
1371            int minutes = restartDelay % 60;
1372            restartHr = nowHours + hours + ((nowMinutes + minutes) / 60);
1373            restartMin = ((nowMinutes + minutes) % 60);
1374            if (restartHr>23){
1375                restartHr=restartHr-24;
1376            }
1377        }
1378        mDispatcher.addDelayedTrain(this, delayType, delaySensor, resetSensorAfter );
1379    }
1380
1381    protected boolean isInAllocatedList(AllocatedSection as) {
1382        for (int i = 0; i < mAllocatedSections.size(); i++) {
1383            if (mAllocatedSections.get(i) == as) {
1384                return true;
1385            }
1386        }
1387        return false;
1388    }
1389
1390    protected boolean isInAllocatedList(Section s) {
1391        for (int i = 0; i < mAllocatedSections.size(); i++) {
1392            if ((mAllocatedSections.get(i)).getSection() == s) {
1393                return true;
1394            }
1395        }
1396        return false;
1397    }
1398
1399
1400    boolean restartPoint = false;
1401
1402    private boolean holdAllocation = false;
1403
1404    protected void holdAllocation(boolean boo) {
1405        holdAllocation = boo;
1406    }
1407
1408    protected boolean holdAllocation() {
1409        return holdAllocation;
1410    }
1411
1412    protected boolean reachedRestartPoint() {
1413        return restartPoint;
1414    }
1415
1416    protected void restart() {
1417        log.debug("{}: restarting", getTrainName());
1418        restartPoint = false;
1419        holdAllocation(false);
1420        setStatus(WAITING);
1421        if (mAutoActiveTrain != null) {
1422            mAutoActiveTrain.setupNewCurrentSignal(null,false);
1423        }
1424    }
1425
1426    public void terminate() {
1427        mDispatcher.removeDelayedTrain(this);
1428        if (getDelaySensor() != null && delaySensorListener != null) {
1429            getDelaySensor().removePropertyChangeListener(delaySensorListener);
1430        }
1431        if (getRestartSensor() != null && restartSensorListener != null) {
1432            getRestartSensor().removePropertyChangeListener(restartSensorListener);
1433        }
1434        setMode(TERMINATED);
1435        mTransit.setState(Transit.IDLE);
1436        deleteAdHocTransit(mTransit);
1437    }
1438
1439    private void deleteAdHocTransit(Transit sysname) {
1440        Transit adht = sysname;
1441        if (adht != null && adht.getTransitType() == TransitType.DYNAMICADHOC) {
1442            List<Section> tmpSecs = new ArrayList<>();
1443            for (TransitSection ts : adht.getTransitSectionList()) {
1444                if (ts.getSection().getSectionType() == SectionType.DYNAMICADHOC) {
1445                    tmpSecs.add(ts.getSection());
1446                }
1447            }
1448            InstanceManager.getDefault(jmri.TransitManager.class).deleteTransit(adht);
1449            for (Section ts : tmpSecs) {
1450                InstanceManager.getDefault(jmri.SectionManager.class).deleteSection(ts);
1451            }
1452        }
1453    }
1454
1455    public void dispose() {
1456        if (getTransit()!=null) {
1457            getTransit().removeTemporarySections();
1458        }
1459    }
1460
1461    // Property Change Support
1462    private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
1463
1464    @OverridingMethodsMustInvokeSuper
1465    protected void firePropertyChange(String p, Object old, Object n) {
1466        pcs.firePropertyChange(p, old, n);
1467    }
1468
1469    @Override
1470    public void addPropertyChangeListener(PropertyChangeListener listener) {
1471        pcs.addPropertyChangeListener(listener);
1472    }
1473
1474    @Override
1475    public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
1476        pcs.addPropertyChangeListener(propertyName, listener);
1477    }
1478
1479    @Override
1480    public PropertyChangeListener[] getPropertyChangeListeners() {
1481        return pcs.getPropertyChangeListeners();
1482    }
1483
1484    @Override
1485    public PropertyChangeListener[] getPropertyChangeListeners(String propertyName) {
1486        return pcs.getPropertyChangeListeners(propertyName);
1487    }
1488
1489    @Override
1490    public void removePropertyChangeListener(PropertyChangeListener listener) {
1491        pcs.removePropertyChangeListener(listener);
1492    }
1493
1494    @Override
1495    public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
1496        pcs.removePropertyChangeListener(propertyName, listener);
1497    }
1498
1499    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActiveTrain.class);
1500
1501}