001package jmri.jmrit.operations.trains;
002
003import java.beans.PropertyChangeListener;
004import java.io.File;
005import java.io.PrintWriter;
006import java.util.*;
007
008import javax.swing.JComboBox;
009
010import org.jdom2.Attribute;
011import org.jdom2.Element;
012
013import jmri.*;
014import jmri.beans.PropertyChangeSupport;
015import jmri.jmrit.operations.OperationsPanel;
016import jmri.jmrit.operations.locations.Location;
017import jmri.jmrit.operations.rollingstock.cars.*;
018import jmri.jmrit.operations.rollingstock.engines.EngineManagerXml;
019import jmri.jmrit.operations.routes.Route;
020import jmri.jmrit.operations.routes.RouteLocation;
021import jmri.jmrit.operations.setup.OperationsSetupXml;
022import jmri.jmrit.operations.setup.Setup;
023import jmri.jmrit.operations.trains.excel.TrainCustomManifest;
024import jmri.jmrit.operations.trains.excel.TrainCustomSwitchList;
025import jmri.jmrit.operations.trains.gui.TrainsTableFrame;
026import jmri.jmrit.operations.trains.schedules.TrainScheduleManager;
027import jmri.jmrit.operations.trains.trainbuilder.TrainCommon;
028import jmri.script.JmriScriptEngineManager;
029import jmri.util.ColorUtil;
030import jmri.util.swing.JmriJOptionPane;
031
032/**
033 * Manages trains.
034 *
035 * @author Bob Jacobsen Copyright (C) 2003
036 * @author Daniel Boudreau Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013,
037 *         2014
038 */
039public class TrainManager extends PropertyChangeSupport implements InstanceManagerAutoDefault, InstanceManagerAutoInitialize, PropertyChangeListener {
040
041    static final String NONE = "";
042
043    // Train frame attributes
044    private String _trainAction = TrainsTableFrame.MOVE; // Trains frame table button action
045    private boolean _buildMessages = true; // when true, show build messages
046    private boolean _buildReport = false; // when true, print/preview build reports
047    private boolean _printPreview = false; // when true, preview train manifest
048    private boolean _openFile = false; // when true, open CSV file manifest
049    private boolean _runFile = false; // when true, run CSV file manifest
050
051    // Conductor attributes
052    private boolean _showLocationHyphenName = false;
053
054    // Trains window row colors
055    private boolean _rowColorManual = true; // when true train colors are manually assigned
056    private String _rowColorBuilt = NONE; // row color when train is built
057    private String _rowColorBuildFailed = NONE; // row color when train build failed
058    private String _rowColorTrainEnRoute = NONE; // row color when train is en route
059    private String _rowColorTerminated = NONE; // row color when train is terminated
060    private String _rowColorReset = NONE; // row color when train is reset
061
062    // Scripts
063    protected List<String> _startUpScripts = new ArrayList<>(); // list of script pathnames to run at start up
064    protected List<String> _shutDownScripts = new ArrayList<>(); // list of script pathnames to run at shut down
065
066    // property changes
067    public static final String LISTLENGTH_CHANGED_PROPERTY = "TrainsListLength"; // NOI18N
068    public static final String PRINTPREVIEW_CHANGED_PROPERTY = "TrainsPrintPreview"; // NOI18N
069    public static final String OPEN_FILE_CHANGED_PROPERTY = "TrainsOpenFile"; // NOI18N
070    public static final String RUN_FILE_CHANGED_PROPERTY = "TrainsRunFile"; // NOI18N
071    public static final String TRAIN_ACTION_CHANGED_PROPERTY = "TrainsAction"; // NOI18N
072    public static final String ROW_COLOR_NAME_CHANGED_PROPERTY = "TrainsRowColorChange"; // NOI18N
073    public static final String TRAINS_BUILT_CHANGED_PROPERTY = "TrainsBuiltChange"; // NOI18N
074    public static final String TRAINS_SHOW_FULL_NAME_PROPERTY = "TrainsShowFullName"; // NOI18N
075    public static final String TRAINS_SAVED_PROPERTY = "TrainsSaved"; // NOI18N
076
077    public TrainManager() {
078    }
079
080    private int _id = 0; // train ids
081
082    /**
083     * Get the number of items in the roster
084     *
085     * @return Number of trains in the roster
086     */
087    public int getNumEntries() {
088        return _trainHashTable.size();
089    }
090
091    /**
092     * @return true if build messages are enabled
093     */
094    public boolean isBuildMessagesEnabled() {
095        return _buildMessages;
096    }
097
098    public void setBuildMessagesEnabled(boolean enable) {
099        boolean old = _buildMessages;
100        _buildMessages = enable;
101        setDirtyAndFirePropertyChange("BuildMessagesEnabled", enable, old); // NOI18N
102    }
103
104    /**
105     * @return true if build reports are enabled
106     */
107    public boolean isBuildReportEnabled() {
108        return _buildReport;
109    }
110
111    public void setBuildReportEnabled(boolean enable) {
112        boolean old = _buildReport;
113        _buildReport = enable;
114        setDirtyAndFirePropertyChange("BuildReportEnabled", enable, old); // NOI18N
115    }
116
117    /**
118     * @return true if open file is enabled
119     */
120    public boolean isOpenFileEnabled() {
121        return _openFile;
122    }
123
124    public void setOpenFileEnabled(boolean enable) {
125        boolean old = _openFile;
126        _openFile = enable;
127        setDirtyAndFirePropertyChange(OPEN_FILE_CHANGED_PROPERTY, old ? "true" : "false", enable ? "true" // NOI18N
128                : "false"); // NOI18N
129    }
130
131    /**
132     * @return true if open file is enabled
133     */
134    public boolean isRunFileEnabled() {
135        return _runFile;
136    }
137
138    public void setRunFileEnabled(boolean enable) {
139        boolean old = _runFile;
140        _runFile = enable;
141        setDirtyAndFirePropertyChange(RUN_FILE_CHANGED_PROPERTY, old ? "true" : "false", enable ? "true" // NOI18N
142                : "false"); // NOI18N
143    }
144
145    /**
146     * @return true if print preview is enabled
147     */
148    public boolean isPrintPreviewEnabled() {
149        return _printPreview;
150    }
151
152    public void setPrintPreviewEnabled(boolean enable) {
153        boolean old = _printPreview;
154        _printPreview = enable;
155        setDirtyAndFirePropertyChange(PRINTPREVIEW_CHANGED_PROPERTY, old ? "Preview" : "Print", // NOI18N
156                enable ? "Preview" : "Print"); // NOI18N
157    }
158
159    /**
160     * When true show entire location name including hyphen
161     * 
162     * @return true when showing entire location name
163     */
164    public boolean isShowLocationHyphenNameEnabled() {
165        return _showLocationHyphenName;
166    }
167
168    public void setShowLocationHyphenNameEnabled(boolean enable) {
169        boolean old = _showLocationHyphenName;
170        _showLocationHyphenName = enable;
171        setDirtyAndFirePropertyChange(TRAINS_SHOW_FULL_NAME_PROPERTY, old, enable);
172    }
173
174    public String getTrainsFrameTrainAction() {
175        return _trainAction;
176    }
177
178    public void setTrainsFrameTrainAction(String action) {
179        String old = _trainAction;
180        _trainAction = action;
181        if (!old.equals(action)) {
182            setDirtyAndFirePropertyChange(TRAIN_ACTION_CHANGED_PROPERTY, old, action);
183        }
184    }
185
186    /**
187     * Add a script to run after trains have been loaded
188     *
189     * @param pathname The script's pathname
190     */
191    public void addStartUpScript(String pathname) {
192        _startUpScripts.add(pathname);
193        setDirtyAndFirePropertyChange("addStartUpScript", pathname, null); // NOI18N
194    }
195
196    public void deleteStartUpScript(String pathname) {
197        _startUpScripts.remove(pathname);
198        setDirtyAndFirePropertyChange("deleteStartUpScript", null, pathname); // NOI18N
199    }
200
201    /**
202     * Gets a list of pathnames to run after trains have been loaded
203     *
204     * @return A list of pathnames to run after trains have been loaded
205     */
206    public List<String> getStartUpScripts() {
207        return _startUpScripts;
208    }
209
210    public void runStartUpScripts() {
211        // use thread to prevent object (Train) thread lock
212        Thread scripts = jmri.util.ThreadingUtil.newThread(new Runnable() {
213            @Override
214            public void run() {
215                for (String scriptPathName : getStartUpScripts()) {
216                    try {
217                        JmriScriptEngineManager.getDefault()
218                                .runScript(new File(jmri.util.FileUtil.getExternalFilename(scriptPathName)));
219                    } catch (Exception e) {
220                        log.error("Problem with script: {}", scriptPathName);
221                    }
222                }
223            }
224        });
225        scripts.setName("Startup Scripts"); // NOI18N
226        scripts.start();
227    }
228
229    /**
230     * Add a script to run at shutdown
231     *
232     * @param pathname The script's pathname
233     */
234    public void addShutDownScript(String pathname) {
235        _shutDownScripts.add(pathname);
236        setDirtyAndFirePropertyChange("addShutDownScript", pathname, null); // NOI18N
237    }
238
239    public void deleteShutDownScript(String pathname) {
240        _shutDownScripts.remove(pathname);
241        setDirtyAndFirePropertyChange("deleteShutDownScript", null, pathname); // NOI18N
242    }
243
244    /**
245     * Gets a list of pathnames to run at shutdown
246     *
247     * @return A list of pathnames to run at shutdown
248     */
249    public List<String> getShutDownScripts() {
250        return _shutDownScripts;
251    }
252
253    public void runShutDownScripts() {
254        for (String scriptPathName : getShutDownScripts()) {
255            try {
256                JmriScriptEngineManager.getDefault()
257                        .runScript(new File(jmri.util.FileUtil.getExternalFilename(scriptPathName)));
258            } catch (Exception e) {
259                log.error("Problem with script: {}", scriptPathName);
260            }
261        }
262    }
263
264    /**
265     * Used to determine if a train has any restrictions with regard to car
266     * built dates.
267     * 
268     * @return true if there's a restriction
269     */
270    public boolean isBuiltRestricted() {
271        for (Train train : getList()) {
272            if (!train.getBuiltStartYear().equals(Train.NONE) || !train.getBuiltEndYear().equals(Train.NONE)) {
273                return true;
274            }
275        }
276        return false;
277    }
278
279    /**
280     * Used to determine if a train has any restrictions with regard to car
281     * loads.
282     * 
283     * @return true if there's a restriction
284     */
285    public boolean isLoadRestricted() {
286        for (Train train : getList()) {
287            if (!train.getLoadOption().equals(Train.ALL_LOADS)) {
288                return true;
289            }
290        }
291        return false;
292    }
293
294    /**
295     * Used to determine if a train has any restrictions with regard to car
296     * roads.
297     * 
298     * @return true if there's a restriction
299     */
300    public boolean isCarRoadRestricted() {
301        for (Train train : getList()) {
302            if (!train.getCarRoadOption().equals(Train.ALL_ROADS)) {
303                return true;
304            }
305        }
306        return false;
307    }
308
309    /**
310     * Used to determine if a train has any restrictions with regard to caboose
311     * roads.
312     * 
313     * @return true if there's a restriction
314     */
315    public boolean isCabooseRoadRestricted() {
316        for (Train train : getList()) {
317            if (!train.getCabooseRoadOption().equals(Train.ALL_ROADS)) {
318                return true;
319            }
320        }
321        return false;
322    }
323
324    /**
325     * Used to determine if a train has any restrictions with regard to
326     * Locomotive roads.
327     * 
328     * @return true if there's a restriction
329     */
330    public boolean isLocoRoadRestricted() {
331        for (Train train : getList()) {
332            if (!train.getLocoRoadOption().equals(Train.ALL_ROADS)) {
333                return true;
334            }
335        }
336        return false;
337    }
338
339    /**
340     * Used to determine if a train has any restrictions with regard to car
341     * owners.
342     * 
343     * @return true if there's a restriction
344     */
345    public boolean isOwnerRestricted() {
346        for (Train train : getList()) {
347            if (!train.getOwnerOption().equals(Train.ALL_OWNERS)) {
348                return true;
349            }
350        }
351        return false;
352    }
353
354    public void dispose() {
355        _trainHashTable.clear();
356        _id = 0;
357    }
358
359    // stores known Train instances by id
360    private final Hashtable<String, Train> _trainHashTable = new Hashtable<>();
361
362    /**
363     * @param name The train's name.
364     * @return requested Train object or null if none exists
365     */
366    public Train getTrainByName(String name) {
367        if (!InstanceManager.getDefault(TrainManagerXml.class).isTrainFileLoaded()) {
368            log.error("TrainManager getTrainByName called before trains completely loaded!");
369        }
370        Train train;
371        Enumeration<Train> en = _trainHashTable.elements();
372        while (en.hasMoreElements()) {
373            train = en.nextElement();
374            // windows file names are case independent
375            if (train.getName().toLowerCase().equals(name.toLowerCase())) {
376                return train;
377            }
378        }
379        log.debug("Train ({}) doesn't exist", name);
380        return null;
381    }
382
383    public Train getTrainById(String id) {
384        if (!InstanceManager.getDefault(TrainManagerXml.class).isTrainFileLoaded()) {
385            log.error("TrainManager getTrainById called before trains completely loaded!");
386        }
387        return _trainHashTable.get(id);
388    }
389
390    /**
391     * Finds an existing train or creates a new train if needed. Requires
392     * train's name and creates a unique id for a new train
393     *
394     * @param name The train's name.
395     * @return new train or existing train
396     */
397    public Train newTrain(String name) {
398        Train train = getTrainByName(name);
399        if (train == null) {
400            _id++;
401            train = new Train(Integer.toString(_id), name);
402            int oldSize = getNumEntries();
403            _trainHashTable.put(train.getId(), train);
404            setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize,
405                    getNumEntries());
406        }
407        return train;
408    }
409
410    /**
411     * Remember a NamedBean Object created outside the manager.
412     *
413     * @param train The Train to be added.
414     */
415    public void register(Train train) {
416        int oldSize = getNumEntries();
417        _trainHashTable.put(train.getId(), train);
418        // find last id created
419        int id = Integer.parseInt(train.getId());
420        if (id > _id) {
421            _id = id;
422        }
423        setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, getNumEntries());
424    }
425
426    /**
427     * Forget a NamedBean Object created outside the manager.
428     *
429     * @param train The Train to delete.
430     */
431    public void deregister(Train train) {
432        if (train == null) {
433            return;
434        }
435        train.dispose();
436        int oldSize = getNumEntries();
437        _trainHashTable.remove(train.getId());
438        setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, getNumEntries());
439    }
440
441    public void replaceLoad(String type, String oldLoadName, String newLoadName) {
442        for (Train train : getList()) {
443            for (String loadName : train.getLoadNames()) {
444                if (loadName.equals(oldLoadName)) {
445                    train.deleteLoadName(oldLoadName);
446                    if (newLoadName != null) {
447                        train.addLoadName(newLoadName);
448                    }
449                }
450                // adjust combination car type and load name
451                String[] splitLoad = loadName.split(CarLoad.SPLIT_CHAR);
452                if (splitLoad.length > 1) {
453                    if (splitLoad[0].equals(type) && splitLoad[1].equals(oldLoadName)) {
454                        train.deleteLoadName(loadName);
455                        if (newLoadName != null) {
456                            train.addLoadName(type + CarLoad.SPLIT_CHAR + newLoadName);
457                        }
458                    }
459                }
460            }
461        }
462    }
463
464    /**
465     * @return true if there's a built train
466     */
467    public boolean isAnyTrainBuilt() {
468        for (Train train : getList()) {
469            if (train.isBuilt()) {
470                return true;
471            }
472        }
473        return false;
474    }
475
476    /**
477     * @return true if there's a train being built
478     */
479    public boolean isAnyTrainBuilding() {
480        if (getTrainBuilding() != null) {
481            return true;
482        }
483        return false;
484    }
485
486    public Train getTrainBuilding() {
487        for (Train train : getList()) {
488            if (train.getStatusCode() == Train.CODE_BUILDING) {
489                log.debug("Train {} is currently building", train.getName());
490                return train;
491            }
492        }
493        return null;
494    }
495
496    /**
497     * Gets the last train built by departure time.
498     * 
499     * @return last train built by departure time, or null if no trains are
500     *         built.
501     */
502    public Train getLastTrainBuiltByDepartureTime() {
503        for (Train train : getTrainsByReverseTimeList()) {
504            if (train.isBuilt() && train.getDepartTimeMinutes() > 0) {
505                return train;
506            }
507        }
508        return null;
509    }
510
511    /**
512     * @param car         The car looking for a train.
513     * @param buildReport The optional build report for logging.
514     * @return Train that can service car from its current location to the its
515     *         destination.
516     */
517    public Train getTrainForCar(Car car, PrintWriter buildReport) {
518        return getTrainForCar(car, new ArrayList<>(), buildReport, false);
519    }
520
521    /**
522     * @param car             The car looking for a train.
523     * @param excludeTrains   The trains not to try.
524     * @param buildReport     The optional build report for logging.
525     * @param isExcludeRoutes When true eliminate trains that have the same
526     *                        route in the exclude trains list.
527     * @return Train that can service car from its current location to the its
528     *         destination.
529     */
530    public Train getTrainForCar(Car car, List<Train> excludeTrains, PrintWriter buildReport, boolean isExcludeRoutes) {
531        addLine(buildReport, TrainCommon.BLANK_LINE);
532        addLine(buildReport, Bundle.getMessage("trainFindForCar", car.toString(), car.getLocationName(),
533                car.getTrackName(), car.getDestinationName(), car.getDestinationTrackName()));
534
535        main: for (Train train : getTrainsByNameList()) {
536            if (excludeTrains.contains(train)) {
537                continue;
538            }
539            if (Setup.isOnlyActiveTrainsEnabled() && !train.isBuildEnabled()) {
540                continue;
541            }
542            if (isExcludeRoutes) {
543                for (Train t : excludeTrains) {
544                    if (t != null && train.getRoute() == t.getRoute()) {
545                        addLine(buildReport, Bundle.getMessage("trainHasSameRoute", train, t));
546                        continue main;
547                    }
548                }
549            }
550            // does this train service this car?
551            if (train.isServiceable(buildReport, car)) {
552                log.debug("Found train ({}) for car ({}) location ({}, {}) destination ({}, {})", train.getName(),
553                        car.toString(), car.getLocationName(), car.getTrackName(), car.getDestinationName(),
554                        car.getDestinationTrackName()); // NOI18N
555                return train;
556            }
557        }
558        return null;
559    }
560
561    public List<Train> getExcludeTrainListForCar(Car car, PrintWriter buildReport) {
562        List<Train> excludeTrains = new ArrayList<>();
563        for (Train train : getTrainsByNameList()) {
564            if (Setup.isOnlyActiveTrainsEnabled() && !train.isBuildEnabled()) {
565                addLine(buildReport, Bundle.getMessage("trainRoutingDisabled", train.getName()));
566                excludeTrains.add(train);
567            } else if (!train.isTrainAbleToService(buildReport, car)) {
568                excludeTrains.add(train);
569            }
570        }
571        return excludeTrains;
572    }
573
574    protected static final String SEVEN = Setup.BUILD_REPORT_VERY_DETAILED;
575
576    private void addLine(PrintWriter buildReport, String string) {
577        if (Setup.getRouterBuildReportLevel().equals(SEVEN)) {
578            TrainCommon.addLine(buildReport, SEVEN, string);
579        }
580    }
581
582    /**
583     * Sort by train name
584     *
585     * @return list of trains ordered by name
586     */
587    public List<Train> getTrainsByNameList() {
588        return getTrainsByList(getList(), GET_TRAIN_NAME);
589    }
590
591    /**
592     * Sort by train departure time
593     *
594     * @return list of trains ordered by departure time
595     */
596    public List<Train> getTrainsByTimeList() {
597        return getTrainsByIntList(getTrainsByNameList(), GET_TRAIN_TIME);
598    }
599
600    public List<Train> getTrainsByReverseTimeList() {
601        List<Train> out = getTrainsByTimeList();
602        Collections.reverse(out);
603        return out;
604    }
605
606    /**
607     * Sort by train departure location name
608     *
609     * @return list of trains ordered by departure name
610     */
611    public List<Train> getTrainsByDepartureList() {
612        return getTrainsByList(getTrainsByTimeList(), GET_TRAIN_DEPARTES_NAME);
613    }
614
615    /**
616     * Sort by train termination location name
617     *
618     * @return list of trains ordered by termination name
619     */
620    public List<Train> getTrainsByTerminatesList() {
621        return getTrainsByList(getTrainsByTimeList(), GET_TRAIN_TERMINATES_NAME);
622    }
623
624    /**
625     * Sort by train route name
626     *
627     * @return list of trains ordered by route name
628     */
629    public List<Train> getTrainsByRouteList() {
630        return getTrainsByList(getTrainsByTimeList(), GET_TRAIN_ROUTE_NAME);
631    }
632
633    /**
634     * Sort by train status
635     *
636     * @return list of trains ordered by status
637     */
638    public List<Train> getTrainsByStatusList() {
639        return getTrainsByList(getTrainsByTimeList(), GET_TRAIN_STATUS);
640    }
641
642    /**
643     * Sort by train description
644     *
645     * @return list of trains ordered by train description
646     */
647    public List<Train> getTrainsByDescriptionList() {
648        return getTrainsByList(getTrainsByTimeList(), GET_TRAIN_DESCRIPTION);
649    }
650
651    /**
652     * Sort by train id
653     *
654     * @return list of trains ordered by id
655     */
656    public List<Train> getTrainsByIdList() {
657        return getTrainsByIntList(getList(), GET_TRAIN_ID);
658    }
659
660    private List<Train> getTrainsByList(List<Train> sortList, int attribute) {
661        List<Train> out = new ArrayList<>();
662        for (Train train : sortList) {
663            String trainAttribute = (String) getTrainAttribute(train, attribute);
664            for (int j = 0; j < out.size(); j++) {
665                if (trainAttribute.compareToIgnoreCase((String) getTrainAttribute(out.get(j), attribute)) < 0) {
666                    out.add(j, train);
667                    break;
668                }
669            }
670            if (!out.contains(train)) {
671                out.add(train);
672            }
673        }
674        return out;
675    }
676
677    private List<Train> getTrainsByIntList(List<Train> sortList, int attribute) {
678        List<Train> out = new ArrayList<>();
679        for (Train train : sortList) {
680            int trainAttribute = (Integer) getTrainAttribute(train, attribute);
681            for (int j = 0; j < out.size(); j++) {
682                if (trainAttribute < (Integer) getTrainAttribute(out.get(j), attribute)) {
683                    out.add(j, train);
684                    break;
685                }
686            }
687            if (!out.contains(train)) {
688                out.add(train);
689            }
690        }
691        return out;
692    }
693
694    // the various sort options for trains
695    private static final int GET_TRAIN_DEPARTES_NAME = 0;
696    private static final int GET_TRAIN_NAME = 1;
697    private static final int GET_TRAIN_ROUTE_NAME = 2;
698    private static final int GET_TRAIN_TERMINATES_NAME = 3;
699    private static final int GET_TRAIN_TIME = 4;
700    private static final int GET_TRAIN_STATUS = 5;
701    private static final int GET_TRAIN_ID = 6;
702    private static final int GET_TRAIN_DESCRIPTION = 7;
703
704    private Object getTrainAttribute(Train train, int attribute) {
705        switch (attribute) {
706            case GET_TRAIN_DEPARTES_NAME:
707                return train.getTrainDepartsName();
708            case GET_TRAIN_NAME:
709                return train.getName();
710            case GET_TRAIN_ROUTE_NAME:
711                return train.getTrainRouteName();
712            case GET_TRAIN_TERMINATES_NAME:
713                return train.getTrainTerminatesName();
714            case GET_TRAIN_TIME:
715                return train.getDepartTimeMinutes();
716            case GET_TRAIN_STATUS:
717                return train.getStatus();
718            case GET_TRAIN_ID:
719                return Integer.parseInt(train.getId());
720            case GET_TRAIN_DESCRIPTION:
721                return train.getDescription();
722            default:
723                return "unknown"; // NOI18N
724        }
725    }
726
727    public List<Train> getList() {
728        if (!InstanceManager.getDefault(TrainManagerXml.class).isTrainFileLoaded()) {
729            log.error("TrainManager getList called before trains completely loaded!");
730        }
731        List<Train> out = new ArrayList<>();
732        Enumeration<Train> en = _trainHashTable.elements();
733        while (en.hasMoreElements()) {
734            out.add(en.nextElement());
735        }
736        return out;
737    }
738
739    public JComboBox<Train> getTrainComboBox() {
740        JComboBox<Train> box = new JComboBox<>();
741        updateTrainComboBox(box);
742        OperationsPanel.padComboBox(box);
743        return box;
744    }
745
746    public void updateTrainComboBox(JComboBox<Train> box) {
747        box.removeAllItems();
748        box.addItem(null);
749        for (Train train : getTrainsByNameList()) {
750            box.addItem(train);
751        }
752    }
753
754    /**
755     * Update combo box with trains that will service this car
756     *
757     * @param box the combo box to update
758     * @param car the car to be serviced
759     */
760    public void updateTrainComboBox(JComboBox<Train> box, Car car) {
761        box.removeAllItems();
762        box.addItem(null);
763        for (Train train : getTrainsByNameList()) {
764            if (train.isServiceable(car)) {
765                box.addItem(train);
766            }
767        }
768    }
769
770    public boolean isRowColorManual() {
771        return _rowColorManual;
772    }
773
774    public void setRowColorsManual(boolean manual) {
775        boolean old = _rowColorManual;
776        _rowColorManual = manual;
777        setDirtyAndFirePropertyChange(ROW_COLOR_NAME_CHANGED_PROPERTY, old, manual);
778    }
779
780    public String getRowColorNameForBuilt() {
781        return _rowColorBuilt;
782    }
783
784    public void setRowColorNameForBuilt(String colorName) {
785        String old = _rowColorBuilt;
786        _rowColorBuilt = colorName;
787        setDirtyAndFirePropertyChange(ROW_COLOR_NAME_CHANGED_PROPERTY, old, colorName);
788    }
789
790    public String getRowColorNameForBuildFailed() {
791        return _rowColorBuildFailed;
792    }
793
794    public void setRowColorNameForBuildFailed(String colorName) {
795        String old = _rowColorBuildFailed;
796        _rowColorBuildFailed = colorName;
797        setDirtyAndFirePropertyChange(ROW_COLOR_NAME_CHANGED_PROPERTY, old, colorName);
798    }
799
800    public String getRowColorNameForTrainEnRoute() {
801        return _rowColorTrainEnRoute;
802    }
803
804    public void setRowColorNameForTrainEnRoute(String colorName) {
805        String old = _rowColorTrainEnRoute;
806        _rowColorTrainEnRoute = colorName;
807        setDirtyAndFirePropertyChange(ROW_COLOR_NAME_CHANGED_PROPERTY, old, colorName);
808    }
809
810    public String getRowColorNameForTerminated() {
811        return _rowColorTerminated;
812    }
813
814    public void setRowColorNameForTerminated(String colorName) {
815        String old = _rowColorTerminated;
816        _rowColorTerminated = colorName;
817        setDirtyAndFirePropertyChange(ROW_COLOR_NAME_CHANGED_PROPERTY, old, colorName);
818    }
819
820    public String getRowColorNameForReset() {
821        return _rowColorReset;
822    }
823
824    public void setRowColorNameForReset(String colorName) {
825        String old = _rowColorReset;
826        _rowColorReset = colorName;
827        setDirtyAndFirePropertyChange(ROW_COLOR_NAME_CHANGED_PROPERTY, old, colorName);
828    }
829
830    /**
831     * JColorChooser is not a replacement for getRowColorComboBox as it doesn't
832     * support no color as a selection.
833     * 
834     * @return the available colors used highlighting table rows including no
835     *         color.
836     */
837    public JComboBox<String> getRowColorComboBox() {
838        JComboBox<String> box = new JComboBox<>();
839        box.addItem(NONE);
840        box.addItem(ColorUtil.ColorBlack);
841        box.addItem(ColorUtil.ColorRed);
842        box.addItem(ColorUtil.ColorPink);
843        box.addItem(ColorUtil.ColorOrange);
844        box.addItem(ColorUtil.ColorYellow);
845        box.addItem(ColorUtil.ColorGreen);
846        box.addItem(ColorUtil.ColorMagenta);
847        box.addItem(ColorUtil.ColorCyan);
848        box.addItem(ColorUtil.ColorBlue);
849        box.addItem(ColorUtil.ColorGray);
850        return box;
851    }
852
853    /**
854     * Makes a copy of an existing train.
855     *
856     * @param train     the train to copy
857     * @param trainName the name of the new train
858     * @return a copy of train
859     */
860    public Train copyTrain(Train train, String trainName) {
861        Train newTrain = newTrain(trainName);
862        // route, departure time and types
863        newTrain.setRoute(train.getRoute());
864        newTrain.setTrainSkipsLocations(train.getTrainSkipsLocations());
865        newTrain.setDepartureTime(train.getDepartureTimeDay(), train.getDepartureTimeHour(),
866                train.getDepartureTimeMinute());
867        newTrain._typeList.clear(); // remove all types loaded by create
868        newTrain.setTypeNames(train.getTypeNames());
869        // set road, load, and owner options
870        newTrain.setCarRoadOption(train.getCarRoadOption());
871        newTrain.setCarRoadNames(train.getCarRoadNames());
872        newTrain.setCabooseRoadNames(train.getCabooseRoadNames());
873        newTrain.setLocoRoadOption(train.getLocoRoadOption());
874        newTrain.setLocoRoadNames(train.getLocoRoadNames());
875        newTrain.setLoadOption(train.getLoadOption());
876        newTrain.setLoadNames(train.getLoadNames());
877        newTrain.setOwnerOption(train.getOwnerOption());
878        newTrain.setOwnerNames(train.getOwnerNames());
879        // build dates
880        newTrain.setBuiltStartYear(train.getBuiltStartYear());
881        newTrain.setBuiltEndYear(train.getBuiltEndYear());
882        // locos start of route
883        newTrain.setNumberEngines(train.getNumberEngines());
884        newTrain.setEngineModel(train.getEngineModel());
885        newTrain.setEngineRoad(train.getEngineRoad());
886        newTrain.setRequirements(train.getRequirements());
887        newTrain.setCabooseRoad(train.getCabooseRoad());
888        // second leg
889        newTrain.setSecondLegNumberEngines(train.getSecondLegNumberEngines());
890        newTrain.setSecondLegEngineModel(train.getSecondLegEngineModel());
891        newTrain.setSecondLegEngineRoad(train.getSecondLegEngineRoad());
892        newTrain.setSecondLegOptions(train.getSecondLegOptions());
893        newTrain.setSecondLegCabooseRoad(train.getSecondLegCabooseRoad());
894        newTrain.setSecondLegStartRouteLocation(train.getSecondLegStartRouteLocation());
895        newTrain.setSecondLegEndRouteLocation(train.getSecondLegEndRouteLocation());
896        // third leg
897        newTrain.setThirdLegNumberEngines(train.getThirdLegNumberEngines());
898        newTrain.setThirdLegEngineModel(train.getThirdLegEngineModel());
899        newTrain.setThirdLegEngineRoad(train.getThirdLegEngineRoad());
900        newTrain.setThirdLegOptions(train.getThirdLegOptions());
901        newTrain.setThirdLegCabooseRoad(train.getThirdLegCabooseRoad());
902        newTrain.setThirdLegStartRouteLocation(train.getThirdLegStartRouteLocation());
903        newTrain.setThirdLegEndRouteLocation(train.getThirdLegEndRouteLocation());
904        // scripts
905        for (String scriptName : train.getBuildScripts()) {
906            newTrain.addBuildScript(scriptName);
907        }
908        for (String scriptName : train.getMoveScripts()) {
909            newTrain.addMoveScript(scriptName);
910        }
911        for (String scriptName : train.getTerminationScripts()) {
912            newTrain.addTerminationScript(scriptName);
913        }
914        // manifest options
915        newTrain.setRailroadName(train.getRailroadName());
916        newTrain.setManifestLogoPathName(train.getManifestLogoPathName());
917        newTrain.setShowArrivalAndDepartureTimes(train.isShowArrivalAndDepartureTimesEnabled());
918        // build options
919        newTrain.setAllowLocalMovesEnabled(train.isAllowLocalMovesEnabled());
920        newTrain.setAllowReturnToStagingEnabled(train.isAllowReturnToStagingEnabled());
921        newTrain.setAllowThroughCarsEnabled(train.isAllowThroughCarsEnabled());
922        newTrain.setBuildConsistEnabled(train.isBuildConsistEnabled());
923        newTrain.setSendCarsWithCustomLoadsToStagingEnabled(train.isSendCarsWithCustomLoadsToStagingEnabled());
924        newTrain.setBuildTrainNormalEnabled(train.isBuildTrainNormalEnabled());
925        newTrain.setSendCarsToTerminalEnabled(train.isSendCarsToTerminalEnabled());
926        newTrain.setServiceAllCarsWithFinalDestinationsEnabled(train.isServiceAllCarsWithFinalDestinationsEnabled());
927        // comment
928        newTrain.setComment(train.getCommentWithColor());
929        // description
930        newTrain.setDescription(train.getRawDescription());
931        return newTrain;
932    }
933
934    /**
935     * Provides a list of trains ordered by arrival time to a location
936     *
937     * @param location The location
938     * @return A list of trains ordered by arrival time.
939     */
940    public List<Train> getTrainsArrivingThisLocationList(Location location) {
941        // get a list of trains
942        List<Train> out = new ArrayList<>();
943        List<Integer> arrivalTimes = new ArrayList<>();
944        for (Train train : getTrainsByTimeList()) {
945            if (!train.isBuilt()) {
946                continue; // train wasn't built so skip
947            }
948            Route route = train.getRoute();
949            if (route == null) {
950                continue; // no route for this train
951            }
952            for (RouteLocation rl : route.getLocationsBySequenceList()) {
953                if (rl.getSplitName().equals(location.getSplitName())) {
954                    int expectedArrivalTime = train.getExpectedTravelTimeInMinutes(rl);
955                    // is already serviced then "-1"
956                    if (expectedArrivalTime == -1) {
957                        out.add(0, train); // place all trains that have already been serviced at the start
958                        arrivalTimes.add(0, expectedArrivalTime);
959                    } // if the train is in route, then expected arrival time is in minutes
960                    else if (train.isTrainEnRoute()) {
961                        for (int j = 0; j < out.size(); j++) {
962                            Train t = out.get(j);
963                            int time = arrivalTimes.get(j);
964                            if (t.isTrainEnRoute() && expectedArrivalTime < time) {
965                                out.add(j, train);
966                                arrivalTimes.add(j, expectedArrivalTime);
967                                break;
968                            }
969                            if (!t.isTrainEnRoute()) {
970                                out.add(j, train);
971                                arrivalTimes.add(j, expectedArrivalTime);
972                                break;
973                            }
974                        }
975                        // Train has not departed
976                    } else {
977                        for (int j = 0; j < out.size(); j++) {
978                            Train t = out.get(j);
979                            int time = arrivalTimes.get(j);
980                            if (!t.isTrainEnRoute() && expectedArrivalTime < time) {
981                                out.add(j, train);
982                                arrivalTimes.add(j, expectedArrivalTime);
983                                break;
984                            }
985                        }
986                    }
987                    if (!out.contains(train)) {
988                        out.add(train);
989                        arrivalTimes.add(expectedArrivalTime);
990                    }
991                    break; // done
992                }
993            }
994        }
995        return out;
996    }
997
998    /**
999     * Loads train icons if needed
1000     */
1001    public void loadTrainIcons() {
1002        for (Train train : getTrainsByIdList()) {
1003            train.loadTrainIcon();
1004        }
1005    }
1006
1007    /**
1008     * Sets the switch list status for all built trains. Used for switch lists
1009     * in consolidated mode.
1010     *
1011     * @param status Train.PRINTED, Train.UNKNOWN
1012     */
1013    public void setTrainsSwitchListStatus(String status) {
1014        for (Train train : getTrainsByTimeList()) {
1015            if (!train.isBuilt()) {
1016                continue; // train isn't built so skip
1017            }
1018            train.setSwitchListStatus(status);
1019        }
1020    }
1021
1022    /**
1023     * Sets all built trains manifests to modified. This causes the train's
1024     * manifest to be recreated.
1025     */
1026    public void setTrainsModified() {
1027        for (Train train : getTrainsByTimeList()) {
1028            if (!train.isBuilt() || train.isTrainEnRoute()) {
1029                continue; // train wasn't built or in route, so skip
1030            }
1031            train.setModified(true);
1032        }
1033    }
1034
1035    public void buildSelectedTrains(List<Train> trains) {
1036        // use a thread to allow table updates during build
1037        Thread build = jmri.util.ThreadingUtil.newThread(new Runnable() {
1038            @Override
1039            public void run() {
1040                for (Train train : trains) {
1041                    if (train.buildIfSelected()) {
1042                        continue;
1043                    }
1044                    if (isBuildMessagesEnabled() && train.isBuildEnabled() && !train.isBuilt()) {
1045                        if (JmriJOptionPane.showConfirmDialog(null, Bundle.getMessage("ContinueBuilding"),
1046                                Bundle.getMessage("buildFailedMsg",
1047                                        train.getName()),
1048                                JmriJOptionPane.YES_NO_OPTION) == JmriJOptionPane.NO_OPTION) {
1049                            break;
1050                        }
1051                    }
1052                }
1053                setDirtyAndFirePropertyChange(TRAINS_BUILT_CHANGED_PROPERTY, false, true);
1054            }
1055        });
1056        build.setName("Build Trains"); // NOI18N
1057        build.start();
1058    }
1059
1060    /**
1061     * Checks to see if using on time build mode and the train to be built has a
1062     * departure time equal to or after all of the other built trains.
1063     * 
1064     * @param train the train wanting to be built
1065     * @return true if okay to build train
1066     */
1067    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value="SLF4J_FORMAT_SHOULD_BE_CONST",
1068            justification="I18N of warning message")
1069    public boolean checkBuildOrder(Train train) {
1070        if (Setup.isBuildOnTime()) {
1071            Train t = getLastTrainBuiltByDepartureTime();
1072            if (t != null && train.getDepartTimeMinutes() < t.getDepartTimeMinutes()) {
1073                if (isBuildMessagesEnabled()) {
1074                    JmriJOptionPane.showMessageDialog(null,
1075                            Bundle.getMessage("TrainBuildTimeError", train.getName(), train.getDepartureTime(),
1076                                    t.getName(), t.getDepartureTime()),
1077                            Bundle.getMessage("TrainBuildTime"), JmriJOptionPane.ERROR_MESSAGE);
1078                } else {
1079                    log.error(Bundle.getMessage("TrainBuildTimeError", train.getName(), train.getDepartureTime(),
1080                            t.getName(), t.getDepartureTime()));
1081                }
1082                return false;
1083            }
1084        }
1085        return true;
1086    }
1087
1088    public boolean printSelectedTrains(List<Train> trains) {
1089        boolean status = true;
1090        for (Train train : trains) {
1091            if (train.isBuildEnabled()) {
1092                if (train.printManifestIfBuilt()) {
1093                    continue;
1094                }
1095                status = false; // failed to print all selected trains
1096                if (isBuildMessagesEnabled()) {
1097                    int response = JmriJOptionPane.showConfirmDialog(null,
1098                            Bundle.getMessage("NeedToBuildBeforePrinting",
1099                                    train.getName(),
1100                                    (isPrintPreviewEnabled() ? Bundle.getMessage("preview")
1101                                            : Bundle.getMessage("print"))),
1102                            Bundle.getMessage("CanNotPrintManifest",
1103                                    isPrintPreviewEnabled() ? Bundle.getMessage("preview")
1104                                            : Bundle.getMessage("print")),
1105                            JmriJOptionPane.OK_CANCEL_OPTION);
1106                    if (response != JmriJOptionPane.OK_OPTION) {
1107                        break;
1108                    }
1109                }
1110            }
1111        }
1112        return status;
1113    }
1114
1115    public boolean terminateSelectedTrains(List<Train> trains) {
1116        if (!confirmTerminateTrains(trains)) {
1117            return false;
1118        }
1119        boolean status = true;
1120        for (Train train : trains) {
1121            if (train.isBuildEnabled() && train.isBuilt()) {
1122                if (train.isPrinted()) {
1123                    train.terminate();
1124                } else {
1125                    status = false;
1126                    int response = JmriJOptionPane.showConfirmDialog(null,
1127                            Bundle.getMessage("WarningTrainManifestNotPrinted"),
1128                            Bundle.getMessage("TerminateTrain",
1129                                    train.getName(), train.getDescription()),
1130                            JmriJOptionPane.YES_NO_CANCEL_OPTION);
1131                    if (response == JmriJOptionPane.YES_OPTION) {
1132                        train.terminate();
1133                    }
1134                    // else Quit?
1135                    if (response == JmriJOptionPane.CLOSED_OPTION || response == JmriJOptionPane.CANCEL_OPTION) {
1136                        break;
1137                    }
1138                }
1139            }
1140        }
1141        return status;
1142    }
1143
1144    private boolean confirmTerminateTrains(List<Train> trains) {
1145        if (isBuildMessagesEnabled()) {
1146            int count = 0;
1147            for (Train train : trains) {
1148                if (train.isBuildEnabled() && train.isBuilt()) {
1149                    count += 1;
1150                }
1151            }
1152            int response = JmriJOptionPane.showConfirmDialog(null,
1153                    Bundle.getMessage("ConfirmTerminate", count),
1154                    Bundle.getMessage("TerminateSelectedTip"),
1155                    JmriJOptionPane.YES_NO_OPTION);
1156            if (response == JmriJOptionPane.NO_OPTION) {
1157                return false;
1158            }
1159        }
1160        return true;
1161    }
1162
1163    public void resetTrains() {
1164        int response = JmriJOptionPane.showConfirmDialog(null,
1165                Bundle.getMessage("ConfirmReset"),
1166                Bundle.getMessage("ConfirmReset"),
1167                JmriJOptionPane.YES_NO_OPTION);
1168        if (response == JmriJOptionPane.YES_OPTION) {
1169            for (Train train : getTrainsByReverseTimeList()) {
1170                train.reset();
1171            }
1172        }
1173    }
1174
1175    public void resetBuildFailedTrains() {
1176        for (Train train : getList()) {
1177            if (train.isBuildFailed())
1178                train.reset();
1179        }
1180    }
1181
1182    int _maxTrainNameLength = 0;
1183
1184    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "SLF4J_FORMAT_SHOULD_BE_CONST",
1185            justification = "I18N of Info Message")
1186    public int getMaxTrainNameLength() {
1187        String trainName = "";
1188        if (_maxTrainNameLength == 0) {
1189            for (Train train : getList()) {
1190                if (train.getName().length() > _maxTrainNameLength) {
1191                    trainName = train.getName();
1192                    _maxTrainNameLength = train.getName().length();
1193                }
1194            }
1195            log.info(Bundle.getMessage("InfoMaxName", trainName, _maxTrainNameLength));
1196        }
1197        return _maxTrainNameLength;
1198    }
1199
1200    public void load(Element root) {
1201        if (root.getChild(Xml.OPTIONS) != null) {
1202            Element options = root.getChild(Xml.OPTIONS);
1203            InstanceManager.getDefault(TrainCustomManifest.class).load(options);
1204            InstanceManager.getDefault(TrainCustomSwitchList.class).load(options);
1205            Element e = options.getChild(Xml.TRAIN_OPTIONS);
1206            Attribute a;
1207            if (e != null) {
1208                if ((a = e.getAttribute(Xml.BUILD_MESSAGES)) != null) {
1209                    _buildMessages = a.getValue().equals(Xml.TRUE);
1210                }
1211                if ((a = e.getAttribute(Xml.BUILD_REPORT)) != null) {
1212                    _buildReport = a.getValue().equals(Xml.TRUE);
1213                }
1214                if ((a = e.getAttribute(Xml.PRINT_PREVIEW)) != null) {
1215                    _printPreview = a.getValue().equals(Xml.TRUE);
1216                }
1217                if ((a = e.getAttribute(Xml.OPEN_FILE)) != null) {
1218                    _openFile = a.getValue().equals(Xml.TRUE);
1219                }
1220                if ((a = e.getAttribute(Xml.RUN_FILE)) != null) {
1221                    _runFile = a.getValue().equals(Xml.TRUE);
1222                }
1223                // verify that the Trains Window action is valid
1224                if ((a = e.getAttribute(Xml.TRAIN_ACTION)) != null &&
1225                        (a.getValue().equals(TrainsTableFrame.MOVE) ||
1226                                a.getValue().equals(TrainsTableFrame.RESET) ||
1227                                a.getValue().equals(TrainsTableFrame.TERMINATE) ||
1228                                a.getValue().equals(TrainsTableFrame.CONDUCTOR))) {
1229                    _trainAction = a.getValue();
1230                }
1231            }
1232
1233            // Conductor options
1234            Element eConductorOptions = options.getChild(Xml.CONDUCTOR_OPTIONS);
1235            if (eConductorOptions != null) {
1236                if ((a = eConductorOptions.getAttribute(Xml.SHOW_HYPHEN_NAME)) != null) {
1237                    _showLocationHyphenName = a.getValue().equals(Xml.TRUE);
1238                }
1239            }
1240
1241            // Row color options
1242            Element eRowColorOptions = options.getChild(Xml.ROW_COLOR_OPTIONS);
1243            if (eRowColorOptions != null) {
1244                if ((a = eRowColorOptions.getAttribute(Xml.ROW_COLOR_MANUAL)) != null) {
1245                    _rowColorManual = a.getValue().equals(Xml.TRUE);
1246                }
1247                if ((a = eRowColorOptions.getAttribute(Xml.ROW_COLOR_BUILD_FAILED)) != null) {
1248                    _rowColorBuildFailed = a.getValue().toLowerCase();
1249                }
1250                if ((a = eRowColorOptions.getAttribute(Xml.ROW_COLOR_BUILT)) != null) {
1251                    _rowColorBuilt = a.getValue().toLowerCase();
1252                }
1253                if ((a = eRowColorOptions.getAttribute(Xml.ROW_COLOR_TRAIN_EN_ROUTE)) != null) {
1254                    _rowColorTrainEnRoute = a.getValue().toLowerCase();
1255                }
1256                if ((a = eRowColorOptions.getAttribute(Xml.ROW_COLOR_TERMINATED)) != null) {
1257                    _rowColorTerminated = a.getValue().toLowerCase();
1258                }
1259                if ((a = eRowColorOptions.getAttribute(Xml.ROW_COLOR_RESET)) != null) {
1260                    _rowColorReset = a.getValue().toLowerCase();
1261                }
1262            }
1263
1264            // moved to train schedule manager
1265            e = options.getChild(jmri.jmrit.operations.trains.schedules.Xml.TRAIN_SCHEDULE_OPTIONS);
1266            if (e != null) {
1267                if ((a = e.getAttribute(jmri.jmrit.operations.trains.schedules.Xml.ACTIVE_ID)) != null) {
1268                    InstanceManager.getDefault(TrainScheduleManager.class).setTrainScheduleActiveId(a.getValue());
1269                }
1270            }
1271            // check for scripts
1272            if (options.getChild(Xml.SCRIPTS) != null) {
1273                List<Element> lm = options.getChild(Xml.SCRIPTS).getChildren(Xml.START_UP);
1274                for (Element es : lm) {
1275                    if ((a = es.getAttribute(Xml.NAME)) != null) {
1276                        addStartUpScript(a.getValue());
1277                    }
1278                }
1279                List<Element> lt = options.getChild(Xml.SCRIPTS).getChildren(Xml.SHUT_DOWN);
1280                for (Element es : lt) {
1281                    if ((a = es.getAttribute(Xml.NAME)) != null) {
1282                        addShutDownScript(a.getValue());
1283                    }
1284                }
1285            }
1286        }
1287        if (root.getChild(Xml.TRAINS) != null) {
1288            List<Element> eTrains = root.getChild(Xml.TRAINS).getChildren(Xml.TRAIN);
1289            log.debug("readFile sees {} trains", eTrains.size());
1290            for (Element eTrain : eTrains) {
1291                register(new Train(eTrain));
1292            }
1293        }
1294    }
1295
1296    /**
1297     * Create an XML element to represent this Entry. This member has to remain
1298     * synchronized with the detailed DTD in operations-trains.dtd.
1299     *
1300     * @param root common Element for operations-trains.dtd.
1301     */
1302    public void store(Element root) {
1303        Element options = new Element(Xml.OPTIONS);
1304        Element e = new Element(Xml.TRAIN_OPTIONS);
1305        e.setAttribute(Xml.BUILD_MESSAGES, isBuildMessagesEnabled() ? Xml.TRUE : Xml.FALSE);
1306        e.setAttribute(Xml.BUILD_REPORT, isBuildReportEnabled() ? Xml.TRUE : Xml.FALSE);
1307        e.setAttribute(Xml.PRINT_PREVIEW, isPrintPreviewEnabled() ? Xml.TRUE : Xml.FALSE);
1308        e.setAttribute(Xml.OPEN_FILE, isOpenFileEnabled() ? Xml.TRUE : Xml.FALSE);
1309        e.setAttribute(Xml.RUN_FILE, isRunFileEnabled() ? Xml.TRUE : Xml.FALSE);
1310        e.setAttribute(Xml.TRAIN_ACTION, getTrainsFrameTrainAction());
1311        options.addContent(e);
1312
1313        // Conductor options
1314        e = new Element(Xml.CONDUCTOR_OPTIONS);
1315        e.setAttribute(Xml.SHOW_HYPHEN_NAME, isShowLocationHyphenNameEnabled() ? Xml.TRUE : Xml.FALSE);
1316        options.addContent(e);
1317
1318        // Trains table row color options
1319        e = new Element(Xml.ROW_COLOR_OPTIONS);
1320        e.setAttribute(Xml.ROW_COLOR_MANUAL, isRowColorManual() ? Xml.TRUE : Xml.FALSE);
1321        e.setAttribute(Xml.ROW_COLOR_BUILD_FAILED, getRowColorNameForBuildFailed());
1322        e.setAttribute(Xml.ROW_COLOR_BUILT, getRowColorNameForBuilt());
1323        e.setAttribute(Xml.ROW_COLOR_TRAIN_EN_ROUTE, getRowColorNameForTrainEnRoute());
1324        e.setAttribute(Xml.ROW_COLOR_TERMINATED, getRowColorNameForTerminated());
1325        e.setAttribute(Xml.ROW_COLOR_RESET, getRowColorNameForReset());
1326        options.addContent(e);
1327
1328        if (getStartUpScripts().size() > 0 || getShutDownScripts().size() > 0) {
1329            // save list of shutdown scripts
1330            Element es = new Element(Xml.SCRIPTS);
1331            for (String scriptName : getStartUpScripts()) {
1332                Element em = new Element(Xml.START_UP);
1333                em.setAttribute(Xml.NAME, scriptName);
1334                es.addContent(em);
1335            }
1336            // save list of termination scripts
1337            for (String scriptName : getShutDownScripts()) {
1338                Element et = new Element(Xml.SHUT_DOWN);
1339                et.setAttribute(Xml.NAME, scriptName);
1340                es.addContent(et);
1341            }
1342            options.addContent(es);
1343        }
1344
1345        InstanceManager.getDefault(TrainCustomManifest.class).store(options); // save custom manifest elements
1346        InstanceManager.getDefault(TrainCustomSwitchList.class).store(options); // save custom switch list elements
1347
1348        root.addContent(options);
1349
1350        Element trains = new Element(Xml.TRAINS);
1351        root.addContent(trains);
1352        // add entries
1353        for (Train train : getTrainsByIdList()) {
1354            trains.addContent(train.store());
1355        }
1356        firePropertyChange(TRAINS_SAVED_PROPERTY, true, false);
1357    }
1358
1359    /**
1360     * Not currently used.
1361     */
1362    @Override
1363    public void propertyChange(java.beans.PropertyChangeEvent e) {
1364        log.debug("TrainManager sees property change: {} old: {} new: {}", e.getPropertyName(), e.getOldValue(),
1365                e.getNewValue());
1366    }
1367
1368    private void setDirtyAndFirePropertyChange(String p, Object old, Object n) {
1369        InstanceManager.getDefault(TrainManagerXml.class).setDirty(true);
1370        firePropertyChange(p, old, n);
1371    }
1372
1373    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TrainManager.class);
1374
1375    @Override
1376    public void initialize() {
1377        InstanceManager.getDefault(OperationsSetupXml.class); // load setup
1378        InstanceManager.getDefault(CarManagerXml.class); // load cars
1379        InstanceManager.getDefault(EngineManagerXml.class); // load engines
1380        InstanceManager.getDefault(TrainManagerXml.class); // load trains
1381    }
1382
1383}