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