001package jmri.jmrit.operations.trains; 002 003import java.io.*; 004import java.nio.charset.StandardCharsets; 005import java.text.MessageFormat; 006import java.util.List; 007 008import org.slf4j.Logger; 009import org.slf4j.LoggerFactory; 010 011import jmri.InstanceManager; 012import jmri.jmrit.operations.locations.Location; 013import jmri.jmrit.operations.rollingstock.cars.Car; 014import jmri.jmrit.operations.rollingstock.engines.Engine; 015import jmri.jmrit.operations.routes.Route; 016import jmri.jmrit.operations.routes.RouteLocation; 017import jmri.jmrit.operations.setup.Setup; 018import jmri.jmrit.operations.trains.schedules.TrainSchedule; 019import jmri.jmrit.operations.trains.schedules.TrainScheduleManager; 020 021/** 022 * Builds a train's manifest. User has the ability to modify the text of the 023 * messages which can cause an IllegalArgumentException. Some messages have more 024 * arguments than the default message allowing the user to customize the message 025 * to their liking. 026 * 027 * @author Daniel Boudreau Copyright (C) 2011, 2012, 2013, 2015 028 * 029 */ 030public class TrainManifest extends TrainCommon { 031 032 private static final Logger log = LoggerFactory.getLogger(TrainManifest.class); 033 034 String messageFormatText = ""; // the text being formated in case there's an exception 035 036 public TrainManifest(Train train) { 037 // create manifest file 038 File file = InstanceManager.getDefault(TrainManagerXml.class).createTrainManifestFile(train.getName()); 039 PrintWriter fileOut; 040 041 try { 042 fileOut = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)), 043 true); 044 } catch (IOException e) { 045 log.error("Can not open train manifest file: {}", file.getName()); 046 return; 047 } 048 049 try { 050 // build header 051 if (!train.getRailroadName().equals(Train.NONE)) { 052 newLine(fileOut, train.getRailroadName()); 053 } else { 054 newLine(fileOut, Setup.getRailroadName()); 055 } 056 newLine(fileOut); // empty line 057 newLine(fileOut, MessageFormat.format(messageFormatText = TrainManifestText.getStringManifestForTrain(), 058 new Object[]{train.getName(), train.getDescription()})); 059 060 String valid = MessageFormat.format(messageFormatText = TrainManifestText.getStringValid(), 061 new Object[]{getDate(true)}); 062 063 if (Setup.isPrintTrainScheduleNameEnabled()) { 064 TrainSchedule sch = InstanceManager.getDefault(TrainScheduleManager.class).getActiveSchedule(); 065 if (sch != null) { 066 valid = valid + " (" + sch.getName() + ")"; 067 } 068 } 069 if (Setup.isPrintValidEnabled()) { 070 newLine(fileOut, valid); 071 } 072 073 if (!train.getCommentWithColor().equals(Train.NONE)) { 074 newLine(fileOut, train.getCommentWithColor()); 075 } 076 077 List<Engine> engineList = engineManager.getByTrainBlockingList(train); 078 079 if (Setup.isPrintRouteCommentsEnabled() && !train.getRoute().getComment().equals(Route.NONE)) { 080 newLine(fileOut, train.getRoute().getComment()); 081 } 082 083 List<Car> carList = carManager.getByTrainDestinationList(train); 084 log.debug("Train has {} cars assigned to it", carList.size()); 085 086 boolean hadWork = false; 087 boolean noWork = false; 088 String previousRouteLocationName = null; 089 List<RouteLocation> routeList = train.getRoute().getLocationsBySequenceList(); 090 091 /* 092 * Go through the train's route and print out the work for each 093 * location. Locations with "similar" names are combined to look 094 * like one location. 095 */ 096 for (RouteLocation rl : routeList) { 097 boolean printHeader = false; 098 boolean hasWork = isThereWorkAtLocation(carList, engineList, rl); 099 // print info only if new location 100 String routeLocationName = rl.getSplitName(); 101 if (!routeLocationName.equals(previousRouteLocationName) || (hasWork && !hadWork)) { 102 if (hasWork) { 103 newLine(fileOut); 104 hadWork = true; 105 noWork = false; 106 printHeader = true; 107 String expectedArrivalTime = train.getExpectedArrivalTime(rl); 108 String workAt = MessageFormat.format(messageFormatText = TrainManifestText 109 .getStringScheduledWork(), new Object[]{routeLocationName, train.getName(), 110 train.getDescription(), rl.getLocation().getDivisionName()}); 111 if (!train.isShowArrivalAndDepartureTimesEnabled()) { 112 newLine(fileOut, workAt); 113 } else if (rl == train.getTrainDepartsRouteLocation()) { 114 newLine(fileOut, MessageFormat.format(messageFormatText = TrainManifestText 115 .getStringWorkDepartureTime(), 116 new Object[]{routeLocationName, 117 train.getFormatedDepartureTime(), train.getName(), 118 train.getDescription(), rl.getLocation().getDivisionName()})); 119 } else if (!rl.getDepartureTime().equals(RouteLocation.NONE)) { 120 newLine(fileOut, MessageFormat.format(messageFormatText = TrainManifestText 121 .getStringWorkDepartureTime(), 122 new Object[]{routeLocationName, 123 rl.getFormatedDepartureTime(), train.getName(), train.getDescription(), 124 rl.getLocation().getDivisionName()})); 125 } else if (Setup.isUseDepartureTimeEnabled() && 126 rl != train.getTrainTerminatesRouteLocation()) { 127 newLine(fileOut, MessageFormat.format(messageFormatText = TrainManifestText 128 .getStringWorkDepartureTime(), 129 new Object[]{routeLocationName, 130 train.getExpectedDepartureTime(rl), train.getName(), 131 train.getDescription(), rl.getLocation().getDivisionName()})); 132 } else if (!expectedArrivalTime.equals(Train.ALREADY_SERVICED)) { 133 newLine(fileOut, MessageFormat.format(messageFormatText = TrainManifestText 134 .getStringWorkArrivalTime(), 135 new Object[]{routeLocationName, expectedArrivalTime, 136 train.getName(), train.getDescription(), 137 rl.getLocation().getDivisionName()})); 138 } else { 139 newLine(fileOut, workAt); 140 } 141 // add route location comment 142 if (!rl.getComment().trim().equals(RouteLocation.NONE)) { 143 newLine(fileOut, rl.getFormatedColorComment()); 144 } 145 146 // add location comment 147 if (Setup.isPrintLocationCommentsEnabled() && 148 !rl.getLocation().getCommentWithColor().equals(Location.NONE)) { 149 newLine(fileOut, rl.getLocation().getCommentWithColor()); 150 } 151 } 152 } 153 // remember location name 154 previousRouteLocationName = routeLocationName; 155 156 // add track comments 157 printTrackComments(fileOut, rl, carList, IS_MANIFEST); 158 159 // engine change or helper service? 160 if (train.getSecondLegOptions() != Train.NO_CABOOSE_OR_FRED) { 161 if (rl == train.getSecondLegStartRouteLocation()) { 162 printChange(fileOut, rl, train, train.getSecondLegOptions()); 163 } 164 if (rl == train.getSecondLegEndRouteLocation() && 165 train.getSecondLegOptions() == Train.HELPER_ENGINES) { 166 newLine(fileOut, 167 MessageFormat.format(messageFormatText = TrainManifestText.getStringRemoveHelpers(), 168 new Object[] { rl.getSplitName(), train.getName(), 169 train.getDescription(), train.getSecondLegNumberEngines(), 170 train.getSecondLegEngineModel(), train.getSecondLegEngineRoad() })); 171 } 172 } 173 if (train.getThirdLegOptions() != Train.NO_CABOOSE_OR_FRED) { 174 if (rl == train.getThirdLegStartRouteLocation()) { 175 printChange(fileOut, rl, train, train.getThirdLegOptions()); 176 } 177 if (rl == train.getThirdLegEndRouteLocation() && 178 train.getThirdLegOptions() == Train.HELPER_ENGINES) { 179 newLine(fileOut, 180 MessageFormat.format(messageFormatText = TrainManifestText.getStringRemoveHelpers(), 181 new Object[] { rl.getSplitName(), train.getName(), 182 train.getDescription(), train.getThirdLegNumberEngines(), 183 train.getThirdLegEngineModel(), train.getThirdLegEngineRoad() })); 184 } 185 } 186 187 if (Setup.getManifestFormat().equals(Setup.STANDARD_FORMAT)) { 188 pickupEngines(fileOut, engineList, rl, IS_MANIFEST); 189 // if switcher show loco drop at end of list 190 if (train.isLocalSwitcher()) { 191 blockCarsByTrack(fileOut, train, carList, rl, printHeader, IS_MANIFEST); 192 dropEngines(fileOut, engineList, rl, IS_MANIFEST); 193 } else { 194 dropEngines(fileOut, engineList, rl, IS_MANIFEST); 195 blockCarsByTrack(fileOut, train, carList, rl, printHeader, IS_MANIFEST); 196 } 197 } else if (Setup.getManifestFormat().equals(Setup.TWO_COLUMN_FORMAT)) { 198 blockLocosTwoColumn(fileOut, engineList, rl, IS_MANIFEST); 199 blockCarsTwoColumn(fileOut, train, carList, rl, printHeader, IS_MANIFEST); 200 } else { 201 blockLocosTwoColumn(fileOut, engineList, rl, IS_MANIFEST); 202 blockCarsByTrackNameTwoColumn(fileOut, train, carList, rl, printHeader, IS_MANIFEST); 203 } 204 205 if (rl != train.getTrainTerminatesRouteLocation()) { 206 // Is the next location the same as the current? 207 RouteLocation rlNext = train.getRoute().getNextRouteLocation(rl); 208 if (routeLocationName.equals(rlNext.getSplitName())) { 209 continue; 210 } 211 if (hadWork) { 212 hadWork = false; 213 if (Setup.isPrintHeadersEnabled() || !Setup.getManifestFormat().equals(Setup.STANDARD_FORMAT)) { 214 printHorizontalLine(fileOut, IS_MANIFEST); 215 } 216 String trainDeparts = ""; 217 if (Setup.isPrintLoadsAndEmptiesEnabled()) { 218 int emptyCars = train.getNumberEmptyCarsInTrain(rl); 219 // Message format: Train departs Boston Westbound with 4 loads, 8 empties, 450 feet, 3000 tons 220 trainDeparts = MessageFormat.format(messageFormatText = TrainManifestText 221 .getStringTrainDepartsLoads(), new Object[]{routeLocationName, 222 rl.getTrainDirectionString(), train.getNumberCarsInTrain(rl) - emptyCars, emptyCars, 223 train.getTrainLength(rl), Setup.getLengthUnit().toLowerCase(), 224 train.getTrainWeight(rl), train.getTrainTerminatesName(), train.getName()}); 225 } else { 226 // Message format: Train departs Boston Westbound with 12 cars, 450 feet, 3000 tons 227 trainDeparts = MessageFormat.format(messageFormatText = TrainManifestText 228 .getStringTrainDepartsCars(), new Object[]{routeLocationName, 229 rl.getTrainDirectionString(), train.getNumberCarsInTrain(rl), train.getTrainLength(rl), 230 Setup.getLengthUnit().toLowerCase(), train.getTrainWeight(rl), 231 train.getTrainTerminatesName(), train.getName()}); 232 } 233 newLine(fileOut, trainDeparts); 234 } else { 235 // no work at this location 236 if (!noWork) { 237 newLine(fileOut); 238 } 239 noWork = true; 240 String s = MessageFormat.format(messageFormatText = TrainManifestText 241 .getStringNoScheduledWork(), new Object[]{routeLocationName, train.getName(), 242 train.getDescription(), rl.getLocation().getDivisionName()}); 243 // if a route comment, then only use location name and route comment, useful for passenger 244 // trains 245 if (!rl.getComment().equals(RouteLocation.NONE)) { 246 s = routeLocationName; 247 if (!rl.getComment().trim().isEmpty()) { 248 s = MessageFormat.format(messageFormatText = TrainManifestText 249 .getStringNoScheduledWorkWithRouteComment(), 250 new Object[]{routeLocationName, rl.getFormatedColorComment(), train.getName(), 251 train.getDescription(), rl.getLocation().getDivisionName()}); 252 } 253 } 254 if (train.isShowArrivalAndDepartureTimesEnabled()) { 255 if (rl == train.getTrainDepartsRouteLocation()) { 256 s += MessageFormat.format(messageFormatText = TrainManifestText 257 .getStringDepartTime(), new Object[]{train.getFormatedDepartureTime()}); 258 } else if (!rl.getDepartureTime().equals(RouteLocation.NONE)) { 259 s += MessageFormat.format(messageFormatText = TrainManifestText 260 .getStringDepartTime(), new Object[]{rl.getFormatedDepartureTime()}); 261 } else if (Setup.isUseDepartureTimeEnabled() && 262 !rl.getComment().equals(RouteLocation.NONE)) { 263 s += MessageFormat 264 .format(messageFormatText = TrainManifestText.getStringDepartTime(), 265 new Object[]{train.getExpectedDepartureTime(rl)}); 266 } 267 } 268 newLine(fileOut, s); 269 270 // add location comment 271 if (Setup.isPrintLocationCommentsEnabled() && 272 !rl.getLocation().getCommentWithColor().equals(Location.NONE)) { 273 newLine(fileOut, rl.getLocation().getCommentWithColor()); 274 } 275 } 276 } else { 277 // last location in the train's route, print train terminates message 278 if (!hadWork) { 279 newLine(fileOut); 280 } else if (Setup.isPrintHeadersEnabled() || 281 !Setup.getManifestFormat().equals(Setup.STANDARD_FORMAT)) { 282 printHorizontalLine(fileOut, IS_MANIFEST); 283 } 284 newLine(fileOut, MessageFormat.format(messageFormatText = TrainManifestText 285 .getStringTrainTerminates(), 286 new Object[]{routeLocationName, train.getName(), 287 train.getDescription(), rl.getLocation().getDivisionName()})); 288 } 289 } 290 // Are there any cars that need to be found? 291 addCarsLocationUnknown(fileOut, IS_MANIFEST); 292 293 } catch (IllegalArgumentException e) { 294 newLine(fileOut, Bundle.getMessage("ErrorIllegalArgument", 295 Bundle.getMessage("TitleManifestText"), e.getLocalizedMessage())); 296 newLine(fileOut, messageFormatText); 297 log.error("Illegal argument", e); 298 } 299 300 fileOut.flush(); 301 fileOut.close(); 302 303 train.setModified(false); 304 } 305 306 private void printChange(PrintWriter fileOut, RouteLocation rl, Train train, int legOptions) 307 throws IllegalArgumentException { 308 if ((legOptions & Train.HELPER_ENGINES) == Train.HELPER_ENGINES) { 309 // assume 2nd leg for helper change 310 String numberEngines = train.getSecondLegNumberEngines(); 311 String endLocationName = train.getSecondLegEndLocationName(); 312 String engineModel = train.getSecondLegEngineModel(); 313 String engineRoad = train.getSecondLegEngineRoad(); 314 if (rl == train.getThirdLegStartRouteLocation()) { 315 numberEngines = train.getThirdLegNumberEngines(); 316 endLocationName = train.getThirdLegEndLocationName(); 317 engineModel = train.getThirdLegEngineModel(); 318 engineRoad = train.getThirdLegEngineRoad(); 319 } 320 newLine(fileOut, 321 MessageFormat.format(messageFormatText = TrainManifestText.getStringAddHelpers(), 322 new Object[] { rl.getSplitName(), train.getName(), train.getDescription(), 323 numberEngines, endLocationName, engineModel, engineRoad })); 324 } else if ((legOptions & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 325 ((legOptions & Train.REMOVE_CABOOSE) == Train.REMOVE_CABOOSE || 326 (legOptions & Train.ADD_CABOOSE) == Train.ADD_CABOOSE)) { 327 newLine(fileOut, MessageFormat.format( 328 messageFormatText = TrainManifestText.getStringLocoAndCabooseChange(), new Object[]{ 329 rl.getSplitName(), train.getName(), train.getDescription(), 330 rl.getLocation().getDivisionName()})); 331 } else if ((legOptions & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES) { 332 newLine(fileOut, MessageFormat.format(messageFormatText = TrainManifestText.getStringLocoChange(), 333 new Object[]{rl.getSplitName(), train.getName(), train.getDescription(), 334 rl.getLocation().getDivisionName()})); 335 } else if ((legOptions & Train.REMOVE_CABOOSE) == Train.REMOVE_CABOOSE || 336 (legOptions & Train.ADD_CABOOSE) == Train.ADD_CABOOSE) { 337 newLine(fileOut, MessageFormat.format(messageFormatText = TrainManifestText.getStringCabooseChange(), 338 new Object[]{rl.getSplitName(), train.getName(), train.getDescription(), 339 rl.getLocation().getDivisionName()})); 340 } 341 } 342 343 private void newLine(PrintWriter file, String string) { 344 if (!string.isEmpty()) { 345 newLine(file, string, IS_MANIFEST); 346 } 347 } 348 349}