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