001package jmri.jmrit.logixng.util;
002
003import java.util.*;
004
005import jmri.*;
006import jmri.beans.Bean;
007import jmri.jmrit.dispatcher.*;
008
009/**
010 * The Dispatcher support in LogixNG provides the ability to start, control, and terminate trains.
011 * <p>
012 * The ActiveTrain class represents a train.  An ActiveTrain can be started manually using the
013 * ActivateTrainFrame panel.  The panel also supports starting predefined trains that are stored
014 * as TrainInfo XML files.  A TrainInfo file can also be used to start a train using a program such
015 * as a Jython script, or in this case, LogixNG.
016 * <p>
017 * Since an active train is a temporary object, the Actions and Expressions use the train info file
018 * name as the reference to possible ActiveTrains.
019 * <p>
020 * The contents of a train info file are not unique.  While only one active train can be using a
021 * transit, multiple files can refer to the transit.
022 * <p>
023 * This class extends Bean so that ExpressionDispatcher objects can listen for changes to the
024 * _activeTrainMap.  These events are used to add and remove ActiveTrain listeners that capture
025 * ActiveTrain status and mode changes.
026 * <p>
027 * This class provides the following features:
028 * <ul>
029 * <li>Provides a list of active train info files.
030 * <li>Provides a map to link train info files names to the current ActiveTrain, if any.
031 * <li>Creates an ActiveTrain on behalf of the Dispatcher Load Action.
032 * <li>Provide the current ActiveTrain object, if any, for the a specific file name.
033 * <li>Gracefully terminate an active train that was started by a LogixNG action.
034 * </ul>
035 */
036public class DispatcherActiveTrainManager extends Bean implements InstanceManagerAutoDefault {
037
038    private final HashMap<String, ActiveTrain> _activeTrainMap;
039
040    public DispatcherActiveTrainManager() {
041        InstanceManager.setDefault(DispatcherActiveTrainManager.class, this);
042        _activeTrainMap = new HashMap<>();
043    }
044
045    /**
046     * Get a list of existing train info file names.
047     * @return an array of file names.
048     */
049    public List<String> getTrainInfoFileNames() {
050        TrainInfoFile tiFiles = new TrainInfoFile();
051        return new ArrayList<>(Arrays.asList(tiFiles.getTrainInfoFileNames()));
052    }
053
054    /**
055     * Get the current ActiveTrain for the specified file name.
056     * If the Dispatcher train no longer exists, remove the hash map entry.
057     * Notify ExpressionDispatcher objects when an ActiveTrain no longer exists.
058     * @param fileName The file name to be used for the lookup.
059     * @return the ActiveTrain instance or null.  Null can mean no related active train or no file name match.
060     */
061    public ActiveTrain getActiveTrain(String fileName) {
062        log.debug("1.1 -- getActiveTrain: {}", fileName);
063        if (fileName == null) return null;
064        if (! _activeTrainMap.containsKey(fileName)) return null;
065
066        ActiveTrain currentTrain = getDispatcherActiveTrain(fileName);
067        ActiveTrain hashTrain = _activeTrainMap.get(fileName);
068        log.debug("1.2 -- getActiveTrain: found {}, current = {}", hashTrain, currentTrain);
069
070        if (hashTrain != null && hashTrain != currentTrain) {
071            log.debug("1.3 -- getActiveTrain: Remove active train");
072            _activeTrainMap.remove(fileName);   // Update hash map
073            firePropertyChange("ActiveTrain", fileName, "");
074            hashTrain = null;
075        }
076        log.debug("1.4 -- getActiveTrain: return {}", hashTrain);
077        return hashTrain;
078    }
079
080    /**
081     * Create an ActiveTrain using the requested train info file.
082     * <p>
083     * If the create was successful, then any dispatcher active train with the same
084     * transit name as the train info file is by defintion the train that was just requested.
085     * @param fileName  The train info file name.
086     * @return the active train or null if the create failed.
087     */
088    public ActiveTrain createActiveTrain(String fileName) {
089        if (fileName == null) return null;
090
091        ActiveTrain oldTrain = getActiveTrain(fileName);
092        if (oldTrain != null) {
093            log.warn("2.1 -- createActiveTrain: train already exists");
094            return null;
095        }
096
097        DispatcherFrame df = InstanceManager.getDefault(DispatcherFrame.class);
098
099        ActiveTrain at = null;
100        int result = df.loadTrainFromTrainInfo(fileName);
101        if (result == 0) {
102            at = getDispatcherActiveTrain(fileName);
103            _activeTrainMap.put(fileName, at);
104            firePropertyChange("ActiveTrain", "", fileName);
105        }
106
107        log.debug("2.2 -- AT is {}", at);
108        return at;
109
110    }
111
112    /**
113     * Terminated the LogxiNG related active train if it still exists.
114     * @param fileName The train info file name.
115     */
116    public void terminateActiveTrain(String fileName) {
117        if (fileName == null) return;
118
119        ActiveTrain oldTrain = getActiveTrain(fileName);
120        if (oldTrain == null) return;
121
122        DispatcherFrame df = InstanceManager.getDefault(DispatcherFrame.class);
123        df.terminateActiveTrain(oldTrain, true, false);
124
125        _activeTrainMap.remove(fileName);
126        firePropertyChange("ActiveTrain", fileName, "");
127    }
128
129    /**
130     * Get the Dispatcher active train for the transit in the train info file.
131     * <p>
132     * The active train may or may not be related to the train info file or the ActiveTrain in
133     * the hash map.
134     * @param fileName The train info file name.
135     * @return the ActiveTrain if a train has the same transit as the file, null if there is no match
136     */
137    public ActiveTrain getDispatcherActiveTrain(String fileName) {
138        log.debug("4.1 -- getDispatcherActiveTrain: {}", fileName);
139        TrainInfo tif = getTrainInfoFile(fileName);
140        if (tif == null) return null;
141
142        String transitName = tif.getTransitName();
143
144        DispatcherFrame df = InstanceManager.getDefault(DispatcherFrame.class);
145        for (ActiveTrain train : df.getActiveTrainsList()) {
146            if (train.getTransitName().equals(transitName)) {
147                log.debug("4.2 -- getDispatcherActiveTrain: file = {}, train = {}", fileName, train);
148                return train;
149            }
150        }
151        return null;
152    }
153
154    /**
155     * Get the TrainInfo object for the requested file name.
156     * @param fileName The name of the train info file.
157     * @return a TrainInfo object or null if not found or invalid.
158     */
159    public TrainInfo getTrainInfoFile(String fileName) {
160        if (fileName == null) return null;
161
162        TrainInfoFile tiFiles = new TrainInfoFile();
163        try {
164            return tiFiles.readTrainInfo(fileName);
165        } catch (org.jdom2.JDOMException | java.io.IOException ex) {
166            log.warn("Unable to read the train info file for {}", fileName);
167        }
168        return null;
169    }
170
171    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DispatcherActiveTrainManager.class);
172}
173