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