001package jmri.jmrit.operations.trains;
002
003import java.awt.Color;
004import java.beans.PropertyChangeListener;
005import java.io.*;
006import java.text.MessageFormat;
007import java.text.SimpleDateFormat;
008import java.util.*;
009
010import org.jdom2.Element;
011
012import jmri.InstanceManager;
013import jmri.beans.Identifiable;
014import jmri.beans.PropertyChangeSupport;
015import jmri.jmrit.display.Editor;
016import jmri.jmrit.display.EditorManager;
017import jmri.jmrit.operations.locations.*;
018import jmri.jmrit.operations.rollingstock.RollingStock;
019import jmri.jmrit.operations.rollingstock.RollingStockManager;
020import jmri.jmrit.operations.rollingstock.cars.*;
021import jmri.jmrit.operations.rollingstock.engines.*;
022import jmri.jmrit.operations.routes.*;
023import jmri.jmrit.operations.setup.Control;
024import jmri.jmrit.operations.setup.Setup;
025import jmri.jmrit.operations.trains.excel.TrainCustomManifest;
026import jmri.jmrit.roster.RosterEntry;
027import jmri.script.JmriScriptEngineManager;
028import jmri.util.FileUtil;
029import jmri.util.swing.JmriJOptionPane;
030
031/**
032 * Represents a train on the layout
033 *
034 * @author Daniel Boudreau Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013,
035 *         2014, 2015
036 * @author Rodney Black Copyright (C) 2011
037 */
038public class Train extends PropertyChangeSupport implements Identifiable, PropertyChangeListener {
039
040    /*
041     * WARNING DO NOT LOAD CAR OR ENGINE MANAGERS WHEN Train.java IS CREATED IT
042     * CAUSES A RECURSIVE LOOP AT LOAD TIME, SEE EXAMPLES BELOW CarManager
043     * carManager = InstanceManager.getDefault(CarManager.class); EngineManager
044     * engineManager = InstanceManager.getDefault(EngineManager.class);
045     */
046
047    // The release date for JMRI operations 10/29/2008
048
049    public static final String NONE = "";
050
051    protected String _id = NONE;
052    protected String _name = NONE;
053    protected String _description = NONE;
054    protected RouteLocation _current = null;// where the train is located in its route
055    protected String _buildFailedMessage = NONE; // the build failed message for this train
056    protected boolean _built = false; // when true, a train manifest has been built
057    protected boolean _modified = false; // when true, user has modified train after being built
058    protected boolean _build = true; // when true, build this train
059    protected boolean _buildFailed = false; // when true, build for this train failed
060    protected boolean _printed = false; // when true, manifest has been printed
061    protected boolean _sendToTerminal = false; // when true, cars picked up by train only go to terminal
062    protected boolean _allowLocalMoves = true; // when true, cars with custom loads can be moved locally
063    protected boolean _allowThroughCars = true; // when true, cars from the origin can be sent to the terminal
064    protected boolean _buildNormal = false; // when true build this train in normal mode
065    protected boolean _allowCarsReturnStaging = false; // when true allow cars to return to staging
066    protected boolean _serviceAllCarsWithFinalDestinations = false; // when true, service cars with final destinations
067    protected boolean _buildConsist = false; // when true, build a consist for this train using single locomotives
068    protected boolean _sendCarsWithCustomLoadsToStaging = false; // when true, send cars to staging if spurs full
069    protected Route _route = null;
070    protected Track _departureTrack; // the departure track from staging
071    protected Track _terminationTrack; // the termination track into staging
072    protected String _carRoadOption = ALL_ROADS;// train car road name restrictions
073    protected List<String> _carRoadList = new ArrayList<>();
074    protected String _cabooseRoadOption = ALL_ROADS;// train caboose road name restrictions
075    protected List<String> _cabooseRoadList = new ArrayList<>();
076    protected String _locoRoadOption = ALL_ROADS;// train engine road name restrictions
077    protected List<String> _locoRoadList = new ArrayList<>();
078    protected int _requires = NO_CABOOSE_OR_FRED; // train requirements, caboose, FRED
079    protected String _numberEngines = "0"; // number of engines this train requires
080    protected String _engineRoad = NONE; // required road name for engines assigned to this train
081    protected String _engineModel = NONE; // required model of engines assigned to this train
082    protected String _cabooseRoad = NONE; // required road name for cabooses assigned to this train
083    protected String _departureTime = "00:00"; // NOI18N departure time for this train
084    protected String _leadEngineId = NONE; // lead engine for train icon info
085    protected String _builtStartYear = NONE; // built start year
086    protected String _builtEndYear = NONE; // built end year
087    protected String _loadOption = ALL_LOADS;// train load restrictions
088    protected String _ownerOption = ALL_OWNERS;// train owner name restrictions
089    protected List<String> _buildScripts = new ArrayList<>(); // list of script pathnames to run before train is built
090    protected List<String> _afterBuildScripts = new ArrayList<>(); // script pathnames to run after train is built
091    protected List<String> _moveScripts = new ArrayList<>(); // list of script pathnames to run when train is moved
092    protected List<String> _terminationScripts = new ArrayList<>(); // script pathnames to run when train is terminated
093    protected String _railroadName = NONE; // optional railroad name for this train
094    protected String _logoPathName = NONE; // optional manifest logo for this train
095    protected boolean _showTimes = true; // when true, show arrival and departure times for this train
096    protected Engine _leadEngine = null; // lead engine for icon
097    protected String _switchListStatus = UNKNOWN; // print switch list status
098    protected String _comment = NONE;
099    protected String _serviceStatus = NONE; // status only if train is being built
100    protected int _statusCode = CODE_UNKNOWN;
101    protected int _oldStatusCode = CODE_UNKNOWN;
102    protected Date _date; // date for last status change for this train
103    protected int _statusCarsRequested = 0;
104    protected String _tableRowColorName = NONE; // color of row in Trains table
105    protected String _tableRowColorResetName = NONE; // color of row in Trains table when reset
106
107    // Engine change and helper engines
108    protected int _leg2Options = NO_CABOOSE_OR_FRED; // options
109    protected RouteLocation _leg2Start = null; // route location when 2nd leg begins
110    protected RouteLocation _end2Leg = null; // route location where 2nd leg ends
111    protected String _leg2Engines = "0"; // number of engines 2nd leg
112    protected String _leg2Road = NONE; // engine road name 2nd leg
113    protected String _leg2Model = NONE; // engine model 2nd leg
114    protected String _leg2CabooseRoad = NONE; // road name for caboose 2nd leg
115
116    protected int _leg3Options = NO_CABOOSE_OR_FRED; // options
117    protected RouteLocation _leg3Start = null; // route location when 3rd leg begins
118    protected RouteLocation _leg3End = null; // route location where 3rd leg ends
119    protected String _leg3Engines = "0"; // number of engines 3rd leg
120    protected String _leg3Road = NONE; // engine road name 3rd leg
121    protected String _leg3Model = NONE; // engine model 3rd leg
122    protected String _leg3CabooseRoad = NONE; // road name for caboose 3rd leg
123
124    // engine change and helper options
125    public static final int CHANGE_ENGINES = 1; // change engines
126    public static final int HELPER_ENGINES = 2; // add helper engines
127    public static final int ADD_CABOOSE = 4; // add caboose
128    public static final int REMOVE_CABOOSE = 8; // remove caboose
129    public static final int ADD_ENGINES = 16; // add engines
130    public static final int REMOVE_ENGINES = 32; // remove engines
131
132    // property change names
133    public static final String DISPOSE_CHANGED_PROPERTY = "TrainDispose"; // NOI18N
134    public static final String STOPS_CHANGED_PROPERTY = "TrainStops"; // NOI18N
135    public static final String TYPES_CHANGED_PROPERTY = "TrainTypes"; // NOI18N
136    public static final String BUILT_CHANGED_PROPERTY = "TrainBuilt"; // NOI18N
137    public static final String BUILT_YEAR_CHANGED_PROPERTY = "TrainBuiltYear"; // NOI18N
138    public static final String BUILD_CHANGED_PROPERTY = "TrainBuild"; // NOI18N
139    public static final String ROADS_CHANGED_PROPERTY = "TrainRoads"; // NOI18N
140    public static final String LOADS_CHANGED_PROPERTY = "TrainLoads"; // NOI18N
141    public static final String OWNERS_CHANGED_PROPERTY = "TrainOwners"; // NOI18N
142    public static final String NAME_CHANGED_PROPERTY = "TrainName"; // NOI18N
143    public static final String DESCRIPTION_CHANGED_PROPERTY = "TrainDescription"; // NOI18N
144    public static final String STATUS_CHANGED_PROPERTY = "TrainStatus"; // NOI18N
145    public static final String DEPARTURETIME_CHANGED_PROPERTY = "TrainDepartureTime"; // NOI18N
146    public static final String TRAIN_LOCATION_CHANGED_PROPERTY = "TrainLocation"; // NOI18N
147    public static final String TRAIN_ROUTE_CHANGED_PROPERTY = "TrainRoute"; // NOI18N
148    public static final String TRAIN_REQUIREMENTS_CHANGED_PROPERTY = "TrainRequirements"; // NOI18N
149    public static final String TRAIN_MOVE_COMPLETE_CHANGED_PROPERTY = "TrainMoveComplete"; // NOI18N
150    public static final String TRAIN_ROW_COLOR_CHANGED_PROPERTY = "TrianRowColor"; // NOI18N
151    public static final String TRAIN_ROW_COLOR_RESET_CHANGED_PROPERTY = "TrianRowColorReset"; // NOI18N
152    public static final String TRAIN_MODIFIED_CHANGED_PROPERTY = "TrainModified"; // NOI18N
153    public static final String TRAIN_CURRENT_CHANGED_PROPERTY = "TrainCurrentLocation"; // NOI18N
154
155    // Train status
156    public static final String TRAIN_RESET = Bundle.getMessage("TrainReset");
157    public static final String RUN_SCRIPTS = Bundle.getMessage("RunScripts");
158    public static final String BUILDING = Bundle.getMessage("Building");
159    public static final String BUILD_FAILED = Bundle.getMessage("BuildFailed");
160    public static final String BUILT = Bundle.getMessage("Built");
161    public static final String PARTIAL_BUILT = Bundle.getMessage("Partial");
162    public static final String TRAIN_EN_ROUTE = Bundle.getMessage("TrainEnRoute");
163    public static final String TERMINATED = Bundle.getMessage("Terminated");
164    public static final String MANIFEST_MODIFIED = Bundle.getMessage("Modified");
165
166    // Train status codes
167    public static final int CODE_TRAIN_RESET = 0;
168    public static final int CODE_RUN_SCRIPTS = 0x100;
169    public static final int CODE_BUILDING = 0x01;
170    public static final int CODE_BUILD_FAILED = 0x02;
171    public static final int CODE_BUILT = 0x10;
172    public static final int CODE_PARTIAL_BUILT = CODE_BUILT + 0x04;
173    public static final int CODE_TRAIN_EN_ROUTE = CODE_BUILT + 0x08;
174    public static final int CODE_TERMINATED = 0x80;
175    public static final int CODE_MANIFEST_MODIFIED = 0x200;
176    public static final int CODE_UNKNOWN = 0xFFFF;
177
178    // train requirements
179    public static final int NO_CABOOSE_OR_FRED = 0; // default
180    public static final int CABOOSE = 1;
181    public static final int FRED = 2;
182
183    // road options
184    public static final String ALL_ROADS = Bundle.getMessage("All");
185    public static final String INCLUDE_ROADS = Bundle.getMessage("Include");
186    public static final String EXCLUDE_ROADS = Bundle.getMessage("Exclude");
187
188    // owner options
189    public static final String ALL_OWNERS = Bundle.getMessage("All");
190    public static final String INCLUDE_OWNERS = Bundle.getMessage("Include");
191    public static final String EXCLUDE_OWNERS = Bundle.getMessage("Exclude");
192
193    // load options
194    public static final String ALL_LOADS = Bundle.getMessage("All");
195    public static final String INCLUDE_LOADS = Bundle.getMessage("Include");
196    public static final String EXCLUDE_LOADS = Bundle.getMessage("Exclude");
197
198    // Switch list status
199    public static final String UNKNOWN = "";
200    public static final String PRINTED = Bundle.getMessage("Printed");
201
202    public static final String AUTO = Bundle.getMessage("Auto");
203    public static final String AUTO_HPT = Bundle.getMessage("AutoHPT");
204
205    public Train(String id, String name) {
206        //       log.debug("New train ({}) id: {}", name, id);
207        _name = name;
208        _id = id;
209        // a new train accepts all types
210        setTypeNames(InstanceManager.getDefault(CarTypes.class).getNames());
211        setTypeNames(InstanceManager.getDefault(EngineTypes.class).getNames());
212        addPropertyChangeListerners();
213    }
214
215    @Override
216    public String getId() {
217        return _id;
218    }
219
220    /**
221     * Sets the name of this train, normally a short name that can fit within
222     * the train icon.
223     *
224     * @param name the train's name.
225     */
226    public void setName(String name) {
227        String old = _name;
228        _name = name;
229        if (!old.equals(name)) {
230            setDirtyAndFirePropertyChange(NAME_CHANGED_PROPERTY, old, name);
231        }
232    }
233
234    // for combo boxes
235    /**
236     * Get's a train's name
237     *
238     * @return train's name
239     */
240    @Override
241    public String toString() {
242        return _name;
243    }
244
245    /**
246     * Get's a train's name
247     *
248     * @return train's name
249     */
250    public String getName() {
251        return _name;
252    }
253
254    /**
255     * @return The name of the color when highlighting the train's row
256     */
257    public String getTableRowColorName() {
258        return _tableRowColorName;
259    }
260
261    public void setTableRowColorName(String colorName) {
262        String old = _tableRowColorName;
263        _tableRowColorName = colorName;
264        if (!old.equals(colorName)) {
265            setDirtyAndFirePropertyChange(TRAIN_ROW_COLOR_CHANGED_PROPERTY, old, colorName);
266        }
267    }
268
269    /**
270     * @return The name of the train row color when the train is reset
271     */
272    public String getTableRowColorNameReset() {
273        return _tableRowColorResetName;
274    }
275
276    public void setTableRowColorNameReset(String colorName) {
277        String old = _tableRowColorResetName;
278        _tableRowColorResetName = colorName;
279        if (!old.equals(colorName)) {
280            setDirtyAndFirePropertyChange(TRAIN_ROW_COLOR_RESET_CHANGED_PROPERTY, old, colorName);
281        }
282    }
283
284    /**
285     * @return The color when highlighting the train's row
286     */
287    public Color getTableRowColor() {
288        String colorName = getTableRowColorName();
289        if (colorName.equals(NONE)) {
290            return null;
291        } else {
292            return Setup.getColor(colorName);
293        }
294    }
295
296    /**
297     * Get's train's departure time
298     *
299     * @return train's departure time in the String format hh:mm
300     */
301    public String getDepartureTime() {
302        // check to see if the route has a departure time
303        RouteLocation rl = getTrainDepartsRouteLocation();
304        if (rl != null) {
305            rl.removePropertyChangeListener(this);
306            rl.addPropertyChangeListener(this);
307            if (!rl.getDepartureTime().equals(RouteLocation.NONE)) {
308                return rl.getDepartureTime();
309            }
310        }
311        return _departureTime;
312    }
313
314    /**
315     * Get's train's departure time in 12hr or 24hr format
316     *
317     * @return train's departure time in the String format hh:mm or hh:mm AM/PM
318     */
319    public String getFormatedDepartureTime() {
320        // check to see if the route has a departure time
321        RouteLocation rl = getTrainDepartsRouteLocation();
322        if (rl != null && !rl.getDepartureTime().equals(RouteLocation.NONE)) {
323            // need to forward any changes to departure time
324            rl.removePropertyChangeListener(this);
325            rl.addPropertyChangeListener(this);
326            return rl.getFormatedDepartureTime();
327        }
328        return (parseTime(getDepartTimeMinutes()));
329    }
330
331    /**
332     * Get train's departure time in minutes from midnight for sorting
333     *
334     * @return int hh*60+mm
335     */
336    public int getDepartTimeMinutes() {
337        int hour = Integer.parseInt(getDepartureTimeHour());
338        int minute = Integer.parseInt(getDepartureTimeMinute());
339        return (hour * 60) + minute;
340    }
341
342    public void setDepartureTime(String hour, String minute) {
343        String old = _departureTime;
344        int h = Integer.parseInt(hour);
345        if (h < 10) {
346            hour = "0" + h;
347        }
348        int m = Integer.parseInt(minute);
349        if (m < 10) {
350            minute = "0" + m;
351        }
352        String time = hour + ":" + minute;
353        _departureTime = time;
354        if (!old.equals(time)) {
355            setDirtyAndFirePropertyChange(DEPARTURETIME_CHANGED_PROPERTY, old, _departureTime);
356            setModified(true);
357        }
358    }
359
360    public String getDepartureTimeHour() {
361        String[] time = getDepartureTime().split(":");
362        return time[0];
363    }
364
365    public String getDepartureTimeMinute() {
366        String[] time = getDepartureTime().split(":");
367        return time[1];
368    }
369
370    public static final String ALREADY_SERVICED = "-1"; // NOI18N
371
372    /**
373     * Gets the expected time when this train will arrive at the location rl.
374     * Expected arrival time is based on the number of car pick up and set outs
375     * for this train. TODO Doesn't provide expected arrival time if train is in
376     * route, instead provides relative time. If train is at or has passed the
377     * location return -1.
378     *
379     * @param routeLocation The RouteLocation.
380     * @return expected arrival time in minutes (append AM or PM if 12 hour
381     *         format)
382     */
383    public String getExpectedArrivalTime(RouteLocation routeLocation) {
384        int minutes = getExpectedTravelTimeInMinutes(routeLocation);
385        if (minutes == -1) {
386            return ALREADY_SERVICED;
387        }
388        log.debug("Expected arrival time for train ({}) at ({}), {} minutes", getName(), routeLocation.getName(),
389                minutes);
390        // TODO use fast clock to get current time vs departure time
391        // for now use relative
392        return parseTime(minutes);
393    }
394
395    public String getExpectedDepartureTime(RouteLocation routeLocation) {
396        int minutes = getExpectedTravelTimeInMinutes(routeLocation);
397        if (minutes == -1) {
398            return ALREADY_SERVICED;
399        }
400        if (!routeLocation.getDepartureTime().equals(RouteLocation.NONE)) {
401            return parseTime(checkForDepartureTime(minutes, routeLocation));
402        }
403        // figure out the work at this location, note that there can be
404        // consecutive locations with the same name
405        if (getRoute() != null) {
406            boolean foundRouteLocation = false;
407            for (RouteLocation rl : getRoute().getLocationsBySequenceList()) {
408                if (rl == routeLocation) {
409                    foundRouteLocation = true;
410                }
411                if (foundRouteLocation) {
412                    if (rl.getSplitName()
413                            .equals(routeLocation.getSplitName())) {
414                        minutes = minutes + getWorkTimeAtLocation(rl);
415                    } else {
416                        break; // done
417                    }
418                }
419            }
420        }
421        log.debug("Expected departure time {} for train ({}) at ({})", minutes, getName(), routeLocation.getName());
422        return parseTime(minutes);
423    }
424
425    public int getWorkTimeAtLocation(RouteLocation routeLocation) {
426        int minutes = 0;
427        // departure?
428        if (routeLocation == getTrainDepartsRouteLocation()) {
429            return minutes;
430        }
431        // add any work at this location
432        for (Car rs : InstanceManager.getDefault(CarManager.class).getList(this)) {
433            if (rs.getRouteLocation() == routeLocation && !rs.getTrackName().equals(RollingStock.NONE)) {
434                minutes += Setup.getSwitchTime();
435            }
436            if (rs.getRouteDestination() == routeLocation) {
437                minutes += Setup.getSwitchTime();
438            }
439        }
440        return minutes;
441    }
442
443    /**
444     * Used to determine when a train will arrive at a train's route location.
445     * Once a train departs, provides an estimated time in route and ignores the
446     * departure times from each route location.
447     * 
448     * @param routeLocation where in the train's route to get time
449     * @return Time in minutes
450     */
451    public int getExpectedTravelTimeInMinutes(RouteLocation routeLocation) {
452        int minutes = 0;
453        if (!isTrainEnRoute()) {
454            minutes += Integer.parseInt(getDepartureTimeMinute());
455            minutes += 60 * Integer.parseInt(getDepartureTimeHour());
456        } else {
457            minutes = -1; // -1 means train has already served the location
458        }
459        // boolean trainAt = false;
460        boolean trainLocFound = false;
461        if (getRoute() != null) {
462            List<RouteLocation> routeList = getRoute().getLocationsBySequenceList();
463            for (int i = 0; i < routeList.size(); i++) {
464                RouteLocation rl = routeList.get(i);
465                if (rl == routeLocation) {
466                    break; // done
467                }
468                // start recording time after finding where the train is
469                if (!trainLocFound && isTrainEnRoute()) {
470                    if (rl == getCurrentRouteLocation()) {
471                        trainLocFound = true;
472                        // add travel time
473                        minutes = Setup.getTravelTime();
474                    }
475                    continue;
476                }
477                // is there a departure time from this location?
478                minutes = checkForDepartureTime(minutes, rl);
479                // add wait time
480                minutes += rl.getWait();
481                // add travel time if new location
482                RouteLocation next = routeList.get(i + 1);
483                if (next != null &&
484                        !rl.getSplitName().equals(next.getSplitName())) {
485                    minutes += Setup.getTravelTime();
486                }
487                // don't count work if there's a departure time
488                if (i == 0 || !rl.getDepartureTime().equals(RouteLocation.NONE) && !isTrainEnRoute()) {
489                    continue;
490                }
491                // now add the work at the location
492                minutes = minutes + getWorkTimeAtLocation(rl);
493            }
494        }
495        return minutes;
496    }
497
498    private int checkForDepartureTime(int minutes, RouteLocation rl) {
499        if (!rl.getDepartureTime().equals(RouteLocation.NONE) && !isTrainEnRoute()) {
500            String dt = rl.getDepartureTime();
501            log.debug("Location {} departure time {}", rl.getName(), dt);
502            String[] time = dt.split(":");
503            int departMinute = 60 * Integer.parseInt(time[0]) + Integer.parseInt(time[1]);
504            // cross into new day?
505            if (minutes > departMinute) {
506                departMinute += 60 * 24; // yes
507            }
508            minutes = departMinute;
509        }
510        return minutes;
511    }
512
513    /**
514     * Returns time in days:hours:minutes format
515     *
516     * @param minutes number of minutes from midnight
517     * @return hour:minute (optionally AM:PM format)
518     */
519    private String parseTime(int minutes) {
520        int hours = 0;
521        int days = 0;
522
523        if (minutes >= 60) {
524            int h = minutes / 60;
525            minutes = minutes - h * 60;
526            hours += h;
527        }
528
529        String d = "";
530        if (hours >= 24) {
531            int nd = hours / 24;
532            hours = hours - nd * 24;
533            days += nd;
534            d = Integer.toString(days) + ":";
535        }
536
537        // AM_PM field
538        String am_pm = "";
539        if (Setup.is12hrFormatEnabled()) {
540            am_pm = " " + Bundle.getMessage("AM");
541            if (hours >= 12) {
542                hours = hours - 12;
543                am_pm = " " + Bundle.getMessage("PM");
544            }
545            if (hours == 0) {
546                hours = 12;
547            }
548        }
549
550        String h = Integer.toString(hours);
551        if (hours < 10) {
552            h = "0" + h;
553        }
554        if (minutes < 10) {
555            return d + h + ":0" + minutes + am_pm; // NOI18N
556        }
557        return d + h + ":" + minutes + am_pm;
558    }
559
560    /**
561     * Set train requirements. If NO_CABOOSE_OR_FRED, then train doesn't require
562     * a caboose or car with FRED.
563     *
564     * @param requires NO_CABOOSE_OR_FRED, CABOOSE, FRED
565     */
566    public void setRequirements(int requires) {
567        int old = _requires;
568        _requires = requires;
569        if (old != requires) {
570            setDirtyAndFirePropertyChange(TRAIN_REQUIREMENTS_CHANGED_PROPERTY, Integer.toString(old),
571                    Integer.toString(requires));
572        }
573    }
574
575    /**
576     * Get a train's requirements with regards to the last car in the train.
577     *
578     * @return NONE CABOOSE FRED
579     */
580    public int getRequirements() {
581        return _requires;
582    }
583
584    public boolean isCabooseNeeded() {
585        return (getRequirements() & CABOOSE) == CABOOSE;
586    }
587
588    public boolean isFredNeeded() {
589        return (getRequirements() & FRED) == FRED;
590    }
591
592    public void setRoute(Route route) {
593        Route old = _route;
594        String oldRoute = NONE;
595        String newRoute = NONE;
596        if (old != null) {
597            old.removePropertyChangeListener(this);
598            oldRoute = old.toString();
599        }
600        if (route != null) {
601            route.addPropertyChangeListener(this);
602            newRoute = route.toString();
603        }
604        _route = route;
605        _skipLocationsList.clear();
606        if (old == null || !old.equals(route)) {
607            setDirtyAndFirePropertyChange(TRAIN_ROUTE_CHANGED_PROPERTY, oldRoute, newRoute);
608        }
609    }
610
611    /**
612     * Gets the train's route
613     *
614     * @return train's route
615     */
616    public Route getRoute() {
617        return _route;
618    }
619
620    /**
621     * Get's the train's route name.
622     *
623     * @return Train's route name.
624     */
625    public String getTrainRouteName() {
626        if (getRoute() == null) {
627            return NONE;
628        }
629        return getRoute().getName();
630    }
631
632    /**
633     * Get the train's departure location's name
634     *
635     * @return train's departure location's name
636     */
637    public String getTrainDepartsName() {
638        if (getTrainDepartsRouteLocation() != null) {
639            return getTrainDepartsRouteLocation().getName();
640        }
641        return NONE;
642    }
643
644    public RouteLocation getTrainDepartsRouteLocation() {
645        if (getRoute() == null) {
646            return null;
647        }
648        return getRoute().getDepartsRouteLocation();
649    }
650
651    public String getTrainDepartsDirection() {
652        String direction = NONE;
653        if (getTrainDepartsRouteLocation() != null) {
654            direction = getTrainDepartsRouteLocation().getTrainDirectionString();
655        }
656        return direction;
657    }
658
659    /**
660     * Get train's final location's name
661     *
662     * @return train's final location's name
663     */
664    public String getTrainTerminatesName() {
665        if (getTrainTerminatesRouteLocation() != null) {
666            return getTrainTerminatesRouteLocation().getName();
667        }
668        return NONE;
669    }
670
671    public RouteLocation getTrainTerminatesRouteLocation() {
672        if (getRoute() == null) {
673            return null;
674        }
675        return getRoute().getTerminatesRouteLocation();
676    }
677
678    /**
679     * Returns the order the train should be blocked.
680     *
681     * @return routeLocations for this train.
682     */
683    public List<RouteLocation> getTrainBlockingOrder() {
684        if (getRoute() == null) {
685            return null;
686        }
687        return getRoute().getBlockingOrder();
688    }
689
690    /**
691     * Set train's current route location
692     *
693     * @param location The current RouteLocation.
694     */
695    protected void setCurrentLocation(RouteLocation location) {
696        RouteLocation old = _current;
697        _current = location;
698        if ((old != null && !old.equals(location)) || (old == null && location != null)) {
699            setDirtyAndFirePropertyChange(TRAIN_CURRENT_CHANGED_PROPERTY, old, location); // NOI18N
700        }
701    }
702
703    /**
704     * Get train's current location name
705     *
706     * @return Train's current route location name
707     */
708    public String getCurrentLocationName() {
709        if (getCurrentRouteLocation() == null) {
710            return NONE;
711        }
712        return getCurrentRouteLocation().getName();
713    }
714
715    /**
716     * Get train's current route location
717     *
718     * @return Train's current route location
719     */
720    public RouteLocation getCurrentRouteLocation() {
721        if (getRoute() == null) {
722            return null;
723        }
724        if (_current == null) {
725            return null;
726        }
727        // this will verify that the current location still exists
728        return getRoute().getRouteLocationById(_current.getId());
729    }
730
731    /**
732     * Get the train's next location name
733     *
734     * @return Train's next route location name
735     */
736    public String getNextLocationName() {
737        return getNextLocationName(1);
738    }
739
740    /**
741     * Get a location name in a train's route from the current train's location.
742     * A number of "1" means get the next location name in a train's route.
743     *
744     * @param number The stop number, must be greater than 0
745     * @return Name of the location that is the number of stops away from the
746     *         train's current location.
747     */
748    public String getNextLocationName(int number) {
749        RouteLocation rl = getCurrentRouteLocation();
750        while (number-- > 0) {
751            rl = getNextRouteLocation(rl);
752            if (rl == null) {
753                return NONE;
754            }
755        }
756        return rl.getName();
757    }
758
759    public RouteLocation getNextRouteLocation(RouteLocation currentRouteLocation) {
760        if (getRoute() == null) {
761            return null;
762        }
763        List<RouteLocation> routeList = getRoute().getLocationsBySequenceList();
764        for (int i = 0; i < routeList.size(); i++) {
765            RouteLocation rl = routeList.get(i);
766            if (rl == currentRouteLocation) {
767                i++;
768                if (i < routeList.size()) {
769                    return routeList.get(i);
770                }
771                break;
772            }
773        }
774        return null; // At end of route
775    }
776
777    public void setDepartureTrack(Track track) {
778        Track old = _departureTrack;
779        _departureTrack = track;
780        if (old != track) {
781            setDirtyAndFirePropertyChange("DepartureTrackChanged", old, track); // NOI18N
782        }
783    }
784
785    public Track getDepartureTrack() {
786        return _departureTrack;
787    }
788
789    public boolean isDepartingStaging() {
790        return getDepartureTrack() != null;
791    }
792
793    public void setTerminationTrack(Track track) {
794        Track old = _terminationTrack;
795        _terminationTrack = track;
796        if (old != track) {
797            setDirtyAndFirePropertyChange("TerminationTrackChanged", old, track); // NOI18N
798        }
799    }
800
801    public Track getTerminationTrack() {
802        return _terminationTrack;
803    }
804
805    /**
806     * Set the train's machine readable status. Calls update train table row
807     * color.
808     *
809     * @param code machine readable
810     */
811    public void setStatusCode(int code) {
812        String oldStatus = getStatus();
813        int oldCode = getStatusCode();
814        _statusCode = code;
815        setDate(Calendar.getInstance().getTime());
816        if (oldCode != getStatusCode()) {
817            setDirtyAndFirePropertyChange(STATUS_CHANGED_PROPERTY, oldStatus, getStatus());
818        }
819        updateTrainTableRowColor();
820    }
821
822    public void updateTrainTableRowColor() {
823        if (!InstanceManager.getDefault(TrainManager.class).isRowColorManual()) {
824            switch (getStatusCode()) {
825                case CODE_TRAIN_RESET:
826                    String color = getTableRowColorNameReset();
827                    if (color.equals(NONE)) {
828                        color = InstanceManager.getDefault(TrainManager.class).getRowColorNameForReset();
829                    }
830                    setTableRowColorName(color);
831                    break;
832                case CODE_BUILT:
833                case CODE_PARTIAL_BUILT:
834                    setTableRowColorName(InstanceManager.getDefault(TrainManager.class).getRowColorNameForBuilt());
835                    break;
836                case CODE_BUILD_FAILED:
837                    setTableRowColorName(
838                            InstanceManager.getDefault(TrainManager.class).getRowColorNameForBuildFailed());
839                    break;
840                case CODE_TRAIN_EN_ROUTE:
841                    setTableRowColorName(
842                            InstanceManager.getDefault(TrainManager.class).getRowColorNameForTrainEnRoute());
843                    break;
844                case CODE_TERMINATED:
845                    setTableRowColorName(InstanceManager.getDefault(TrainManager.class).getRowColorNameForTerminated());
846                    break;
847                default: // all other cases do nothing
848                    break;
849            }
850        }
851    }
852
853    /**
854     * Get train's status in the default locale.
855     *
856     * @return Human-readable status
857     */
858    public String getStatus() {
859        return this.getStatus(Locale.getDefault());
860    }
861
862    /**
863     * Get train's status in the specified locale.
864     *
865     * @param locale The Locale.
866     * @return Human-readable status
867     */
868    public String getStatus(Locale locale) {
869        return this.getStatus(locale, this.getStatusCode());
870    }
871
872    /**
873     * Get the human-readable status for the requested status code.
874     *
875     * @param locale The Locale.
876     * @param code   requested status
877     * @return Human-readable status
878     */
879    public String getStatus(Locale locale, int code) {
880        switch (code) {
881            case CODE_RUN_SCRIPTS:
882                return RUN_SCRIPTS;
883            case CODE_BUILDING:
884                return BUILDING;
885            case CODE_BUILD_FAILED:
886                return BUILD_FAILED;
887            case CODE_BUILT:
888                return Bundle.getMessage(locale, "StatusBuilt", this.getNumberCarsWorked()); // NOI18N
889            case CODE_PARTIAL_BUILT:
890                return Bundle.getMessage(locale, "StatusPartialBuilt", this.getNumberCarsWorked(),
891                        this.getNumberCarsRequested()); // NOI18N
892            case CODE_TERMINATED:
893                return Bundle.getMessage(locale, "StatusTerminated", this.getSortDate()); // NOI18N
894            case CODE_TRAIN_EN_ROUTE:
895                return Bundle.getMessage(locale, "StatusEnRoute", this.getNumberCarsInTrain(), this.getTrainLength(),
896                        Setup.getLengthUnit().toLowerCase(), this.getTrainWeight()); // NOI18N
897            case CODE_TRAIN_RESET:
898                return TRAIN_RESET;
899            case CODE_MANIFEST_MODIFIED:
900                return MANIFEST_MODIFIED;
901            case CODE_UNKNOWN:
902            default:
903                return UNKNOWN;
904        }
905    }
906
907    public String getMRStatus() {
908        switch (getStatusCode()) {
909            case CODE_PARTIAL_BUILT:
910                return getStatusCode() + "||" + this.getNumberCarsRequested(); // NOI18N
911            case CODE_TERMINATED:
912                return getStatusCode() + "||" + this.getSortDate(); // NOI18N
913            default:
914                return Integer.toString(getStatusCode());
915        }
916    }
917
918    public int getStatusCode() {
919        return _statusCode;
920    }
921
922    protected void setOldStatusCode(int code) {
923        _oldStatusCode = code;
924    }
925
926    protected int getOldStatusCode() {
927        return _oldStatusCode;
928    }
929
930    /**
931     * Used to determine if train has departed the first location in the train's
932     * route
933     *
934     * @return true if train has departed
935     */
936    public boolean isTrainEnRoute() {
937        return !getCurrentLocationName().equals(NONE) && getTrainDepartsRouteLocation() != getCurrentRouteLocation();
938    }
939
940    /**
941     * Used to determine if train is a local switcher serving one location. Note
942     * the train can have more than location in its route, but all location
943     * names must be "same". See TrainCommon.splitString(String name) for the
944     * definition of the "same" name.
945     *
946     * @return true if local switcher
947     */
948    public boolean isLocalSwitcher() {
949        String departureName = TrainCommon.splitString(getTrainDepartsName());
950        Route route = getRoute();
951        if (route != null) {
952            for (RouteLocation rl : route.getLocationsBySequenceList()) {
953                if (!departureName.equals(rl.getSplitName())) {
954                    return false; // not a local switcher
955                }
956            }
957        }
958        return true;
959    }
960
961    public boolean isTurn() {
962        return !isLocalSwitcher() &&
963                TrainCommon.splitString(getTrainDepartsName())
964                        .equals(TrainCommon.splitString(getTrainTerminatesName()));
965    }
966
967    /**
968     * Used to determine if train is carrying only passenger cars.
969     *
970     * @return true if only passenger cars have been assigned to this train.
971     */
972    public boolean isOnlyPassengerCars() {
973        for (Car car : InstanceManager.getDefault(CarManager.class).getList(this)) {
974            if (!car.isPassenger()) {
975                return false;
976            }
977        }
978        return true;
979    }
980
981    List<String> _skipLocationsList = new ArrayList<>();
982
983    protected String[] getTrainSkipsLocations() {
984        String[] locationIds = new String[_skipLocationsList.size()];
985        for (int i = 0; i < _skipLocationsList.size(); i++) {
986            locationIds[i] = _skipLocationsList.get(i);
987        }
988        return locationIds;
989    }
990
991    protected void setTrainSkipsLocations(String[] locationIds) {
992        if (locationIds.length > 0) {
993            Arrays.sort(locationIds);
994            for (String id : locationIds) {
995                _skipLocationsList.add(id);
996            }
997        }
998    }
999
1000    /**
1001     * Train will skip the RouteLocation
1002     *
1003     * @param rl RouteLocation
1004     */
1005    public void addTrainSkipsLocation(RouteLocation rl) {
1006        // insert at start of _skipLocationsList, sort later
1007        if (!_skipLocationsList.contains(rl.getId())) {
1008            _skipLocationsList.add(0, rl.getId());
1009            setDirtyAndFirePropertyChange(STOPS_CHANGED_PROPERTY, _skipLocationsList.size() - 1,
1010                    _skipLocationsList.size());
1011        }
1012    }
1013
1014    public void deleteTrainSkipsLocation(RouteLocation rl) {
1015        _skipLocationsList.remove(rl.getId());
1016        setDirtyAndFirePropertyChange(STOPS_CHANGED_PROPERTY, _skipLocationsList.size() + 1, _skipLocationsList.size());
1017    }
1018
1019    /**
1020     * Determines if this train skips a location (doesn't service the location).
1021     *
1022     * @param rl The route location.
1023     * @return true if the train will not service the location.
1024     */
1025    public boolean isLocationSkipped(RouteLocation rl) {
1026        return _skipLocationsList.contains(rl.getId());
1027    }
1028
1029    List<String> _typeList = new ArrayList<>();
1030
1031    /**
1032     * Get's the type names of rolling stock this train will service
1033     *
1034     * @return The type names for cars and or engines
1035     */
1036    protected String[] getTypeNames() {
1037        return _typeList.toArray(new String[0]);
1038    }
1039
1040    public String[] getCarTypeNames() {
1041        List<String> list = new ArrayList<>();
1042        for (String type : _typeList) {
1043            if (InstanceManager.getDefault(CarTypes.class).containsName(type)) {
1044                list.add(type);
1045            }
1046        }
1047        return list.toArray(new String[0]);
1048    }
1049
1050    public String[] getLocoTypeNames() {
1051        List<String> list = new ArrayList<>();
1052        for (String type : _typeList) {
1053            if (InstanceManager.getDefault(EngineTypes.class).containsName(type)) {
1054                list.add(type);
1055            }
1056        }
1057        return list.toArray(new String[0]);
1058    }
1059
1060    /**
1061     * Set the type of cars or engines this train will service, see types in
1062     * Cars and Engines.
1063     *
1064     * @param types The type names for cars and or engines
1065     */
1066    protected void setTypeNames(String[] types) {
1067        if (types.length > 0) {
1068            Arrays.sort(types);
1069            for (String type : types) {
1070                _typeList.add(type);
1071            }
1072        }
1073    }
1074
1075    /**
1076     * Add a car or engine type name that this train will service.
1077     *
1078     * @param type The new type name to service.
1079     */
1080    public void addTypeName(String type) {
1081        // insert at start of list, sort later
1082        if (type == null || _typeList.contains(type)) {
1083            return;
1084        }
1085        _typeList.add(0, type);
1086        log.debug("Train ({}) add car type ({})", getName(), type);
1087        setDirtyAndFirePropertyChange(TYPES_CHANGED_PROPERTY, _typeList.size() - 1, _typeList.size());
1088    }
1089
1090    public void deleteTypeName(String type) {
1091        if (_typeList.remove(type)) {
1092            log.debug("Train ({}) delete car type ({})", getName(), type);
1093            setDirtyAndFirePropertyChange(TYPES_CHANGED_PROPERTY, _typeList.size() + 1, _typeList.size());
1094        }
1095    }
1096
1097    /**
1098     * Returns true if this train will service the type of car or engine.
1099     *
1100     * @param type The car or engine type name.
1101     * @return true if this train will service the particular type.
1102     */
1103    public boolean isTypeNameAccepted(String type) {
1104        return _typeList.contains(type);
1105    }
1106
1107    protected void replaceType(String oldType, String newType) {
1108        if (isTypeNameAccepted(oldType)) {
1109            deleteTypeName(oldType);
1110            addTypeName(newType);
1111            // adjust loads with type in them
1112            for (String load : getLoadNames()) {
1113                String[] splitLoad = load.split(CarLoad.SPLIT_CHAR);
1114                if (splitLoad.length > 1) {
1115                    if (splitLoad[0].equals(oldType)) {
1116                        deleteLoadName(load);
1117                        if (newType != null) {
1118                            load = newType + CarLoad.SPLIT_CHAR + splitLoad[1];
1119                            addLoadName(load);
1120                        }
1121                    }
1122                }
1123            }
1124        }
1125    }
1126
1127    /**
1128     * Get how this train deals with car road names.
1129     *
1130     * @return ALL_ROADS INCLUDE_ROADS EXCLUDE_ROADS
1131     */
1132    public String getCarRoadOption() {
1133        return _carRoadOption;
1134    }
1135
1136    /**
1137     * Set how this train deals with car road names.
1138     *
1139     * @param option ALL_ROADS INCLUDE_ROADS EXCLUDE_ROADS
1140     */
1141    public void setCarRoadOption(String option) {
1142        String old = _carRoadOption;
1143        _carRoadOption = option;
1144        setDirtyAndFirePropertyChange(ROADS_CHANGED_PROPERTY, old, option);
1145    }
1146
1147    protected void setCarRoadNames(String[] roads) {
1148        setRoadNames(roads, _carRoadList);
1149    }
1150
1151    /**
1152     * Provides a list of car road names that the train will either service or
1153     * exclude. See setCarRoadOption
1154     *
1155     * @return Array of sorted road names as Strings
1156     */
1157    public String[] getCarRoadNames() {
1158        String[] roads = _carRoadList.toArray(new String[0]);
1159        if (_carRoadList.size() > 0) {
1160            Arrays.sort(roads);
1161        }
1162        return roads;
1163    }
1164
1165    /**
1166     * Add a car road name that the train will either service or exclude. See
1167     * setCarRoadOption
1168     *
1169     * @param road The string road name.
1170     * @return true if road name was added, false if road name wasn't in the
1171     *         list.
1172     */
1173    public boolean addCarRoadName(String road) {
1174        if (_carRoadList.contains(road)) {
1175            return false;
1176        }
1177        _carRoadList.add(road);
1178        log.debug("train ({}) add car road {}", getName(), road);
1179        setDirtyAndFirePropertyChange(ROADS_CHANGED_PROPERTY, _carRoadList.size() - 1, _carRoadList.size());
1180        return true;
1181    }
1182
1183    /**
1184     * Delete a car road name that the train will either service or exclude. See
1185     * setRoadOption
1186     *
1187     * @param road The string road name to delete.
1188     * @return true if road name was removed, false if road name wasn't in the
1189     *         list.
1190     */
1191    public boolean deleteCarRoadName(String road) {
1192        if (_carRoadList.remove(road)) {
1193            log.debug("train ({}) delete car road {}", getName(), road);
1194            setDirtyAndFirePropertyChange(ROADS_CHANGED_PROPERTY, _carRoadList.size() + 1, _carRoadList.size());
1195            return true;
1196        }
1197        return false;
1198    }
1199
1200    /**
1201     * Determine if train will service a specific road name for a car.
1202     *
1203     * @param road the road name to check.
1204     * @return true if train will service this road name.
1205     */
1206    public boolean isCarRoadNameAccepted(String road) {
1207        if (_carRoadOption.equals(ALL_ROADS)) {
1208            return true;
1209        }
1210        if (_carRoadOption.equals(INCLUDE_ROADS)) {
1211            return _carRoadList.contains(road);
1212        }
1213        // exclude!
1214        return !_carRoadList.contains(road);
1215    }
1216
1217    /**
1218     * Get how this train deals with caboose road names.
1219     *
1220     * @return ALL_ROADS INCLUDE_ROADS EXCLUDE_ROADS
1221     */
1222    public String getCabooseRoadOption() {
1223        return _cabooseRoadOption;
1224    }
1225
1226    /**
1227     * Set how this train deals with caboose road names.
1228     *
1229     * @param option ALL_ROADS INCLUDE_ROADS EXCLUDE_ROADS
1230     */
1231    public void setCabooseRoadOption(String option) {
1232        String old = _cabooseRoadOption;
1233        _cabooseRoadOption = option;
1234        setDirtyAndFirePropertyChange(ROADS_CHANGED_PROPERTY, old, option);
1235    }
1236
1237    protected void setCabooseRoadNames(String[] roads) {
1238        setRoadNames(roads, _cabooseRoadList);
1239    }
1240
1241    /**
1242     * Provides a list of caboose road names that the train will either service
1243     * or exclude. See setCabooseRoadOption
1244     *
1245     * @return Array of sorted road names as Strings
1246     */
1247    public String[] getCabooseRoadNames() {
1248        String[] roads = _cabooseRoadList.toArray(new String[0]);
1249        if (_cabooseRoadList.size() > 0) {
1250            Arrays.sort(roads);
1251        }
1252        return roads;
1253    }
1254
1255    /**
1256     * Add a caboose road name that the train will either service or exclude.
1257     * See setCabooseRoadOption
1258     *
1259     * @param road The string road name.
1260     * @return true if road name was added, false if road name wasn't in the
1261     *         list.
1262     */
1263    public boolean addCabooseRoadName(String road) {
1264        if (_cabooseRoadList.contains(road)) {
1265            return false;
1266        }
1267        _cabooseRoadList.add(road);
1268        log.debug("train ({}) add caboose road {}", getName(), road);
1269        setDirtyAndFirePropertyChange(ROADS_CHANGED_PROPERTY, _cabooseRoadList.size() - 1, _cabooseRoadList.size());
1270        return true;
1271    }
1272
1273    /**
1274     * Delete a caboose road name that the train will either service or exclude.
1275     * See setRoadOption
1276     *
1277     * @param road The string road name to delete.
1278     * @return true if road name was removed, false if road name wasn't in the
1279     *         list.
1280     */
1281    public boolean deleteCabooseRoadName(String road) {
1282        if (_cabooseRoadList.remove(road)) {
1283            log.debug("train ({}) delete caboose road {}", getName(), road);
1284            setDirtyAndFirePropertyChange(ROADS_CHANGED_PROPERTY, _cabooseRoadList.size() + 1, _cabooseRoadList.size());
1285            return true;
1286        }
1287        return false;
1288    }
1289
1290    /**
1291     * Determine if train will service a specific road name for a caboose.
1292     *
1293     * @param road the road name to check.
1294     * @return true if train will service this road name.
1295     */
1296    public boolean isCabooseRoadNameAccepted(String road) {
1297        if (_cabooseRoadOption.equals(ALL_ROADS)) {
1298            return true;
1299        }
1300        if (_cabooseRoadOption.equals(INCLUDE_ROADS)) {
1301            return _cabooseRoadList.contains(road);
1302        }
1303        // exclude!
1304        return !_cabooseRoadList.contains(road);
1305    }
1306
1307    /**
1308     * Get how this train deals with locomotive road names.
1309     *
1310     * @return ALL_ROADS INCLUDE_ROADS EXCLUDE_ROADS
1311     */
1312    public String getLocoRoadOption() {
1313        return _locoRoadOption;
1314    }
1315
1316    /**
1317     * Set how this train deals with locomotive road names.
1318     *
1319     * @param option ALL_ROADS INCLUDE_ROADS EXCLUDE_ROADS
1320     */
1321    public void setLocoRoadOption(String option) {
1322        String old = _locoRoadOption;
1323        _locoRoadOption = option;
1324        setDirtyAndFirePropertyChange(ROADS_CHANGED_PROPERTY, old, option);
1325    }
1326
1327    protected void setLocoRoadNames(String[] roads) {
1328        setRoadNames(roads, _locoRoadList);
1329    }
1330
1331    private void setRoadNames(String[] roads, List<String> list) {
1332        if (roads.length > 0) {
1333            Arrays.sort(roads);
1334            for (String road : roads) {
1335                if (!road.isEmpty()) {
1336                    list.add(road);
1337                }
1338            }
1339        }
1340    }
1341
1342    /**
1343     * Provides a list of engine road names that the train will either service
1344     * or exclude. See setLocoRoadOption
1345     *
1346     * @return Array of sorted road names as Strings
1347     */
1348    public String[] getLocoRoadNames() {
1349        String[] roads = _locoRoadList.toArray(new String[0]);
1350        if (_locoRoadList.size() > 0) {
1351            Arrays.sort(roads);
1352        }
1353        return roads;
1354    }
1355
1356    /**
1357     * Add a engine road name that the train will either service or exclude. See
1358     * setLocoRoadOption
1359     *
1360     * @param road The string road name.
1361     * @return true if road name was added, false if road name wasn't in the
1362     *         list.
1363     */
1364    public boolean addLocoRoadName(String road) {
1365        if (road.isBlank() || _locoRoadList.contains(road)) {
1366            return false;
1367        }
1368        _locoRoadList.add(road);
1369        log.debug("train ({}) add engine road {}", getName(), road);
1370        setDirtyAndFirePropertyChange(ROADS_CHANGED_PROPERTY, _locoRoadList.size() - 1, _locoRoadList.size());
1371        return true;
1372    }
1373
1374    /**
1375     * Delete a engine road name that the train will either service or exclude.
1376     * See setLocoRoadOption
1377     *
1378     * @param road The string road name to delete.
1379     * @return true if road name was removed, false if road name wasn't in the
1380     *         list.
1381     */
1382    public boolean deleteLocoRoadName(String road) {
1383        if (_locoRoadList.remove(road)) {
1384            log.debug("train ({}) delete engine road {}", getName(), road);
1385            setDirtyAndFirePropertyChange(ROADS_CHANGED_PROPERTY, _locoRoadList.size() + 1, _locoRoadList.size());
1386            return true;
1387        }
1388        return false;
1389    }
1390
1391    /**
1392     * Determine if train will service a specific road name for an engine.
1393     *
1394     * @param road the road name to check.
1395     * @return true if train will service this road name.
1396     */
1397    public boolean isLocoRoadNameAccepted(String road) {
1398        if (_locoRoadOption.equals(ALL_ROADS)) {
1399            return true;
1400        }
1401        if (_locoRoadOption.equals(INCLUDE_ROADS)) {
1402            return _locoRoadList.contains(road);
1403        }
1404        // exclude!
1405        return !_locoRoadList.contains(road);
1406    }
1407
1408    protected void replaceRoad(String oldRoad, String newRoad) {
1409        if (newRoad != null) {
1410            if (deleteCarRoadName(oldRoad)) {
1411                addCarRoadName(newRoad);
1412            }
1413            if (deleteCabooseRoadName(oldRoad)) {
1414                addCabooseRoadName(newRoad);
1415            }
1416            if (deleteLocoRoadName(oldRoad)) {
1417                addLocoRoadName(newRoad);
1418            }
1419            if (getEngineRoad().equals(oldRoad)) {
1420                setEngineRoad(newRoad);
1421            }
1422            if (getCabooseRoad().equals(oldRoad)) {
1423                setCabooseRoad(newRoad);
1424            }
1425            if (getSecondLegEngineRoad().equals(oldRoad)) {
1426                setSecondLegEngineRoad(newRoad);
1427            }
1428            if (getSecondLegCabooseRoad().equals(oldRoad)) {
1429                setSecondLegCabooseRoad(newRoad);
1430            }
1431            if (getThirdLegEngineRoad().equals(oldRoad)) {
1432                setThirdLegEngineRoad(newRoad);
1433            }
1434            if (getThirdLegCabooseRoad().equals(oldRoad)) {
1435                setThirdLegCabooseRoad(newRoad);
1436            }
1437        }
1438    }
1439
1440    /**
1441     * Gets the car load option for this train.
1442     *
1443     * @return ALL_LOADS INCLUDE_LOADS EXCLUDE_LOADS
1444     */
1445    public String getLoadOption() {
1446        return _loadOption;
1447    }
1448
1449    /**
1450     * Set how this train deals with car loads
1451     *
1452     * @param option ALL_LOADS INCLUDE_LOADS EXCLUDE_LOADS
1453     */
1454    public void setLoadOption(String option) {
1455        String old = _loadOption;
1456        _loadOption = option;
1457        setDirtyAndFirePropertyChange(LOADS_CHANGED_PROPERTY, old, option);
1458    }
1459
1460    List<String> _loadList = new ArrayList<>();
1461
1462    protected void setLoadNames(String[] loads) {
1463        if (loads.length > 0) {
1464            Arrays.sort(loads);
1465            for (String load : loads) {
1466                if (!load.isEmpty()) {
1467                    _loadList.add(load);
1468                }
1469            }
1470        }
1471    }
1472
1473    /**
1474     * Provides a list of loads that the train will either service or exclude.
1475     * See setLoadOption
1476     *
1477     * @return Array of load names as Strings
1478     */
1479    public String[] getLoadNames() {
1480        String[] loads = _loadList.toArray(new String[0]);
1481        if (_loadList.size() > 0) {
1482            Arrays.sort(loads);
1483        }
1484        return loads;
1485    }
1486
1487    /**
1488     * Add a load that the train will either service or exclude. See
1489     * setLoadOption
1490     *
1491     * @param load The string load name.
1492     * @return true if load name was added, false if load name wasn't in the
1493     *         list.
1494     */
1495    public boolean addLoadName(String load) {
1496        if (_loadList.contains(load)) {
1497            return false;
1498        }
1499        _loadList.add(load);
1500        log.debug("train ({}) add car load {}", getName(), load);
1501        setDirtyAndFirePropertyChange(LOADS_CHANGED_PROPERTY, _loadList.size() - 1, _loadList.size());
1502        return true;
1503    }
1504
1505    /**
1506     * Delete a load name that the train will either service or exclude. See
1507     * setLoadOption
1508     *
1509     * @param load The string load name.
1510     * @return true if load name was removed, false if load name wasn't in the
1511     *         list.
1512     */
1513    public boolean deleteLoadName(String load) {
1514        if (_loadList.remove(load)) {
1515            log.debug("train ({}) delete car load {}", getName(), load);
1516            setDirtyAndFirePropertyChange(LOADS_CHANGED_PROPERTY, _loadList.size() + 1, _loadList.size());
1517            return true;
1518        }
1519        return false;
1520    }
1521
1522    /**
1523     * Determine if train will service a specific load name.
1524     *
1525     * @param load the load name to check.
1526     * @return true if train will service this load.
1527     */
1528    public boolean isLoadNameAccepted(String load) {
1529        if (_loadOption.equals(ALL_LOADS)) {
1530            return true;
1531        }
1532        if (_loadOption.equals(INCLUDE_LOADS)) {
1533            return _loadList.contains(load);
1534        }
1535        // exclude!
1536        return !_loadList.contains(load);
1537    }
1538
1539    /**
1540     * Determine if train will service a specific load and car type.
1541     *
1542     * @param load the load name to check.
1543     * @param type the type of car used to carry the load.
1544     * @return true if train will service this load.
1545     */
1546    public boolean isLoadNameAccepted(String load, String type) {
1547        if (_loadOption.equals(ALL_LOADS)) {
1548            return true;
1549        }
1550        if (_loadOption.equals(INCLUDE_LOADS)) {
1551            return _loadList.contains(load) || _loadList.contains(type + CarLoad.SPLIT_CHAR + load);
1552        }
1553        // exclude!
1554        return !_loadList.contains(load) && !_loadList.contains(type + CarLoad.SPLIT_CHAR + load);
1555    }
1556
1557    public String getOwnerOption() {
1558        return _ownerOption;
1559    }
1560
1561    /**
1562     * Set how this train deals with car owner names
1563     *
1564     * @param option ALL_OWNERS INCLUDE_OWNERS EXCLUDE_OWNERS
1565     */
1566    public void setOwnerOption(String option) {
1567        String old = _ownerOption;
1568        _ownerOption = option;
1569        setDirtyAndFirePropertyChange(OWNERS_CHANGED_PROPERTY, old, option);
1570    }
1571
1572    List<String> _ownerList = new ArrayList<>();
1573
1574    protected void setOwnerNames(String[] owners) {
1575        if (owners.length > 0) {
1576            Arrays.sort(owners);
1577            for (String owner : owners) {
1578                if (!owner.isEmpty()) {
1579                    _ownerList.add(owner);
1580                }
1581            }
1582        }
1583    }
1584
1585    /**
1586     * Provides a list of owner names that the train will either service or
1587     * exclude. See setOwnerOption
1588     *
1589     * @return Array of owner names as Strings
1590     */
1591    public String[] getOwnerNames() {
1592        String[] owners = _ownerList.toArray(new String[0]);
1593        if (_ownerList.size() > 0) {
1594            Arrays.sort(owners);
1595        }
1596        return owners;
1597    }
1598
1599    /**
1600     * Add a owner name that the train will either service or exclude. See
1601     * setOwnerOption
1602     *
1603     * @param owner The string representing the owner's name.
1604     * @return true if owner name was added, false if owner name wasn't in the
1605     *         list.
1606     */
1607    public boolean addOwnerName(String owner) {
1608        if (_ownerList.contains(owner)) {
1609            return false;
1610        }
1611        _ownerList.add(owner);
1612        log.debug("train ({}) add car owner {}", getName(), owner);
1613        setDirtyAndFirePropertyChange(OWNERS_CHANGED_PROPERTY, _ownerList.size() - 1, _ownerList.size());
1614        return true;
1615    }
1616
1617    /**
1618     * Delete a owner name that the train will either service or exclude. See
1619     * setOwnerOption
1620     *
1621     * @param owner The string representing the owner's name.
1622     * @return true if owner name was removed, false if owner name wasn't in the
1623     *         list.
1624     */
1625    public boolean deleteOwnerName(String owner) {
1626        if (_ownerList.remove(owner)) {
1627            log.debug("train ({}) delete car owner {}", getName(), owner);
1628            setDirtyAndFirePropertyChange(OWNERS_CHANGED_PROPERTY, _ownerList.size() + 1, _ownerList.size());
1629            return true;
1630        }
1631        return false;
1632    }
1633
1634    /**
1635     * Determine if train will service a specific owner name.
1636     *
1637     * @param owner the owner name to check.
1638     * @return true if train will service this owner name.
1639     */
1640    public boolean isOwnerNameAccepted(String owner) {
1641        if (_ownerOption.equals(ALL_OWNERS)) {
1642            return true;
1643        }
1644        if (_ownerOption.equals(INCLUDE_OWNERS)) {
1645            return _ownerList.contains(owner);
1646        }
1647        // exclude!
1648        return !_ownerList.contains(owner);
1649    }
1650
1651    protected void replaceOwner(String oldName, String newName) {
1652        if (deleteOwnerName(oldName)) {
1653            addOwnerName(newName);
1654        }
1655    }
1656
1657    /**
1658     * Only rolling stock built in or after this year will be used.
1659     *
1660     * @param year A string representing a year.
1661     */
1662    public void setBuiltStartYear(String year) {
1663        String old = _builtStartYear;
1664        _builtStartYear = year;
1665        if (!old.equals(year)) {
1666            setDirtyAndFirePropertyChange(BUILT_YEAR_CHANGED_PROPERTY, old, year);
1667        }
1668    }
1669
1670    public String getBuiltStartYear() {
1671        return _builtStartYear;
1672    }
1673
1674    /**
1675     * Only rolling stock built in or before this year will be used.
1676     *
1677     * @param year A string representing a year.
1678     */
1679    public void setBuiltEndYear(String year) {
1680        String old = _builtEndYear;
1681        _builtEndYear = year;
1682        if (!old.equals(year)) {
1683            setDirtyAndFirePropertyChange(BUILT_YEAR_CHANGED_PROPERTY, old, year);
1684        }
1685    }
1686
1687    public String getBuiltEndYear() {
1688        return _builtEndYear;
1689    }
1690
1691    /**
1692     * Determine if train will service rolling stock by built date.
1693     *
1694     * @param date A string representing the built date for a car or engine.
1695     * @return true is built date is in the acceptable range.
1696     */
1697    public boolean isBuiltDateAccepted(String date) {
1698        if (getBuiltStartYear().equals(NONE) && getBuiltEndYear().equals(NONE)) {
1699            return true; // range dates not defined
1700        }
1701        int startYear = 0; // default start year;
1702        int endYear = 99999; // default end year;
1703        int builtYear = -1900;
1704        if (!getBuiltStartYear().equals(NONE)) {
1705            try {
1706                startYear = Integer.parseInt(getBuiltStartYear());
1707            } catch (NumberFormatException e) {
1708                log.debug("Train ({}) built start date not initialized, start: {}", getName(), getBuiltStartYear());
1709            }
1710        }
1711        if (!getBuiltEndYear().equals(NONE)) {
1712            try {
1713                endYear = Integer.parseInt(getBuiltEndYear());
1714            } catch (NumberFormatException e) {
1715                log.debug("Train ({}) built end date not initialized, end: {}", getName(), getBuiltEndYear());
1716            }
1717        }
1718        try {
1719            builtYear = Integer.parseInt(RollingStockManager.convertBuildDate(date));
1720        } catch (NumberFormatException e) {
1721            log.debug("Unable to parse car built date {}", date);
1722        }
1723        if (startYear < builtYear && builtYear < endYear) {
1724            return true;
1725        }
1726        return false;
1727    }
1728
1729    private final boolean debugFlag = false;
1730
1731    /**
1732     * Determines if this train will service this car. Note this code doesn't
1733     * check the location or tracks that needs to be done separately. See
1734     * Router.java.
1735     *
1736     * @param car The car to be tested.
1737     * @return true if this train can service the car.
1738     */
1739    public boolean isServiceable(Car car) {
1740        return isServiceable(null, car);
1741    }
1742
1743    /**
1744     * Note that this code was written after TrainBuilder. It does pretty much
1745     * the same as TrainBuilder but with much fewer build report messages.
1746     *
1747     * @param buildReport PrintWriter
1748     * @param car         the car to be tested
1749     * @return true if this train can service the car.
1750     */
1751    public boolean isServiceable(PrintWriter buildReport, Car car) {
1752        setServiceStatus(NONE);
1753        // check to see if train can carry car
1754        if (!isTypeNameAccepted(car.getTypeName())) {
1755            addLine(buildReport, Bundle.getMessage("trainCanNotServiceCarType",
1756                    getName(), car.toString(), car.getTypeName()));
1757            return false;
1758        }
1759        if (!isLoadNameAccepted(car.getLoadName(), car.getTypeName())) {
1760            addLine(buildReport, Bundle.getMessage("trainCanNotServiceCarLoad",
1761                    getName(), car.toString(), car.getTypeName(), car.getLoadName()));
1762            return false;
1763        }
1764        if (!isBuiltDateAccepted(car.getBuilt()) ||
1765                !isOwnerNameAccepted(car.getOwnerName()) ||
1766                (!car.isCaboose() && !isCarRoadNameAccepted(car.getRoadName())) ||
1767                (car.isCaboose() && !isCabooseRoadNameAccepted(car.getRoadName()))) {
1768            addLine(buildReport, Bundle.getMessage("trainCanNotServiceCar",
1769                    getName(), car.toString()));
1770            return false;
1771        }
1772
1773        Route route = getRoute();
1774        if (route == null) {
1775            return false;
1776        }
1777
1778        if (car.getLocation() == null || car.getTrack() == null) {
1779            return false;
1780        }
1781
1782        // determine if the car's location is serviced by this train
1783        if (route.getLastLocationByName(car.getLocationName()) == null) {
1784            addLine(buildReport, Bundle.getMessage("trainNotThisLocation",
1785                    getName(), car.getLocationName()));
1786            return false;
1787        }
1788        // determine if the car's destination is serviced by this train
1789        // check to see if destination is staging and is also the last location in the train's route
1790        if (car.getDestination() != null &&
1791                (route.getLastLocationByName(car.getDestinationName()) == null ||
1792                        (car.getDestination().isStaging() &&
1793                                getTrainTerminatesRouteLocation().getLocation() != car.getDestination()))) {
1794            addLine(buildReport, Bundle.getMessage("trainNotThisLocation",
1795                    getName(), car.getDestinationName()));
1796            return false;
1797        }
1798        // now find the car in the train's route
1799        List<RouteLocation> rLocations = route.getLocationsBySequenceList();
1800        for (RouteLocation rLoc : rLocations) {
1801            if (rLoc.getName().equals(car.getLocationName())) {
1802                if (rLoc.getMaxCarMoves() <= 0 ||
1803                        isLocationSkipped(rLoc) ||
1804                        !rLoc.isPickUpAllowed() && !car.isLocalMove() ||
1805                        !rLoc.isLocalMovesAllowed() && car.isLocalMove()) {
1806                    addLine(buildReport, Bundle.getMessage("trainCanNotServiceCarFrom",
1807                            getName(), car.toString(), car.getLocationName(), car.getTrackName(), rLoc.getId()));
1808                    continue;
1809                }
1810                // check train and car's location direction
1811                if ((car.getLocation().getTrainDirections() & rLoc.getTrainDirection()) == 0 && !isLocalSwitcher()) {
1812                    addLine(buildReport,
1813                            Bundle.getMessage("trainCanNotServiceCarLocation",
1814                                    getName(), car.toString(), car.getLocationName(), car.getTrackName(),
1815                                    rLoc.getId(), car.getLocationName(), rLoc.getTrainDirectionString()));
1816                    continue;
1817                }
1818                // check train and car's track direction
1819                if ((car.getTrack().getTrainDirections() & rLoc.getTrainDirection()) == 0 && !isLocalSwitcher()) {
1820                    addLine(buildReport,
1821                            Bundle.getMessage("trainCanNotServiceCarTrack",
1822                                    getName(), car.toString(), car.getLocationName(), car.getTrackName(),
1823                                    rLoc.getId(), car.getTrackName(), rLoc.getTrainDirectionString()));
1824                    continue;
1825                }
1826                // can train pull this car?
1827                if (!car.getTrack().isPickupTrainAccepted(this)) {
1828                    addLine(buildReport,
1829                            Bundle.getMessage("trainCanNotServiceCarPickup",
1830                                    getName(), car.toString(), car.getLocationName(), car.getTrackName(),
1831                                    rLoc.getId(), car.getTrackName(), getName()));
1832                    continue;
1833                }
1834                if (debugFlag) {
1835                    log.debug("Car ({}) can be picked up by train ({}) location ({}, {}) destination ({}, {})",
1836                            car.toString(), getName(), car.getLocationName(), car.getTrackName(),
1837                            car.getDestinationName(), car.getDestinationTrackName());
1838                }
1839                addLine(buildReport, Bundle.getMessage("trainCanPickUpCar",
1840                        getName(), car.toString(), car.getLocationName(), car.getTrackName(), rLoc.getId()));
1841                if (car.getDestination() == null) {
1842                    if (debugFlag) {
1843                        log.debug("Car ({}) does not have a destination", car.toString());
1844                    }
1845                    return true; // done
1846                }
1847                // now check car's destination
1848                return isServiceableDestination(buildReport, car, rLoc, rLocations);
1849            }
1850        }
1851        if (debugFlag) {
1852            log.debug("Train ({}) can't service car ({}) from ({}, {})", getName(), car.toString(),
1853                    car.getLocationName(), car.getTrackName());
1854        }
1855        return false;
1856    }
1857
1858    /**
1859     * Second step in determining if train can service car, check to see if
1860     * car's destination is serviced by this train's route.
1861     *
1862     * @param buildReport add messages if needed to build report
1863     * @param car         The test car
1864     * @param rLoc        Where in the train's route the car was found
1865     * @param rLocations  The ordered routeLocations in this train's route
1866     * @return true if car's destination can be serviced
1867     */
1868    private boolean isServiceableDestination(PrintWriter buildReport, Car car, RouteLocation rLoc,
1869            List<RouteLocation> rLocations) {
1870        // car can be a kernel so get total length
1871        int length = car.getTotalKernelLength();
1872        // now see if the train's route services the car's destination
1873        for (int k = rLocations.indexOf(rLoc); k < rLocations.size(); k++) {
1874            RouteLocation rldest = rLocations.get(k);
1875            if (rldest.getName().equals(car.getDestinationName()) &&
1876                    (rldest.isDropAllowed() && !car.isLocalMove() ||
1877                            rldest.isLocalMovesAllowed() && car.isLocalMove()) &&
1878                    rldest.getMaxCarMoves() > 0 &&
1879                    !isLocationSkipped(rldest) &&
1880                    (!Setup.isCheckCarDestinationEnabled() ||
1881                            car.getTrack().isDestinationAccepted(car.getDestination()))) {
1882                // found the car's destination
1883                // check track and train direction
1884                if ((car.getDestination().getTrainDirections() & rldest.getTrainDirection()) == 0 &&
1885                        !isLocalSwitcher()) {
1886                    addLine(buildReport, Bundle.getMessage("trainCanNotServiceCarDestination",
1887                            getName(), car.toString(), car.getDestinationName(), rldest.getId(),
1888                            rldest.getTrainDirectionString()));
1889                    continue;
1890                }
1891                //check destination track
1892                if (car.getDestinationTrack() != null) {
1893                    if (!isServicableTrack(buildReport, car, rldest, car.getDestinationTrack())) {
1894                        continue;
1895                    }
1896                    // car doesn't have a destination track
1897                    // car going to staging?
1898                } else if (!isCarToStaging(buildReport, rldest, car)) {
1899                    continue;
1900                } else {
1901                    if (debugFlag) {
1902                        log.debug("Find track for car ({}) at destination ({})", car.toString(),
1903                                car.getDestinationName());
1904                    }
1905                    // determine if there's a destination track that is willing to accept this car
1906                    String status = "";
1907                    List<Track> tracks = rldest.getLocation().getTracksList();
1908                    for (Track track : tracks) {
1909                        if (!isServicableTrack(buildReport, car, rldest, track)) {
1910                            continue;
1911                        }
1912                        // will the track accept this car?
1913                        status = track.isRollingStockAccepted(car);
1914                        if (status.equals(Track.OKAY) || status.startsWith(Track.LENGTH)) {
1915                            if (debugFlag) {
1916                                log.debug("Found track ({}) for car ({})", track.getName(), car.toString());
1917                            }
1918                            break; // found track
1919                        }
1920                    }
1921                    if (!status.equals(Track.OKAY) && !status.startsWith(Track.LENGTH)) {
1922                        if (debugFlag) {
1923                            log.debug("Destination ({}) can not service car ({}) using train ({}) no track available",
1924                                    car.getDestinationName(), car.toString(), getName()); // NOI18N
1925                        }
1926                        addLine(buildReport, Bundle.getMessage("trainCanNotDeliverNoTracks",
1927                                getName(), car.toString(), car.getDestinationName(), rldest.getId()));
1928                        continue;
1929                    }
1930                }
1931                // restriction to only carry cars to terminal?
1932                if (!isOnlyToTerminal(buildReport, car)) {
1933                    continue;
1934                }
1935                // don't allow local move when car is in staging
1936                if (!isTurn() &&
1937                        car.getTrack().isStaging() &&
1938                        rldest.getLocation() == car.getLocation()) {
1939                    log.debug(
1940                            "Car ({}) at ({}, {}) not allowed to perform local move in staging ({})",
1941                            car.toString(), car.getLocationName(), car.getTrackName(), rldest.getName());
1942                    continue;
1943                }
1944                // allow car to return to staging?
1945                if (isAllowReturnToStagingEnabled() &&
1946                        car.getTrack().isStaging() &&
1947                        rldest.getLocation() == car.getLocation()) {
1948                    addLine(buildReport,
1949                            Bundle.getMessage("trainCanReturnCarToStaging",
1950                                    getName(), car.toString(), car.getDestinationName(),
1951                                    car.getDestinationTrackName()));
1952                    return true; // done
1953                }
1954                // is this local move allowed?
1955                if (!isLocalMoveAllowed(buildReport, car, rLoc, rldest)) {
1956                    continue;
1957                }
1958                // Can cars travel from origin to terminal?
1959                if (!isTravelOriginToTerminalAllowed(buildReport, rLoc, rldest, car)) {
1960                    continue;
1961                }
1962                // check to see if moves are available
1963                if (!isRouteMovesAvailable(buildReport, rldest)) {
1964                    continue;
1965                }
1966                if (debugFlag) {
1967                    log.debug("Car ({}) can be dropped by train ({}) to ({}, {})", car.toString(), getName(),
1968                            car.getDestinationName(), car.getDestinationTrackName());
1969                }
1970                return true; // done
1971            }
1972            // check to see if train length is okay
1973            if (!isTrainLengthOkay(buildReport, car, rldest, length)) {
1974                return false;
1975            }
1976        }
1977        addLine(buildReport, Bundle.getMessage("trainCanNotDeliverToDestination",
1978                getName(), car.toString(), car.getDestinationName(), car.getDestinationTrackName()));
1979        return false;
1980    }
1981
1982    private boolean isServicableTrack(PrintWriter buildReport, Car car, RouteLocation rldest, Track track) {
1983        // train and track direction
1984        if ((track.getTrainDirections() & rldest.getTrainDirection()) == 0 && !isLocalSwitcher()) {
1985            addLine(buildReport, Bundle.getMessage("buildCanNotDropRsUsingTrain",
1986                    car.toString(), rldest.getTrainDirectionString(), track.getName()));
1987            return false;
1988        }
1989        if (!track.isDropTrainAccepted(this)) {
1990            addLine(buildReport, Bundle.getMessage("buildCanNotDropTrain",
1991                    car.toString(), getName(), track.getTrackTypeName(), track.getLocation().getName(),
1992                    track.getName()));
1993            return false;
1994        }
1995        return true;
1996    }
1997
1998    private boolean isCarToStaging(PrintWriter buildReport, RouteLocation rldest, Car car) {
1999        if (rldest.getLocation().isStaging() &&
2000                getStatusCode() == CODE_BUILDING &&
2001                getTerminationTrack() != null &&
2002                getTerminationTrack().getLocation() == rldest.getLocation()) {
2003            if (debugFlag) {
2004                log.debug("Car ({}) destination is staging, check train ({}) termination track ({})",
2005                        car.toString(), getName(), getTerminationTrack().getName());
2006            }
2007            String status = car.checkDestination(getTerminationTrack().getLocation(), getTerminationTrack());
2008            if (!status.equals(Track.OKAY)) {
2009                addLine(buildReport,
2010                        Bundle.getMessage("trainCanNotDeliverToStaging",
2011                                getName(), car.toString(),
2012                                getTerminationTrack().getLocation().getName(),
2013                                getTerminationTrack().getName(), status));
2014                setServiceStatus(status);
2015                return false;
2016            }
2017        }
2018        return true;
2019    }
2020
2021    private boolean isOnlyToTerminal(PrintWriter buildReport, Car car) {
2022        // ignore send to terminal if a local move
2023        if (isSendCarsToTerminalEnabled() &&
2024                !car.isLocalMove() &&
2025                !car.getSplitLocationName()
2026                        .equals(TrainCommon.splitString(getTrainDepartsName())) &&
2027                !car.getSplitDestinationName()
2028                        .equals(TrainCommon.splitString(getTrainTerminatesName()))) {
2029            if (debugFlag) {
2030                log.debug("option send cars to terminal is enabled");
2031            }
2032            addLine(buildReport,
2033                    Bundle.getMessage("trainCanNotCarryCarOption",
2034                            getName(), car.toString(), car.getLocationName(),
2035                            car.getTrackName(), car.getDestinationName(),
2036                            car.getDestinationTrackName()));
2037            return false;
2038        }
2039        return true;
2040    }
2041
2042    private boolean isLocalMoveAllowed(PrintWriter buildReport, Car car, RouteLocation rLoc, RouteLocation rldest) {
2043        if ((!isAllowLocalMovesEnabled() || !rLoc.isLocalMovesAllowed() || !rldest.isLocalMovesAllowed()) &&
2044                !isLocalSwitcher() &&
2045                !car.isCaboose() &&
2046                !car.hasFred() &&
2047                !car.isPassenger() &&
2048                car.isLocalMove()) {
2049            if (debugFlag) {
2050                log.debug("Local move not allowed");
2051            }
2052            addLine(buildReport, Bundle.getMessage("trainCanNotPerformLocalMove",
2053                    getName(), car.toString(), car.getLocationName()));
2054            return false;
2055        }
2056        return true;
2057    }
2058
2059    private boolean isTravelOriginToTerminalAllowed(PrintWriter buildReport, RouteLocation rLoc, RouteLocation rldest,
2060            Car car) {
2061        if (!isAllowThroughCarsEnabled() &&
2062                TrainCommon.splitString(getTrainDepartsName())
2063                        .equals(rLoc.getSplitName()) &&
2064                TrainCommon.splitString(getTrainTerminatesName())
2065                        .equals(rldest.getSplitName()) &&
2066                !TrainCommon.splitString(getTrainDepartsName())
2067                        .equals(TrainCommon.splitString(getTrainTerminatesName())) &&
2068                !isLocalSwitcher() &&
2069                !car.isCaboose() &&
2070                !car.hasFred() &&
2071                !car.isPassenger()) {
2072            if (debugFlag) {
2073                log.debug("Through car ({}) not allowed", car.toString());
2074            }
2075            addLine(buildReport, Bundle.getMessage("trainDoesNotCarryOriginTerminal",
2076                    getName(), car.getLocationName(), car.getDestinationName()));
2077            return false;
2078        }
2079        return true;
2080    }
2081
2082    private boolean isRouteMovesAvailable(PrintWriter buildReport, RouteLocation rldest) {
2083        if (getStatusCode() == CODE_BUILDING && rldest.getMaxCarMoves() - rldest.getCarMoves() <= 0) {
2084            setServiceStatus(Bundle.getMessage("trainNoMoves",
2085                    getName(), getRoute().getName(), rldest.getId(), rldest.getName()));
2086            if (debugFlag) {
2087                log.debug("No available moves for destination {}", rldest.getName());
2088            }
2089            addLine(buildReport, getServiceStatus());
2090            return false;
2091        }
2092        return true;
2093    }
2094
2095    private boolean isTrainLengthOkay(PrintWriter buildReport, Car car, RouteLocation rldest, int length) {
2096        if (getStatusCode() == CODE_BUILDING && rldest.getTrainLength() + length > rldest.getMaxTrainLength()) {
2097            setServiceStatus(Bundle.getMessage("trainExceedsMaximumLength",
2098                    getName(), getRoute().getName(), rldest.getId(), rldest.getMaxTrainLength(),
2099                    Setup.getLengthUnit().toLowerCase(), rldest.getName(), car.toString(),
2100                    rldest.getTrainLength() + length - rldest.getMaxTrainLength()));
2101            if (debugFlag) {
2102                log.debug("Car ({}) exceeds maximum train length {} when departing ({})", car.toString(),
2103                        rldest.getMaxTrainLength(), rldest.getName());
2104            }
2105            addLine(buildReport, getServiceStatus());
2106            return false;
2107        }
2108        return true;
2109    }
2110
2111    protected static final String SEVEN = Setup.BUILD_REPORT_VERY_DETAILED;
2112
2113    private void addLine(PrintWriter buildReport, String string) {
2114        if (Setup.getRouterBuildReportLevel().equals(SEVEN)) {
2115            TrainCommon.addLine(buildReport, SEVEN, string);
2116        }
2117    }
2118
2119    protected void setServiceStatus(String status) {
2120        _serviceStatus = status;
2121    }
2122
2123    /**
2124     * Returns the statusCode of the "isServiceable(Car)" routine. There are two
2125     * statusCodes that need special consideration when the train is being
2126     * built, the moves in a train's route and the maximum train length. NOTE:
2127     * The code using getServiceStatus() currently assumes that if there's a
2128     * service status that the issue is either route moves or maximum train
2129     * length.
2130     *
2131     * @return The statusCode.
2132     */
2133    public String getServiceStatus() {
2134        return _serviceStatus;
2135    }
2136
2137    /**
2138     * @return The number of cars worked by this train
2139     */
2140    public int getNumberCarsWorked() {
2141        int count = 0;
2142        for (Car rs : InstanceManager.getDefault(CarManager.class).getList(this)) {
2143            if (rs.getRouteLocation() != null) {
2144                count++;
2145            }
2146        }
2147        return count;
2148    }
2149
2150    public void setNumberCarsRequested(int number) {
2151        _statusCarsRequested = number;
2152    }
2153
2154    public int getNumberCarsRequested() {
2155        return _statusCarsRequested;
2156    }
2157
2158    public void setDate(Date date) {
2159        _date = date;
2160    }
2161
2162    public String getSortDate() {
2163        if (_date == null) {
2164            return NONE;
2165        }
2166        SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); // NOI18N
2167        return format.format(_date);
2168    }
2169
2170    public String getDate() {
2171        if (_date == null) {
2172            return NONE;
2173        }
2174        SimpleDateFormat format = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss"); // NOI18N
2175        return format.format(_date);
2176    }
2177
2178    /**
2179     * Gets the number of cars in the train at the current location in the
2180     * train's route.
2181     *
2182     * @return The number of cars currently in the train
2183     */
2184    public int getNumberCarsInTrain() {
2185        return getNumberCarsInTrain(getCurrentRouteLocation());
2186    }
2187
2188    /**
2189     * Gets the number of cars in the train when train departs the route
2190     * location.
2191     *
2192     * @param routeLocation The RouteLocation.
2193     * @return The number of cars in the train departing the route location.
2194     */
2195    public int getNumberCarsInTrain(RouteLocation routeLocation) {
2196        int number = 0;
2197        Route route = getRoute();
2198        if (route != null) {
2199            for (RouteLocation rl : route.getLocationsBySequenceList()) {
2200                for (Car rs : InstanceManager.getDefault(CarManager.class).getList(this)) {
2201                    if (rs.getRouteLocation() == rl) {
2202                        number++;
2203                    }
2204                    if (rs.getRouteDestination() == rl) {
2205                        number--;
2206                    }
2207                }
2208                if (rl == routeLocation) {
2209                    break;
2210                }
2211            }
2212        }
2213        return number;
2214    }
2215
2216    /**
2217     * Gets the number of empty cars in the train when train departs the route
2218     * location.
2219     *
2220     * @param routeLocation The RouteLocation.
2221     * @return The number of empty cars in the train departing the route
2222     *         location.
2223     */
2224    public int getNumberEmptyCarsInTrain(RouteLocation routeLocation) {
2225        int number = 0;
2226        Route route = getRoute();
2227        if (route != null) {
2228            for (RouteLocation rl : route.getLocationsBySequenceList()) {
2229                for (Car car : InstanceManager.getDefault(CarManager.class).getList(this)) {
2230                    if (!car.getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY)) {
2231                        continue;
2232                    }
2233                    if (car.getRouteLocation() == rl) {
2234                        number++;
2235                    }
2236                    if (car.getRouteDestination() == rl) {
2237                        number--;
2238                    }
2239                }
2240                if (rl == routeLocation) {
2241                    break;
2242                }
2243            }
2244        }
2245
2246        return number;
2247    }
2248
2249    public int getNumberLoadedCarsInTrain(RouteLocation routeLocation) {
2250        return getNumberCarsInTrain(routeLocation) - getNumberEmptyCarsInTrain(routeLocation);
2251    }
2252
2253    public int getNumberCarsPickedUp() {
2254        return getNumberCarsPickedUp(getCurrentRouteLocation());
2255    }
2256
2257    /**
2258     * Gets the number of cars pulled from a location
2259     *
2260     * @param routeLocation the location
2261     * @return number of pick ups
2262     */
2263    public int getNumberCarsPickedUp(RouteLocation routeLocation) {
2264        int number = 0;
2265        for (Car rs : InstanceManager.getDefault(CarManager.class).getList(this)) {
2266            if (rs.getRouteLocation() == routeLocation && rs.getTrack() != null) {
2267                number++;
2268            }
2269        }
2270        return number;
2271    }
2272
2273    public int getNumberCarsSetout() {
2274        return getNumberCarsSetout(getCurrentRouteLocation());
2275    }
2276
2277    /**
2278     * Gets the number of cars delivered to a location
2279     *
2280     * @param routeLocation the location
2281     * @return number of set outs
2282     */
2283    public int getNumberCarsSetout(RouteLocation routeLocation) {
2284        int number = 0;
2285        for (Car rs : InstanceManager.getDefault(CarManager.class).getList(this)) {
2286            if (rs.getRouteDestination() == routeLocation) {
2287                number++;
2288            }
2289        }
2290        return number;
2291    }
2292
2293    /**
2294     * Gets the train's length at the current location in the train's route.
2295     *
2296     * @return The train length at the train's current location
2297     */
2298    public int getTrainLength() {
2299        return getTrainLength(getCurrentRouteLocation());
2300    }
2301
2302    /**
2303     * Gets the train's length at the route location specified
2304     *
2305     * @param routeLocation The route location
2306     * @return The train length at the route location
2307     */
2308    public int getTrainLength(RouteLocation routeLocation) {
2309        int length = 0;
2310        Route route = getRoute();
2311        if (route != null) {
2312            for (RouteLocation rl : route.getLocationsBySequenceList()) {
2313                for (RollingStock rs : InstanceManager.getDefault(EngineManager.class).getList(this)) {
2314                    if (rs.getRouteLocation() == rl) {
2315                        length += rs.getTotalLength();
2316                    }
2317                    if (rs.getRouteDestination() == rl) {
2318                        length += -rs.getTotalLength();
2319                    }
2320                }
2321                for (RollingStock rs : InstanceManager.getDefault(CarManager.class).getList(this)) {
2322                    if (rs.getRouteLocation() == rl) {
2323                        length += rs.getTotalLength();
2324                    }
2325                    if (rs.getRouteDestination() == rl) {
2326                        length += -rs.getTotalLength();
2327                    }
2328                }
2329                if (rl == routeLocation) {
2330                    break;
2331                }
2332            }
2333        }
2334        return length;
2335    }
2336
2337    /**
2338     * Get the train's weight at the current location.
2339     *
2340     * @return Train's weight in tons.
2341     */
2342    public int getTrainWeight() {
2343        return getTrainWeight(getCurrentRouteLocation());
2344    }
2345
2346    public int getTrainWeight(RouteLocation routeLocation) {
2347        int weight = 0;
2348        Route route = getRoute();
2349        if (route != null) {
2350            for (RouteLocation rl : route.getLocationsBySequenceList()) {
2351                for (RollingStock rs : InstanceManager.getDefault(EngineManager.class).getList(this)) {
2352                    if (rs.getRouteLocation() == rl) {
2353                        weight += rs.getAdjustedWeightTons();
2354                    }
2355                    if (rs.getRouteDestination() == rl) {
2356                        weight += -rs.getAdjustedWeightTons();
2357                    }
2358                }
2359                for (Car car : InstanceManager.getDefault(CarManager.class).getList(this)) {
2360                    if (car.getRouteLocation() == rl) {
2361                        weight += car.getAdjustedWeightTons(); // weight depends
2362                                                               // on car load
2363                    }
2364                    if (car.getRouteDestination() == rl) {
2365                        weight += -car.getAdjustedWeightTons();
2366                    }
2367                }
2368                if (rl == routeLocation) {
2369                    break;
2370                }
2371            }
2372        }
2373        return weight;
2374    }
2375
2376    /**
2377     * Gets the train's locomotive horsepower at the route location specified
2378     *
2379     * @param routeLocation The route location
2380     * @return The train's locomotive horsepower at the route location
2381     */
2382    public int getTrainHorsePower(RouteLocation routeLocation) {
2383        int hp = 0;
2384        Route route = getRoute();
2385        if (route != null) {
2386            for (RouteLocation rl : route.getLocationsBySequenceList()) {
2387                for (Engine eng : InstanceManager.getDefault(EngineManager.class).getList(this)) {
2388                    if (eng.getRouteLocation() == rl) {
2389                        hp += eng.getHpInteger();
2390                    }
2391                    if (eng.getRouteDestination() == rl) {
2392                        hp += -eng.getHpInteger();
2393                    }
2394                }
2395                if (rl == routeLocation) {
2396                    break;
2397                }
2398            }
2399        }
2400        return hp;
2401    }
2402
2403    /**
2404     * Gets the current caboose road and number if there's one assigned to the
2405     * train.
2406     *
2407     * @return Road and number of caboose.
2408     */
2409    public String getCabooseRoadAndNumber() {
2410        String cabooseRoadNumber = NONE;
2411        RouteLocation rl = getCurrentRouteLocation();
2412        List<Car> cars = InstanceManager.getDefault(CarManager.class).getByTrainList(this);
2413        for (Car car : cars) {
2414            if (car.getRouteLocation() == rl && car.isCaboose()) {
2415                cabooseRoadNumber =
2416                        car.getRoadName().split(TrainCommon.HYPHEN)[0] + " " + TrainCommon.splitString(car.getNumber());
2417            }
2418        }
2419        return cabooseRoadNumber;
2420    }
2421
2422    public void setDescription(String description) {
2423        String old = _description;
2424        _description = description;
2425        if (!old.equals(description)) {
2426            setDirtyAndFirePropertyChange(DESCRIPTION_CHANGED_PROPERTY, old, description);
2427        }
2428    }
2429
2430    public String getRawDescription() {
2431        return _description;
2432    }
2433
2434    /**
2435     * Returns a formated string providing the train's description. {0} = lead
2436     * engine number, {1} = train's departure direction {2} = lead engine road
2437     * {3} = DCC address of lead engine.
2438     *
2439     * @return The train's description.
2440     */
2441    public String getDescription() {
2442        try {
2443            String description = MessageFormat.format(getRawDescription(), new Object[]{getLeadEngineNumber(),
2444                    getTrainDepartsDirection(), getLeadEngineRoadName(), getLeadEngineDccAddress()});
2445            return description;
2446        } catch (IllegalArgumentException e) {
2447            return "ERROR IN FORMATTING: " + getRawDescription();
2448        }
2449    }
2450
2451    public void setNumberEngines(String number) {
2452        String old = _numberEngines;
2453        _numberEngines = number;
2454        if (!old.equals(number)) {
2455            setDirtyAndFirePropertyChange("trainNmberEngines", old, number); // NOI18N
2456        }
2457    }
2458
2459    /**
2460     * Get the number of engines that this train requires.
2461     *
2462     * @return The number of engines that this train requires.
2463     */
2464    public String getNumberEngines() {
2465        return _numberEngines;
2466    }
2467
2468    /**
2469     * Get the number of engines needed for the second set.
2470     *
2471     * @return The number of engines needed in route
2472     */
2473    public String getSecondLegNumberEngines() {
2474        return _leg2Engines;
2475    }
2476
2477    public void setSecondLegNumberEngines(String number) {
2478        String old = _leg2Engines;
2479        _leg2Engines = number;
2480        if (!old.equals(number)) {
2481            setDirtyAndFirePropertyChange("trainNmberEngines", old, number); // NOI18N
2482        }
2483    }
2484
2485    /**
2486     * Get the number of engines needed for the third set.
2487     *
2488     * @return The number of engines needed in route
2489     */
2490    public String getThirdLegNumberEngines() {
2491        return _leg3Engines;
2492    }
2493
2494    public void setThirdLegNumberEngines(String number) {
2495        String old = _leg3Engines;
2496        _leg3Engines = number;
2497        if (!old.equals(number)) {
2498            setDirtyAndFirePropertyChange("trainNmberEngines", old, number); // NOI18N
2499        }
2500    }
2501
2502    /**
2503     * Set the road name of engines servicing this train.
2504     *
2505     * @param road The road name of engines servicing this train.
2506     */
2507    public void setEngineRoad(String road) {
2508        String old = _engineRoad;
2509        _engineRoad = road;
2510        if (!old.equals(road)) {
2511            setDirtyAndFirePropertyChange("trainEngineRoad", old, road); // NOI18N
2512        }
2513    }
2514
2515    /**
2516     * Get the road name of engines servicing this train.
2517     *
2518     * @return The road name of engines servicing this train.
2519     */
2520    public String getEngineRoad() {
2521        return _engineRoad;
2522    }
2523
2524    /**
2525     * Set the road name of engines servicing this train 2nd leg.
2526     *
2527     * @param road The road name of engines servicing this train.
2528     */
2529    public void setSecondLegEngineRoad(String road) {
2530        String old = _leg2Road;
2531        _leg2Road = road;
2532        if (!old.equals(road)) {
2533            setDirtyAndFirePropertyChange("trainEngineRoad", old, road); // NOI18N
2534        }
2535    }
2536
2537    /**
2538     * Get the road name of engines servicing this train 2nd leg.
2539     *
2540     * @return The road name of engines servicing this train.
2541     */
2542    public String getSecondLegEngineRoad() {
2543        return _leg2Road;
2544    }
2545
2546    /**
2547     * Set the road name of engines servicing this train 3rd leg.
2548     *
2549     * @param road The road name of engines servicing this train.
2550     */
2551    public void setThirdLegEngineRoad(String road) {
2552        String old = _leg3Road;
2553        _leg3Road = road;
2554        if (!old.equals(road)) {
2555            setDirtyAndFirePropertyChange("trainEngineRoad", old, road); // NOI18N
2556        }
2557    }
2558
2559    /**
2560     * Get the road name of engines servicing this train 3rd leg.
2561     *
2562     * @return The road name of engines servicing this train.
2563     */
2564    public String getThirdLegEngineRoad() {
2565        return _leg3Road;
2566    }
2567
2568    /**
2569     * Set the model name of engines servicing this train.
2570     *
2571     * @param model The model name of engines servicing this train.
2572     */
2573    public void setEngineModel(String model) {
2574        String old = _engineModel;
2575        _engineModel = model;
2576        if (!old.equals(model)) {
2577            setDirtyAndFirePropertyChange("trainEngineModel", old, model); // NOI18N
2578        }
2579    }
2580
2581    public String getEngineModel() {
2582        return _engineModel;
2583    }
2584
2585    /**
2586     * Set the model name of engines servicing this train's 2nd leg.
2587     *
2588     * @param model The model name of engines servicing this train.
2589     */
2590    public void setSecondLegEngineModel(String model) {
2591        String old = _leg2Model;
2592        _leg2Model = model;
2593        if (!old.equals(model)) {
2594            setDirtyAndFirePropertyChange("trainEngineModel", old, model); // NOI18N
2595        }
2596    }
2597
2598    public String getSecondLegEngineModel() {
2599        return _leg2Model;
2600    }
2601
2602    /**
2603     * Set the model name of engines servicing this train's 3rd leg.
2604     *
2605     * @param model The model name of engines servicing this train.
2606     */
2607    public void setThirdLegEngineModel(String model) {
2608        String old = _leg3Model;
2609        _leg3Model = model;
2610        if (!old.equals(model)) {
2611            setDirtyAndFirePropertyChange("trainEngineModel", old, model); // NOI18N
2612        }
2613    }
2614
2615    public String getThirdLegEngineModel() {
2616        return _leg3Model;
2617    }
2618
2619    protected void replaceModel(String oldModel, String newModel) {
2620        if (getEngineModel().equals(oldModel)) {
2621            setEngineModel(newModel);
2622        }
2623        if (getSecondLegEngineModel().equals(oldModel)) {
2624            setSecondLegEngineModel(newModel);
2625        }
2626        if (getThirdLegEngineModel().equals(oldModel)) {
2627            setThirdLegEngineModel(newModel);
2628        }
2629    }
2630
2631    /**
2632     * Set the road name of the caboose servicing this train.
2633     *
2634     * @param road The road name of the caboose servicing this train.
2635     */
2636    public void setCabooseRoad(String road) {
2637        String old = _cabooseRoad;
2638        _cabooseRoad = road;
2639        if (!old.equals(road)) {
2640            setDirtyAndFirePropertyChange("trainCabooseRoad", old, road); // NOI18N
2641        }
2642    }
2643
2644    public String getCabooseRoad() {
2645        return _cabooseRoad;
2646    }
2647
2648    /**
2649     * Set the road name of the second leg caboose servicing this train.
2650     *
2651     * @param road The road name of the caboose servicing this train's 2nd leg.
2652     */
2653    public void setSecondLegCabooseRoad(String road) {
2654        String old = _leg2CabooseRoad;
2655        _leg2CabooseRoad = road;
2656        if (!old.equals(road)) {
2657            setDirtyAndFirePropertyChange("trainCabooseRoad", old, road); // NOI18N
2658        }
2659    }
2660
2661    public String getSecondLegCabooseRoad() {
2662        return _leg2CabooseRoad;
2663    }
2664
2665    /**
2666     * Set the road name of the third leg caboose servicing this train.
2667     *
2668     * @param road The road name of the caboose servicing this train's 3rd leg.
2669     */
2670    public void setThirdLegCabooseRoad(String road) {
2671        String old = _leg3CabooseRoad;
2672        _leg3CabooseRoad = road;
2673        if (!old.equals(road)) {
2674            setDirtyAndFirePropertyChange("trainCabooseRoad", old, road); // NOI18N
2675        }
2676    }
2677
2678    public String getThirdLegCabooseRoad() {
2679        return _leg3CabooseRoad;
2680    }
2681
2682    public void setSecondLegStartRouteLocation(RouteLocation rl) {
2683        _leg2Start = rl;
2684    }
2685
2686    public RouteLocation getSecondLegStartRouteLocation() {
2687        return _leg2Start;
2688    }
2689
2690    public String getSecondLegStartLocationName() {
2691        if (getSecondLegStartRouteLocation() == null) {
2692            return NONE;
2693        }
2694        return getSecondLegStartRouteLocation().getName();
2695    }
2696
2697    public void setThirdLegStartRouteLocation(RouteLocation rl) {
2698        _leg3Start = rl;
2699    }
2700
2701    public RouteLocation getThirdLegStartRouteLocation() {
2702        return _leg3Start;
2703    }
2704
2705    public String getThirdLegStartLocationName() {
2706        if (getThirdLegStartRouteLocation() == null) {
2707            return NONE;
2708        }
2709        return getThirdLegStartRouteLocation().getName();
2710    }
2711
2712    public void setSecondLegEndRouteLocation(RouteLocation rl) {
2713        _end2Leg = rl;
2714    }
2715
2716    public String getSecondLegEndLocationName() {
2717        if (getSecondLegEndRouteLocation() == null) {
2718            return NONE;
2719        }
2720        return getSecondLegEndRouteLocation().getName();
2721    }
2722
2723    public RouteLocation getSecondLegEndRouteLocation() {
2724        return _end2Leg;
2725    }
2726
2727    public void setThirdLegEndRouteLocation(RouteLocation rl) {
2728        _leg3End = rl;
2729    }
2730
2731    public RouteLocation getThirdLegEndRouteLocation() {
2732        return _leg3End;
2733    }
2734
2735    public String getThirdLegEndLocationName() {
2736        if (getThirdLegEndRouteLocation() == null) {
2737            return NONE;
2738        }
2739        return getThirdLegEndRouteLocation().getName();
2740    }
2741
2742    /**
2743     * Optional changes to train while en route.
2744     *
2745     * @param options NO_CABOOSE_OR_FRED, CHANGE_ENGINES, ADD_CABOOSE,
2746     *                HELPER_ENGINES, REMOVE_CABOOSE
2747     */
2748    public void setSecondLegOptions(int options) {
2749        int old = _leg2Options;
2750        _leg2Options = options;
2751        if (old != options) {
2752            setDirtyAndFirePropertyChange("trainLegOptions", old, options); // NOI18N
2753        }
2754    }
2755
2756    public int getSecondLegOptions() {
2757        return _leg2Options;
2758    }
2759
2760    /**
2761     * Optional changes to train while en route.
2762     *
2763     * @param options NO_CABOOSE_OR_FRED, CHANGE_ENGINES, ADD_CABOOSE,
2764     *                HELPER_ENGINES, REMOVE_CABOOSE
2765     */
2766    public void setThirdLegOptions(int options) {
2767        int old = _leg3Options;
2768        _leg3Options = options;
2769        if (old != options) {
2770            setDirtyAndFirePropertyChange("trainLegOptions", old, options); // NOI18N
2771        }
2772    }
2773
2774    public int getThirdLegOptions() {
2775        return _leg3Options;
2776    }
2777
2778    public void setComment(String comment) {
2779        String old = _comment;
2780        _comment = comment;
2781        if (!old.equals(comment)) {
2782            setDirtyAndFirePropertyChange("trainComment", old, comment); // NOI18N
2783        }
2784    }
2785
2786    public String getComment() {
2787        return TrainCommon.getTextColorString(getCommentWithColor());
2788    }
2789
2790    public String getCommentWithColor() {
2791        return _comment;
2792    }
2793
2794    /**
2795     * Add a script to run before a train is built
2796     *
2797     * @param pathname The script's pathname
2798     */
2799    public void addBuildScript(String pathname) {
2800        _buildScripts.add(pathname);
2801        setDirtyAndFirePropertyChange("addBuildScript", pathname, null); // NOI18N
2802    }
2803
2804    public void deleteBuildScript(String pathname) {
2805        _buildScripts.remove(pathname);
2806        setDirtyAndFirePropertyChange("deleteBuildScript", null, pathname); // NOI18N
2807    }
2808
2809    /**
2810     * Gets a list of pathnames (scripts) to run before this train is built
2811     *
2812     * @return A list of pathnames to run before this train is built
2813     */
2814    public List<String> getBuildScripts() {
2815        return _buildScripts;
2816    }
2817
2818    /**
2819     * Add a script to run after a train is built
2820     *
2821     * @param pathname The script's pathname
2822     */
2823    public void addAfterBuildScript(String pathname) {
2824        _afterBuildScripts.add(pathname);
2825        setDirtyAndFirePropertyChange("addAfterBuildScript", pathname, null); // NOI18N
2826    }
2827
2828    public void deleteAfterBuildScript(String pathname) {
2829        _afterBuildScripts.remove(pathname);
2830        setDirtyAndFirePropertyChange("deleteAfterBuildScript", null, pathname); // NOI18N
2831    }
2832
2833    /**
2834     * Gets a list of pathnames (scripts) to run after this train is built
2835     *
2836     * @return A list of pathnames to run after this train is built
2837     */
2838    public List<String> getAfterBuildScripts() {
2839        return _afterBuildScripts;
2840    }
2841
2842    /**
2843     * Add a script to run when train is moved
2844     *
2845     * @param pathname The script's pathname
2846     */
2847    public void addMoveScript(String pathname) {
2848        _moveScripts.add(pathname);
2849        setDirtyAndFirePropertyChange("addMoveScript", pathname, null); // NOI18N
2850    }
2851
2852    public void deleteMoveScript(String pathname) {
2853        _moveScripts.remove(pathname);
2854        setDirtyAndFirePropertyChange("deleteMoveScript", null, pathname); // NOI18N
2855    }
2856
2857    /**
2858     * Gets a list of pathnames (scripts) to run when this train moved
2859     *
2860     * @return A list of pathnames to run when this train moved
2861     */
2862    public List<String> getMoveScripts() {
2863        return _moveScripts;
2864    }
2865
2866    /**
2867     * Add a script to run when train is terminated
2868     *
2869     * @param pathname The script's pathname
2870     */
2871    public void addTerminationScript(String pathname) {
2872        _terminationScripts.add(pathname);
2873        setDirtyAndFirePropertyChange("addTerminationScript", pathname, null); // NOI18N
2874    }
2875
2876    public void deleteTerminationScript(String pathname) {
2877        _terminationScripts.remove(pathname);
2878        setDirtyAndFirePropertyChange("deleteTerminationScript", null, pathname); // NOI18N
2879    }
2880
2881    /**
2882     * Gets a list of pathnames (scripts) to run when this train terminates
2883     *
2884     * @return A list of pathnames to run when this train terminates
2885     */
2886    public List<String> getTerminationScripts() {
2887        return _terminationScripts;
2888    }
2889
2890    /**
2891     * Gets the optional railroad name for this train.
2892     *
2893     * @return Train's railroad name.
2894     */
2895    public String getRailroadName() {
2896        return _railroadName;
2897    }
2898
2899    /**
2900     * Overrides the default railroad name for this train.
2901     *
2902     * @param name The railroad name for this train.
2903     */
2904    public void setRailroadName(String name) {
2905        String old = _railroadName;
2906        _railroadName = name;
2907        if (!old.equals(name)) {
2908            setDirtyAndFirePropertyChange("trainRailroadName", old, name); // NOI18N
2909        }
2910    }
2911
2912    public String getManifestLogoPathName() {
2913        return _logoPathName;
2914    }
2915
2916    /**
2917     * Overrides the default logo for this train.
2918     *
2919     * @param pathName file location for the logo.
2920     */
2921    public void setManifestLogoPathName(String pathName) {
2922        _logoPathName = pathName;
2923    }
2924
2925    public boolean isShowArrivalAndDepartureTimesEnabled() {
2926        return _showTimes;
2927    }
2928
2929    public void setShowArrivalAndDepartureTimes(boolean enable) {
2930        boolean old = _showTimes;
2931        _showTimes = enable;
2932        if (old != enable) {
2933            setDirtyAndFirePropertyChange("showArrivalAndDepartureTimes", old ? "true" : "false", // NOI18N
2934                    enable ? "true" : "false"); // NOI18N
2935        }
2936    }
2937
2938    public boolean isSendCarsToTerminalEnabled() {
2939        return _sendToTerminal;
2940    }
2941
2942    public void setSendCarsToTerminalEnabled(boolean enable) {
2943        boolean old = _sendToTerminal;
2944        _sendToTerminal = enable;
2945        if (old != enable) {
2946            setDirtyAndFirePropertyChange("send cars to terminal", old ? "true" : "false", enable ? "true" // NOI18N
2947                    : "false"); // NOI18N
2948        }
2949    }
2950
2951    /**
2952     * Allow local moves if car has a custom load or Final Destination
2953     *
2954     * @return true if local move is allowed
2955     */
2956    public boolean isAllowLocalMovesEnabled() {
2957        return _allowLocalMoves;
2958    }
2959
2960    public void setAllowLocalMovesEnabled(boolean enable) {
2961        boolean old = _allowLocalMoves;
2962        _allowLocalMoves = enable;
2963        if (old != enable) {
2964            setDirtyAndFirePropertyChange("allow local moves", old ? "true" : "false", enable ? "true" // NOI18N
2965                    : "false"); // NOI18N
2966        }
2967    }
2968
2969    public boolean isAllowThroughCarsEnabled() {
2970        return _allowThroughCars;
2971    }
2972
2973    public void setAllowThroughCarsEnabled(boolean enable) {
2974        boolean old = _allowThroughCars;
2975        _allowThroughCars = enable;
2976        if (old != enable) {
2977            setDirtyAndFirePropertyChange("allow through cars", old ? "true" : "false", enable ? "true" // NOI18N
2978                    : "false"); // NOI18N
2979        }
2980    }
2981
2982    public boolean isBuildTrainNormalEnabled() {
2983        return _buildNormal;
2984    }
2985
2986    public void setBuildTrainNormalEnabled(boolean enable) {
2987        boolean old = _buildNormal;
2988        _buildNormal = enable;
2989        if (old != enable) {
2990            setDirtyAndFirePropertyChange("build train normal", old ? "true" : "false", enable ? "true" // NOI18N
2991                    : "false"); // NOI18N
2992        }
2993    }
2994
2995    /**
2996     * When true allow a turn to return cars to staging. A turn is a train that
2997     * departs and terminates at the same location.
2998     *
2999     * @return true if cars can return to staging
3000     */
3001    public boolean isAllowReturnToStagingEnabled() {
3002        return _allowCarsReturnStaging;
3003    }
3004
3005    public void setAllowReturnToStagingEnabled(boolean enable) {
3006        boolean old = _allowCarsReturnStaging;
3007        _allowCarsReturnStaging = enable;
3008        if (old != enable) {
3009            setDirtyAndFirePropertyChange("allow cars to return to staging", old ? "true" : "false", // NOI18N
3010                    enable ? "true" : "false"); // NOI18N
3011        }
3012    }
3013
3014    public boolean isServiceAllCarsWithFinalDestinationsEnabled() {
3015        return _serviceAllCarsWithFinalDestinations;
3016    }
3017
3018    public void setServiceAllCarsWithFinalDestinationsEnabled(boolean enable) {
3019        boolean old = _serviceAllCarsWithFinalDestinations;
3020        _serviceAllCarsWithFinalDestinations = enable;
3021        if (old != enable) {
3022            setDirtyAndFirePropertyChange("TrainServiceAllCarsWithFinalDestinations", old ? "true" : "false", // NOI18N
3023                    enable ? "true" : "false"); // NOI18N
3024        }
3025    }
3026
3027    public boolean isBuildConsistEnabled() {
3028        return _buildConsist;
3029    }
3030
3031    public void setBuildConsistEnabled(boolean enable) {
3032        boolean old = _buildConsist;
3033        _buildConsist = enable;
3034        if (old != enable) {
3035            setDirtyAndFirePropertyChange("TrainBuildConsist", old ? "true" : "false", // NOI18N
3036                    enable ? "true" : "false"); // NOI18N
3037        }
3038    }
3039
3040    public boolean isSendCarsWithCustomLoadsToStagingEnabled() {
3041        return _sendCarsWithCustomLoadsToStaging;
3042    }
3043
3044    public void setSendCarsWithCustomLoadsToStagingEnabled(boolean enable) {
3045        boolean old = _sendCarsWithCustomLoadsToStaging;
3046        _sendCarsWithCustomLoadsToStaging = enable;
3047        if (old != enable) {
3048            setDirtyAndFirePropertyChange("SendCarsWithCustomLoadsToStaging", old ? "true" : "false", // NOI18N
3049                    enable ? "true" : "false"); // NOI18N
3050        }
3051    }
3052
3053    protected void setBuilt(boolean built) {
3054        boolean old = _built;
3055        _built = built;
3056        if (old != built) {
3057            setDirtyAndFirePropertyChange(BUILT_CHANGED_PROPERTY, old, built); // NOI18N
3058        }
3059    }
3060
3061    /**
3062     * Used to determine if this train has been built.
3063     *
3064     * @return true if the train was successfully built.
3065     */
3066    public boolean isBuilt() {
3067        return _built;
3068    }
3069
3070    /**
3071     * Set true whenever the train's manifest has been modified. For example
3072     * adding or removing a car from a train, or changing the manifest format.
3073     * Once the manifest has been regenerated (modified == false), the old
3074     * status for the train is restored.
3075     *
3076     * @param modified True if train's manifest has been modified.
3077     */
3078    public void setModified(boolean modified) {
3079        log.debug("Set modified {}", modified);
3080        if (!isBuilt()) {
3081            _modified = false;
3082            return; // there isn't a manifest to modify
3083        }
3084        boolean old = _modified;
3085        _modified = modified;
3086        if (modified) {
3087            setPrinted(false);
3088        }
3089        if (old != modified) {
3090            if (modified) {
3091                // scripts can call setModified() for a train
3092                if (getStatusCode() != CODE_RUN_SCRIPTS) {
3093                    setOldStatusCode(getStatusCode());
3094                }
3095                setStatusCode(CODE_MANIFEST_MODIFIED);
3096            } else {
3097                setStatusCode(getOldStatusCode()); // restore previous train
3098                                                   // status
3099            }
3100        }
3101        setDirtyAndFirePropertyChange(TRAIN_MODIFIED_CHANGED_PROPERTY, null, modified); // NOI18N
3102    }
3103
3104    public boolean isModified() {
3105        return _modified;
3106    }
3107
3108    /**
3109     * Control flag used to decide if this train is to be built.
3110     *
3111     * @param build When true, build this train.
3112     */
3113    public void setBuildEnabled(boolean build) {
3114        boolean old = _build;
3115        _build = build;
3116        if (old != build) {
3117            setDirtyAndFirePropertyChange(BUILD_CHANGED_PROPERTY, old, build); // NOI18N
3118        }
3119    }
3120
3121    /**
3122     * Used to determine if train is to be built.
3123     *
3124     * @return true if train is to be built.
3125     */
3126    public boolean isBuildEnabled() {
3127        return _build;
3128    }
3129
3130    /**
3131     * Build this train if the build control flag is true.
3132     *
3133     * @return True only if train is successfully built.
3134     */
3135    public boolean buildIfSelected() {
3136        if (isBuildEnabled() && !isBuilt()) {
3137            return build();
3138        }
3139        log.debug("Train ({}) not selected or already built, skipping build", getName());
3140        return false;
3141    }
3142
3143    /**
3144     * Build this train. Creates a train manifest.
3145     *
3146     * @return True if build successful.
3147     */
3148    public synchronized boolean build() {
3149        reset();
3150        // check to see if any other trains are building
3151        int count = 1200; // wait up to 120 seconds
3152        while (InstanceManager.getDefault(TrainManager.class).isAnyTrainBuilding() && count > 0) {
3153            count--;
3154            try {
3155                wait(100); // 100 msec
3156            } catch (InterruptedException e) {
3157                // TODO Auto-generated catch block
3158                log.error("Thread unexpectedly interrupted", e);
3159            }
3160        }
3161        // timed out?
3162        if (count <= 0) {
3163            log.warn("Build timeout for train ({})", getName());
3164            setBuildFailed(true);
3165            setStatusCode(CODE_BUILD_FAILED);
3166            return false;
3167        }
3168        // run before build scripts
3169        runScripts(getBuildScripts());
3170        TrainBuilder tb = new TrainBuilder();
3171        boolean results = tb.build(this);
3172        // run after build scripts
3173        runScripts(getAfterBuildScripts());
3174        return results;
3175    }
3176
3177    /**
3178     * Run train scripts, waits for completion before returning.
3179     */
3180    private synchronized void runScripts(List<String> scripts) {
3181        if (scripts.size() > 0) {
3182            // save the current status
3183            setOldStatusCode(getStatusCode());
3184            setStatusCode(CODE_RUN_SCRIPTS);
3185            // create the python interpreter thread
3186            JmriScriptEngineManager.getDefault().initializeAllEngines();
3187            // find the number of active threads
3188            ThreadGroup root = Thread.currentThread().getThreadGroup();
3189            int numberOfThreads = root.activeCount();
3190            // log.debug("Number of active threads: {}", numberOfThreads);
3191            for (String scriptPathname : scripts) {
3192                try {
3193                    JmriScriptEngineManager.getDefault()
3194                            .runScript(new File(jmri.util.FileUtil.getExternalFilename(scriptPathname)));
3195                } catch (Exception e) {
3196                    log.error("Problem with script: {}", scriptPathname);
3197                }
3198            }
3199            // need to wait for scripts to complete or 4 seconds maximum
3200            int count = 0;
3201            while (root.activeCount() > numberOfThreads) {
3202                log.debug("Number of active threads: {}, at start: {}", root.activeCount(), numberOfThreads);
3203                try {
3204                    wait(40);
3205                } catch (InterruptedException e) {
3206                    Thread.currentThread().interrupt();
3207                }
3208                if (count++ > 100) {
3209                    break; // 4 seconds maximum 40*100 = 4000
3210                }
3211            }
3212            setStatusCode(getOldStatusCode());
3213        }
3214    }
3215
3216    public boolean printBuildReport() {
3217        boolean isPreview = (InstanceManager.getDefault(TrainManager.class).isPrintPreviewEnabled() ||
3218                Setup.isBuildReportAlwaysPreviewEnabled());
3219        return printBuildReport(isPreview);
3220    }
3221
3222    public boolean printBuildReport(boolean isPreview) {
3223        File buildFile = InstanceManager.getDefault(TrainManagerXml.class).getTrainBuildReportFile(getName());
3224        if (!buildFile.exists()) {
3225            log.warn("Build file missing for train {}", getName());
3226            return false;
3227        }
3228
3229        if (isPreview && Setup.isBuildReportEditorEnabled()) {
3230            TrainPrintUtilities.editReport(buildFile, getName());
3231        } else {
3232            TrainPrintUtilities.printReport(buildFile,
3233                    Bundle.getMessage("buildReport", getDescription()),
3234                    isPreview, NONE, true, NONE, NONE, Setup.PORTRAIT, Setup.getBuildReportFontSize(), true, null);
3235        }
3236        return true;
3237    }
3238
3239    protected void setBuildFailed(boolean status) {
3240        boolean old = _buildFailed;
3241        _buildFailed = status;
3242        if (old != status) {
3243            setDirtyAndFirePropertyChange("buildFailed", old ? "true" : "false", status ? "true" : "false"); // NOI18N
3244        }
3245    }
3246
3247    /**
3248     * Returns true if the train build failed. Note that returning false doesn't
3249     * mean the build was successful.
3250     *
3251     * @return true if train build failed.
3252     */
3253    public boolean isBuildFailed() {
3254        return _buildFailed;
3255    }
3256
3257    protected void setBuildFailedMessage(String message) {
3258        String old = _buildFailedMessage;
3259        _buildFailedMessage = message;
3260        if (!old.equals(message)) {
3261            setDirtyAndFirePropertyChange("buildFailedMessage", old, message); // NOI18N
3262        }
3263    }
3264
3265    protected String getBuildFailedMessage() {
3266        return _buildFailedMessage;
3267    }
3268
3269    /**
3270     * Print manifest for train if already built.
3271     *
3272     * @return true if print successful.
3273     */
3274    public boolean printManifestIfBuilt() {
3275        if (isBuilt()) {
3276            boolean isPreview = InstanceManager.getDefault(TrainManager.class).isPrintPreviewEnabled();
3277            try {
3278                return (printManifest(isPreview));
3279            } catch (BuildFailedException e) {
3280                log.error("Print Manifest failed: {}", e.getMessage());
3281            }
3282        } else {
3283            log.debug("Need to build train ({}) before printing manifest", getName());
3284        }
3285        return false;
3286    }
3287
3288    /**
3289     * Print manifest for train.
3290     *
3291     * @param isPreview True if preview.
3292     * @return true if print successful, false if train print file not found.
3293     * @throws BuildFailedException if unable to create new Manifests
3294     */
3295    public boolean printManifest(boolean isPreview) throws BuildFailedException {
3296        if (isModified()) {
3297            new TrainManifest(this);
3298            try {
3299                new JsonManifest(this).build();
3300            } catch (IOException ex) {
3301                log.error("Unable to create JSON manifest {}", ex.getLocalizedMessage());
3302            }
3303            new TrainCsvManifest(this);
3304        }
3305        File file = InstanceManager.getDefault(TrainManagerXml.class).getTrainManifestFile(getName());
3306        if (!file.exists()) {
3307            log.warn("Manifest file missing for train ({})", getName());
3308            return false;
3309        }
3310        if (isPreview && Setup.isManifestEditorEnabled()) {
3311            TrainUtilities.openDesktop(file);
3312            return true;
3313        }
3314        String logoURL = Setup.NONE;
3315        if (!getManifestLogoPathName().equals(NONE)) {
3316            logoURL = FileUtil.getExternalFilename(getManifestLogoPathName());
3317        } else if (!Setup.getManifestLogoURL().equals(Setup.NONE)) {
3318            logoURL = FileUtil.getExternalFilename(Setup.getManifestLogoURL());
3319        }
3320        Location departs = InstanceManager.getDefault(LocationManager.class).getLocationByName(getTrainDepartsName());
3321        String printerName = Location.NONE;
3322        if (departs != null) {
3323            printerName = departs.getDefaultPrinterName();
3324        }
3325        // the train description shouldn't exceed half of the page width or the
3326        // page number will be overwritten
3327        String name = getDescription();
3328        if (name.length() > TrainCommon.getManifestHeaderLineLength() / 2) {
3329            name = name.substring(0, TrainCommon.getManifestHeaderLineLength() / 2);
3330        }
3331        TrainPrintUtilities.printReport(file, name, isPreview, Setup.getFontName(), false, logoURL, printerName,
3332                Setup.getManifestOrientation(), Setup.getManifestFontSize(), Setup.isPrintPageHeaderEnabled(),
3333                Setup.getPrintDuplexSides());
3334        if (!isPreview) {
3335            setPrinted(true);
3336        }
3337        return true;
3338    }
3339
3340    public boolean openFile() {
3341        File file = createCsvManifestFile();
3342        if (file == null || !file.exists()) {
3343            log.warn("CSV manifest file missing for train {}", getName());
3344            return false;
3345        }
3346        TrainUtilities.openDesktop(file);
3347        return true;
3348    }
3349
3350    public boolean runFile() {
3351        File file = createCsvManifestFile();
3352        if (file == null || !file.exists()) {
3353            log.warn("CSV manifest file missing for train {}", getName());
3354            return false;
3355        }
3356        // Set up to process the CSV file by the external Manifest program
3357        InstanceManager.getDefault(TrainCustomManifest.class).addCsvFile(file);
3358        if (!InstanceManager.getDefault(TrainCustomManifest.class).process()) {
3359            if (!InstanceManager.getDefault(TrainCustomManifest.class).excelFileExists()) {
3360                JmriJOptionPane.showMessageDialog(null,
3361                        Bundle.getMessage("LoadDirectoryNameFileName",
3362                                InstanceManager.getDefault(TrainCustomManifest.class).getDirectoryPathName(),
3363                                InstanceManager.getDefault(TrainCustomManifest.class).getFileName()),
3364                        Bundle.getMessage("ManifestCreatorNotFound"), JmriJOptionPane.ERROR_MESSAGE);
3365            }
3366            return false;
3367        }
3368        return true;
3369    }
3370
3371    public File createCsvManifestFile() {
3372        if (isModified()) {
3373            try {
3374                new TrainManifest(this);
3375                try {
3376                    new JsonManifest(this).build();
3377                } catch (IOException ex) {
3378                    log.error("Unable to create JSON manifest {}", ex.getLocalizedMessage());
3379                }
3380                new TrainCsvManifest(this);
3381            } catch (BuildFailedException e) {
3382                log.error("Could not create CVS Manifest files");
3383            }
3384        }
3385        File file = InstanceManager.getDefault(TrainManagerXml.class).getTrainCsvManifestFile(getName());
3386        if (!file.exists()) {
3387            log.warn("CSV manifest file was not created for train ({})", getName());
3388            return null;
3389        }
3390        return file;
3391    }
3392
3393    public void setPrinted(boolean printed) {
3394        boolean old = _printed;
3395        _printed = printed;
3396        if (old != printed) {
3397            setDirtyAndFirePropertyChange("trainPrinted", old ? "true" : "false", printed ? "true" : "false"); // NOI18N
3398        }
3399    }
3400
3401    /**
3402     * Used to determine if train manifest was printed.
3403     *
3404     * @return true if the train manifest was printed.
3405     */
3406    public boolean isPrinted() {
3407        return _printed;
3408    }
3409
3410    /**
3411     * Sets the panel position for the train icon for the current route
3412     * location.
3413     *
3414     * @return true if train coordinates can be set
3415     */
3416    public boolean setTrainIconCoordinates() {
3417        if (Setup.isTrainIconCordEnabled() && getCurrentRouteLocation() != null && _trainIcon != null) {
3418            getCurrentRouteLocation().setTrainIconX(_trainIcon.getX());
3419            getCurrentRouteLocation().setTrainIconY(_trainIcon.getY());
3420            return true;
3421        }
3422        return false;
3423    }
3424
3425    /**
3426     * Terminate train.
3427     */
3428    public void terminate() {
3429        while (isBuilt()) {
3430            move();
3431        }
3432    }
3433
3434    /**
3435     * Move train to next location in the route. Will move engines, cars, and
3436     * train icon. Will also terminate a train after it arrives at its final
3437     * destination.
3438     */
3439    public void move() {
3440        log.debug("Move train ({})", getName());
3441        if (getRoute() == null || getCurrentRouteLocation() == null) {
3442            setBuilt(false); // break terminate loop
3443            return;
3444        }
3445        if (!isBuilt()) {
3446            log.error("ERROR attempt to move train ({}) that hasn't been built", getName());
3447            return;
3448        }
3449        RouteLocation rl = getCurrentRouteLocation();
3450        RouteLocation rlNext = getNextRouteLocation(rl);
3451
3452        setCurrentLocation(rlNext);
3453
3454        // cars and engines will move via property change
3455        setDirtyAndFirePropertyChange(TRAIN_LOCATION_CHANGED_PROPERTY, rl, rlNext);
3456        moveTrainIcon(rlNext);
3457        updateStatus(rl, rlNext);
3458        // tell GUI that train has complete its move
3459        setDirtyAndFirePropertyChange(TRAIN_MOVE_COMPLETE_CHANGED_PROPERTY, rl, rlNext);
3460    }
3461
3462    /**
3463     * Move train to a location in the train's route. Code checks to see if the
3464     * location requested is part of the train's route and if the train hasn't
3465     * already visited the location. This command can only move the train
3466     * forward in its route. Note that you can not terminate the train using
3467     * this command. See move() or terminate().
3468     *
3469     * @param locationName The name of the location to move this train.
3470     * @return true if train was able to move to the named location.
3471     */
3472    public boolean move(String locationName) {
3473        log.info("Move train ({}) to location ({})", getName(), locationName);
3474        if (getRoute() == null || getCurrentRouteLocation() == null) {
3475            return false;
3476        }
3477        List<RouteLocation> routeList = getRoute().getLocationsBySequenceList();
3478        for (int i = 0; i < routeList.size(); i++) {
3479            RouteLocation rl = routeList.get(i);
3480            if (getCurrentRouteLocation() == rl) {
3481                for (int j = i + 1; j < routeList.size(); j++) {
3482                    rl = routeList.get(j);
3483                    if (rl.getName().equals(locationName)) {
3484                        log.debug("Found location ({}) moving train to this location", locationName);
3485                        for (j = i + 1; j < routeList.size(); j++) {
3486                            rl = routeList.get(j);
3487                            move();
3488                            if (rl.getName().equals(locationName)) {
3489                                return true;
3490                            }
3491                        }
3492                    }
3493                }
3494                break; // done
3495            }
3496        }
3497        return false;
3498    }
3499
3500    /**
3501     * Moves the train to the specified route location
3502     *
3503     * @param rl route location
3504     * @return true if successful
3505     */
3506    public boolean move(RouteLocation rl) {
3507        if (rl == null) {
3508            return false;
3509        }
3510        log.debug("Move train ({}) to location ({})", getName(), rl.getName());
3511        if (getRoute() == null || getCurrentRouteLocation() == null) {
3512            return false;
3513        }
3514        boolean foundCurrent = false;
3515        for (RouteLocation xrl : getRoute().getLocationsBySequenceList()) {
3516            if (getCurrentRouteLocation() == xrl) {
3517                foundCurrent = true;
3518            }
3519            if (xrl == rl) {
3520                if (foundCurrent) {
3521                    return true; // done
3522                } else {
3523                    break; // train passed this location
3524                }
3525            }
3526            if (foundCurrent) {
3527                move();
3528            }
3529        }
3530        return false;
3531    }
3532
3533    /**
3534     * Move train to the next location in the train's route. The location name
3535     * provided must be equal to the next location name in the train's route.
3536     *
3537     * @param locationName The next location name in the train's route.
3538     * @return true if successful.
3539     */
3540    public boolean moveToNextLocation(String locationName) {
3541        if (getNextLocationName().equals(locationName)) {
3542            move();
3543            return true;
3544        }
3545        return false;
3546    }
3547
3548    public void loadTrainIcon() {
3549        if (getCurrentRouteLocation() != null) {
3550            moveTrainIcon(getCurrentRouteLocation());
3551        }
3552    }
3553
3554    private final boolean animation = true; // when true use animation for icon
3555                                            // moves
3556    TrainIconAnimation _ta;
3557
3558    /*
3559     * The train icon is moved to route location (rl) for this train
3560     */
3561    protected void moveTrainIcon(RouteLocation rl) {
3562        // create train icon if at departure, if program has been restarted, or removed
3563        if (rl == getTrainDepartsRouteLocation() || _trainIcon == null || !_trainIcon.isActive()) {
3564            createTrainIcon(rl);
3565        }
3566        // is the lead engine still in train
3567        if (getLeadEngine() != null && getLeadEngine().getRouteDestination() == rl && rl != null) {
3568            log.debug("Engine ({}) arriving at destination {}", getLeadEngine().toString(), rl.getName());
3569        }
3570        if (_trainIcon != null && _trainIcon.isActive()) {
3571            setTrainIconColor();
3572            _trainIcon.setShowToolTip(true);
3573            String txt = null;
3574            if (getCurrentLocationName().equals(NONE)) {
3575                txt = getDescription() + " " + Bundle.getMessage("Terminated") + " (" + getTrainTerminatesName() + ")";
3576            } else {
3577                txt = Bundle.getMessage("TrainAtNext",
3578                        getDescription(), getCurrentLocationName(), getNextLocationName(), getTrainLength(),
3579                        Setup.getLengthUnit().toLowerCase());
3580            }
3581            _trainIcon.getToolTip().setText(txt);
3582            _trainIcon.getToolTip().setBackgroundColor(Color.white);
3583            // rl can be null when train is terminated.
3584            if (rl != null) {
3585                if (rl.getTrainIconX() != 0 || rl.getTrainIconY() != 0) {
3586                    if (animation) {
3587                        TrainIconAnimation ta = new TrainIconAnimation(_trainIcon, rl, _ta);
3588                        ta.start(); // start the animation
3589                        _ta = ta;
3590                    } else {
3591                        _trainIcon.setLocation(rl.getTrainIconX(), rl.getTrainIconY());
3592                    }
3593                }
3594            }
3595        }
3596    }
3597
3598    public String getIconName() {
3599        String name = getName();
3600        if (isBuilt() && getLeadEngine() != null && Setup.isTrainIconAppendEnabled()) {
3601            name += " " + getLeadEngine().getNumber();
3602        }
3603        return name;
3604    }
3605
3606    public String getLeadEngineNumber() {
3607        if (getLeadEngine() == null) {
3608            return NONE;
3609        }
3610        return getLeadEngine().getNumber();
3611    }
3612
3613    public String getLeadEngineRoadName() {
3614        if (getLeadEngine() == null) {
3615            return NONE;
3616        }
3617        return getLeadEngine().getRoadName();
3618    }
3619
3620    public String getLeadEngineRoadAndNumber() {
3621        if (getLeadEngine() == null) {
3622            return NONE;
3623        }
3624        return getLeadEngine().toString();
3625    }
3626
3627    public String getLeadEngineDccAddress() {
3628        if (getLeadEngine() == null) {
3629            return NONE;
3630        }
3631        return getLeadEngine().getDccAddress();
3632    }
3633
3634    /**
3635     * Gets the lead engine, will create it if the program has been restarted
3636     *
3637     * @return lead engine for this train
3638     */
3639    public Engine getLeadEngine() {
3640        if (_leadEngine == null && !_leadEngineId.equals(NONE)) {
3641            _leadEngine = InstanceManager.getDefault(EngineManager.class).getById(_leadEngineId);
3642        }
3643        return _leadEngine;
3644    }
3645
3646    public void setLeadEngine(Engine engine) {
3647        if (engine == null) {
3648            _leadEngineId = NONE;
3649        }
3650        _leadEngine = engine;
3651    }
3652
3653    /**
3654     * Returns the lead engine in a train's route. There can be up to two
3655     * changes in the lead engine for a train.
3656     *
3657     * @param routeLocation where in the train's route to find the lead engine.
3658     * @return lead engine
3659     */
3660    public Engine getLeadEngine(RouteLocation routeLocation) {
3661        Engine lead = null;
3662        for (RouteLocation rl : getRoute().getLocationsBySequenceList()) {
3663            for (Engine engine : InstanceManager.getDefault(EngineManager.class).getByTrainList(this)) {
3664                if (engine.getRouteLocation() == rl && (engine.getConsist() == null || engine.isLead())) {
3665                    lead = engine;
3666                    break;
3667                }
3668            }
3669            if (rl == routeLocation) {
3670                break;
3671            }
3672        }
3673        return lead;
3674    }
3675
3676    protected TrainIcon _trainIcon = null;
3677
3678    public TrainIcon getTrainIcon() {
3679        return _trainIcon;
3680    }
3681
3682    public void createTrainIcon(RouteLocation rl) {
3683        if (_trainIcon != null && _trainIcon.isActive()) {
3684            _trainIcon.remove();
3685        }
3686        // if there's a panel specified, get it and place icon
3687        if (!Setup.getPanelName().isEmpty()) {
3688            Editor editor = InstanceManager.getDefault(EditorManager.class).getTargetFrame(Setup.getPanelName());
3689            if (editor != null) {
3690                try {
3691                    _trainIcon = editor.addTrainIcon(getIconName());
3692                } catch (Exception e) {
3693                    log.error("Error placing train ({}) icon on panel ({})", getName(), Setup.getPanelName(), e);
3694                    return;
3695                }
3696                _trainIcon.setTrain(this);
3697                if (getIconName().length() > 9) {
3698                    _trainIcon.setFont(_trainIcon.getFont().deriveFont(8.f));
3699                }
3700                if (rl != null) {
3701                    _trainIcon.setLocation(rl.getTrainIconX(), rl.getTrainIconY());
3702                }
3703                // add throttle if there's a throttle manager
3704                if (jmri.InstanceManager.getNullableDefault(jmri.ThrottleManager.class) != null) {
3705                    // add throttle if JMRI loco roster entry exist
3706                    RosterEntry entry = null;
3707                    if (getLeadEngine() != null) {
3708                        // first try and find a match based on loco road number
3709                        entry = getLeadEngine().getRosterEntry();
3710                    }
3711                    if (entry != null) {
3712                        _trainIcon.setRosterEntry(entry);
3713                        if (getLeadEngine().getConsist() != null) {
3714                            _trainIcon.setConsistNumber(getLeadEngine().getConsist().getConsistNumber());
3715                        }
3716                    } else {
3717                        log.debug("Loco roster entry not found for train ({})", getName());
3718                    }
3719                }
3720            }
3721        }
3722    }
3723
3724    private void setTrainIconColor() {
3725        // Terminated train?
3726        if (getCurrentLocationName().equals(NONE)) {
3727            _trainIcon.setLocoColor(Setup.getTrainIconColorTerminate());
3728            return;
3729        }
3730        // local train serving only one location?
3731        if (isLocalSwitcher()) {
3732            _trainIcon.setLocoColor(Setup.getTrainIconColorLocal());
3733            return;
3734        }
3735        // set color based on train direction at current location
3736        if (getCurrentRouteLocation().getTrainDirection() == RouteLocation.NORTH) {
3737            _trainIcon.setLocoColor(Setup.getTrainIconColorNorth());
3738        }
3739        if (getCurrentRouteLocation().getTrainDirection() == RouteLocation.SOUTH) {
3740            _trainIcon.setLocoColor(Setup.getTrainIconColorSouth());
3741        }
3742        if (getCurrentRouteLocation().getTrainDirection() == RouteLocation.EAST) {
3743            _trainIcon.setLocoColor(Setup.getTrainIconColorEast());
3744        }
3745        if (getCurrentRouteLocation().getTrainDirection() == RouteLocation.WEST) {
3746            _trainIcon.setLocoColor(Setup.getTrainIconColorWest());
3747        }
3748    }
3749
3750    private void updateStatus(RouteLocation old, RouteLocation next) {
3751        if (next != null) {
3752            setStatusCode(CODE_TRAIN_EN_ROUTE);
3753            // run move scripts
3754            runScripts(getMoveScripts());
3755        } else {
3756            log.debug("Train ({}) terminated", getName());
3757            setStatusCode(CODE_TERMINATED);
3758            setBuilt(false);
3759            // run termination scripts
3760            runScripts(getTerminationScripts());
3761        }
3762    }
3763
3764    /**
3765     * Sets the print status for switch lists
3766     *
3767     * @param status UNKNOWN PRINTED
3768     */
3769    public void setSwitchListStatus(String status) {
3770        String old = _switchListStatus;
3771        _switchListStatus = status;
3772        if (!old.equals(status)) {
3773            setDirtyAndFirePropertyChange("switch list train status", old, status); // NOI18N
3774        }
3775    }
3776
3777    public String getSwitchListStatus() {
3778        return _switchListStatus;
3779    }
3780
3781    /**
3782     * Resets the train, removes engines and cars from this train.
3783     *
3784     * @return true if reset successful
3785     */
3786    public boolean reset() {
3787        // is this train in route?
3788        if (isTrainEnRoute()) {
3789            log.info("Train ({}) has started its route, can not be reset", getName());
3790            return false;
3791        }
3792        setCurrentLocation(null);
3793        setDepartureTrack(null);
3794        setTerminationTrack(null);
3795        setBuilt(false);
3796        setBuildFailed(false);
3797        setBuildFailedMessage(NONE);
3798        setPrinted(false);
3799        setModified(false);
3800        // remove cars and engines from this train via property change
3801        setStatusCode(CODE_TRAIN_RESET);
3802        // remove train icon
3803        if (_trainIcon != null && _trainIcon.isActive()) {
3804            _trainIcon.remove();
3805        }
3806        return true;
3807    }
3808
3809    public void dispose() {
3810        if (getRoute() != null) {
3811            getRoute().removePropertyChangeListener(this);
3812        }
3813        InstanceManager.getDefault(CarRoads.class).removePropertyChangeListener(this);
3814        InstanceManager.getDefault(CarTypes.class).removePropertyChangeListener(this);
3815        InstanceManager.getDefault(EngineTypes.class).removePropertyChangeListener(this);
3816        InstanceManager.getDefault(CarOwners.class).removePropertyChangeListener(this);
3817        InstanceManager.getDefault(EngineModels.class).removePropertyChangeListener(this);
3818
3819        setDirtyAndFirePropertyChange(DISPOSE_CHANGED_PROPERTY, null, "Dispose"); // NOI18N
3820    }
3821
3822    /**
3823     * Construct this Entry from XML. This member has to remain synchronized
3824     * with the detailed DTD in operations-trains.dtd
3825     *
3826     * @param e Consist XML element
3827     */
3828    public Train(Element e) {
3829        org.jdom2.Attribute a;
3830        if ((a = e.getAttribute(Xml.ID)) != null) {
3831            _id = a.getValue();
3832        } else {
3833            log.warn("no id attribute in train element when reading operations");
3834        }
3835        if ((a = e.getAttribute(Xml.NAME)) != null) {
3836            _name = a.getValue();
3837        }
3838        if ((a = e.getAttribute(Xml.DESCRIPTION)) != null) {
3839            _description = a.getValue();
3840        }
3841        if ((a = e.getAttribute(Xml.DEPART_HOUR)) != null) {
3842            String hour = a.getValue();
3843            if ((a = e.getAttribute(Xml.DEPART_MINUTE)) != null) {
3844                String minute = a.getValue();
3845                _departureTime = hour + ":" + minute;
3846            }
3847        }
3848
3849        // Trains table row color
3850        Element eRowColor = e.getChild(Xml.ROW_COLOR);
3851        if (eRowColor != null && (a = eRowColor.getAttribute(Xml.NAME)) != null) {
3852            _tableRowColorName = a.getValue().toLowerCase();
3853        }
3854        if (eRowColor != null && (a = eRowColor.getAttribute(Xml.RESET_ROW_COLOR)) != null) {
3855            _tableRowColorResetName = a.getValue().toLowerCase();
3856        }
3857
3858        Element eRoute = e.getChild(Xml.ROUTE);
3859        if (eRoute != null) {
3860            if ((a = eRoute.getAttribute(Xml.ID)) != null) {
3861                setRoute(InstanceManager.getDefault(RouteManager.class).getRouteById(a.getValue()));
3862            }
3863            if (eRoute.getChild(Xml.SKIPS) != null) {
3864                List<Element> skips = eRoute.getChild(Xml.SKIPS).getChildren(Xml.LOCATION);
3865                String[] locs = new String[skips.size()];
3866                for (int i = 0; i < skips.size(); i++) {
3867                    Element loc = skips.get(i);
3868                    if ((a = loc.getAttribute(Xml.ID)) != null) {
3869                        locs[i] = a.getValue();
3870                    }
3871                }
3872                setTrainSkipsLocations(locs);
3873            }
3874        } else {
3875            // old format
3876            // try and first get the route by id then by name
3877            if ((a = e.getAttribute(Xml.ROUTE_ID)) != null) {
3878                setRoute(InstanceManager.getDefault(RouteManager.class).getRouteById(a.getValue()));
3879            } else if ((a = e.getAttribute(Xml.ROUTE)) != null) {
3880                setRoute(InstanceManager.getDefault(RouteManager.class).getRouteByName(a.getValue()));
3881            }
3882            if ((a = e.getAttribute(Xml.SKIP)) != null) {
3883                String locationIds = a.getValue();
3884                String[] locs = locationIds.split("%%"); // NOI18N
3885                // log.debug("Train skips: {}", locationIds);
3886                setTrainSkipsLocations(locs);
3887            }
3888        }
3889        // new way of reading car types using elements
3890        if (e.getChild(Xml.TYPES) != null) {
3891            List<Element> carTypes = e.getChild(Xml.TYPES).getChildren(Xml.CAR_TYPE);
3892            String[] types = new String[carTypes.size()];
3893            for (int i = 0; i < carTypes.size(); i++) {
3894                Element type = carTypes.get(i);
3895                if ((a = type.getAttribute(Xml.NAME)) != null) {
3896                    types[i] = a.getValue();
3897                }
3898            }
3899            setTypeNames(types);
3900            List<Element> locoTypes = e.getChild(Xml.TYPES).getChildren(Xml.LOCO_TYPE);
3901            types = new String[locoTypes.size()];
3902            for (int i = 0; i < locoTypes.size(); i++) {
3903                Element type = locoTypes.get(i);
3904                if ((a = type.getAttribute(Xml.NAME)) != null) {
3905                    types[i] = a.getValue();
3906                }
3907            }
3908            setTypeNames(types);
3909        } // old way of reading car types up to version 2.99.6
3910        else if ((a = e.getAttribute(Xml.CAR_TYPES)) != null) {
3911            String names = a.getValue();
3912            String[] types = names.split("%%"); // NOI18N
3913            // log.debug("Car types: {}", names);
3914            setTypeNames(types);
3915        }
3916        // old misspelled format
3917        if ((a = e.getAttribute(Xml.CAR_ROAD_OPERATION)) != null) {
3918            _carRoadOption = a.getValue();
3919        }
3920        if ((a = e.getAttribute(Xml.CAR_ROAD_OPTION)) != null) {
3921            _carRoadOption = a.getValue();
3922        }
3923        // new way of reading car roads using elements
3924        if (e.getChild(Xml.CAR_ROADS) != null) {
3925            List<Element> carRoads = e.getChild(Xml.CAR_ROADS).getChildren(Xml.CAR_ROAD);
3926            String[] roads = new String[carRoads.size()];
3927            for (int i = 0; i < carRoads.size(); i++) {
3928                Element road = carRoads.get(i);
3929                if ((a = road.getAttribute(Xml.NAME)) != null) {
3930                    roads[i] = a.getValue();
3931                }
3932            }
3933            setCarRoadNames(roads);
3934        } // old way of reading car roads up to version 2.99.6
3935        else if ((a = e.getAttribute(Xml.CAR_ROADS)) != null) {
3936            String names = a.getValue();
3937            String[] roads = names.split("%%"); // NOI18N
3938            log.debug("Train ({}) {} car roads: {}", getName(), getCarRoadOption(), names);
3939            setCarRoadNames(roads);
3940        }
3941
3942        if ((a = e.getAttribute(Xml.CABOOSE_ROAD_OPTION)) != null) {
3943            _cabooseRoadOption = a.getValue();
3944        }
3945        // new way of reading caboose roads using elements
3946        if (e.getChild(Xml.CABOOSE_ROADS) != null) {
3947            List<Element> carRoads = e.getChild(Xml.CABOOSE_ROADS).getChildren(Xml.CAR_ROAD);
3948            String[] roads = new String[carRoads.size()];
3949            for (int i = 0; i < carRoads.size(); i++) {
3950                Element road = carRoads.get(i);
3951                if ((a = road.getAttribute(Xml.NAME)) != null) {
3952                    roads[i] = a.getValue();
3953                }
3954            }
3955            setCabooseRoadNames(roads);
3956        }
3957
3958        if ((a = e.getAttribute(Xml.LOCO_ROAD_OPTION)) != null) {
3959            _locoRoadOption = a.getValue();
3960        }
3961        // new way of reading engine roads using elements
3962        if (e.getChild(Xml.LOCO_ROADS) != null) {
3963            List<Element> locoRoads = e.getChild(Xml.LOCO_ROADS).getChildren(Xml.LOCO_ROAD);
3964            String[] roads = new String[locoRoads.size()];
3965            for (int i = 0; i < locoRoads.size(); i++) {
3966                Element road = locoRoads.get(i);
3967                if ((a = road.getAttribute(Xml.NAME)) != null) {
3968                    roads[i] = a.getValue();
3969                }
3970            }
3971            setLocoRoadNames(roads);
3972        }
3973
3974        if ((a = e.getAttribute(Xml.CAR_LOAD_OPTION)) != null) {
3975            _loadOption = a.getValue();
3976        }
3977        if ((a = e.getAttribute(Xml.CAR_OWNER_OPTION)) != null) {
3978            _ownerOption = a.getValue();
3979        }
3980        if ((a = e.getAttribute(Xml.BUILT_START_YEAR)) != null) {
3981            _builtStartYear = a.getValue();
3982        }
3983        if ((a = e.getAttribute(Xml.BUILT_END_YEAR)) != null) {
3984            _builtEndYear = a.getValue();
3985        }
3986        // new way of reading car loads using elements
3987        if (e.getChild(Xml.CAR_LOADS) != null) {
3988            List<Element> carLoads = e.getChild(Xml.CAR_LOADS).getChildren(Xml.CAR_LOAD);
3989            String[] loads = new String[carLoads.size()];
3990            for (int i = 0; i < carLoads.size(); i++) {
3991                Element load = carLoads.get(i);
3992                if ((a = load.getAttribute(Xml.NAME)) != null) {
3993                    loads[i] = a.getValue();
3994                }
3995            }
3996            setLoadNames(loads);
3997        } // old way of reading car loads up to version 2.99.6
3998        else if ((a = e.getAttribute(Xml.CAR_LOADS)) != null) {
3999            String names = a.getValue();
4000            String[] loads = names.split("%%"); // NOI18N
4001            log.debug("Train ({}) {} car loads: {}", getName(), getLoadOption(), names);
4002            setLoadNames(loads);
4003        }
4004        // new way of reading car owners using elements
4005        if (e.getChild(Xml.CAR_OWNERS) != null) {
4006            List<Element> carOwners = e.getChild(Xml.CAR_OWNERS).getChildren(Xml.CAR_OWNER);
4007            String[] owners = new String[carOwners.size()];
4008            for (int i = 0; i < carOwners.size(); i++) {
4009                Element owner = carOwners.get(i);
4010                if ((a = owner.getAttribute(Xml.NAME)) != null) {
4011                    owners[i] = a.getValue();
4012                }
4013            }
4014            setOwnerNames(owners);
4015        } // old way of reading car owners up to version 2.99.6
4016        else if ((a = e.getAttribute(Xml.CAR_OWNERS)) != null) {
4017            String names = a.getValue();
4018            String[] owners = names.split("%%"); // NOI18N
4019            log.debug("Train ({}) {} car owners: {}", getName(), getOwnerOption(), names);
4020            setOwnerNames(owners);
4021        }
4022
4023        if ((a = e.getAttribute(Xml.NUMBER_ENGINES)) != null) {
4024            _numberEngines = a.getValue();
4025        }
4026        if ((a = e.getAttribute(Xml.LEG2_ENGINES)) != null) {
4027            _leg2Engines = a.getValue();
4028        }
4029        if ((a = e.getAttribute(Xml.LEG3_ENGINES)) != null) {
4030            _leg3Engines = a.getValue();
4031        }
4032        if ((a = e.getAttribute(Xml.ENGINE_ROAD)) != null) {
4033            _engineRoad = a.getValue();
4034        }
4035        if ((a = e.getAttribute(Xml.LEG2_ROAD)) != null) {
4036            _leg2Road = a.getValue();
4037        }
4038        if ((a = e.getAttribute(Xml.LEG3_ROAD)) != null) {
4039            _leg3Road = a.getValue();
4040        }
4041        if ((a = e.getAttribute(Xml.ENGINE_MODEL)) != null) {
4042            _engineModel = a.getValue();
4043        }
4044        if ((a = e.getAttribute(Xml.LEG2_MODEL)) != null) {
4045            _leg2Model = a.getValue();
4046        }
4047        if ((a = e.getAttribute(Xml.LEG3_MODEL)) != null) {
4048            _leg3Model = a.getValue();
4049        }
4050        if ((a = e.getAttribute(Xml.REQUIRES)) != null) {
4051            try {
4052                _requires = Integer.parseInt(a.getValue());
4053            } catch (NumberFormatException ee) {
4054                log.error("Requires ({}) isn't a valid number for train ({})", a.getValue(), getName());
4055            }
4056        }
4057        if ((a = e.getAttribute(Xml.CABOOSE_ROAD)) != null) {
4058            _cabooseRoad = a.getValue();
4059        }
4060        if ((a = e.getAttribute(Xml.LEG2_CABOOSE_ROAD)) != null) {
4061            _leg2CabooseRoad = a.getValue();
4062        }
4063        if ((a = e.getAttribute(Xml.LEG3_CABOOSE_ROAD)) != null) {
4064            _leg3CabooseRoad = a.getValue();
4065        }
4066        if ((a = e.getAttribute(Xml.LEG2_OPTIONS)) != null) {
4067            try {
4068                _leg2Options = Integer.parseInt(a.getValue());
4069            } catch (NumberFormatException ee) {
4070                log.error("Leg 2 options ({}) isn't a valid number for train ({})", a.getValue(), getName());
4071            }
4072        }
4073        if ((a = e.getAttribute(Xml.LEG3_OPTIONS)) != null) {
4074            try {
4075                _leg3Options = Integer.parseInt(a.getValue());
4076            } catch (NumberFormatException ee) {
4077                log.error("Leg 3 options ({}) isn't a valid number for train ({})", a.getValue(), getName());
4078            }
4079        }
4080        if ((a = e.getAttribute(Xml.BUILD_NORMAL)) != null) {
4081            _buildNormal = a.getValue().equals(Xml.TRUE);
4082        }
4083        if ((a = e.getAttribute(Xml.TO_TERMINAL)) != null) {
4084            _sendToTerminal = a.getValue().equals(Xml.TRUE);
4085        }
4086        if ((a = e.getAttribute(Xml.ALLOW_LOCAL_MOVES)) != null) {
4087            _allowLocalMoves = a.getValue().equals(Xml.TRUE);
4088        }
4089        if ((a = e.getAttribute(Xml.ALLOW_THROUGH_CARS)) != null) {
4090            _allowThroughCars = a.getValue().equals(Xml.TRUE);
4091        }
4092        if ((a = e.getAttribute(Xml.ALLOW_RETURN)) != null) {
4093            _allowCarsReturnStaging = a.getValue().equals(Xml.TRUE);
4094        }
4095        if ((a = e.getAttribute(Xml.SERVICE_ALL)) != null) {
4096            _serviceAllCarsWithFinalDestinations = a.getValue().equals(Xml.TRUE);
4097        }
4098        if ((a = e.getAttribute(Xml.BUILD_CONSIST)) != null) {
4099            _buildConsist = a.getValue().equals(Xml.TRUE);
4100        }
4101        if ((a = e.getAttribute(Xml.SEND_CUSTOM_STAGING)) != null) {
4102            _sendCarsWithCustomLoadsToStaging = a.getValue().equals(Xml.TRUE);
4103        }
4104        if ((a = e.getAttribute(Xml.BUILT)) != null) {
4105            _built = a.getValue().equals(Xml.TRUE);
4106        }
4107        if ((a = e.getAttribute(Xml.BUILD)) != null) {
4108            _build = a.getValue().equals(Xml.TRUE);
4109        }
4110        if ((a = e.getAttribute(Xml.BUILD_FAILED)) != null) {
4111            _buildFailed = a.getValue().equals(Xml.TRUE);
4112        }
4113        if ((a = e.getAttribute(Xml.BUILD_FAILED_MESSAGE)) != null) {
4114            _buildFailedMessage = a.getValue();
4115        }
4116        if ((a = e.getAttribute(Xml.PRINTED)) != null) {
4117            _printed = a.getValue().equals(Xml.TRUE);
4118        }
4119        if ((a = e.getAttribute(Xml.MODIFIED)) != null) {
4120            _modified = a.getValue().equals(Xml.TRUE);
4121        }
4122        if ((a = e.getAttribute(Xml.SWITCH_LIST_STATUS)) != null) {
4123            _switchListStatus = a.getValue();
4124        }
4125        if ((a = e.getAttribute(Xml.LEAD_ENGINE)) != null) {
4126            _leadEngineId = a.getValue();
4127        }
4128        if ((a = e.getAttribute(Xml.TERMINATION_DATE)) != null) {
4129            _date = TrainCommon.convertStringToDate(a.getValue());
4130        }
4131        if ((a = e.getAttribute(Xml.REQUESTED_CARS)) != null) {
4132            try {
4133                _statusCarsRequested = Integer.parseInt(a.getValue());
4134            } catch (NumberFormatException ee) {
4135                log.error("Status cars requested ({}) isn't a valid number for train ({})", a.getValue(), getName());
4136            }
4137        }
4138        if ((a = e.getAttribute(Xml.STATUS_CODE)) != null) {
4139            try {
4140                _statusCode = Integer.parseInt(a.getValue());
4141            } catch (NumberFormatException ee) {
4142                log.error("Status code ({}) isn't a valid number for train ({})", a.getValue(), getName());
4143            }
4144        } else if ((a = e.getAttribute(Xml.STATUS)) != null) {
4145            // attempt to recover status code
4146            String status = a.getValue();
4147            if (status.startsWith(BUILD_FAILED)) {
4148                _statusCode = CODE_BUILD_FAILED;
4149            } else if (status.startsWith(BUILT)) {
4150                _statusCode = CODE_BUILT;
4151            } else if (status.startsWith(PARTIAL_BUILT)) {
4152                _statusCode = CODE_PARTIAL_BUILT;
4153            } else if (status.startsWith(TERMINATED)) {
4154                _statusCode = CODE_TERMINATED;
4155            } else if (status.startsWith(TRAIN_EN_ROUTE)) {
4156                _statusCode = CODE_TRAIN_EN_ROUTE;
4157            } else if (status.startsWith(TRAIN_RESET)) {
4158                _statusCode = CODE_TRAIN_RESET;
4159            } else {
4160                _statusCode = CODE_UNKNOWN;
4161            }
4162        }
4163        if ((a = e.getAttribute(Xml.OLD_STATUS_CODE)) != null) {
4164            try {
4165                _oldStatusCode = Integer.parseInt(a.getValue());
4166            } catch (NumberFormatException ee) {
4167                log.error("Old status code ({}) isn't a valid number for train ({})", a.getValue(), getName());
4168            }
4169        } else {
4170            _oldStatusCode = getStatusCode(); // use current status code if one
4171                                              // wasn't saved
4172        }
4173        if ((a = e.getAttribute(Xml.COMMENT)) != null) {
4174            _comment = a.getValue();
4175        }
4176        if (getRoute() != null) {
4177            if ((a = e.getAttribute(Xml.CURRENT)) != null) {
4178                _current = getRoute().getRouteLocationById(a.getValue());
4179            }
4180            if ((a = e.getAttribute(Xml.LEG2_START)) != null) {
4181                _leg2Start = getRoute().getRouteLocationById(a.getValue());
4182            }
4183            if ((a = e.getAttribute(Xml.LEG3_START)) != null) {
4184                _leg3Start = getRoute().getRouteLocationById(a.getValue());
4185            }
4186            if ((a = e.getAttribute(Xml.LEG2_END)) != null) {
4187                _end2Leg = getRoute().getRouteLocationById(a.getValue());
4188            }
4189            if ((a = e.getAttribute(Xml.LEG3_END)) != null) {
4190                _leg3End = getRoute().getRouteLocationById(a.getValue());
4191            }
4192            if ((a = e.getAttribute(Xml.DEPARTURE_TRACK)) != null) {
4193                Location location = InstanceManager.getDefault(LocationManager.class)
4194                        .getLocationByName(getTrainDepartsName());
4195                if (location != null) {
4196                    _departureTrack = location.getTrackById(a.getValue());
4197                } else {
4198                    log.error("Departure location not found for track {}", a.getValue());
4199                }
4200            }
4201            if ((a = e.getAttribute(Xml.TERMINATION_TRACK)) != null) {
4202                Location location = InstanceManager.getDefault(LocationManager.class)
4203                        .getLocationByName(getTrainTerminatesName());
4204                if (location != null) {
4205                    _terminationTrack = location.getTrackById(a.getValue());
4206                } else {
4207                    log.error("Termiation location not found for track {}", a.getValue());
4208                }
4209            }
4210        }
4211
4212        // check for scripts
4213        if (e.getChild(Xml.SCRIPTS) != null) {
4214            List<Element> lb = e.getChild(Xml.SCRIPTS).getChildren(Xml.BUILD);
4215            for (Element es : lb) {
4216                if ((a = es.getAttribute(Xml.NAME)) != null) {
4217                    addBuildScript(a.getValue());
4218                }
4219            }
4220            List<Element> lab = e.getChild(Xml.SCRIPTS).getChildren(Xml.AFTER_BUILD);
4221            for (Element es : lab) {
4222                if ((a = es.getAttribute(Xml.NAME)) != null) {
4223                    addAfterBuildScript(a.getValue());
4224                }
4225            }
4226            List<Element> lm = e.getChild(Xml.SCRIPTS).getChildren(Xml.MOVE);
4227            for (Element es : lm) {
4228                if ((a = es.getAttribute(Xml.NAME)) != null) {
4229                    addMoveScript(a.getValue());
4230                }
4231            }
4232            List<Element> lt = e.getChild(Xml.SCRIPTS).getChildren(Xml.TERMINATE);
4233            for (Element es : lt) {
4234                if ((a = es.getAttribute(Xml.NAME)) != null) {
4235                    addTerminationScript(a.getValue());
4236                }
4237            }
4238        }
4239        // check for optional railroad name and logo
4240        if ((e.getChild(Xml.RAIL_ROAD) != null) && (a = e.getChild(Xml.RAIL_ROAD).getAttribute(Xml.NAME)) != null) {
4241            String name = a.getValue();
4242            setRailroadName(name);
4243        }
4244        if ((e.getChild(Xml.MANIFEST_LOGO) != null)) {
4245            if ((a = e.getChild(Xml.MANIFEST_LOGO).getAttribute(Xml.NAME)) != null) {
4246                setManifestLogoPathName(a.getValue());
4247            }
4248        }
4249        if ((a = e.getAttribute(Xml.SHOW_TIMES)) != null) {
4250            _showTimes = a.getValue().equals(Xml.TRUE);
4251        }
4252
4253        addPropertyChangeListerners();
4254    }
4255
4256    private void addPropertyChangeListerners() {
4257        InstanceManager.getDefault(CarRoads.class).addPropertyChangeListener(this);
4258        InstanceManager.getDefault(CarTypes.class).addPropertyChangeListener(this);
4259        InstanceManager.getDefault(EngineTypes.class).addPropertyChangeListener(this);
4260        InstanceManager.getDefault(CarOwners.class).addPropertyChangeListener(this);
4261        InstanceManager.getDefault(EngineModels.class).addPropertyChangeListener(this);
4262    }
4263
4264    /**
4265     * Create an XML element to represent this Entry. This member has to remain
4266     * synchronized with the detailed DTD in operations-trains.dtd.
4267     *
4268     * @return Contents in a JDOM Element
4269     */
4270    public Element store() {
4271        Element e = new Element(Xml.TRAIN);
4272        e.setAttribute(Xml.ID, getId());
4273        e.setAttribute(Xml.NAME, getName());
4274        e.setAttribute(Xml.DESCRIPTION, getRawDescription());
4275        e.setAttribute(Xml.DEPART_HOUR, getDepartureTimeHour());
4276        e.setAttribute(Xml.DEPART_MINUTE, getDepartureTimeMinute());
4277
4278        Element eRowColor = new Element(Xml.ROW_COLOR);
4279        eRowColor.setAttribute(Xml.NAME, getTableRowColorName());
4280        eRowColor.setAttribute(Xml.RESET_ROW_COLOR, getTableRowColorNameReset());
4281        e.addContent(eRowColor);
4282
4283        Element eRoute = new Element(Xml.ROUTE);
4284        if (getRoute() != null) {
4285            eRoute.setAttribute(Xml.NAME, getRoute().getName());
4286            eRoute.setAttribute(Xml.ID, getRoute().getId());
4287            e.addContent(eRoute);
4288            // build list of locations that this train skips
4289            String[] locationIds = getTrainSkipsLocations();
4290            if (locationIds.length > 0) {
4291                Element eSkips = new Element(Xml.SKIPS);
4292                for (String id : locationIds) {
4293                    Element eLoc = new Element(Xml.LOCATION);
4294                    RouteLocation rl = getRoute().getRouteLocationById(id);
4295                    if (rl != null) {
4296                        eLoc.setAttribute(Xml.NAME, rl.getName());
4297                        eLoc.setAttribute(Xml.ID, id);
4298                        eSkips.addContent(eLoc);
4299                    }
4300                }
4301                eRoute.addContent(eSkips);
4302            }
4303        }
4304        // build list of locations that this train skips
4305        if (getCurrentRouteLocation() != null) {
4306            e.setAttribute(Xml.CURRENT, getCurrentRouteLocation().getId());
4307        }
4308        if (getDepartureTrack() != null) {
4309            e.setAttribute(Xml.DEPARTURE_TRACK, getDepartureTrack().getId());
4310        }
4311        if (getTerminationTrack() != null) {
4312            e.setAttribute(Xml.TERMINATION_TRACK, getTerminationTrack().getId());
4313        }
4314        e.setAttribute(Xml.BUILT_START_YEAR, getBuiltStartYear());
4315        e.setAttribute(Xml.BUILT_END_YEAR, getBuiltEndYear());
4316        e.setAttribute(Xml.NUMBER_ENGINES, getNumberEngines());
4317        e.setAttribute(Xml.ENGINE_ROAD, getEngineRoad());
4318        e.setAttribute(Xml.ENGINE_MODEL, getEngineModel());
4319        e.setAttribute(Xml.REQUIRES, Integer.toString(getRequirements()));
4320        e.setAttribute(Xml.CABOOSE_ROAD, getCabooseRoad());
4321        e.setAttribute(Xml.BUILD_NORMAL, isBuildTrainNormalEnabled() ? Xml.TRUE : Xml.FALSE);
4322        e.setAttribute(Xml.TO_TERMINAL, isSendCarsToTerminalEnabled() ? Xml.TRUE : Xml.FALSE);
4323        e.setAttribute(Xml.ALLOW_LOCAL_MOVES, isAllowLocalMovesEnabled() ? Xml.TRUE : Xml.FALSE);
4324        e.setAttribute(Xml.ALLOW_RETURN, isAllowReturnToStagingEnabled() ? Xml.TRUE : Xml.FALSE);
4325        e.setAttribute(Xml.ALLOW_THROUGH_CARS, isAllowThroughCarsEnabled() ? Xml.TRUE : Xml.FALSE);
4326        e.setAttribute(Xml.SERVICE_ALL, isServiceAllCarsWithFinalDestinationsEnabled() ? Xml.TRUE : Xml.FALSE);
4327        e.setAttribute(Xml.SEND_CUSTOM_STAGING, isSendCarsWithCustomLoadsToStagingEnabled() ? Xml.TRUE : Xml.FALSE);
4328        e.setAttribute(Xml.BUILD_CONSIST, isBuildConsistEnabled() ? Xml.TRUE : Xml.FALSE);
4329        e.setAttribute(Xml.BUILT, isBuilt() ? Xml.TRUE : Xml.FALSE);
4330        e.setAttribute(Xml.BUILD, isBuildEnabled() ? Xml.TRUE : Xml.FALSE);
4331        e.setAttribute(Xml.BUILD_FAILED, isBuildFailed() ? Xml.TRUE : Xml.FALSE);
4332        e.setAttribute(Xml.BUILD_FAILED_MESSAGE, getBuildFailedMessage());
4333        e.setAttribute(Xml.PRINTED, isPrinted() ? Xml.TRUE : Xml.FALSE);
4334        e.setAttribute(Xml.MODIFIED, isModified() ? Xml.TRUE : Xml.FALSE);
4335        e.setAttribute(Xml.SWITCH_LIST_STATUS, getSwitchListStatus());
4336        if (getLeadEngine() != null) {
4337            e.setAttribute(Xml.LEAD_ENGINE, getLeadEngine().getId());
4338        }
4339        e.setAttribute(Xml.STATUS, getStatus());
4340        e.setAttribute(Xml.TERMINATION_DATE, getDate());
4341        e.setAttribute(Xml.REQUESTED_CARS, Integer.toString(getNumberCarsRequested()));
4342        e.setAttribute(Xml.STATUS_CODE, Integer.toString(getStatusCode()));
4343        e.setAttribute(Xml.OLD_STATUS_CODE, Integer.toString(getOldStatusCode()));
4344        e.setAttribute(Xml.COMMENT, getCommentWithColor());
4345        e.setAttribute(Xml.SHOW_TIMES, isShowArrivalAndDepartureTimesEnabled() ? Xml.TRUE : Xml.FALSE);
4346        // build list of car types for this train
4347        String[] types = getTypeNames();
4348        // new way of saving car types
4349        Element eTypes = new Element(Xml.TYPES);
4350        for (String type : types) {
4351            // don't save types that have been deleted by user
4352            if (InstanceManager.getDefault(EngineTypes.class).containsName(type)) {
4353                Element eType = new Element(Xml.LOCO_TYPE);
4354                eType.setAttribute(Xml.NAME, type);
4355                eTypes.addContent(eType);
4356            } else if (InstanceManager.getDefault(CarTypes.class).containsName(type)) {
4357                Element eType = new Element(Xml.CAR_TYPE);
4358                eType.setAttribute(Xml.NAME, type);
4359                eTypes.addContent(eType);
4360            }
4361        }
4362        e.addContent(eTypes);
4363        // save list of car roads for this train
4364        if (!getCarRoadOption().equals(ALL_ROADS)) {
4365            e.setAttribute(Xml.CAR_ROAD_OPTION, getCarRoadOption());
4366            String[] roads = getCarRoadNames();
4367            // new way of saving road names
4368            Element eRoads = new Element(Xml.CAR_ROADS);
4369            for (String road : roads) {
4370                Element eRoad = new Element(Xml.CAR_ROAD);
4371                eRoad.setAttribute(Xml.NAME, road);
4372                eRoads.addContent(eRoad);
4373            }
4374            e.addContent(eRoads);
4375        }
4376        // save list of caboose roads for this train
4377        if (!getCabooseRoadOption().equals(ALL_ROADS)) {
4378            e.setAttribute(Xml.CABOOSE_ROAD_OPTION, getCabooseRoadOption());
4379            String[] roads = getCabooseRoadNames();
4380            // new way of saving road names
4381            Element eRoads = new Element(Xml.CABOOSE_ROADS);
4382            for (String road : roads) {
4383                Element eRoad = new Element(Xml.CAR_ROAD);
4384                eRoad.setAttribute(Xml.NAME, road);
4385                eRoads.addContent(eRoad);
4386            }
4387            e.addContent(eRoads);
4388        }
4389        // save list of engine roads for this train
4390        if (!getLocoRoadOption().equals(ALL_ROADS)) {
4391            e.setAttribute(Xml.LOCO_ROAD_OPTION, getLocoRoadOption());
4392            String[] roads = getLocoRoadNames();
4393            Element eRoads = new Element(Xml.LOCO_ROADS);
4394            for (String road : roads) {
4395                Element eRoad = new Element(Xml.LOCO_ROAD);
4396                eRoad.setAttribute(Xml.NAME, road);
4397                eRoads.addContent(eRoad);
4398            }
4399            e.addContent(eRoads);
4400        }
4401        // save list of car loads for this train
4402        if (!getLoadOption().equals(ALL_LOADS)) {
4403            e.setAttribute(Xml.CAR_LOAD_OPTION, getLoadOption());
4404            String[] loads = getLoadNames();
4405            // new way of saving car loads
4406            Element eLoads = new Element(Xml.CAR_LOADS);
4407            for (String load : loads) {
4408                Element eLoad = new Element(Xml.CAR_LOAD);
4409                eLoad.setAttribute(Xml.NAME, load);
4410                eLoads.addContent(eLoad);
4411            }
4412            e.addContent(eLoads);
4413        }
4414        // save list of car owners for this train
4415        if (!getOwnerOption().equals(ALL_OWNERS)) {
4416            e.setAttribute(Xml.CAR_OWNER_OPTION, getOwnerOption());
4417            String[] owners = getOwnerNames();
4418            // new way of saving car owners
4419            Element eOwners = new Element(Xml.CAR_OWNERS);
4420            for (String owner : owners) {
4421                Element eOwner = new Element(Xml.CAR_OWNER);
4422                eOwner.setAttribute(Xml.NAME, owner);
4423                eOwners.addContent(eOwner);
4424            }
4425            e.addContent(eOwners);
4426        }
4427        // save list of scripts for this train
4428        if (getBuildScripts().size() > 0 ||
4429                getAfterBuildScripts().size() > 0 ||
4430                getMoveScripts().size() > 0 ||
4431                getTerminationScripts().size() > 0) {
4432            Element es = new Element(Xml.SCRIPTS);
4433            if (getBuildScripts().size() > 0) {
4434                for (String scriptPathname : getBuildScripts()) {
4435                    Element em = new Element(Xml.BUILD);
4436                    em.setAttribute(Xml.NAME, scriptPathname);
4437                    es.addContent(em);
4438                }
4439            }
4440            if (getAfterBuildScripts().size() > 0) {
4441                for (String scriptPathname : getAfterBuildScripts()) {
4442                    Element em = new Element(Xml.AFTER_BUILD);
4443                    em.setAttribute(Xml.NAME, scriptPathname);
4444                    es.addContent(em);
4445                }
4446            }
4447            if (getMoveScripts().size() > 0) {
4448                for (String scriptPathname : getMoveScripts()) {
4449                    Element em = new Element(Xml.MOVE);
4450                    em.setAttribute(Xml.NAME, scriptPathname);
4451                    es.addContent(em);
4452                }
4453            }
4454            // save list of termination scripts for this train
4455            if (getTerminationScripts().size() > 0) {
4456                for (String scriptPathname : getTerminationScripts()) {
4457                    Element et = new Element(Xml.TERMINATE);
4458                    et.setAttribute(Xml.NAME, scriptPathname);
4459                    es.addContent(et);
4460                }
4461            }
4462            e.addContent(es);
4463        }
4464        if (!getRailroadName().equals(NONE)) {
4465            Element r = new Element(Xml.RAIL_ROAD);
4466            r.setAttribute(Xml.NAME, getRailroadName());
4467            e.addContent(r);
4468        }
4469        if (!getManifestLogoPathName().equals(NONE)) {
4470            Element l = new Element(Xml.MANIFEST_LOGO);
4471            l.setAttribute(Xml.NAME, getManifestLogoPathName());
4472            e.addContent(l);
4473        }
4474
4475        if (getSecondLegOptions() != NO_CABOOSE_OR_FRED) {
4476            e.setAttribute(Xml.LEG2_OPTIONS, Integer.toString(getSecondLegOptions()));
4477            e.setAttribute(Xml.LEG2_ENGINES, getSecondLegNumberEngines());
4478            e.setAttribute(Xml.LEG2_ROAD, getSecondLegEngineRoad());
4479            e.setAttribute(Xml.LEG2_MODEL, getSecondLegEngineModel());
4480            e.setAttribute(Xml.LEG2_CABOOSE_ROAD, getSecondLegCabooseRoad());
4481            if (getSecondLegStartRouteLocation() != null) {
4482                e.setAttribute(Xml.LEG2_START, getSecondLegStartRouteLocation().getId());
4483            }
4484            if (getSecondLegEndRouteLocation() != null) {
4485                e.setAttribute(Xml.LEG2_END, getSecondLegEndRouteLocation().getId());
4486            }
4487        }
4488        if (getThirdLegOptions() != NO_CABOOSE_OR_FRED) {
4489            e.setAttribute(Xml.LEG3_OPTIONS, Integer.toString(getThirdLegOptions()));
4490            e.setAttribute(Xml.LEG3_ENGINES, getThirdLegNumberEngines());
4491            e.setAttribute(Xml.LEG3_ROAD, getThirdLegEngineRoad());
4492            e.setAttribute(Xml.LEG3_MODEL, getThirdLegEngineModel());
4493            e.setAttribute(Xml.LEG3_CABOOSE_ROAD, getThirdLegCabooseRoad());
4494            if (getThirdLegStartRouteLocation() != null) {
4495                e.setAttribute(Xml.LEG3_START, getThirdLegStartRouteLocation().getId());
4496            }
4497            if (getThirdLegEndRouteLocation() != null) {
4498                e.setAttribute(Xml.LEG3_END, getThirdLegEndRouteLocation().getId());
4499            }
4500        }
4501        return e;
4502    }
4503
4504    @Override
4505    public void propertyChange(java.beans.PropertyChangeEvent e) {
4506        if (Control.SHOW_PROPERTY) {
4507            log.debug("Train ({}) sees property change: ({}) old: ({}) new: ({})", getName(), e.getPropertyName(),
4508                    e.getOldValue(), e.getNewValue());
4509        }
4510        if (e.getPropertyName().equals(Route.DISPOSE)) {
4511            setRoute(null);
4512        }
4513        if (e.getPropertyName().equals(CarTypes.CARTYPES_NAME_CHANGED_PROPERTY) ||
4514                e.getPropertyName().equals(CarTypes.CARTYPES_CHANGED_PROPERTY) ||
4515                e.getPropertyName().equals(EngineTypes.ENGINETYPES_NAME_CHANGED_PROPERTY)) {
4516            replaceType((String) e.getOldValue(), (String) e.getNewValue());
4517        }
4518        if (e.getPropertyName().equals(CarRoads.CARROADS_NAME_CHANGED_PROPERTY)) {
4519            replaceRoad((String) e.getOldValue(), (String) e.getNewValue());
4520        }
4521        if (e.getPropertyName().equals(CarOwners.CAROWNERS_NAME_CHANGED_PROPERTY)) {
4522            replaceOwner((String) e.getOldValue(), (String) e.getNewValue());
4523        }
4524        if (e.getPropertyName().equals(EngineModels.ENGINEMODELS_NAME_CHANGED_PROPERTY)) {
4525            replaceModel((String) e.getOldValue(), (String) e.getNewValue());
4526        }
4527        // forward route departure time property changes
4528        if (e.getPropertyName().equals(RouteLocation.DEPARTURE_TIME_CHANGED_PROPERTY)) {
4529            setDirtyAndFirePropertyChange(DEPARTURETIME_CHANGED_PROPERTY, e.getOldValue(), e.getNewValue());
4530        }
4531        // forward any property changes in this train's route
4532        if (e.getSource().getClass().equals(Route.class)) {
4533            setDirtyAndFirePropertyChange(e.getPropertyName(), e.getOldValue(), e.getNewValue());
4534        }
4535    }
4536
4537    protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) {
4538        InstanceManager.getDefault(TrainManagerXml.class).setDirty(true);
4539        firePropertyChange(p, old, n);
4540    }
4541
4542    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Train.class);
4543
4544}