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