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