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