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     * @return true if there's a train being built
487     */
488    public boolean isAnyTrainBuilding() {
489        if (getTrainBuilding() != null) {
490            return true;
491        }
492        return false;
493    }
494
495    public Train getTrainBuilding() {
496        for (Train train : getList()) {
497            if (train.getStatusCode() == Train.CODE_BUILDING) {
498                log.debug("Train {} is currently building", train.getName());
499                return train;
500            }
501        }
502        return null;
503    }
504
505    /**
506     * @param car         The car looking for a train.
507     * @param buildReport The optional build report for logging.
508     * @return Train that can service car from its current location to the its
509     *         destination.
510     */
511    public Train getTrainForCar(Car car, PrintWriter buildReport) {
512        return getTrainForCar(car, new ArrayList<>(), buildReport, false);
513    }
514
515    /**
516     * @param car           The car looking for a train.
517     * @param excludeTrains The trains not to try.
518     * @param buildReport   The optional build report for logging.
519     * @param isExcludeRoutes When true eliminate trains that have the same route in the exclude trains list.
520     * @return Train that can service car from its current location to the its
521     *         destination.
522     */
523    public Train getTrainForCar(Car car, List<Train> excludeTrains, PrintWriter buildReport, boolean isExcludeRoutes) {
524        addLine(buildReport, TrainCommon.BLANK_LINE);
525        addLine(buildReport, Bundle.getMessage("trainFindForCar", car.toString(), car.getLocationName(),
526                car.getTrackName(), car.getDestinationName(), car.getDestinationTrackName()));
527
528        main: for (Train train : getTrainsByNameList()) {
529            if (excludeTrains.contains(train)) {
530                continue;
531            }
532            if (Setup.isOnlyActiveTrainsEnabled() && !train.isBuildEnabled()) {
533                continue;
534            }
535            if (isExcludeRoutes) {
536                for (Train t : excludeTrains) {
537                    if (t != null && train.getRoute() == t.getRoute()) {
538                        addLine(buildReport, Bundle.getMessage("trainHasSameRoute", train, t));
539                        continue main;
540                    }
541                }
542            }
543            // does this train service this car?
544            if (train.isServiceable(buildReport, car)) {
545                log.debug("Found train ({}) for car ({}) location ({}, {}) destination ({}, {})", train.getName(),
546                        car.toString(), car.getLocationName(), car.getTrackName(), car.getDestinationName(),
547                        car.getDestinationTrackName()); // NOI18N
548                return train;
549            }
550        }
551        return null;
552    }
553    
554    public List<Train> getExcludeTrainListForCar(Car car, PrintWriter buildReport) {
555        List<Train> excludeTrains = new ArrayList<>();
556        for (Train train : getTrainsByNameList()) {
557            if (Setup.isOnlyActiveTrainsEnabled() && !train.isBuildEnabled()) {
558                addLine(buildReport, Bundle.getMessage("trainRoutingDisabled", train.getName()));
559                excludeTrains.add(train);
560            }
561            else if (!train.isTrainAbleToService(buildReport, car)) {
562                excludeTrains.add(train);
563            }
564        }
565        return excludeTrains;
566    }
567
568    protected static final String SEVEN = Setup.BUILD_REPORT_VERY_DETAILED;
569
570    private void addLine(PrintWriter buildReport, String string) {
571        if (Setup.getRouterBuildReportLevel().equals(SEVEN)) {
572            TrainCommon.addLine(buildReport, SEVEN, string);
573        }
574    }
575
576    /**
577     * Sort by train name
578     *
579     * @return list of trains ordered by name
580     */
581    public List<Train> getTrainsByNameList() {
582        return getTrainsByList(getList(), GET_TRAIN_NAME);
583    }
584
585    /**
586     * Sort by train departure time
587     *
588     * @return list of trains ordered by departure time
589     */
590    public List<Train> getTrainsByTimeList() {
591        return getTrainsByIntList(getTrainsByNameList(), GET_TRAIN_TIME);
592    }
593
594    /**
595     * Sort by train departure location name
596     *
597     * @return list of trains ordered by departure name
598     */
599    public List<Train> getTrainsByDepartureList() {
600        return getTrainsByList(getTrainsByTimeList(), GET_TRAIN_DEPARTES_NAME);
601    }
602
603    /**
604     * Sort by train termination location name
605     *
606     * @return list of trains ordered by termination name
607     */
608    public List<Train> getTrainsByTerminatesList() {
609        return getTrainsByList(getTrainsByTimeList(), GET_TRAIN_TERMINATES_NAME);
610    }
611
612    /**
613     * Sort by train route name
614     *
615     * @return list of trains ordered by route name
616     */
617    public List<Train> getTrainsByRouteList() {
618        return getTrainsByList(getTrainsByTimeList(), GET_TRAIN_ROUTE_NAME);
619    }
620
621    /**
622     * Sort by train status
623     *
624     * @return list of trains ordered by status
625     */
626    public List<Train> getTrainsByStatusList() {
627        return getTrainsByList(getTrainsByTimeList(), GET_TRAIN_STATUS);
628    }
629
630    /**
631     * Sort by train description
632     *
633     * @return list of trains ordered by train description
634     */
635    public List<Train> getTrainsByDescriptionList() {
636        return getTrainsByList(getTrainsByTimeList(), GET_TRAIN_DESCRIPTION);
637    }
638
639    /**
640     * Sort by train id
641     *
642     * @return list of trains ordered by id
643     */
644    public List<Train> getTrainsByIdList() {
645        return getTrainsByIntList(getList(), GET_TRAIN_ID);
646    }
647
648    private List<Train> getTrainsByList(List<Train> sortList, int attribute) {
649        List<Train> out = new ArrayList<>();
650        for (Train train : sortList) {
651            String trainAttribute = (String) getTrainAttribute(train, attribute);
652            for (int j = 0; j < out.size(); j++) {
653                if (trainAttribute.compareToIgnoreCase((String) getTrainAttribute(out.get(j), attribute)) < 0) {
654                    out.add(j, train);
655                    break;
656                }
657            }
658            if (!out.contains(train)) {
659                out.add(train);
660            }
661        }
662        return out;
663    }
664
665    private List<Train> getTrainsByIntList(List<Train> sortList, int attribute) {
666        List<Train> out = new ArrayList<>();
667        for (Train train : sortList) {
668            int trainAttribute = (Integer) getTrainAttribute(train, attribute);
669            for (int j = 0; j < out.size(); j++) {
670                if (trainAttribute < (Integer) getTrainAttribute(out.get(j), attribute)) {
671                    out.add(j, train);
672                    break;
673                }
674            }
675            if (!out.contains(train)) {
676                out.add(train);
677            }
678        }
679        return out;
680    }
681
682    // the various sort options for trains
683    private static final int GET_TRAIN_DEPARTES_NAME = 0;
684    private static final int GET_TRAIN_NAME = 1;
685    private static final int GET_TRAIN_ROUTE_NAME = 2;
686    private static final int GET_TRAIN_TERMINATES_NAME = 3;
687    private static final int GET_TRAIN_TIME = 4;
688    private static final int GET_TRAIN_STATUS = 5;
689    private static final int GET_TRAIN_ID = 6;
690    private static final int GET_TRAIN_DESCRIPTION = 7;
691
692    private Object getTrainAttribute(Train train, int attribute) {
693        switch (attribute) {
694            case GET_TRAIN_DEPARTES_NAME:
695                return train.getTrainDepartsName();
696            case GET_TRAIN_NAME:
697                return train.getName();
698            case GET_TRAIN_ROUTE_NAME:
699                return train.getTrainRouteName();
700            case GET_TRAIN_TERMINATES_NAME:
701                return train.getTrainTerminatesName();
702            case GET_TRAIN_TIME:
703                return train.getDepartTimeMinutes();
704            case GET_TRAIN_STATUS:
705                return train.getStatus();
706            case GET_TRAIN_ID:
707                return Integer.parseInt(train.getId());
708            case GET_TRAIN_DESCRIPTION:
709                return train.getDescription();
710            default:
711                return "unknown"; // NOI18N
712        }
713    }
714
715    public List<Train> getList() {
716        if (!InstanceManager.getDefault(TrainManagerXml.class).isTrainFileLoaded()) {
717            log.error("TrainManager getList called before trains completely loaded!");
718        }
719        List<Train> out = new ArrayList<>();
720        Enumeration<Train> en = _trainHashTable.elements();
721        while (en.hasMoreElements()) {
722            out.add(en.nextElement());
723        }
724        return out;
725    }
726
727    public JComboBox<Train> getTrainComboBox() {
728        JComboBox<Train> box = new JComboBox<>();
729        updateTrainComboBox(box);
730        OperationsPanel.padComboBox(box);
731        return box;
732    }
733
734    public void updateTrainComboBox(JComboBox<Train> box) {
735        box.removeAllItems();
736        box.addItem(null);
737        for (Train train : getTrainsByNameList()) {
738            box.addItem(train);
739        }
740    }
741
742    /**
743     * Update combo box with trains that will service this car
744     *
745     * @param box the combo box to update
746     * @param car the car to be serviced
747     */
748    public void updateTrainComboBox(JComboBox<Train> box, Car car) {
749        box.removeAllItems();
750        box.addItem(null);
751        for (Train train : getTrainsByNameList()) {
752            if (train.isServiceable(car)) {
753                box.addItem(train);
754            }
755        }
756    }
757
758    public boolean isRowColorManual() {
759        return _rowColorManual;
760    }
761
762    public void setRowColorsManual(boolean manual) {
763        boolean old = _rowColorManual;
764        _rowColorManual = manual;
765        setDirtyAndFirePropertyChange(ROW_COLOR_NAME_CHANGED_PROPERTY, old, manual);
766    }
767
768    public String getRowColorNameForBuilt() {
769        return _rowColorBuilt;
770    }
771
772    public void setRowColorNameForBuilt(String colorName) {
773        String old = _rowColorBuilt;
774        _rowColorBuilt = colorName;
775        setDirtyAndFirePropertyChange(ROW_COLOR_NAME_CHANGED_PROPERTY, old, colorName);
776    }
777
778    public String getRowColorNameForBuildFailed() {
779        return _rowColorBuildFailed;
780    }
781
782    public void setRowColorNameForBuildFailed(String colorName) {
783        String old = _rowColorBuildFailed;
784        _rowColorBuildFailed = colorName;
785        setDirtyAndFirePropertyChange(ROW_COLOR_NAME_CHANGED_PROPERTY, old, colorName);
786    }
787
788    public String getRowColorNameForTrainEnRoute() {
789        return _rowColorTrainEnRoute;
790    }
791
792    public void setRowColorNameForTrainEnRoute(String colorName) {
793        String old = _rowColorTrainEnRoute;
794        _rowColorTrainEnRoute = colorName;
795        setDirtyAndFirePropertyChange(ROW_COLOR_NAME_CHANGED_PROPERTY, old, colorName);
796    }
797
798    public String getRowColorNameForTerminated() {
799        return _rowColorTerminated;
800    }
801
802    public void setRowColorNameForTerminated(String colorName) {
803        String old = _rowColorTerminated;
804        _rowColorTerminated = colorName;
805        setDirtyAndFirePropertyChange(ROW_COLOR_NAME_CHANGED_PROPERTY, old, colorName);
806    }
807    
808    public String getRowColorNameForReset() {
809        return _rowColorReset;
810    }
811
812    public void setRowColorNameForReset(String colorName) {
813        String old = _rowColorReset;
814        _rowColorReset = colorName;
815        setDirtyAndFirePropertyChange(ROW_COLOR_NAME_CHANGED_PROPERTY, old, colorName);
816    }
817
818    /**
819     * JColorChooser is not a replacement for getRowColorComboBox as it doesn't
820     * support no color as a selection.
821     * 
822     * @return the available colors used highlighting table rows including no color.
823     */
824    public JComboBox<String> getRowColorComboBox() {
825        JComboBox<String> box = new JComboBox<>();
826        box.addItem(NONE);
827        box.addItem(ColorUtil.ColorBlack);
828        box.addItem(ColorUtil.ColorRed);
829        box.addItem(ColorUtil.ColorPink);
830        box.addItem(ColorUtil.ColorOrange);
831        box.addItem(ColorUtil.ColorYellow);
832        box.addItem(ColorUtil.ColorGreen);
833        box.addItem(ColorUtil.ColorMagenta);
834        box.addItem(ColorUtil.ColorCyan);
835        box.addItem(ColorUtil.ColorBlue);
836        box.addItem(ColorUtil.ColorGray);
837        return box;
838    }
839
840    /**
841     * Makes a copy of an existing train.
842     *
843     * @param train     the train to copy
844     * @param trainName the name of the new train
845     * @return a copy of train
846     */
847    public Train copyTrain(Train train, String trainName) {
848        Train newTrain = newTrain(trainName);
849        // route, departure time and types
850        newTrain.setRoute(train.getRoute());
851        newTrain.setTrainSkipsLocations(train.getTrainSkipsLocations());
852        newTrain.setDepartureTime(train.getDepartureTimeHour(), train.getDepartureTimeMinute());
853        newTrain._typeList.clear(); // remove all types loaded by create
854        newTrain.setTypeNames(train.getTypeNames());
855        // set road, load, and owner options
856        newTrain.setCarRoadOption(train.getCarRoadOption());
857        newTrain.setCarRoadNames(train.getCarRoadNames());
858        newTrain.setCabooseRoadNames(train.getCabooseRoadNames());
859        newTrain.setLocoRoadOption(train.getLocoRoadOption());
860        newTrain.setLocoRoadNames(train.getLocoRoadNames());
861        newTrain.setLoadOption(train.getLoadOption());
862        newTrain.setLoadNames(train.getLoadNames());
863        newTrain.setOwnerOption(train.getOwnerOption());
864        newTrain.setOwnerNames(train.getOwnerNames());
865        // build dates
866        newTrain.setBuiltStartYear(train.getBuiltStartYear());
867        newTrain.setBuiltEndYear(train.getBuiltEndYear());
868        // locos start of route
869        newTrain.setNumberEngines(train.getNumberEngines());
870        newTrain.setEngineModel(train.getEngineModel());
871        newTrain.setEngineRoad(train.getEngineRoad());
872        newTrain.setRequirements(train.getRequirements());
873        newTrain.setCabooseRoad(train.getCabooseRoad());
874        // second leg
875        newTrain.setSecondLegNumberEngines(train.getSecondLegNumberEngines());
876        newTrain.setSecondLegEngineModel(train.getSecondLegEngineModel());
877        newTrain.setSecondLegEngineRoad(train.getSecondLegEngineRoad());
878        newTrain.setSecondLegOptions(train.getSecondLegOptions());
879        newTrain.setSecondLegCabooseRoad(train.getSecondLegCabooseRoad());
880        newTrain.setSecondLegStartRouteLocation(train.getSecondLegStartRouteLocation());
881        newTrain.setSecondLegEndRouteLocation(train.getSecondLegEndRouteLocation());
882        // third leg
883        newTrain.setThirdLegNumberEngines(train.getThirdLegNumberEngines());
884        newTrain.setThirdLegEngineModel(train.getThirdLegEngineModel());
885        newTrain.setThirdLegEngineRoad(train.getThirdLegEngineRoad());
886        newTrain.setThirdLegOptions(train.getThirdLegOptions());
887        newTrain.setThirdLegCabooseRoad(train.getThirdLegCabooseRoad());
888        newTrain.setThirdLegStartRouteLocation(train.getThirdLegStartRouteLocation());
889        newTrain.setThirdLegEndRouteLocation(train.getThirdLegEndRouteLocation());
890        // scripts
891        for (String scriptName : train.getBuildScripts()) {
892            newTrain.addBuildScript(scriptName);
893        }
894        for (String scriptName : train.getMoveScripts()) {
895            newTrain.addMoveScript(scriptName);
896        }
897        for (String scriptName : train.getTerminationScripts()) {
898            newTrain.addTerminationScript(scriptName);
899        }
900        // manifest options
901        newTrain.setRailroadName(train.getRailroadName());
902        newTrain.setManifestLogoPathName(train.getManifestLogoPathName());
903        newTrain.setShowArrivalAndDepartureTimes(train.isShowArrivalAndDepartureTimesEnabled());
904        // build options
905        newTrain.setAllowLocalMovesEnabled(train.isAllowLocalMovesEnabled());
906        newTrain.setAllowReturnToStagingEnabled(train.isAllowReturnToStagingEnabled());
907        newTrain.setAllowThroughCarsEnabled(train.isAllowThroughCarsEnabled());
908        newTrain.setBuildConsistEnabled(train.isBuildConsistEnabled());
909        newTrain.setSendCarsWithCustomLoadsToStagingEnabled(train.isSendCarsWithCustomLoadsToStagingEnabled());
910        newTrain.setBuildTrainNormalEnabled(train.isBuildTrainNormalEnabled());
911        newTrain.setSendCarsToTerminalEnabled(train.isSendCarsToTerminalEnabled());
912        newTrain.setServiceAllCarsWithFinalDestinationsEnabled(train.isServiceAllCarsWithFinalDestinationsEnabled());
913        // comment
914        newTrain.setComment(train.getCommentWithColor());
915        // description
916        newTrain.setDescription(train.getRawDescription());
917        return newTrain;
918    }
919
920    /**
921     * Provides a list of trains ordered by arrival time to a location
922     *
923     * @param location The location
924     * @return A list of trains ordered by arrival time.
925     */
926    public List<Train> getTrainsArrivingThisLocationList(Location location) {
927        // get a list of trains
928        List<Train> out = new ArrayList<>();
929        List<Integer> arrivalTimes = new ArrayList<>();
930        for (Train train : getTrainsByTimeList()) {
931            if (!train.isBuilt()) {
932                continue; // train wasn't built so skip
933            }
934            Route route = train.getRoute();
935            if (route == null) {
936                continue; // no route for this train
937            }
938            for (RouteLocation rl : route.getLocationsBySequenceList()) {
939                if (rl.getSplitName().equals(location.getSplitName())) {
940                    int expectedArrivalTime = train.getExpectedTravelTimeInMinutes(rl);
941                    // is already serviced then "-1"
942                    if (expectedArrivalTime == -1) {
943                        out.add(0, train); // place all trains that have already been serviced at the start
944                        arrivalTimes.add(0, expectedArrivalTime);
945                    } // if the train is in route, then expected arrival time is in minutes
946                    else if (train.isTrainEnRoute()) {
947                        for (int j = 0; j < out.size(); j++) {
948                            Train t = out.get(j);
949                            int time = arrivalTimes.get(j);
950                            if (t.isTrainEnRoute() && expectedArrivalTime < time) {
951                                out.add(j, train);
952                                arrivalTimes.add(j, expectedArrivalTime);
953                                break;
954                            }
955                            if (!t.isTrainEnRoute()) {
956                                out.add(j, train);
957                                arrivalTimes.add(j, expectedArrivalTime);
958                                break;
959                            }
960                        }
961                        // Train has not departed
962                    } else {
963                        for (int j = 0; j < out.size(); j++) {
964                            Train t = out.get(j);
965                            int time = arrivalTimes.get(j);
966                            if (!t.isTrainEnRoute() && expectedArrivalTime < time) {
967                                out.add(j, train);
968                                arrivalTimes.add(j, expectedArrivalTime);
969                                break;
970                            }
971                        }
972                    }
973                    if (!out.contains(train)) {
974                        out.add(train);
975                        arrivalTimes.add(expectedArrivalTime);
976                    }
977                    break; // done
978                }
979            }
980        }
981        return out;
982    }
983
984    /**
985     * Loads train icons if needed
986     */
987    public void loadTrainIcons() {
988        for (Train train : getTrainsByIdList()) {
989            train.loadTrainIcon();
990        }
991    }
992
993    /**
994     * Sets the switch list status for all built trains. Used for switch lists in
995     * consolidated mode.
996     *
997     * @param status Train.PRINTED, Train.UNKNOWN
998     */
999    public void setTrainsSwitchListStatus(String status) {
1000        for (Train train : getTrainsByTimeList()) {
1001            if (!train.isBuilt()) {
1002                continue; // train isn't built so skip
1003            }
1004            train.setSwitchListStatus(status);
1005        }
1006    }
1007
1008    /**
1009     * Sets all built trains manifests to modified. This causes the train's manifest
1010     * to be recreated.
1011     */
1012    public void setTrainsModified() {
1013        for (Train train : getTrainsByTimeList()) {
1014            if (!train.isBuilt() || train.isTrainEnRoute()) {
1015                continue; // train wasn't built or in route, so skip
1016            }
1017            train.setModified(true);
1018        }
1019    }
1020
1021    public void buildSelectedTrains(List<Train> trains) {
1022        // use a thread to allow table updates during build
1023        Thread build = jmri.util.ThreadingUtil.newThread(new Runnable() {
1024            @Override
1025            public void run() {
1026                for (Train train : trains) {
1027                    if (train.buildIfSelected()) {
1028                        continue;
1029                    }
1030                    if (isBuildMessagesEnabled() && train.isBuildEnabled() && !train.isBuilt()) {
1031                        if (JmriJOptionPane.showConfirmDialog(null, Bundle.getMessage("ContinueBuilding"),
1032                                Bundle.getMessage("buildFailedMsg",
1033                                        train.getName()),
1034                                JmriJOptionPane.YES_NO_OPTION) == JmriJOptionPane.NO_OPTION) {
1035                            break;
1036                        }
1037                    }
1038                }
1039                setDirtyAndFirePropertyChange(TRAINS_BUILT_CHANGED_PROPERTY, false, true);
1040            }
1041        });
1042        build.setName("Build Trains"); // NOI18N
1043        build.start();
1044    }
1045
1046    public boolean printSelectedTrains(List<Train> trains) {
1047        boolean status = true;
1048        for (Train train : trains) {
1049            if (train.isBuildEnabled()) {
1050                if (train.printManifestIfBuilt()) {
1051                    continue;
1052                }
1053                status = false; // failed to print all selected trains
1054                if (isBuildMessagesEnabled()) {
1055                    int response = JmriJOptionPane.showConfirmDialog(null,
1056                            Bundle.getMessage("NeedToBuildBeforePrinting",
1057                                    train.getName(),
1058                                            (isPrintPreviewEnabled() ? Bundle.getMessage("preview")
1059                                                    : Bundle.getMessage("print"))),
1060                            Bundle.getMessage("CanNotPrintManifest",
1061                                    isPrintPreviewEnabled() ? Bundle.getMessage("preview")
1062                                            : Bundle.getMessage("print")),
1063                            JmriJOptionPane.OK_CANCEL_OPTION);
1064                    if (response != JmriJOptionPane.OK_OPTION ) {
1065                        break;
1066                    }
1067                }
1068            }
1069        }
1070        return status;
1071    }
1072
1073    public boolean terminateSelectedTrains(List<Train> trains) {
1074        boolean status = true;
1075        for (Train train : trains) {
1076            if (train.isBuildEnabled() && train.isBuilt()) {
1077                if (train.isPrinted()) {
1078                    train.terminate();
1079                } else {
1080                    status = false;
1081                    int response = JmriJOptionPane.showConfirmDialog(null,
1082                            Bundle.getMessage("WarningTrainManifestNotPrinted"),
1083                            Bundle.getMessage("TerminateTrain",
1084                                    train.getName(), train.getDescription()),
1085                            JmriJOptionPane.YES_NO_CANCEL_OPTION);
1086                    if (response == JmriJOptionPane.YES_OPTION) {
1087                        train.terminate();
1088                    }
1089                    // else Quit?
1090                    if (response == JmriJOptionPane.CLOSED_OPTION || response == JmriJOptionPane.CANCEL_OPTION) {
1091                        break;
1092                    }
1093                }
1094            }
1095        }
1096        return status;
1097    }
1098
1099    public void resetBuildFailedTrains() {
1100        for (Train train : getList()) {
1101            if (train.isBuildFailed())
1102                train.reset();
1103        }
1104    }
1105
1106    int _maxTrainNameLength = 0;
1107
1108    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value="SLF4J_FORMAT_SHOULD_BE_CONST",
1109            justification="I18N of Info Message")
1110    public int getMaxTrainNameLength() {
1111        String trainName = "";
1112        if (_maxTrainNameLength == 0) {
1113            for (Train train : getList()) {
1114                if (train.getName().length() > _maxTrainNameLength) {
1115                    trainName = train.getName();
1116                    _maxTrainNameLength = train.getName().length();
1117                }
1118            }
1119            log.info(Bundle.getMessage("InfoMaxName", trainName, _maxTrainNameLength));
1120        }
1121        return _maxTrainNameLength;
1122    }
1123
1124    public void load(Element root) {
1125        if (root.getChild(Xml.OPTIONS) != null) {
1126            Element options = root.getChild(Xml.OPTIONS);
1127            InstanceManager.getDefault(TrainCustomManifest.class).load(options);
1128            InstanceManager.getDefault(TrainCustomSwitchList.class).load(options);
1129            Element e = options.getChild(Xml.TRAIN_OPTIONS);
1130            Attribute a;
1131            if (e != null) {
1132                if ((a = e.getAttribute(Xml.BUILD_MESSAGES)) != null) {
1133                    _buildMessages = a.getValue().equals(Xml.TRUE);
1134                }
1135                if ((a = e.getAttribute(Xml.BUILD_REPORT)) != null) {
1136                    _buildReport = a.getValue().equals(Xml.TRUE);
1137                }
1138                if ((a = e.getAttribute(Xml.PRINT_PREVIEW)) != null) {
1139                    _printPreview = a.getValue().equals(Xml.TRUE);
1140                }
1141                if ((a = e.getAttribute(Xml.OPEN_FILE)) != null) {
1142                    _openFile = a.getValue().equals(Xml.TRUE);
1143                }
1144                if ((a = e.getAttribute(Xml.RUN_FILE)) != null) {
1145                    _runFile = a.getValue().equals(Xml.TRUE);
1146                }
1147                // verify that the Trains Window action is valid
1148                if ((a = e.getAttribute(Xml.TRAIN_ACTION)) != null &&
1149                        (a.getValue().equals(TrainsTableFrame.MOVE) ||
1150                                a.getValue().equals(TrainsTableFrame.RESET) ||
1151                                a.getValue().equals(TrainsTableFrame.TERMINATE) ||
1152                                a.getValue().equals(TrainsTableFrame.CONDUCTOR))) {
1153                    _trainAction = a.getValue();
1154                }
1155            }
1156
1157            // Conductor options
1158            Element eConductorOptions = options.getChild(Xml.CONDUCTOR_OPTIONS);
1159            if (eConductorOptions != null) {
1160                if ((a = eConductorOptions.getAttribute(Xml.SHOW_HYPHEN_NAME)) != null) {
1161                    _showLocationHyphenName = a.getValue().equals(Xml.TRUE);
1162                }
1163            }
1164
1165            // Row color options
1166            Element eRowColorOptions = options.getChild(Xml.ROW_COLOR_OPTIONS);
1167            if (eRowColorOptions != null) {
1168                if ((a = eRowColorOptions.getAttribute(Xml.ROW_COLOR_MANUAL)) != null) {
1169                    _rowColorManual = a.getValue().equals(Xml.TRUE);
1170                }
1171                if ((a = eRowColorOptions.getAttribute(Xml.ROW_COLOR_BUILD_FAILED)) != null) {
1172                    _rowColorBuildFailed = a.getValue().toLowerCase();
1173                }
1174                if ((a = eRowColorOptions.getAttribute(Xml.ROW_COLOR_BUILT)) != null) {
1175                    _rowColorBuilt = a.getValue().toLowerCase();
1176                }
1177                if ((a = eRowColorOptions.getAttribute(Xml.ROW_COLOR_TRAIN_EN_ROUTE)) != null) {
1178                    _rowColorTrainEnRoute = a.getValue().toLowerCase();
1179                }
1180                if ((a = eRowColorOptions.getAttribute(Xml.ROW_COLOR_TERMINATED)) != null) {
1181                    _rowColorTerminated = a.getValue().toLowerCase();
1182                }
1183                if ((a = eRowColorOptions.getAttribute(Xml.ROW_COLOR_RESET)) != null) {
1184                    _rowColorReset = a.getValue().toLowerCase();
1185                }
1186            }
1187
1188            // moved to train schedule manager
1189            e = options.getChild(jmri.jmrit.operations.trains.schedules.Xml.TRAIN_SCHEDULE_OPTIONS);
1190            if (e != null) {
1191                if ((a = e.getAttribute(jmri.jmrit.operations.trains.schedules.Xml.ACTIVE_ID)) != null) {
1192                    InstanceManager.getDefault(TrainScheduleManager.class).setTrainScheduleActiveId(a.getValue());
1193                }
1194            }
1195            // check for scripts
1196            if (options.getChild(Xml.SCRIPTS) != null) {
1197                List<Element> lm = options.getChild(Xml.SCRIPTS).getChildren(Xml.START_UP);
1198                for (Element es : lm) {
1199                    if ((a = es.getAttribute(Xml.NAME)) != null) {
1200                        addStartUpScript(a.getValue());
1201                    }
1202                }
1203                List<Element> lt = options.getChild(Xml.SCRIPTS).getChildren(Xml.SHUT_DOWN);
1204                for (Element es : lt) {
1205                    if ((a = es.getAttribute(Xml.NAME)) != null) {
1206                        addShutDownScript(a.getValue());
1207                    }
1208                }
1209            }
1210        }
1211        if (root.getChild(Xml.TRAINS) != null) {
1212            List<Element> eTrains = root.getChild(Xml.TRAINS).getChildren(Xml.TRAIN);
1213            log.debug("readFile sees {} trains", eTrains.size());
1214            for (Element eTrain : eTrains) {
1215                register(new Train(eTrain));
1216            }
1217        }
1218    }
1219
1220    /**
1221     * Create an XML element to represent this Entry. This member has to remain
1222     * synchronized with the detailed DTD in operations-trains.dtd.
1223     *
1224     * @param root common Element for operations-trains.dtd.
1225     *
1226     */
1227    public void store(Element root) {
1228        Element options = new Element(Xml.OPTIONS);
1229        Element e = new Element(Xml.TRAIN_OPTIONS);
1230        e.setAttribute(Xml.BUILD_MESSAGES, isBuildMessagesEnabled() ? Xml.TRUE : Xml.FALSE);
1231        e.setAttribute(Xml.BUILD_REPORT, isBuildReportEnabled() ? Xml.TRUE : Xml.FALSE);
1232        e.setAttribute(Xml.PRINT_PREVIEW, isPrintPreviewEnabled() ? Xml.TRUE : Xml.FALSE);
1233        e.setAttribute(Xml.OPEN_FILE, isOpenFileEnabled() ? Xml.TRUE : Xml.FALSE);
1234        e.setAttribute(Xml.RUN_FILE, isRunFileEnabled() ? Xml.TRUE : Xml.FALSE);
1235        e.setAttribute(Xml.TRAIN_ACTION, getTrainsFrameTrainAction());
1236        options.addContent(e);
1237
1238        // Conductor options
1239        e = new Element(Xml.CONDUCTOR_OPTIONS);
1240        e.setAttribute(Xml.SHOW_HYPHEN_NAME, isShowLocationHyphenNameEnabled() ? Xml.TRUE : Xml.FALSE);
1241        options.addContent(e);
1242
1243        // Trains table row color options
1244        e = new Element(Xml.ROW_COLOR_OPTIONS);
1245        e.setAttribute(Xml.ROW_COLOR_MANUAL, isRowColorManual() ? Xml.TRUE : Xml.FALSE);
1246        e.setAttribute(Xml.ROW_COLOR_BUILD_FAILED, getRowColorNameForBuildFailed());
1247        e.setAttribute(Xml.ROW_COLOR_BUILT, getRowColorNameForBuilt());
1248        e.setAttribute(Xml.ROW_COLOR_TRAIN_EN_ROUTE, getRowColorNameForTrainEnRoute());
1249        e.setAttribute(Xml.ROW_COLOR_TERMINATED, getRowColorNameForTerminated());
1250        e.setAttribute(Xml.ROW_COLOR_RESET, getRowColorNameForReset());
1251        options.addContent(e);
1252
1253        if (getStartUpScripts().size() > 0 || getShutDownScripts().size() > 0) {
1254            // save list of shutdown scripts
1255            Element es = new Element(Xml.SCRIPTS);
1256            for (String scriptName : getStartUpScripts()) {
1257                Element em = new Element(Xml.START_UP);
1258                em.setAttribute(Xml.NAME, scriptName);
1259                es.addContent(em);
1260            }
1261            // save list of termination scripts
1262            for (String scriptName : getShutDownScripts()) {
1263                Element et = new Element(Xml.SHUT_DOWN);
1264                et.setAttribute(Xml.NAME, scriptName);
1265                es.addContent(et);
1266            }
1267            options.addContent(es);
1268        }
1269
1270        InstanceManager.getDefault(TrainCustomManifest.class).store(options); // save custom manifest elements
1271        InstanceManager.getDefault(TrainCustomSwitchList.class).store(options); // save custom switch list elements
1272
1273        root.addContent(options);
1274
1275        Element trains = new Element(Xml.TRAINS);
1276        root.addContent(trains);
1277        // add entries
1278        for (Train train : getTrainsByIdList()) {
1279            trains.addContent(train.store());
1280        }
1281        firePropertyChange(TRAINS_SAVED_PROPERTY, true, false);
1282    }
1283
1284    /**
1285     * Not currently used.
1286     */
1287    @Override
1288    public void propertyChange(java.beans.PropertyChangeEvent e) {
1289        log.debug("TrainManager sees property change: {} old: {} new: {}", e.getPropertyName(), e.getOldValue(),
1290                e.getNewValue());
1291    }
1292
1293    private void setDirtyAndFirePropertyChange(String p, Object old, Object n) {
1294        InstanceManager.getDefault(TrainManagerXml.class).setDirty(true);
1295        firePropertyChange(p, old, n);
1296    }
1297
1298    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TrainManager.class);
1299
1300    @Override
1301    public void initialize() {
1302        InstanceManager.getDefault(OperationsSetupXml.class); // load setup
1303        InstanceManager.getDefault(CarManagerXml.class); // load cars
1304        InstanceManager.getDefault(EngineManagerXml.class); // load engines
1305        InstanceManager.getDefault(TrainManagerXml.class); // load trains
1306    }
1307
1308}