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