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 (rs.getClass().equals(Car.class)) { 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 fileOut.flush(); 111 fileOut.close(); 112 } catch (IOException e) { 113 log.error("Exception while opening log file: {}", e.getLocalizedMessage()); 114 } 115 } 116 117 /* 118 * Returns the common log file for all rolling stock 119 */ 120 private File getFile() { 121 File fileLogger = null; 122 if (Setup.isEngineLoggerEnabled() || Setup.isCarLoggerEnabled()) { 123 try { 124 if (!checkFile(getFullLoggerFileName())) { 125 // The file/directory does not exist, create it before writing 126 fileLogger = new java.io.File(getFullLoggerFileName()); 127 File parentDir = fileLogger.getParentFile(); 128 if (!parentDir.exists()) { 129 if (!parentDir.mkdirs()) { 130 log.error("logger directory not created"); 131 } 132 } 133 if (fileLogger.createNewFile()) { 134 log.debug("new file created"); 135 // add header 136 fileOut(getHeader()); 137 } 138 } else { 139 fileLogger = new java.io.File(getFullLoggerFileName()); 140 } 141 } catch (IOException e) { 142 log.error("Exception while making logging directory: {}", e.getLocalizedMessage()); 143 } 144 } 145 return fileLogger; 146 } 147 148 private List<Object> getHeader() { 149 return Arrays.asList(new Object[]{Bundle.getMessage("Number"), 150 Bundle.getMessage("Road"), 151 Bundle.getMessage("Type"), 152 Bundle.getMessage("Load"), 153 Bundle.getMessage("Location"), 154 Bundle.getMessage("Track"), 155 Bundle.getMessage("FinalDestination"), 156 Bundle.getMessage("Track"), 157 Bundle.getMessage("Train"), 158 Bundle.getMessage("Moves"), 159 Bundle.getMessage("DateAndTime")}); 160 } 161 162 /* 163 * Gets the individual log file for a specific car or loco. 164 */ 165 private File getFile(RollingStock rs) { 166 File file = null; 167 if (Setup.isEngineLoggerEnabled() || Setup.isCarLoggerEnabled()) { 168 // create the logging file for this rolling stock 169 try { 170 if (!checkFile(getFullLoggerFileName(rs))) { 171 // The file/directory does not exist, create it before writing 172 file = new java.io.File(getFullLoggerFileName(rs)); 173 File parentDir = file.getParentFile(); 174 if (!parentDir.exists()) { 175 if (!parentDir.mkdirs()) { 176 log.error("logger directory not created"); 177 } 178 } 179 if (file.createNewFile()) { 180 log.debug("new file created"); 181 // add header 182 fileOut(getHeader(), rs); 183 } 184 } else { 185 file = new java.io.File(getFullLoggerFileName(rs)); 186 } 187 } catch (IOException e) { 188 log.error("Exception while making logging directory: {}", e.getLocalizedMessage()); 189 } 190 } 191 return file; 192 } 193 194 public String getFullLoggerFileName() { 195 return loggingDirectory + File.separator + getFileName(); 196 } 197 198 private String operationsDirectory = 199 OperationsSetupXml.getFileLocation() + OperationsSetupXml.getOperationsDirectoryName(); 200 201 private String loggingDirectory = operationsDirectory + File.separator + "logger"; // NOI18N 202 203 public String getDirectoryName() { 204 return loggingDirectory; 205 } 206 207 public void setDirectoryName(String name) { 208 loggingDirectory = name; 209 } 210 211 // Use the same common file even if the session crosses midnight 212 private String fileName; 213 214 public String getFileName() { 215 if (fileName == null) { 216 fileName = getDate() + ".csv"; // NOI18N 217 } 218 return fileName; 219 } 220 221 /** 222 * Individual files for each rolling stock stored in a directory called 223 * "rollingStock" inside the "logger" directory. 224 * @param rs The RollingStock to log. 225 * @return Full path name of log file. 226 * 227 */ 228 public String getFullLoggerFileName(RollingStock rs) { 229 if (!OperationsXml.checkFileName(rs.toString())) { // NOI18N 230 log.error("Rolling stock name ({}) must not contain reserved characters", rs); 231 return loggingDirectory + File.separator + "rollingStock" + File.separator + "ERROR" + ".csv"; // NOI18N 232 } 233 return loggingDirectory + File.separator + "rollingStock" + File.separator + rs.toString() + ".csv"; // NOI18N 234 } 235 236 private String getDate() { 237 Date date = Calendar.getInstance().getTime(); 238 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy_MM_dd"); // NOI18N 239 return simpleDateFormat.format(date); 240 } 241 242 /** 243 * Return the date and time in an MS Excel friendly format yyyy/MM/dd 244 * HH:mm:ss 245 * 246 */ 247 private String getTime() { 248 String time = Calendar.getInstance().getTime().toString(); 249 SimpleDateFormat dt = new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy"); // NOI18N 250 SimpleDateFormat dtout = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); // NOI18N 251 try { 252 return dtout.format(dt.parse(time)); 253 } catch (ParseException e) { 254 return time; // there was an issue, use the old format 255 } 256 } 257 258 private void addCarListeners() { 259 if (Setup.isCarLoggerEnabled() && !carLog) { 260 log.debug("Rolling Stock Logger adding car listerners"); 261 carLog = true; 262 List<Car> cars = InstanceManager.getDefault(CarManager.class).getList(); 263 cars.forEach(car -> car.addPropertyChangeListener(this)); 264 // listen for new rolling stock being added 265 InstanceManager.getDefault(CarManager.class).addPropertyChangeListener(this); 266 } 267 } 268 269 private void addEngineListeners() { 270 if (Setup.isEngineLoggerEnabled() && !engLog) { 271 engLog = true; 272 log.debug("Rolling Stock Logger adding engine listerners"); 273 List<Engine> engines = InstanceManager.getDefault(EngineManager.class).getList(); 274 engines.forEach(engine -> engine.addPropertyChangeListener(this)); 275 // listen for new rolling stock being added 276 InstanceManager.getDefault(EngineManager.class).addPropertyChangeListener(this); 277 } 278 } 279 280 private void removeCarListeners() { 281 if (carLog) { 282 log.debug("Rolling Stock Logger removing car listerners"); 283 List<Car> cars = InstanceManager.getDefault(CarManager.class).getList(); 284 cars.forEach(car -> car.removePropertyChangeListener(this)); 285 InstanceManager.getDefault(CarManager.class).removePropertyChangeListener(this); 286 } 287 carLog = false; 288 } 289 290 private void removeEngineListeners() { 291 if (engLog) { 292 log.debug("Rolling Stock Logger removing engine listerners"); 293 List<Engine> engines = InstanceManager.getDefault(EngineManager.class).getList(); 294 engines.forEach(engine -> engine.removePropertyChangeListener(this)); 295 InstanceManager.getDefault(EngineManager.class).removePropertyChangeListener(this); 296 } 297 engLog = false; 298 } 299 300 public void dispose() { 301 removeCarListeners(); 302 removeEngineListeners(); 303 } 304 305 @Override 306 public void propertyChange(PropertyChangeEvent e) { 307 if (e.getPropertyName().equals(RollingStock.TRACK_CHANGED_PROPERTY)) { 308 if (Control.SHOW_PROPERTY) { 309 log.debug("Logger sees property change for car {}", e.getSource()); 310 } 311 store((RollingStock) e.getSource()); 312 } 313 if (e.getPropertyName().equals(RollingStockManager.LISTLENGTH_CHANGED_PROPERTY)) { 314 if ((Integer) e.getNewValue() > (Integer) e.getOldValue()) { 315 // a car or engine has been added 316 if (e.getSource().getClass().equals(CarManager.class)) { 317 removeCarListeners(); 318 addCarListeners(); 319 } else if (e.getSource().getClass().equals(EngineManager.class)) { 320 removeEngineListeners(); 321 addEngineListeners(); 322 } 323 } 324 } 325 } 326 327 private final static Logger log = LoggerFactory.getLogger(RollingStockLogger.class); 328}