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