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