001package jmri.jmrit.operations.rollingstock; 002 003import java.beans.PropertyChangeEvent; 004import java.beans.PropertyChangeListener; 005import java.io.*; 006import java.nio.charset.StandardCharsets; 007import java.text.ParseException; 008import java.text.SimpleDateFormat; 009import java.util.*; 010 011import org.apache.commons.csv.CSVFormat; 012import org.apache.commons.csv.CSVPrinter; 013import org.slf4j.Logger; 014import org.slf4j.LoggerFactory; 015 016import jmri.InstanceManager; 017import jmri.InstanceManagerAutoDefault; 018import jmri.jmrit.XmlFile; 019import jmri.jmrit.operations.OperationsXml; 020import jmri.jmrit.operations.rollingstock.cars.Car; 021import jmri.jmrit.operations.rollingstock.cars.CarManager; 022import jmri.jmrit.operations.rollingstock.engines.Engine; 023import jmri.jmrit.operations.rollingstock.engines.EngineManager; 024import jmri.jmrit.operations.setup.*; 025 026/** 027 * Logs rolling stock movements by writing their locations to a file. 028 * 029 * @author Daniel Boudreau Copyright (C) 2010, 2016 030 */ 031public class RollingStockLogger extends XmlFile implements InstanceManagerAutoDefault, PropertyChangeListener { 032 033 private boolean engLog = false; // when true logging engine movements 034 private boolean carLog = false; // when true logging car movements 035 036 public RollingStockLogger() { 037 // nothing to do 038 } 039 040 public void enableCarLogging(boolean enable) { 041 if (enable) { 042 addCarListeners(); 043 } else { 044 removeCarListeners(); 045 } 046 } 047 048 public void enableEngineLogging(boolean enable) { 049 if (enable) { 050 addEngineListeners(); 051 } else { 052 removeEngineListeners(); 053 } 054 } 055 056 private boolean mustHaveTrack = true; // when true only updates that have a track are saved 057 058 private void store(RollingStock rs) { 059 060 if (rs.getTrack() == null && mustHaveTrack) { 061 return; 062 } 063 064 String carLoad = ""; 065 String carFinalDest = ""; 066 String carFinalDestTrack = ""; 067 if (Car.class.isInstance(rs)) { 068 Car car = (Car) rs; 069 carLoad = car.getLoadName(); 070 carFinalDest = car.getFinalDestinationName(); 071 carFinalDestTrack = car.getFinalDestinationTrackName(); 072 } 073 074 List<Object> line = Arrays.asList(new Object[]{rs.getNumber(), 075 rs.getRoadName(), 076 rs.getTypeName(), 077 carLoad, 078 rs.getLocationName(), 079 rs.getTrackName(), 080 carFinalDest, 081 carFinalDestTrack, 082 rs.getTrainName(), 083 rs.getMoves(), 084 getTime()}); 085 086 fileOut(line); // append line to common file 087 fileOut(line, rs); // append line to individual file 088 } 089 090 /* 091 * Appends one line to common log file. 092 */ 093 private void fileOut(List<Object> line) { 094 fileOut(line, getFile()); 095 } 096 097 /* 098 * Appends one line to the rolling stock's individual file. 099 */ 100 private void fileOut(List<Object> line, RollingStock rs) { 101 fileOut(line, getFile(rs)); 102 } 103 104 private void fileOut(List<Object> line, File file) { 105 // FileOutputStream is set to append 106 try (CSVPrinter fileOut = new CSVPrinter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file, true), 107 StandardCharsets.UTF_8)), CSVFormat.DEFAULT)) { 108 log.debug("Log: {}", line); 109 fileOut.printRecord(line); 110 } catch (IOException e) { 111 log.error("Exception while opening log file: {}", e.getLocalizedMessage()); 112 } 113 } 114 115 /* 116 * Returns the common log file for all rolling stock 117 */ 118 private File getFile() { 119 File fileLogger = null; 120 if (Setup.isEngineLoggerEnabled() || Setup.isCarLoggerEnabled()) { 121 try { 122 if (!checkFile(getFullLoggerFileName())) { 123 // The file/directory does not exist, create it before writing 124 fileLogger = new java.io.File(getFullLoggerFileName()); 125 File parentDir = fileLogger.getParentFile(); 126 if (!parentDir.exists()) { 127 if (!parentDir.mkdirs()) { 128 log.error("logger directory not created"); 129 } 130 } 131 if (fileLogger.createNewFile()) { 132 log.debug("new file created"); 133 // add header 134 fileOut(getHeader()); 135 } 136 } else { 137 fileLogger = new java.io.File(getFullLoggerFileName()); 138 } 139 } catch (IOException e) { 140 log.error("Exception while making logging directory: {}", e.getLocalizedMessage()); 141 } 142 } 143 return fileLogger; 144 } 145 146 private List<Object> getHeader() { 147 return Arrays.asList(new Object[]{Bundle.getMessage("Number"), 148 Bundle.getMessage("Road"), 149 Bundle.getMessage("Type"), 150 Bundle.getMessage("Load"), 151 Bundle.getMessage("Location"), 152 Bundle.getMessage("Track"), 153 Bundle.getMessage("FinalDestination"), 154 Bundle.getMessage("Track"), 155 Bundle.getMessage("Train"), 156 Bundle.getMessage("Moves"), 157 Bundle.getMessage("DateAndTime")}); 158 } 159 160 /* 161 * Gets the individual log file for a specific car or loco. 162 */ 163 private File getFile(RollingStock rs) { 164 File file = null; 165 if (Setup.isEngineLoggerEnabled() || Setup.isCarLoggerEnabled()) { 166 // create the logging file for this rolling stock 167 try { 168 if (!checkFile(getFullLoggerFileName(rs))) { 169 // The file/directory does not exist, create it before writing 170 file = new java.io.File(getFullLoggerFileName(rs)); 171 File parentDir = file.getParentFile(); 172 if (!parentDir.exists()) { 173 if (!parentDir.mkdirs()) { 174 log.error("logger directory not created"); 175 } 176 } 177 if (file.createNewFile()) { 178 log.debug("new file created"); 179 // add header 180 fileOut(getHeader(), rs); 181 } 182 } else { 183 file = new java.io.File(getFullLoggerFileName(rs)); 184 } 185 } catch (IOException e) { 186 log.error("Exception while making logging directory: {}", e.getLocalizedMessage()); 187 } 188 } 189 return file; 190 } 191 192 public String getFullLoggerFileName() { 193 return loggingDirectory + File.separator + getFileName(); 194 } 195 196 private String operationsDirectory = 197 OperationsSetupXml.getFileLocation() + OperationsSetupXml.getOperationsDirectoryName(); 198 199 private String loggingDirectory = operationsDirectory + File.separator + "logger"; // NOI18N 200 201 public String getDirectoryName() { 202 return loggingDirectory; 203 } 204 205 public void setDirectoryName(String name) { 206 loggingDirectory = name; 207 } 208 209 // Use the same common file even if the session crosses midnight 210 private String fileName; 211 212 public String getFileName() { 213 if (fileName == null) { 214 fileName = getDate() + ".csv"; // NOI18N 215 } 216 return fileName; 217 } 218 219 /** 220 * Individual files for each rolling stock stored in a directory called 221 * "rollingStock" inside the "logger" directory. 222 * @param rs The RollingStock to log. 223 * @return Full path name of log file. 224 * 225 */ 226 public String getFullLoggerFileName(RollingStock rs) { 227 if (!OperationsXml.checkFileName(rs.toString())) { // NOI18N 228 log.error("Rolling stock name ({}) must not contain reserved characters", rs); 229 return loggingDirectory + File.separator + "rollingStock" + File.separator + "ERROR" + ".csv"; // NOI18N 230 } 231 String rsName = rs.toString(); 232 // put clones in the same file as original car 233 if (rs.isClone()) { 234 rsName = rs.getRoadName() + " " + rs.getNumber().split(Car.CLONE_REGEX)[0]; 235 } 236 return loggingDirectory + File.separator + "rollingStock" + File.separator + rsName + ".csv"; // NOI18N 237 } 238 239 private String getDate() { 240 Date date = Calendar.getInstance().getTime(); 241 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy_MM_dd"); // NOI18N 242 return simpleDateFormat.format(date); 243 } 244 245 /** 246 * Return the date and time in an MS Excel friendly format yyyy/MM/dd 247 * HH:mm:ss 248 * 249 */ 250 private String getTime() { 251 String time = Calendar.getInstance().getTime().toString(); 252 SimpleDateFormat dt = new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy"); // NOI18N 253 SimpleDateFormat dtout = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); // NOI18N 254 try { 255 return dtout.format(dt.parse(time)); 256 } catch (ParseException e) { 257 return time; // there was an issue, use the old format 258 } 259 } 260 261 private void addCarListeners() { 262 if (Setup.isCarLoggerEnabled() && !carLog) { 263 log.debug("Rolling Stock Logger adding car listerners"); 264 carLog = true; 265 List<Car> cars = InstanceManager.getDefault(CarManager.class).getList(); 266 cars.forEach(car -> car.addPropertyChangeListener(this)); 267 // listen for new rolling stock being added 268 InstanceManager.getDefault(CarManager.class).addPropertyChangeListener(this); 269 } 270 } 271 272 private void addEngineListeners() { 273 if (Setup.isEngineLoggerEnabled() && !engLog) { 274 engLog = true; 275 log.debug("Rolling Stock Logger adding engine listerners"); 276 List<Engine> engines = InstanceManager.getDefault(EngineManager.class).getList(); 277 engines.forEach(engine -> engine.addPropertyChangeListener(this)); 278 // listen for new rolling stock being added 279 InstanceManager.getDefault(EngineManager.class).addPropertyChangeListener(this); 280 } 281 } 282 283 private void removeCarListeners() { 284 if (carLog) { 285 log.debug("Rolling Stock Logger removing car listerners"); 286 List<Car> cars = InstanceManager.getDefault(CarManager.class).getList(); 287 cars.forEach(car -> car.removePropertyChangeListener(this)); 288 InstanceManager.getDefault(CarManager.class).removePropertyChangeListener(this); 289 } 290 carLog = false; 291 } 292 293 private void removeEngineListeners() { 294 if (engLog) { 295 log.debug("Rolling Stock Logger removing engine listerners"); 296 List<Engine> engines = InstanceManager.getDefault(EngineManager.class).getList(); 297 engines.forEach(engine -> engine.removePropertyChangeListener(this)); 298 InstanceManager.getDefault(EngineManager.class).removePropertyChangeListener(this); 299 } 300 engLog = false; 301 } 302 303 public void dispose() { 304 removeCarListeners(); 305 removeEngineListeners(); 306 } 307 308 @Override 309 public void propertyChange(PropertyChangeEvent e) { 310 if (e.getPropertyName().equals(RollingStock.TRACK_CHANGED_PROPERTY)) { 311 if (Control.SHOW_PROPERTY) { 312 log.debug("Logger sees property change for car {}", e.getSource()); 313 } 314 store((RollingStock) e.getSource()); 315 } 316 if (e.getPropertyName().equals(RollingStockManager.LISTLENGTH_CHANGED_PROPERTY)) { 317 if ((Integer) e.getNewValue() > (Integer) e.getOldValue()) { 318 // a car or engine has been added 319 if (e.getSource().getClass().equals(CarManager.class)) { 320 removeCarListeners(); 321 addCarListeners(); 322 } else if (e.getSource().getClass().equals(EngineManager.class)) { 323 removeEngineListeners(); 324 addEngineListeners(); 325 } 326 } 327 } 328 } 329 330 private final static Logger log = LoggerFactory.getLogger(RollingStockLogger.class); 331}