001package jmri.jmris; 002 003import java.beans.PropertyChangeEvent; 004import java.beans.PropertyChangeListener; 005import java.io.IOException; 006import java.util.ArrayList; 007import java.util.HashMap; 008import java.util.List; 009import java.util.Map; 010import javax.management.Attribute; 011 012import jmri.JmriException; 013import jmri.jmrit.operations.locations.LocationManager; 014import jmri.jmrit.operations.rollingstock.engines.Engine; 015import jmri.jmrit.operations.trains.Train; 016import jmri.jmrit.operations.trains.TrainManager; 017 018import org.slf4j.Logger; 019import org.slf4j.LoggerFactory; 020 021/** 022 * Abstract interface between the JMRI operations and a network connection 023 * 024 * @author Paul Bender Copyright (C) 2010 025 * @author Dan Boudreau Copyright (C) 2012 (added checks for null train) 026 * @author Rodney Black Copyright (C) 2012 027 * @author Randall Wood Copyright (C) 2012, 2014 028 */ 029abstract public class AbstractOperationsServer implements PropertyChangeListener { 030 031 protected final TrainManager tm; 032 protected final LocationManager lm; 033 protected final HashMap<String, TrainListener> trains; 034 035// @SuppressWarnings("LeakingThisInConstructor") 036 public AbstractOperationsServer() { 037 tm = jmri.InstanceManager.getDefault(TrainManager.class); 038 tm.addPropertyChangeListener(this); 039 lm = jmri.InstanceManager.getDefault(LocationManager.class); 040 lm.addPropertyChangeListener(this); 041 addPropertyChangeListeners(); 042 trains = new HashMap<>(); 043 } 044 045 public abstract void sendTrainList(); 046 047 public abstract void sendLocationList(); 048 049 /** 050 * constructs a String containing the status of a train 051 * 052 * @param trainName is the name of the train. If not found in Operations, an 053 * error message is sent to the client. 054 * @return the train's status as known by Operations 055 * @throws IOException on failure to send an error message to the client 056 */ 057 public String constructTrainStatus(String trainName) throws IOException { 058 Train train = tm.getTrainByName(trainName); 059 if (train != null) { 060 return train.getStatus(); 061 } 062 sendErrorStatus("ERROR train name doesn't exist " + trainName); 063 return null; 064 } 065 066 /** 067 * constructs a String containing the location of a train 068 * 069 * @param trainName is the name of the desired train. If not found in 070 * Operations, an error message is sent to the client 071 * @return the train's location, as known by Operations 072 * @throws IOException on failure to send an error message to the client 073 */ 074 public String constructTrainLocation(String trainName) throws IOException { 075 Train train = tm.getTrainByName(trainName); 076 if (train != null) { 077 return train.getCurrentLocationName(); 078 } 079 sendErrorStatus("ERROR train name doesn't exist " + trainName); 080 return null; 081 } 082 083 /** 084 * constructs a String containing the location of a train 085 * 086 * @param trainName is the name of the desired train. If not found in 087 * Operations, an error message is sent to the client 088 * @param locationName is the name of the desired location. 089 * @return the train's location, as known by Operations 090 * @throws IOException on failure to send an error message to the client 091 */ 092 public String setTrainLocation(String trainName, String locationName) 093 throws IOException { 094 log.debug("Set train {} Location {}", trainName, locationName); 095 Train train = tm.getTrainByName(trainName); 096 if (train != null) { 097 if (!exactLocationName && train.move(locationName) 098 || exactLocationName && train.moveToNextLocation(locationName)) { 099 return constructTrainLocation(trainName); 100 } else { 101 sendErrorStatus("WARNING move of " + trainName + " to location " + locationName 102 + " failed. Train's current location " + train.getCurrentLocationName() 103 + " next location " + train.getNextLocationName()); 104 } 105 } else { 106 sendErrorStatus("ERROR train name doesn't exist " + trainName); 107 } 108 return null; 109 } 110 111 private static boolean exactLocationName = true; 112 113 public static void setExactLocationName(boolean enabled) { 114 exactLocationName = enabled; 115 } 116 117 public static boolean isExactLoationNameEnabled() { 118 return exactLocationName; 119 } 120 121 /** 122 * constructs a String containing the length of a train 123 * 124 * @param trainName is the name of the desired train. If not found in 125 * Operations, an error message is sent to the client 126 * @return the train's length, as known by Operations 127 * @throws IOException on failure to send an error message to the client 128 */ 129 public String constructTrainLength(String trainName) throws IOException { 130 Train train = tm.getTrainByName(trainName); 131 if (train != null) { 132 return String.valueOf(train.getTrainLength()); 133 } 134 sendErrorStatus("ERROR train name doesn't exist " + trainName); 135 return null; 136 } 137 138 /** 139 * constructs a String containing the tonnage of a train 140 * 141 * @param trainName is the name of the desired train. If not found in 142 * Operations, an error message is sent to the client 143 * @return the train's tonnage, as known by Operations 144 * @throws IOException on failure to send an error message to the client 145 */ 146 public String constructTrainWeight(String trainName) throws IOException { 147 Train train = tm.getTrainByName(trainName); 148 if (train != null) { 149 return String.valueOf(train.getTrainWeight()); 150 } 151 sendErrorStatus("ERROR train name doesn't exist " + trainName); 152 return null; 153 } 154 155 /** 156 * constructs a String containing the number of cars in a train 157 * 158 * @param trainName is the name of the desired train. If not found in 159 * Operations, an error message is sent to the client 160 * @return the number of cars in a train, as known by Operations 161 * @throws IOException on failure to send an error message to the client 162 */ 163 public String constructTrainNumberOfCars(String trainName) throws IOException { 164 Train train = tm.getTrainByName(trainName); 165 if (train != null) { 166 return String.valueOf(train.getNumberCarsInTrain()); 167 } 168 sendErrorStatus("ERROR train name doesn't exist " + trainName); 169 return null; 170 } 171 172 /** 173 * Constructs a String containing the road and number of lead loco, if 174 * there's one assigned to the train. 175 * 176 * @param trainName is the name of the desired train. If not found in 177 * Operations, an error message is sent to the client 178 * @return the lead loco 179 * @throws IOException on failure to send an error message to the client 180 */ 181 public String constructTrainLeadLoco(String trainName) throws IOException { 182 Train train = tm.getTrainByName(trainName); 183 if (train != null) { 184 Engine leadEngine = train.getLeadEngine(); 185 if (leadEngine != null) { 186 return leadEngine.toString(); 187 } 188 } else { // train is null 189 sendErrorStatus("ERROR train name doesn't exist " + trainName); 190 } 191 return null; 192 } 193 194 /** 195 * constructs a String containing the caboose on a train 196 * 197 * @param trainName is the name of the desired train. If not found in 198 * Operations, an error message is sent to the client 199 * @return the caboose on a train, as known by Operations 200 * @throws IOException on failure to send an error message to the client 201 */ 202 public String constructTrainCaboose(String trainName) throws IOException { 203 Train train = tm.getTrainByName(trainName); 204 if (train != null) { 205 return train.getCabooseRoadAndNumber(); 206 } 207 sendErrorStatus("ERROR train name doesn't exist " + trainName); 208 return null; 209 } 210 211 /** 212 * tells Operations that a train has terminated. If not found in Operations, 213 * an error message is sent to the client 214 * 215 * @param trainName is the name of the train 216 * @return the termination String 217 * @throws IOException on failure to send an error message to the client 218 */ 219 public String terminateTrain(String trainName) throws IOException { 220 Train train = tm.getTrainByName(trainName); 221 if (train != null) { 222 train.terminate(); 223 return constructTrainStatus(trainName); 224 } 225 sendErrorStatus("ERROR train name doesn't exist " + trainName); 226 return null; 227 } 228 229 /** 230 * sends the full status for a train to a client 231 * 232 * @param train is the name of the desired train. If not found, an error 233 * is sent to the client 234 * @throws IOException on failure to send an error message 235 */ 236 //public void sendFullStatus(String trainName) throws IOException { 237 // Train train = tm.getTrainByName(trainName); 238 // if (train != null) { 239 // sendFullStatus(train); 240 // } else { 241 // sendErrorStatus("ERROR train name doesn't exist " + trainName); 242 // } 243 //} 244 245 /** 246 * sends the full status for a train to a client 247 * 248 * @param train is the Train object we are sending information about. 249 * @throws IOException on failure to send an error message 250 */ 251 public abstract void sendFullStatus(Train train) throws IOException; 252 253 private void addPropertyChangeListeners() { 254 List<Train> trainList = tm.getTrainsByNameList(); 255 for (Train train : trainList) { 256 train.addPropertyChangeListener(this); 257 } 258 } 259 260 private void removePropertyChangeListeners() { 261 List<Train> trainList = tm.getTrainsByNameList(); 262 for (Train train : trainList) { 263 train.removePropertyChangeListener(this); 264 } 265 } 266 267 @Override 268 public abstract void propertyChange(PropertyChangeEvent e); 269 270 synchronized protected void addTrainToList(String trainId) { 271 if (!trains.containsKey(trainId)) { 272 trains.put(trainId, new TrainListener(trainId)); 273 jmri.InstanceManager.getDefault(TrainManager.class).getTrainById(trainId).addPropertyChangeListener(trains.get(trainId)); 274 } 275 } 276 277 synchronized protected void removeTrainFromList(String trainId) { 278 if (trains.containsKey(trainId)) { 279 jmri.InstanceManager.getDefault(TrainManager.class).getTrainById(trainId).removePropertyChangeListener(trains.get(trainId)); 280 trains.remove(trainId); 281 } 282 } 283 284 protected TrainListener getListener(String trainId) { 285 return new TrainListener(trainId); 286 } 287 288 public void dispose() { 289 if (tm != null) { 290 tm.removePropertyChangeListener(this); 291 removePropertyChangeListeners(); 292 } 293 if (lm != null) { 294 lm.removePropertyChangeListener(this); 295 } 296 for (Map.Entry<String, TrainListener> train : this.trains.entrySet()) { 297 jmri.InstanceManager.getDefault(TrainManager.class).getTrainById(train.getKey()).removePropertyChangeListener(train.getValue()); 298 } 299 this.trains.clear(); 300 } 301 302 /* 303 * Protocol Specific Abstract Functions 304 */ 305 abstract public void sendMessage(ArrayList<Attribute> contents) throws IOException; 306 307 abstract public void sendErrorStatus(String errorStatus) throws IOException; 308 309 abstract public void parseStatus(String statusString) throws JmriException, IOException; 310 311 private final static Logger log = LoggerFactory.getLogger(AbstractOperationsServer.class); 312 313 /* 314 * This isn't currently used for operations 315 */ 316 protected class TrainListener implements PropertyChangeListener { 317 318 private final Train train; 319 320 protected TrainListener(String trainId) { 321 this.train = jmri.InstanceManager.getDefault(TrainManager.class).getTrainById(trainId); 322 } 323 324 @Override 325 public void propertyChange(PropertyChangeEvent e) { 326 try { 327 sendFullStatus(this.train); 328 } catch (IOException ie) { 329 log.debug("Error Sending Status"); 330 // if we get an error, de-register 331 this.train.removePropertyChangeListener(this); 332 removeTrainFromList(this.train.getId()); 333 } 334 } 335 } 336 337}