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