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}