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