001package jmri.jmrit.operations.trains.csv; 002 003import java.io.*; 004import java.nio.charset.StandardCharsets; 005import java.util.ArrayList; 006import java.util.List; 007 008import org.apache.commons.csv.CSVFormat; 009import org.apache.commons.csv.CSVPrinter; 010import org.slf4j.Logger; 011import org.slf4j.LoggerFactory; 012 013import jmri.InstanceManager; 014import jmri.jmrit.operations.locations.Location; 015import jmri.jmrit.operations.rollingstock.cars.Car; 016import jmri.jmrit.operations.rollingstock.cars.CarManager; 017import jmri.jmrit.operations.rollingstock.engines.Engine; 018import jmri.jmrit.operations.rollingstock.engines.EngineManager; 019import jmri.jmrit.operations.routes.Route; 020import jmri.jmrit.operations.routes.RouteLocation; 021import jmri.jmrit.operations.setup.Setup; 022import jmri.jmrit.operations.trains.*; 023 024/** 025 * Builds a comma separated value (csv) switch list for a location on the 026 * railroad. 027 * 028 * @author Daniel Boudreau (C) Copyright 2011, 2013, 2014, 2015, 2021 029 * 030 * 031 */ 032public class TrainCsvSwitchLists extends TrainCsvCommon { 033 034 /** 035 * builds a csv file containing the switch list for a location 036 * 037 * @param location The Location requesting a switch list. 038 * 039 * @return File 040 */ 041 public File buildSwitchList(Location location) { 042 if (!Setup.isGenerateCsvSwitchListEnabled()) { 043 return null; // done, not enabled 044 } 045 046 // Append switch list data if not operating in real time 047 boolean append = false; // add text to end of file when true 048 049 if (!Setup.isSwitchListRealTime()) { 050 if (location.getStatus().equals(Location.UPDATED)) { 051 return null; // nothing to add 052 } 053 append = location.getSwitchListState() == Location.SW_APPEND; 054 } 055 // create CSV switch list file 056 File file = InstanceManager.getDefault(TrainManagerXml.class).createCsvSwitchListFile(location.getName()); 057 058 log.debug("Append CSV file: {} for location ({})", append, location.getName()); 059 060 // need to delete CSV data from file from tags "END" which is car hold list for 061 // this location 062 if (append) { 063 trimCvsFile(file, location); 064 } 065 066 try (CSVPrinter fileOut = new CSVPrinter( 067 new OutputStreamWriter(new FileOutputStream(file, append), StandardCharsets.UTF_8), 068 CSVFormat.DEFAULT)) { 069 if (!append) { 070 // build header 071 printHeader(fileOut); 072 fileOut.printRecord("SWL", Bundle.getMessage("csvSwitchList")); // NOI18N 073 printRailroadName(fileOut, Setup.getRailroadName()); 074 printLocationName(fileOut, location.getSplitName()); 075 printPrinterName(fileOut, location.getDefaultPrinterName()); 076 printLocationSwitchListComment(fileOut, location); 077 printLocationComment(fileOut, location); 078 } 079 printValidity(fileOut, getDate(true)); 080 081 // get a list of trains sorted by arrival time 082 List<Train> trains = InstanceManager.getDefault(TrainManager.class) 083 .getTrainsArrivingThisLocationList(location); 084 for (Train train : trains) { 085 if (!train.isBuilt()) { 086 continue; // train wasn't built so skip 087 } 088 if (!Setup.isSwitchListRealTime() && train.getSwitchListStatus().equals(Train.PRINTED)) { 089 continue; // already loaded this train 090 } 091 int pickupCars = 0; 092 int dropCars = 0; 093 int stops = 1; 094 boolean trainDone = false; 095 List<Car> carList = InstanceManager.getDefault(CarManager.class).getByTrainDestinationList(train); 096 List<Engine> enginesList = InstanceManager.getDefault(EngineManager.class) 097 .getByTrainBlockingList(train); 098 // does the train stop once or more at this location? 099 Route route = train.getRoute(); 100 if (route == null) { 101 continue; // no route for this train 102 } 103 List<RouteLocation> routeList = route.getLocationsBySequenceList(); 104 RouteLocation rlPrevious = null; 105 // need to know where in the route we are for the various comments 106 for (RouteLocation rl : routeList) { 107 if (!rl.getSplitName().equals(location.getSplitName())) { 108 rlPrevious = rl; 109 continue; 110 } 111 String expectedArrivalTime = train.getExpectedArrivalTime(rl); 112 if (expectedArrivalTime.equals(Train.ALREADY_SERVICED)) { 113 trainDone = true; 114 } 115 // First time a train stops at a location provide: 116 // train name 117 // train description 118 // if the train has started its route 119 // the arrival time or relative time if the train has started its route 120 // the departure location 121 // the departure time 122 // the train's direction when it arrives 123 // if it terminates at this location 124 if (stops == 1) { 125 // newLine(fileOut); 126 printTrainName(fileOut, train.getName()); 127 printTrainDescription(fileOut, train.getDescription()); 128 printTrainComment(fileOut, train); 129 printRouteComment(fileOut, train); 130 131 if (train.isTrainEnRoute()) { 132 fileOut.printRecord("TIR", Bundle.getMessage("csvTrainEnRoute")); // NOI18N 133 printEstimatedTimeEnRoute(fileOut, expectedArrivalTime); 134 } else { 135 fileOut.printRecord("DL", Bundle.getMessage("csvDepartureLocationName"), 136 splitString(train.getTrainDepartsName())); // NOI18N 137 printDepartureTime(fileOut, train.getFormatedDepartureTime()); 138 if (rl == train.getTrainDepartsRouteLocation() && !train.isLocalSwitcher()) { 139 printTrainDeparts(fileOut, rl.getSplitName(), rl.getTrainDirectionString()); 140 } 141 if (rl != train.getTrainDepartsRouteLocation()) { 142 printExpectedTimeArrival(fileOut, expectedArrivalTime); 143 printTrainArrives(fileOut, rl.getSplitName(), rl.getTrainDirectionString()); 144 } 145 } 146 if (rl == train.getTrainTerminatesRouteLocation()) { 147 printTrainTerminates(fileOut, rl.getSplitName()); 148 } 149 } 150 if (stops > 1) { 151 // Print visit number, etc. only if previous location wasn't the same 152 if (rlPrevious == null || 153 !rl.getSplitName().equals(rlPrevious.getSplitName())) { 154 // After the first time a train stops at a location provide: 155 // if the train has started its route 156 // the arrival time or relative time if the train has started its route 157 // the train's direction when it arrives 158 // if it terminate at this location 159 160 fileOut.printRecord("VN", Bundle.getMessage("csvVisitNumber"), stops); 161 if (train.isTrainEnRoute()) { 162 printEstimatedTimeEnRoute(fileOut, expectedArrivalTime); 163 } else { 164 printExpectedTimeArrival(fileOut, expectedArrivalTime); 165 } 166 printTrainArrives(fileOut, rl.getSplitName(), rl.getTrainDirectionString()); 167 if (rl == train.getTrainTerminatesRouteLocation()) { 168 printTrainTerminates(fileOut, rl.getSplitName()); 169 } 170 } else { 171 stops--; // don't bump stop count, same location 172 // Does the train change direction? 173 if (rl.getTrainDirection() != rlPrevious.getTrainDirection()) { 174 fileOut.printRecord("TDC", Bundle.getMessage("csvTrainChangesDirection"), 175 rl.getTrainDirectionString()); // NOI18N 176 } 177 } 178 } 179 180 rlPrevious = rl; 181 printRouteLocationComment(fileOut, rl); 182 printTrackComments(fileOut, rl, carList); 183 184 // engine change or helper service? 185 checkForEngineOrCabooseChange(fileOut, train, rl); 186 187 // go through the list of engines and determine if the engine departs here 188 for (Engine engine : enginesList) { 189 if (engine.getRouteLocation() == rl && engine.getTrack() != null) { 190 printEngine(fileOut, engine, "PL", Bundle.getMessage("csvPickUpLoco")); 191 } 192 } 193 // now block out train 194 // caboose or FRED is placed at end of the train 195 // passenger cars are already blocked in the car list 196 // passenger cars with negative block numbers are placed at 197 // the front of the train, positive numbers at the end of 198 // the train. 199 for (RouteLocation rld : train.getTrainBlockingOrder()) { 200 for (Car car : carList) { 201 if (isNextCar(car, rl, rld)) { 202 pickupCars++; 203 int count = 0; 204 if (car.isUtility()) { 205 count = countPickupUtilityCars(carList, car, !IS_MANIFEST); 206 if (count == 0) { 207 continue; // already done this set of 208 // utility cars 209 } 210 } 211 printCar(fileOut, car, "PC", Bundle.getMessage("csvPickUpCar"), count); 212 } 213 } 214 } 215 216 for (Engine engine : enginesList) { 217 if (engine.getRouteDestination() == rl) { 218 printEngine(fileOut, engine, "SL", Bundle.getMessage("csvSetOutLoco")); 219 } 220 } 221 // now do car set outs 222 for (Car car : carList) { 223 if (car.getRouteDestination() == rl) { 224 dropCars++; 225 int count = 0; 226 if (car.isUtility()) { 227 count = countSetoutUtilityCars(carList, car, !LOCAL, !IS_MANIFEST); 228 if (count == 0) { 229 continue; // already done this set of utility cars 230 } 231 } 232 printCar(fileOut, car, "SC", Bundle.getMessage("csvSetOutCar"), count); 233 } 234 } 235 stops++; 236 if (rl != train.getTrainTerminatesRouteLocation()) { 237 printTrainLength(fileOut, train.getTrainLength(rl), train.getNumberEmptyCarsInTrain(rl), 238 train.getNumberCarsInTrain(rl)); 239 printTrainWeight(fileOut, train.getTrainWeight(rl)); 240 } 241 } 242 if (trainDone && pickupCars == 0 && dropCars == 0) { 243 fileOut.printRecord("TDONE", Bundle.getMessage("csvTrainHasAlreadyServiced")); 244 } else if (stops > 1) { 245 if (pickupCars == 0) { 246 fileOut.printRecord("NCPU", Bundle.getMessage("csvNoCarPickUp")); 247 } 248 if (dropCars == 0) { 249 fileOut.printRecord("NCSO", Bundle.getMessage("csvNoCarSetOut")); 250 } 251 fileOut.printRecord("TEND", Bundle.getMessage("csvTrainEnd"), train.getName()); // done with this 252 // train // NOI18N 253 } 254 } 255 printEnd(fileOut); // done with switch list 256 257 if (Setup.isSwitchListRealTime() && Setup.isPrintTrackSummaryEnabled()) { 258 // now list hold cars 259 List<Car> rsByLocation = InstanceManager.getDefault(CarManager.class).getByLocationList(); 260 List<Car> carList = new ArrayList<>(); 261 for (Car rs : rsByLocation) { 262 if (rs.getLocation() != null && 263 rs.getLocation().getSplitName().equals(location.getSplitName()) && 264 rs.getRouteLocation() == null) { 265 carList.add(rs); 266 } 267 } 268 clearUtilityCarTypes(); // list utility cars by quantity 269 for (Car car : carList) { 270 int count = 0; 271 if (car.isUtility()) { 272 count = countPickupUtilityCars(carList, car, !IS_MANIFEST); 273 if (count == 0) { 274 continue; // already done this set of utility cars 275 } 276 } 277 printCar(fileOut, car, "HOLD", Bundle.getMessage("csvHoldCar"), count); 278 } 279 } 280 printEnd(fileOut); // done with hold cars 281 282 // Are there any cars that need to be found? 283 listCarsLocationUnknown(fileOut); 284 fileOut.flush(); 285 fileOut.close(); 286 } catch (IOException e) { 287 log.error("Can not open CSV switch list file: {}", e.getLocalizedMessage()); 288 return null; 289 } 290 return file; 291 } 292 293 protected final void printEnd(CSVPrinter printer) throws IOException { 294 printer.printRecord("END", Bundle.getMessage("csvEnd")); // NOI18N 295 } 296 297 protected final void printExpectedTimeArrival(CSVPrinter printer, String time) throws IOException { 298 printer.printRecord("ETA", Bundle.getMessage("csvExpectedTimeArrival"), time); // NOI18N 299 } 300 301 protected final void printEstimatedTimeEnRoute(CSVPrinter printer, String time) throws IOException { 302 printer.printRecord("ETE", Bundle.getMessage("csvEstimatedTimeEnRoute"), time); // NOI18N 303 } 304 305 protected final void printTrainArrives(CSVPrinter printer, String name, String direction) throws IOException { 306 printer.printRecord("TA", Bundle.getMessage("csvTrainArrives"), name, direction); // NOI18N 307 } 308 309 /* 310 * Used to delete CSV data from file from tags "END" which is car hold list for 311 * this location. Creates a backup file and then copies the needed lines back 312 * into the original file. 313 */ 314 private void trimCvsFile(File file, Location location) { 315 // need to delete CSV data from file from tags "END" which is car hold list for 316 // this location 317 try (PrintWriter fileOut = new PrintWriter( 318 new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)), true)) { 319 File backupFile = new File( 320 InstanceManager.getDefault(TrainManagerXml.class).backupFileName(file.getAbsolutePath())); 321 try (BufferedReader in = new BufferedReader( 322 new InputStreamReader(new FileInputStream(backupFile), StandardCharsets.UTF_8))) { 323 while (true) { 324 String line = in.readLine(); 325 if (line == null) { 326 break; // done 327 } 328 if (!line.startsWith("END")) { 329 fileOut.println(line); 330 } else { 331 break; // done 332 } 333 } 334 in.close(); 335 } catch (FileNotFoundException e) { 336 log.error("Can not open CSV switch list file: {}", file.getName()); 337 } 338 fileOut.flush(); 339 fileOut.close(); 340 } catch (IOException e) { 341 log.error("Can not open CSV switch list file: {}", e.getLocalizedMessage()); 342 } 343 } 344 345 private final static Logger log = LoggerFactory.getLogger(TrainCsvSwitchLists.class); 346}