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    public String getStatusText() {
319        if (mStatus == RUNNING) {
320            return Bundle.getMessage("RUNNING");
321        } else if (mStatus == PAUSED) {
322            return Bundle.getMessage("PAUSED");
323        } else if (mStatus == WAITING) {
324            if (!mStarted) {
325                if (mDelayedStart == TIMEDDELAY) {
326                    return jmri.jmrit.beantable.LogixTableAction.formatTime(mDepartureTimeHr,
327                            mDepartureTimeMin) + " " + Bundle.getMessage("START");
328                } else if (mDelayedStart == SENSORDELAY) {
329                    return (Bundle.getMessage("BeanNameSensor") + " " + getDelaySensorName());
330                }
331            }
332            return Bundle.getMessage("WAITING");
333        } else if (mStatus == WORKING) {
334            return Bundle.getMessage("WORKING");
335        } else if (mStatus == READY) {
336            if (restartPoint && getDelayedRestart() == TIMEDDELAY) {
337                return jmri.jmrit.beantable.LogixTableAction.formatTime(restartHr,
338                        restartMin) + " " + Bundle.getMessage("START");
339            } else if (restartPoint && getDelayedRestart() == SENSORDELAY) {
340                return (Bundle.getMessage("BeanNameSensor") + " " + getRestartSensorName());
341            }
342            return Bundle.getMessage("READY");
343        } else if (mStatus == STOPPED) {
344            return Bundle.getMessage("STOPPED");
345        } else if (mStatus == DONE) {
346            return Bundle.getMessage("DONE");
347        }
348        return ("");
349    }
350
351    /**
352     * sets the train detection type
353     * @param value {@link ActiveTrain.TrainDetection}
354     */
355    public void setTrainDetection(TrainDetection value) {
356        trainDetection = value;
357    }
358
359    /**
360     * Gets the train detection type
361     * @return {@link ActiveTrain.TrainDetection}
362     */
363    public TrainDetection getTrainDetection() {
364        return trainDetection;
365    }
366
367    public boolean isTransitReversed() {
368        return mTransitReversed;
369    }
370
371    public void setTransitReversed(boolean set) {
372        mTransitReversed = set;
373    }
374
375    public boolean isAllocationReversed() {
376        return mAllocationReversed;
377    }
378
379    public void setAllocationReversed(boolean set) {
380        mAllocationReversed = set;
381    }
382
383    public int getDelayedStart() {
384        return mDelayedStart;
385    }
386
387    public void setNextTrain(String nextTrain) {
388        mNextTrain = nextTrain;
389    }
390
391    public String getNextTrain() {
392        return mNextTrain;
393    }
394
395    public void setDelayedStart(int delay) {
396        mDelayedStart = delay;
397    }
398
399    public int getDelayedRestart() {
400        return mDelayedRestart;
401    }
402
403    public void setDelayedRestart(int delay) {
404        mDelayedRestart = delay;
405    }
406
407    public int getDelayReverseRestart() {
408        return mDelayReverseRestart;
409    }
410
411    public void setReverseDelayRestart(int delay) {
412        mDelayReverseRestart = delay;
413    }
414
415    public int getDepartureTimeHr() {
416        return mDepartureTimeHr;
417    }
418
419    public void setDepartureTimeHr(int hr) {
420        mDepartureTimeHr = hr;
421    }
422
423    public int getDepartureTimeMin() {
424        return mDepartureTimeMin;
425    }
426
427    public void setDepartureTimeMin(int min) {
428        mDepartureTimeMin = min;
429    }
430
431    public void setRestartDelay(int min) {
432        mRestartDelay = min;
433    }
434
435    public int getRestartDelay() {
436        return mRestartDelay;
437    }
438
439    int mReverseRestartDelay;
440    public int getReverseRestartDelay() {
441        return mReverseRestartDelay;
442    }
443    public void setReverseRestartDelay(int min) {
444        mReverseRestartDelay = min;
445    }
446
447    int restartHr = 0;
448    int restartMin = 0;
449
450    public int getRestartDepartHr() {
451        return restartHr;
452    }
453
454    public int getRestartDepartMin() {
455        return restartMin;
456    }
457
458    public void setTerminateWhenDone(boolean boo) {
459        terminateWhenFinished = boo;
460    }
461
462    public jmri.Sensor getDelaySensor() {
463        if (mStartSensor == null) {
464            return null;
465        }
466        return mStartSensor.getBean();
467    }
468
469    public String getDelaySensorName() {
470        if (mStartSensor == null) {
471            return null;
472        }
473        return mStartSensor.getName();
474    }
475
476    public void setDelaySensor(jmri.Sensor s) {
477        if (s == null) {
478            mStartSensor = null;
479            return;
480        }
481        mStartSensor = jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(s.getDisplayName(), s);
482    }
483
484    public void setResetStartSensor(boolean b) {
485        resetStartSensor = b;
486    }
487
488    public boolean getResetStartSensor() {
489        return resetStartSensor;
490    }
491
492    public jmri.Sensor getReverseRestartSensor() {
493        if (mReverseRestartSensor == null) {
494            return null;
495        }
496        return mReverseRestartSensor.getBean();
497    }
498
499    public String getReverseRestartSensorName() {
500        if (mReverseRestartSensor == null) {
501            return null;
502        }
503        return mReverseRestartSensor.getName();
504    }
505
506    public void setReverseDelaySensor(jmri.Sensor s) {
507        if (s == null) {
508            mReverseRestartSensor = null;
509            return;
510        }
511        mReverseRestartSensor = jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(s.getDisplayName(), s);
512    }
513
514    public void setReverseResetRestartSensor(boolean b) {
515        resetReverseRestartSensor = b;
516    }
517
518    public boolean getResetReverseRestartSensor() {
519        return resetReverseRestartSensor;
520    }
521
522    public jmri.Sensor getRestartSensor() {
523        if (mRestartSensor == null) {
524            return null;
525        }
526        return mRestartSensor.getBean();
527    }
528
529    public String getRestartSensorName() {
530        if (mRestartSensor == null) {
531            return null;
532        }
533        return mRestartSensor.getName();
534    }
535
536    public void setRestartSensor(jmri.Sensor s) {
537        if (s == null) {
538            mRestartSensor = null;
539            return;
540        }
541        mRestartSensor = jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(s.getDisplayName(), s);
542    }
543
544    public void setResetRestartSensor(boolean b) {
545        resetRestartSensor = b;
546    }
547    public boolean getResetRestartSensor() {
548        return resetRestartSensor;
549    }
550
551
552    private java.beans.PropertyChangeListener delaySensorListener = null;
553    private java.beans.PropertyChangeListener restartSensorListener = null;
554    private java.beans.PropertyChangeListener restartAllocationSensorListener = null;
555
556    public void initializeDelaySensor() {
557        if (mStartSensor == null) {
558            log.error("Call to initialise delay on start sensor, but none specified");
559            return;
560        }
561        if (delaySensorListener == null) {
562            final ActiveTrain at = this;
563            delaySensorListener = new java.beans.PropertyChangeListener() {
564                @Override
565                public void propertyChange(java.beans.PropertyChangeEvent e) {
566                    if (e.getPropertyName().equals("KnownState")) {
567                        if (((Integer) e.getNewValue()).intValue() == jmri.Sensor.ACTIVE) {
568                            getDelaySensor().removePropertyChangeListener(delaySensorListener);
569                            InstanceManager.getDefault(DispatcherFrame.class).removeDelayedTrain(at);
570                            setStarted();
571                            InstanceManager.getDefault(DispatcherFrame.class).queueScanOfAllocationRequests();
572                            if (resetStartSensor) {
573                                try {
574                                    getDelaySensor().setKnownState(jmri.Sensor.INACTIVE);
575                                    log.debug("Start sensor {} set back to inActive", getDelaySensor().getDisplayName(USERSYS));
576                                } catch (jmri.JmriException ex) {
577                                    log.error("Error resetting start sensor {} back to inActive", getDelaySensor().getDisplayName(USERSYS));
578                                }
579                            }
580                        }
581                    }
582                }
583            };
584        }
585        getDelaySensor().addPropertyChangeListener(delaySensorListener);
586    }
587
588    public void initializeRestartSensor(Sensor restartSensor, boolean resetSensor) {
589        if (restartSensor == null) {
590            log.error("Call to initialise delay on restart sensor, but none specified");
591            return;
592        }
593        if (restartSensorListener == null) {
594            final ActiveTrain at = this;
595            restartSensorListener = new java.beans.PropertyChangeListener() {
596                @Override
597                public void propertyChange(java.beans.PropertyChangeEvent e) {
598                    if (e.getPropertyName().equals("KnownState")) {
599                        if (((Integer) e.getNewValue()).intValue() == jmri.Sensor.ACTIVE) {
600                            restartSensor.removePropertyChangeListener(restartSensorListener);
601                            restartSensorListener = null;
602                            InstanceManager.getDefault(DispatcherFrame.class).removeDelayedTrain(at);
603                            restart();
604                            InstanceManager.getDefault(DispatcherFrame.class).queueScanOfAllocationRequests();
605                            if (resetSensor) {
606                                try {
607                                    restartSensor.setKnownState(jmri.Sensor.INACTIVE);
608                                    log.debug("Restart sensor {} set back to inActive", getRestartSensor().getDisplayName(USERSYS));
609                                } catch (jmri.JmriException ex) {
610                                    log.error("Error resetting restart sensor back to inActive");
611                                }
612                            }
613                        }
614                    }
615                }
616            };
617        }
618        restartSensor.addPropertyChangeListener(restartSensorListener);
619    }
620
621    public void initializeRestartAllocationSensor(NamedBeanHandle<jmri.Sensor> restartAllocationSensor) {
622        if (restartAllocationSensor == null) {
623            log.error("Call to initialise delay on restart allocation sensor, but none specified");
624            return;
625        }
626        if (restartAllocationSensorListener == null) {
627            restartAllocationSensorListener = new java.beans.PropertyChangeListener() {
628                @Override
629                public void propertyChange(java.beans.PropertyChangeEvent e) {
630                    if (e.getPropertyName().equals("KnownState")) {
631                        if (((Integer) e.getNewValue()).intValue() == jmri.Sensor.INACTIVE) {
632                            restartAllocationSensor.getBean().removePropertyChangeListener(restartAllocationSensorListener);
633                            restartAllocationSensorListener = null;
634                            InstanceManager.getDefault(DispatcherFrame.class).queueScanOfAllocationRequests();
635                        }
636                    }
637                }
638            };
639        }
640        restartAllocationSensor.getBean().addPropertyChangeListener(restartAllocationSensorListener);
641    }
642
643    public void setTrainType(int type) {
644        mTrainType = type;
645    }
646
647    /**
648     * set train type using localized string name as stored
649     *
650     * @param sType  name, such as "LOCAL_PASSENGER"
651     */
652    public void setTrainType(String sType) {
653        if (sType.equals(Bundle.getMessage("LOCAL_FREIGHT"))) {
654            setTrainType(LOCAL_FREIGHT);
655        } else if (sType.equals(Bundle.getMessage("LOCAL_PASSENGER"))) {
656            setTrainType(LOCAL_PASSENGER);
657        } else if (sType.equals(Bundle.getMessage("THROUGH_FREIGHT"))) {
658            setTrainType(THROUGH_FREIGHT);
659        } else if (sType.equals(Bundle.getMessage("THROUGH_PASSENGER"))) {
660            setTrainType(THROUGH_PASSENGER);
661        } else if (sType.equals(Bundle.getMessage("EXPRESS_FREIGHT"))) {
662            setTrainType(EXPRESS_FREIGHT);
663        } else if (sType.equals(Bundle.getMessage("EXPRESS_PASSENGER"))) {
664            setTrainType(EXPRESS_PASSENGER);
665        } else if (sType.equals(Bundle.getMessage("MOW"))) {
666            setTrainType(MOW);
667        }
668    }
669
670    public int getTrainType() {
671        return mTrainType;
672    }
673
674    public String getTrainTypeText() {
675        if (mTrainType == LOCAL_FREIGHT) {
676            return Bundle.getMessage("LOCAL_FREIGHT");
677        } else if (mTrainType == LOCAL_PASSENGER) {
678            return Bundle.getMessage("LOCAL_PASSENGER");
679        } else if (mTrainType == THROUGH_FREIGHT) {
680            return Bundle.getMessage("THROUGH_FREIGHT");
681        } else if (mTrainType == THROUGH_PASSENGER) {
682            return Bundle.getMessage("THROUGH_PASSENGER");
683        } else if (mTrainType == EXPRESS_FREIGHT) {
684            return Bundle.getMessage("EXPRESS_FREIGHT");
685        } else if (mTrainType == EXPRESS_PASSENGER) {
686            return Bundle.getMessage("EXPRESS_PASSENGER");
687        } else if (mTrainType == MOW) {
688            return Bundle.getMessage("MOW");
689        }
690        return ("");
691    }
692
693    public int getMode() {
694        return mMode;
695    }
696
697    public void setMode(int mode) {
698        if ((mode == AUTOMATIC) || (mode == MANUAL)
699                || (mode == DISPATCHED || mode == TERMINATED)) {
700            int old = mMode;
701            mMode = mode;
702            firePropertyChange("mode", Integer.valueOf(old), Integer.valueOf(mMode));
703        } else {
704            log.error("Attempt to set ActiveTrain mode to illegal value - {}", mode);
705        }
706    }
707
708    public String getModeText() {
709        if (mMode == AUTOMATIC) {
710            return Bundle.getMessage("AUTOMATIC");
711        } else if (mMode == MANUAL) {
712            return Bundle.getMessage("MANUAL");
713        } else if (mMode == DISPATCHED) {
714            return Bundle.getMessage("DISPATCHED");
715        } else if (mMode == TERMINATED) {
716            return Bundle.getMessage("TERMINATED");
717        }
718        return ("");
719    }
720
721    public void setAutoActiveTrain(AutoActiveTrain aat) {
722        mAutoActiveTrain = aat;
723    }
724
725    public AutoActiveTrain getAutoActiveTrain() {
726        return mAutoActiveTrain;
727    }
728
729    public int getRunningDirectionFromSectionAndSeq(jmri.Section s, int seqNo) {
730        int dir = mTransit.getDirectionFromSectionAndSeq(s, seqNo);
731        if (mTransitReversed) {
732            if (dir == jmri.Section.FORWARD) {
733                dir = jmri.Section.REVERSE;
734            } else {
735                dir = jmri.Section.FORWARD;
736            }
737        }
738        return dir;
739    }
740
741    public int getAllocationDirectionFromSectionAndSeq(jmri.Section s, int seqNo) {
742        int dir = mTransit.getDirectionFromSectionAndSeq(s, seqNo);
743        if (mAllocationReversed) {
744            if (dir == jmri.Section.FORWARD) {
745                dir = jmri.Section.REVERSE;
746            } else {
747                dir = jmri.Section.FORWARD;
748            }
749        }
750        return dir;
751    }
752
753    public void addAllocatedSection(AllocatedSection as) {
754        if (as != null) {
755            mAllocatedSections.add(as);
756            if (as.getSection() == mNextSectionToAllocate) {
757                // this  is the next Section in the Transit, update pointers
758                mLastAllocatedSection = as.getSection();
759                mLastAllocatedSectionSeqNumber = mNextSectionSeqNumber;
760                mNextSectionToAllocate = as.getNextSection();
761                mNextSectionSeqNumber = as.getNextSectionSequence();
762                mNextSectionDirection = getAllocationDirectionFromSectionAndSeq(
763                        mNextSectionToAllocate, mNextSectionSeqNumber);
764                as.setAllocationNumber(mNextAllocationNumber);
765                mNextAllocationNumber++;
766            } else {
767                // this is an extra allocated Section
768                as.setAllocationNumber(-1);
769            }
770            if ((mStatus == WAITING) && mStarted) {
771                setStatus(RUNNING);
772            }
773            if (as.getSequence() == 2) {
774                mSecondAllocatedSection = as.getSection();
775            }
776            if (InstanceManager.getDefault(DispatcherFrame.class).getNameInAllocatedBlock()) {
777                if (InstanceManager.getDefault(DispatcherFrame.class).getRosterEntryInBlock() && getRosterEntry() != null) {
778                    as.getSection().setNameFromActiveBlock(getRosterEntry());
779                } else {
780                    as.getSection().setNameInBlocks(mTrainName);
781                }
782                as.getSection().suppressNameUpdate(true);
783            }
784            if (InstanceManager.getDefault(DispatcherFrame.class).getExtraColorForAllocated()) {
785                as.getSection().setAlternateColorFromActiveBlock(true);
786            }
787            // notify anyone interested
788            pcs.firePropertyChange("sectionallocated",as , null);
789            refreshPanel();
790        } else {
791            log.error("Null Allocated Section reference in addAllocatedSection of ActiveTrain");
792        }
793    }
794
795    private void refreshPanel() {
796        var editorManager = InstanceManager.getDefault(jmri.jmrit.display.EditorManager.class);
797        for (var panel : editorManager.getAll(jmri.jmrit.display.layoutEditor.LayoutEditor.class)) {
798            panel.redrawPanel();
799        }
800    }
801
802    public void removeAllocatedSection(AllocatedSection as) {
803        if (as == null) {
804            log.error("Null AllocatedSection reference in removeAllocatedSection of ActiveTrain");
805            return;
806        }
807        int index = -1;
808        for (int i = 0; i < mAllocatedSections.size(); i++) {
809            if (as == mAllocatedSections.get(i)) {
810                index = i;
811            }
812        }
813        if (index < 0) {
814            log.error("Attempt to remove an unallocated Section {}", as.getSection().getDisplayName(USERSYS));
815            return;
816        }
817        mAllocatedSections.remove(index);
818        if (InstanceManager.getDefault(DispatcherFrame.class).getNameInAllocatedBlock()) {
819            as.getSection().clearNameInUnoccupiedBlocks();
820            as.getSection().suppressNameUpdate(false);
821        }
822        for (Block b: as.getSection().getBlockList()) {
823            if (!InstanceManager.getDefault(DispatcherFrame.class).checkForBlockInAllocatedSection(b, as.getSection())) {
824                String userName = b.getUserName();
825                if (userName != null) {
826                    LayoutBlock lb = InstanceManager.getDefault(LayoutBlockManager.class).getByUserName(userName);
827                    if (lb != null) {
828                        lb.setUseExtraColor(false);
829                    }
830                }
831            }
832        }
833        refreshPanel();
834        if (as.getSection() == mLastAllocatedSection) {
835            mLastAllocatedSection = null;
836            if (mAllocatedSections.size() > 0) {
837                mLastAllocatedSection = mAllocatedSections.get(
838                        mAllocatedSections.size() - 1).getSection();
839                mLastAllocatedSectionSeqNumber = mAllocatedSections.size() - 1;
840            }
841        }
842    }
843
844    /**
845     * This resets the state of the ActiveTrain so that it can be reallocated.
846     */
847    public void allocateAFresh() {
848        setStatus(WAITING);
849        holdAllocation = false;
850        setTransitReversed(false);
851        List<AllocatedSection> sectionsToRelease = new ArrayList<>();
852        for (AllocatedSection as : InstanceManager.getDefault(DispatcherFrame.class).getAllocatedSectionsList()) {
853            if (as.getActiveTrain() == this) {
854                sectionsToRelease.add(as);
855            }
856        }
857        for (AllocatedSection as : sectionsToRelease) {
858            InstanceManager.getDefault(DispatcherFrame.class).releaseAllocatedSection(as, true); // need to find Allocated Section
859            InstanceManager.getDefault(DispatcherFrame.class).queueWaitForEmpty(); //ensure release processed before proceding.
860            as.getSection().setState(jmri.Section.FREE);
861        }
862        if (mLastAllocatedSection != null) {
863            mLastAllocatedSection.setState(jmri.Section.FREE);
864        }
865        resetAllAllocatedSections();
866        clearAllocations();
867        setAllocationReversed(false);
868        // wait for AutoAllocate to do complete.
869        InstanceManager.getDefault(DispatcherFrame.class).queueWaitForEmpty();
870        if (mAutoRun) {
871            mAutoActiveTrain.allocateAFresh();
872        }
873        InstanceManager.getDefault(DispatcherFrame.class).allocateNewActiveTrain(this);
874    }
875
876    public void clearAllocations() {
877        for (AllocatedSection as : getAllocatedSectionList()) {
878            removeAllocatedSection(as);
879        }
880    }
881
882    public List<AllocatedSection> getAllocatedSectionList() {
883        List<AllocatedSection> list = new ArrayList<>();
884        for (int i = 0; i < mAllocatedSections.size(); i++) {
885            list.add(mAllocatedSections.get(i));
886        }
887        return list;
888    }
889
890    /**
891     * Returns list of all Blocks occupied by or allocated to this train. They
892     * are in order from the tail of the train to the head of the train then on
893     * to the forward-most allocated block. Note that unoccupied blocks can
894     * exist before and after the occupied blocks.
895     *
896     * TODO: doesn't handle reversing of adjacent multi-block sections well
897     *
898     * @return the list of blocks order of occupation
899     */
900    public List<Block> getBlockList() {
901        List<Block> list = new ArrayList<>();
902        for (int i = 0; i < mAllocatedSections.size(); i++) { // loop thru allocated sections, then all blocks for each section
903            Section s = mAllocatedSections.get(i).getSection();
904            List<Block> bl = s.getBlockList();
905            if (bl.size() > 1) { //sections with multiple blocks need extra logic
906
907                boolean blocksConnected = true;
908                //determine if blocks should be added in forward or reverse order based on connectivity
909                if (i == 0) { //for first section, compare last block to first of next section
910                    if (mAllocatedSections.size() > 1
911                            && //only one section, assume forward
912                            !connected(bl.get(bl.size() - 1), mAllocatedSections.get(i + 1).getSection().getBlockList().get(0))) {
913                        blocksConnected = false;
914                    }
915                } else { //not first section, check for connectivity between last block in list, and first block in this section
916                    if (!connected(list.get(list.size() - 1), bl.get(0))) { //last block is not connected to first block, add reverse
917                        blocksConnected = false;
918                    }
919                }
920                if (blocksConnected) { //blocks were connected, so add to outgoing in forward order
921                    for (int j = 0; j < bl.size(); j++) {
922                        Block b = bl.get(j);
923                        list.add(b);
924                        log.trace("block {} ({}) added to list for Section {} (fwd)", b.getDisplayName(USERSYS),
925                                (b.getState() == Block.OCCUPIED ? "OCCUPIED" : "UNOCCUPIED"),
926                                s.getDisplayName(USERSYS));
927                    }
928                } else { //not connected, add in reverse order
929                    for (int j = bl.size() - 1; j >= 0; j--) {
930                        Block b = bl.get(j);
931                        list.add(b);
932                        log.trace("block {} ({}) added to list for Section {} (rev)", b.getDisplayName(USERSYS),
933                                (b.getState() == Block.OCCUPIED ? "OCCUPIED" : "UNOCCUPIED"),
934                                s.getDisplayName(USERSYS));
935                    }
936                }
937
938            } else { //single block sections are simply added to the outgoing list
939                Block b = bl.get(0);
940                list.add(b);
941                log.trace("block {} ({}) added to list for Section {} (one)", b.getDisplayName(USERSYS),
942                        (b.getState() == Block.OCCUPIED ? "OCCUPIED" : "UNOCCUPIED"),
943                        s.getDisplayName(USERSYS));
944            }
945        }
946        return list;
947    }
948
949    /* copied from Section.java */
950    private boolean connected(Block b1, Block b2) {
951        if ((b1 != null) && (b2 != null)) {
952            List<Path> paths = b1.getPaths();
953            for (int i = 0; i < paths.size(); i++) {
954                if (paths.get(i).getBlock() == b2) {
955                    return true;
956                }
957            }
958        }
959        return false;
960    }
961
962    public jmri.Section getLastAllocatedSection() {
963        return mLastAllocatedSection;
964    }
965
966    public int getLastAllocatedSectionSeqNumber() {
967        return mLastAllocatedSectionSeqNumber;
968    }
969
970    public String getLastAllocatedSectionName() {
971        if (mLastAllocatedSection == null) {
972            return "<" + Bundle.getMessage("None").toLowerCase() + ">"; // <none>
973        }
974        return getSectionName(mLastAllocatedSection);
975    }
976
977    public jmri.Section getNextSectionToAllocate() {
978        return mNextSectionToAllocate;
979    }
980
981    public int getNextSectionSeqNumber() {
982        return mNextSectionSeqNumber;
983    }
984
985    public String getNextSectionToAllocateName() {
986        if (mNextSectionToAllocate == null) {
987            return "<" + Bundle.getMessage("None").toLowerCase() + ">"; // <none>
988        }
989        return getSectionName(mNextSectionToAllocate);
990    }
991
992    private String getSectionName(jmri.Section sc) {
993        String s = sc.getDisplayName();
994        return s;
995    }
996
997    public jmri.Block getStartBlock() {
998        return mStartBlock;
999    }
1000
1001    public void setStartBlock(jmri.Block sBlock) {
1002        mStartBlock = sBlock;
1003    }
1004
1005    public int getStartBlockSectionSequenceNumber() {
1006        return mStartBlockSectionSequenceNumber;
1007    }
1008
1009    public void setStartBlockSectionSequenceNumber(int sBlockSeqNum) {
1010        mStartBlockSectionSequenceNumber = sBlockSeqNum;
1011    }
1012
1013    public jmri.Block getEndBlock() {
1014        return mEndBlock;
1015    }
1016
1017    public void setEndBlock(jmri.Block eBlock) {
1018        mEndBlock = eBlock;
1019    }
1020
1021    public jmri.Section getEndBlockSection() {
1022        return mEndBlockSection;
1023    }
1024
1025    public void setEndBlockSection(jmri.Section eSection) {
1026        mEndBlockSection = eSection;
1027    }
1028
1029    public int getEndBlockSectionSequenceNumber() {
1030        return mEndBlockSectionSequenceNumber;
1031    }
1032
1033    public void setEndBlockSectionSequenceNumber(int eBlockSeqNum) {
1034        mEndBlockSectionSequenceNumber = eBlockSeqNum;
1035    }
1036
1037    public int getPriority() {
1038        return mPriority;
1039    }
1040
1041    public void setPriority(int priority) {
1042        mPriority = priority;
1043    }
1044
1045    public boolean getAutoRun() {
1046        return mAutoRun;
1047    }
1048
1049    public void setAutoRun(boolean autoRun) {
1050        mAutoRun = autoRun;
1051    }
1052
1053    public String getDccAddress() {
1054        return mDccAddress;
1055    }
1056
1057    public void setDccAddress(String dccAddress) {
1058        mDccAddress = dccAddress;
1059    }
1060
1061    public boolean getResetWhenDone() {
1062        return mResetWhenDone;
1063    }
1064
1065    public void setResetWhenDone(boolean s) {
1066        mResetWhenDone = s;
1067    }
1068
1069    public boolean getReverseAtEnd() {
1070        return mReverseAtEnd;
1071    }
1072
1073    public void setReverseAtEnd(boolean s) {
1074        mReverseAtEnd = s;
1075    }
1076
1077    protected jmri.Section getSecondAllocatedSection() {
1078        return mSecondAllocatedSection;
1079    }
1080
1081    /**
1082     * Returns the AllocateM Method to be used by autoAllocate
1083     *
1084     * @return The number of Blocks ahead to be allocated or 0 = Allocate By Safe
1085     *         sections or -1 - Allocate All The Way.
1086     */
1087    public int getAllocateMethod() {
1088        return mAllocateMethod;
1089    }
1090
1091    /**
1092     * Sets the Allocation Method to be used bu autoAllocate
1093     * @param i The number of Blocks ahead to be allocated or 0 = Allocate By Safe
1094     *          sections or -1 - Allocate All The Way.
1095     */
1096    public void setAllocateMethod(int i) {
1097        mAllocateMethod = i;
1098    }
1099
1100    //
1101    // Operating methods
1102    //
1103    public AllocationRequest initializeFirstAllocation() {
1104        if (mAllocatedSections.size() > 0) {
1105            log.error("ERROR - Request to initialize first allocation, when allocations already present");
1106            return null;
1107        }
1108        if ((mStartBlockSectionSequenceNumber > 0) && (mStartBlock != null)) {
1109            mNextSectionToAllocate = mTransit.getSectionFromBlockAndSeq(mStartBlock,
1110                    mStartBlockSectionSequenceNumber);
1111            if (mNextSectionToAllocate == null) {
1112                mNextSectionToAllocate = mTransit.getSectionFromConnectedBlockAndSeq(mStartBlock,
1113                        mStartBlockSectionSequenceNumber);
1114                if (mNextSectionToAllocate == null) {
1115                    log.error("ERROR - Cannot find Section for first allocation of ActiveTrain{}", getActiveTrainName());
1116                    return null;
1117                }
1118            }
1119            mNextSectionSeqNumber = mStartBlockSectionSequenceNumber;
1120            mNextSectionDirection = getAllocationDirectionFromSectionAndSeq(mNextSectionToAllocate,
1121                    mNextSectionSeqNumber);
1122        } else {
1123            log.error("ERROR - Insufficient information to initialize first allocation");
1124            return null;
1125        }
1126        if (!InstanceManager.getDefault(DispatcherFrame.class).requestAllocation(this,
1127                mNextSectionToAllocate, mNextSectionDirection, mNextSectionSeqNumber, true, null, true)) {
1128            log.error("Allocation request failed for first allocation of {}", getActiveTrainName());
1129        }
1130        if (InstanceManager.getDefault(DispatcherFrame.class).getRosterEntryInBlock() && getRosterEntry() != null) {
1131            mStartBlock.setValue(getRosterEntry());
1132        } else if (InstanceManager.getDefault(DispatcherFrame.class).getShortNameInBlock()) {
1133            mStartBlock.setValue(mTrainName);
1134        }
1135        AllocationRequest ar = InstanceManager.getDefault(DispatcherFrame.class).findAllocationRequestInQueue(mNextSectionToAllocate,
1136                mNextSectionSeqNumber, mNextSectionDirection, this);
1137        return ar;
1138    }
1139
1140    protected boolean addEndSection(jmri.Section s, int seq) {
1141        AllocatedSection as = mAllocatedSections.get(mAllocatedSections.size() - 1);
1142        if (!as.setNextSection(s, seq)) {
1143            return false;
1144        }
1145        setEndBlockSection(s);
1146        setEndBlockSectionSequenceNumber(seq);
1147        //At this stage the section direction hasn't been set, by default the exit block returned is the reverse if the section is free
1148        setEndBlock(s.getExitBlock());
1149        mNextSectionSeqNumber = seq;
1150        mNextSectionToAllocate = s;
1151        return true;
1152    }
1153
1154    /*This is for use where the transit has been extended, then the last section has been cancelled no
1155     checks are performed, these should be done by a higher level code*/
1156    protected void removeLastAllocatedSection() {
1157        AllocatedSection as = mAllocatedSections.get(mAllocatedSections.size() - 1);
1158        //Set the end block using the AllocatedSections exit block before clearing the next section in the allocatedsection
1159        setEndBlock(as.getExitBlock());
1160
1161        as.setNextSection(null, 0);
1162        setEndBlockSection(as.getSection());
1163
1164        setEndBlockSectionSequenceNumber(getEndBlockSectionSequenceNumber() - 1);
1165        // In theory the following values should have already been set if there are no more sections to allocate.
1166        mNextSectionSeqNumber = 0;
1167        mNextSectionToAllocate = null;
1168    }
1169
1170    protected AllocatedSection reverseAllAllocatedSections() {
1171        AllocatedSection aSec = null;
1172        for (int i = 0; i < mAllocatedSections.size(); i++) {
1173            aSec = mAllocatedSections.get(i);
1174            int dir = mTransit.getDirectionFromSectionAndSeq(aSec.getSection(), aSec.getSequence());
1175            if (dir == jmri.Section.FORWARD) {
1176                aSec.getSection().setState(jmri.Section.REVERSE);
1177            } else {
1178                aSec.getSection().setState(jmri.Section.FORWARD);
1179            }
1180            aSec.setStoppingSensors();
1181        }
1182        return aSec;
1183    }
1184
1185    protected void resetAllAllocatedSections() {
1186        for (int i = 0; i < mAllocatedSections.size(); i++) {
1187            AllocatedSection aSec = mAllocatedSections.get(i);
1188            int dir = mTransit.getDirectionFromSectionAndSeq(aSec.getSection(), aSec.getSequence());
1189            aSec.getSection().setState(dir);
1190            aSec.setStoppingSensors();
1191        }
1192    }
1193
1194    protected void setRestart(int delayType, int restartDelay, Sensor delaySensor, boolean resetSensorAfter) {
1195        if (delayType == NODELAY) {
1196            holdAllocation(false);
1197            return;
1198        }
1199
1200        setStatus(READY);
1201        restartPoint = true;
1202        if (delayType == TIMEDDELAY) {
1203            Date now = jmri.InstanceManager.getDefault(jmri.Timebase.class).getTime();
1204            @SuppressWarnings("deprecation") // Date.getHours
1205            int nowHours = now.getHours();
1206            @SuppressWarnings("deprecation") // Date.getMinutes
1207            int nowMinutes = now.getMinutes();
1208            int hours = restartDelay / 60;
1209            int minutes = restartDelay % 60;
1210            restartHr = nowHours + hours + ((nowMinutes + minutes) / 60);
1211            restartMin = ((nowMinutes + minutes) % 60);
1212            if (restartHr>23){
1213                restartHr=restartHr-24;
1214            }
1215        }
1216        InstanceManager.getDefault(DispatcherFrame.class).addDelayedTrain(this, delayType, delaySensor, resetSensorAfter );
1217    }
1218
1219    protected boolean isInAllocatedList(AllocatedSection as) {
1220        for (int i = 0; i < mAllocatedSections.size(); i++) {
1221            if (mAllocatedSections.get(i) == as) {
1222                return true;
1223            }
1224        }
1225        return false;
1226    }
1227
1228    protected boolean isInAllocatedList(Section s) {
1229        for (int i = 0; i < mAllocatedSections.size(); i++) {
1230            if ((mAllocatedSections.get(i)).getSection() == s) {
1231                return true;
1232            }
1233        }
1234        return false;
1235    }
1236
1237
1238    boolean restartPoint = false;
1239
1240    private boolean holdAllocation = false;
1241
1242    protected void holdAllocation(boolean boo) {
1243        holdAllocation = boo;
1244    }
1245
1246    protected boolean holdAllocation() {
1247        return holdAllocation;
1248    }
1249
1250    protected boolean reachedRestartPoint() {
1251        return restartPoint;
1252    }
1253
1254    protected void restart() {
1255        log.debug("{}: restarting", getTrainName());
1256        restartPoint = false;
1257        holdAllocation(false);
1258        setStatus(WAITING);
1259        if (mAutoActiveTrain != null) {
1260            mAutoActiveTrain.setupNewCurrentSignal(null,true);
1261        }
1262    }
1263
1264    public void terminate() {
1265        InstanceManager.getDefault(DispatcherFrame.class).removeDelayedTrain(this);
1266        if (getDelaySensor() != null && delaySensorListener != null) {
1267            getDelaySensor().removePropertyChangeListener(delaySensorListener);
1268        }
1269        if (getRestartSensor() != null && restartSensorListener != null) {
1270            getRestartSensor().removePropertyChangeListener(restartSensorListener);
1271        }
1272        setMode(TERMINATED);
1273        mTransit.setState(Transit.IDLE);
1274    }
1275
1276    public void dispose() {
1277        getTransit().removeTemporarySections();
1278    }
1279
1280    // Property Change Support
1281    private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
1282
1283    @OverridingMethodsMustInvokeSuper
1284    protected void firePropertyChange(String p, Object old, Object n) {
1285        pcs.firePropertyChange(p, old, n);
1286    }
1287
1288    @Override
1289    public void addPropertyChangeListener(PropertyChangeListener listener) {
1290        pcs.addPropertyChangeListener(listener);
1291    }
1292
1293    @Override
1294    public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
1295        pcs.addPropertyChangeListener(propertyName, listener);
1296    }
1297
1298    @Override
1299    public PropertyChangeListener[] getPropertyChangeListeners() {
1300        return pcs.getPropertyChangeListeners();
1301    }
1302
1303    @Override
1304    public PropertyChangeListener[] getPropertyChangeListeners(String propertyName) {
1305        return pcs.getPropertyChangeListeners(propertyName);
1306    }
1307
1308    @Override
1309    public void removePropertyChangeListener(PropertyChangeListener listener) {
1310        pcs.removePropertyChangeListener(listener);
1311    }
1312
1313    @Override
1314    public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
1315        pcs.removePropertyChangeListener(propertyName, listener);
1316    }
1317
1318    private final static Logger log = LoggerFactory.getLogger(ActiveTrain.class);
1319
1320}