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