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