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