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