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