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.Track;
015import jmri.jmrit.operations.rollingstock.cars.Car;
016import jmri.jmrit.operations.rollingstock.engines.Engine;
017import jmri.jmrit.operations.routes.RouteLocation;
018import jmri.jmrit.operations.setup.Setup;
019
020/**
021 * Builds a train's manifest using Comma Separated Values (csv).
022 *
023 * @author Daniel Boudreau Copyright (C) 2011, 2015
024 *
025 */
026public class TrainCsvManifest extends TrainCsvCommon {
027
028    public TrainCsvManifest(Train train) throws BuildFailedException {
029        if (!Setup.isGenerateCsvManifestEnabled()) {
030            return;
031        }
032        // create comma separated value manifest file
033        File file = InstanceManager.getDefault(TrainManagerXml.class).createTrainCsvManifestFile(train.getName());
034
035        try (CSVPrinter fileOut = new CSVPrinter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)),
036                CSVFormat.DEFAULT)) {
037            // build header
038            printHeader(fileOut);
039            printRailroadName(fileOut,
040                    train.getRailroadName().isEmpty() ? Setup.getRailroadName() : train.getRailroadName());
041            printTrainName(fileOut, train.getName());
042            printTrainDescription(fileOut, train.getDescription());
043            printPrinterName(fileOut, locationManager.getLocationByName(train.getTrainDepartsName()).getDefaultPrinterName());
044            printLogoURL(fileOut, train);
045            printValidity(fileOut, getDate(true));
046            printTrainComment(fileOut, train);
047            printRouteComment(fileOut, train);
048
049            // get engine and car lists
050            List<Engine> engineList = engineManager.getByTrainBlockingList(train);
051            List<Car> carList = carManager.getByTrainDestinationList(train);
052
053            boolean newWork = false;
054            String previousRouteLocationName = null;
055            List<RouteLocation> routeList = train.getRoute().getLocationsBySequenceList();
056            for (RouteLocation rl : routeList) {
057                // print info only if new location
058                if (!rl.getSplitName().equals(previousRouteLocationName)) {
059                    printLocationName(fileOut, rl.getSplitName());
060                    if (rl != train.getTrainDepartsRouteLocation()) {
061                        fileOut.printRecord("AT", Bundle.getMessage("csvArrivalTime"), train.getExpectedArrivalTime(rl)); // NOI18N
062                    }
063                    if (rl == train.getTrainDepartsRouteLocation()) {
064                        fileOut.printRecord("DT", Bundle.getMessage("csvDepartureTime"), train.getFormatedDepartureTime()); // NOI18N
065                    } else if (!rl.getDepartureTime().equals(RouteLocation.NONE)) {
066                        fileOut.printRecord("DTR", Bundle.getMessage("csvDepartureTimeRoute"), rl.getFormatedDepartureTime()); // NOI18N
067                    } else {
068                        fileOut.printRecord("EDT", Bundle.getMessage("csvEstimatedDepartureTime"), train.getExpectedDepartureTime(rl)); // NOI18N
069                    }
070                    printLocationComment(fileOut, rl.getLocation());
071                    if (Setup.isPrintTruncateManifestEnabled() && rl.getLocation().isSwitchListEnabled()) {
072                        fileOut.printRecord("TRUN", Bundle.getMessage("csvTruncate"));
073                    }
074                }
075                printRouteLocationComment(fileOut, rl);
076                printTrackComments(fileOut, rl, carList);
077
078                // engine change or helper service?
079                checkForEngineOrCabooseChange(fileOut, train, rl);
080
081                for (Engine engine : engineList) {
082                    if (engine.getRouteLocation() == rl) {
083                        printEngine(fileOut, engine, "PL", Bundle.getMessage("csvPickUpLoco"));
084                    }
085                }
086                for (Engine engine : engineList) {
087                    if (engine.getRouteDestination() == rl) {
088                        printEngine(fileOut, engine, "SL", Bundle.getMessage("csvSetOutLoco"));
089                    }
090                }
091                // block pick up cars
092                // caboose or FRED is placed at end of the train
093                // passenger cars are already blocked in the car list
094                // passenger cars with negative block numbers are placed at
095                // the front of the train, positive numbers at the end of
096                // the train.
097                for (RouteLocation rld : train.getTrainBlockingOrder()) {
098                    for (Car car : carList) {
099                        if (isNextCar(car, rl, rld)) {
100                            newWork = true;
101                            int count = 0;
102                            if (car.isUtility()) {
103                                count = countPickupUtilityCars(carList, car, IS_MANIFEST);
104                                if (count == 0) {
105                                    continue; // already done this set of
106                                              // utility cars
107                                }
108                            }
109                            printCar(fileOut, car, "PC", Bundle.getMessage("csvPickUpCar"), count);
110                        }
111                    }
112                }
113                // car set outs
114                for (Car car : carList) {
115                    if (car.getRouteDestination() == rl) {
116                        newWork = true;
117                        int count = 0;
118                        if (car.isUtility()) {
119                            count = countSetoutUtilityCars(carList, car, false, IS_MANIFEST);
120                            if (count == 0) {
121                                continue; // already done this set of utility cars
122                            }
123                        }
124                        printCar(fileOut, car, "SC", Bundle.getMessage("csvSetOutCar"), count);
125                    }
126                }
127                // car holds
128                List<Car> carsByLocation = carManager.getByLocationList();
129                List<Car> cList = new ArrayList<>();
130                for (Car car : carsByLocation) {
131                    if (car.getLocation() == rl.getLocation() && car.getRouteLocation() == null && car.getTrack() != null) {
132                        cList.add(car);
133                    }
134                }
135                clearUtilityCarTypes(); // list utility cars by quantity
136                for (Car car : cList) {
137                    // list cars on tracks that only this train can service
138                    if (!car.getTrack().getLocation().isStaging()
139                            && car.getTrack().isPickupTrainAccepted(train) && car.getTrack().getPickupIds().length == 1
140                            && car.getTrack().getPickupOption().equals(Track.TRAINS)) {
141                        int count = 0;
142                        if (car.isUtility()) {
143                            count = countPickupUtilityCars(cList, car, !IS_MANIFEST);
144                            if (count == 0) {
145                                continue; // already done this set of utility cars
146                            }
147                        }
148                        printCar(fileOut, car, "HOLD", Bundle.getMessage("csvHoldCar"), count);
149                    }
150                }
151                if (rl != train.getTrainTerminatesRouteLocation()) {
152                    // Is the next location the same as the previous?
153                    RouteLocation rlNext = train.getRoute().getNextRouteLocation(rl);
154                    if (!rl.getSplitName().equals(rlNext.getSplitName())) {
155                        if (newWork) {
156                            printTrainDeparts(fileOut, rl.getSplitName(), rl.getTrainDirectionString());
157                            printTrainLength(fileOut, train.getTrainLength(rl), train.getNumberEmptyCarsInTrain(rl),
158                                    train.getNumberCarsInTrain(rl));
159                            printTrainWeight(fileOut, train.getTrainWeight(rl));
160                            newWork = false;
161                        } else {
162                            fileOut.printRecord("NW", Bundle.getMessage("csvNoWork"));
163                        }
164                    }
165                } else {
166                    printTrainTerminates(fileOut, rl.getSplitName());
167                }
168                previousRouteLocationName = rl.getSplitName();
169            }
170            // Are there any cars that need to be found?
171            listCarsLocationUnknown(fileOut);
172
173            fileOut.flush();
174            fileOut.close();
175        } catch (IOException e) {
176            log.error("Can not open CSV manifest file: {}", e.getLocalizedMessage());
177            throw new BuildFailedException(e);
178        }
179    }
180
181    private final static Logger log = LoggerFactory.getLogger(TrainCsvManifest.class);
182}