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