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