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