001package jmri.jmrit.operations.locations.tools;
002
003import java.io.*;
004import java.nio.charset.StandardCharsets;
005import java.util.List;
006
007import org.apache.commons.csv.CSVFormat;
008import org.apache.commons.csv.CSVPrinter;
009
010import jmri.InstanceManager;
011import jmri.jmrit.XmlFile;
012import jmri.jmrit.operations.locations.*;
013import jmri.jmrit.operations.routes.Route;
014import jmri.jmrit.operations.routes.RouteManager;
015import jmri.jmrit.operations.setup.OperationsSetupXml;
016import jmri.jmrit.operations.setup.Setup;
017import jmri.jmrit.operations.trains.Train;
018import jmri.jmrit.operations.trains.TrainManager;
019import jmri.util.swing.JmriJOptionPane;
020
021/**
022 * Exports the location roster into a comma delimited file (CSV). Keep
023 * ImportLocations.java in sync with export
024 *
025 * @author Daniel Boudreau Copyright (C) 2018, 2023, 2025
026 */
027public class ExportLocations extends XmlFile {
028
029    TrainManager trainManager = InstanceManager.getDefault(TrainManager.class);
030    RouteManager routeManager = InstanceManager.getDefault(RouteManager.class);
031    LocationManager locationManager = InstanceManager.getDefault(LocationManager.class);
032
033    public void writeOperationsLocationFile() {
034        makeBackupFile(defaultOperationsFilename());
035        try {
036            if (!checkFile(defaultOperationsFilename())) {
037                // The file does not exist, create it before writing
038                java.io.File file = new java.io.File(defaultOperationsFilename());
039                java.io.File parentDir = file.getParentFile();
040                if (!parentDir.exists()) {
041                    if (!parentDir.mkdir()) {
042                        log.error("Directory wasn't created");
043                    }
044                }
045                if (file.createNewFile()) {
046                    log.debug("File created");
047                }
048            }
049            writeFile(defaultOperationsFilename());
050        } catch (IOException e) {
051            log.error("Exception while writing the new CSV operations file, may not be complete: {}",
052                    e.getLocalizedMessage());
053        }
054    }
055
056    public void writeFile(String name) {
057        log.debug("writeFile {}", name);
058        File file = findFile(name);
059        if (file == null) {
060            file = new File(name);
061        }
062
063        try (CSVPrinter fileOut = new CSVPrinter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)),
064                CSVFormat.DEFAULT)) {
065            // create header
066            fileOut.printRecord(Bundle.getMessage("Location"),
067                    Bundle.getMessage("Track"),
068                    Bundle.getMessage("Type"),
069                    Bundle.getMessage("Length"),
070                    Bundle.getMessage("Used"),
071                    Bundle.getMessage("Cars"),
072                    Bundle.getMessage("Engines"),
073                    Bundle.getMessage("Moves"),
074                    Bundle.getMessage("Division"),
075                    Bundle.getMessage("ServicedByTrains"),
076                    Bundle.getMessage("RollingStock"),
077                    Bundle.getMessage("ServiceOrder"),
078                    Bundle.getMessage("RoadOption"),
079                    Bundle.getMessage("Roads"),
080                    Bundle.getMessage("LoadOption"),
081                    Bundle.getMessage("Loads"),
082                    Bundle.getMessage("ShipLoadOption"),
083                    Bundle.getMessage("Ships"),
084                    Bundle.getMessage("SetOutRestrictions"),
085                    Bundle.getMessage("Restrictions"),
086                    Bundle.getMessage("PickUpRestrictions"),
087                    Bundle.getMessage("Restrictions"),
088                    Bundle.getMessage("ScheduleName"),
089                    Bundle.getMessage("ScheduleMode"),
090                    Bundle.getMessage("PercentStaging"),
091                    Bundle.getMessage("AlternateTrack"),
092                    Bundle.getMessage("PoolName"),
093                    Bundle.getMessage("Minimum"),
094                    Bundle.getMessage("Maximum"),
095                    Bundle.getMessage("TitleTrackBlockingOrder"),
096                    Bundle.getMessage("MenuItemPlannedPickups"),
097                    Bundle.getMessage("MenuItemDestinations"),
098                    Bundle.getMessage("Destinations"),
099                    Bundle.getMessage("QuickService"),
100                    Bundle.getMessage("HoldCarsWithCustomLoads"),
101                    Bundle.getMessage("DisableLoadChange"),
102                    Bundle.getMessage("SwapCarLoads"),
103                    Bundle.getMessage("EmptyDefaultCarLoads"),
104                    Bundle.getMessage("EmptyCarLoads"),
105                    Bundle.getMessage("LoadCarLoads"),
106                    Bundle.getMessage("LoadAnyCarLoads"),
107                    Bundle.getMessage("LoadsStaging"),
108                    Bundle.getMessage("BlockCars"),
109                    Bundle.getMessage("Comment"),
110                    Bundle.getMessage("CommentBoth"),
111                    Bundle.getMessage("CommentPickup"),
112                    Bundle.getMessage("CommentSetout"));
113
114            List<Location> locations = locationManager.getLocationsByNameList();
115            for (Location location : locations) {
116                for (Track track : location.getTracksByNameList(null)) {
117
118                    StringBuilder trainDirections = new StringBuilder();
119                    String[] directions = Setup.getDirectionStrings(
120                            Setup.getTrainDirection() & location.getTrainDirections() & track.getTrainDirections());
121                    for (String dir : directions) {
122                        if (dir != null) {
123                            trainDirections.append(dir).append("; ");
124                        }
125                    }
126
127                    StringBuilder rollingStockNames = new StringBuilder();
128                    for (String rollingStockName : track.getTypeNames()) {
129                        rollingStockNames.append(rollingStockName).append("; ");
130                    }
131
132                    StringBuilder roadNames = new StringBuilder();
133                    if (!track.getRoadOption().equals(Track.ALL_ROADS)) {
134                        for (String roadName : track.getRoadNames()) {
135                            roadNames.append(roadName).append("; ");
136                        }
137                    }
138
139                    StringBuilder loadNames = new StringBuilder();
140                    if (!track.getLoadOption().equals(Track.ALL_LOADS)) {
141                        for (String loadName : track.getLoadNames()) {
142                            loadNames.append(loadName).append("; ");
143                        }
144                    }
145
146                    StringBuilder shipNames = new StringBuilder();
147                    if (!track.getShipLoadOption().equals(Track.ALL_LOADS)) {
148                        for (String shipName : track.getShipLoadNames()) {
149                            shipNames.append(shipName).append("; ");
150                        }
151                    }
152
153                    String setOutRestriction = Bundle.getMessage("None");
154                    switch (track.getDropOption()) {
155                        case Track.TRAINS:
156                            setOutRestriction = Bundle.getMessage("Trains");
157                            break;
158                        case Track.ROUTES:
159                            setOutRestriction = Bundle.getMessage("Routes");
160                            break;
161                        case Track.EXCLUDE_TRAINS:
162                            setOutRestriction = Bundle.getMessage("ExcludeTrains");
163                            break;
164                        case Track.EXCLUDE_ROUTES:
165                            setOutRestriction = Bundle.getMessage("ExcludeRoutes");
166                            break;
167                        default:
168                            break;
169                    }
170
171                    StringBuilder setOutRestrictions = new StringBuilder();
172                    if (track.getDropOption().equals(Track.TRAINS) || track.getDropOption().equals(Track.EXCLUDE_TRAINS)) {
173                        for (String id : track.getDropIds()) {
174                            Train train = trainManager.getTrainById(id);
175                            if (train != null) {
176                                setOutRestrictions.append(train.getName()).append("; ");
177                            }
178                        }
179                    }
180                    if (track.getDropOption().equals(Track.ROUTES) || track.getDropOption().equals(Track.EXCLUDE_ROUTES)) {
181                        for (String id : track.getDropIds()) {
182                            Route route = routeManager.getRouteById(id);
183                            if (route != null) {
184                                setOutRestrictions.append(route.getName()).append("; ");
185                            }
186                        }
187                    }
188
189                    String pickUpRestriction = Bundle.getMessage("None");
190                    switch (track.getPickupOption()) {
191                        case Track.TRAINS:
192                            pickUpRestriction = Bundle.getMessage("Trains");
193                            break;
194                        case Track.ROUTES:
195                            pickUpRestriction = Bundle.getMessage("Routes");
196                            break;
197                        case Track.EXCLUDE_TRAINS:
198                            pickUpRestriction = Bundle.getMessage("ExcludeTrains");
199                            break;
200                        case Track.EXCLUDE_ROUTES:
201                            pickUpRestriction = Bundle.getMessage("ExcludeRoutes");
202                            break;
203                        default:
204                            break;
205                    }
206
207                    StringBuilder pickUpRestrictions = new StringBuilder();
208                    if (track.getPickupOption().equals(Track.TRAINS)
209                            || track.getPickupOption().equals(Track.EXCLUDE_TRAINS)) {
210                        for (String id : track.getPickupIds()) {
211                            Train train = trainManager.getTrainById(id);
212                            if (train != null) {
213                                pickUpRestrictions.append(train.getName()).append("; ");
214                            }
215                        }
216                    }
217                    if (track.getPickupOption().equals(Track.ROUTES)
218                            || track.getPickupOption().equals(Track.EXCLUDE_ROUTES)) {
219                        for (String id : track.getPickupIds()) {
220                            Route route = routeManager.getRouteById(id);
221                            if (route != null) {
222                                pickUpRestrictions.append(route.getName()).append("; ");
223                            }
224                        }
225                    }
226
227                    String alternateTrackName = "";
228                    if (track.getAlternateTrack() != null) {
229                        alternateTrackName = track.getAlternateTrack().getName();
230                    }
231                    if (track.isAlternate()) {
232                        alternateTrackName = Bundle.getMessage("ButtonYes");
233                    }
234
235                    StringBuilder destinationNames = new StringBuilder();
236                    for (String id : track.getDestinationIds()) {
237                        Location destination = locationManager.getLocationById(id);
238                        if (destination != null) {
239                            destinationNames.append(destination.getName()).append("; ");
240                        }
241                    }
242
243                    fileOut.printRecord(location.getName(),
244                            track.getName(),
245                            track.getTrackTypeName(),
246                            track.getLength(),
247                            track.getUsedLength(),
248                            track.getNumberCars(),
249                            track.getNumberEngines(),
250                            track.getMoves(),
251                            track.getDivision(),
252                            trainDirections.toString(),
253                            rollingStockNames.toString(),
254                            track.getServiceOrder(),
255                            track.getRoadOptionString(),
256                            roadNames.toString(),
257                            track.getLoadOptionString(),
258                            loadNames.toString(),
259                            track.getShipLoadOptionString(),
260                            shipNames.toString(),
261                            setOutRestriction,
262                            setOutRestrictions.toString(),
263                            pickUpRestriction,
264                            pickUpRestrictions.toString(),
265                            track.getScheduleName(),
266                            track.getScheduleModeName(),
267                            track.getReservationFactor(),
268                            alternateTrackName,
269                            track.getPoolName(),
270                            track.getPoolMinimumLength(),
271                            track.getPoolMaximumLength(),
272                            track.getBlockingOrder(),
273                            track.getIgnoreUsedLengthPercentage(),
274                            Bundle.getMessage(track.getDestinationOption().equals(Track.ALL_DESTINATIONS) ? "All" : "Include"),
275                            destinationNames.toString(),
276                            (track.isQuickServiceEnabled() ? Bundle.getMessage("ButtonYes") : ""),
277                            (track.isHoldCarsWithCustomLoadsEnabled() ? Bundle.getMessage("ButtonYes") : ""),
278                            (track.isDisableLoadChangeEnabled() ? Bundle.getMessage("ButtonYes") : ""),
279                            (track.isLoadSwapEnabled() ? Bundle.getMessage("ButtonYes") : ""),
280                            (track.isLoadEmptyEnabled() ? Bundle.getMessage("ButtonYes") : ""),
281                            (track.isRemoveCustomLoadsEnabled() ? Bundle.getMessage("ButtonYes") : ""),
282                            (track.isAddCustomLoadsEnabled() ? Bundle.getMessage("ButtonYes") : ""),
283                            (track.isAddCustomLoadsAnySpurEnabled() ? Bundle.getMessage("ButtonYes") : ""),
284                            (track.isAddCustomLoadsAnyStagingTrackEnabled() ? Bundle.getMessage("ButtonYes") : ""),
285                            (track.isBlockCarsEnabled() ? Bundle.getMessage("ButtonYes") : ""),
286                            // strip line feeds, parse EOL error when importing
287                            track.getComment().replace('\n', ' '),
288                            track.getCommentBoth().replace('\n', ' '),
289                            track.getCommentPickup().replace('\n', ' '),
290                            track.getCommentSetout().replace('\n', ' '));
291                }
292            }
293            log.info("Exported {} locations to file {}", locations.size(), defaultOperationsFilename());
294            JmriJOptionPane.showMessageDialog(null,
295                    Bundle.getMessage("ExportedLocationsToFile", locations.size(), defaultOperationsFilename()),
296                    Bundle.getMessage("ExportComplete"), JmriJOptionPane.INFORMATION_MESSAGE);
297        } catch (IOException e) {
298            log.error("Can not open export locations CSV file: {}", e.getLocalizedMessage());
299            JmriJOptionPane.showMessageDialog(null,
300                    Bundle.getMessage("ExportedLocationsToFile", 0, defaultOperationsFilename()),
301                    Bundle.getMessage("ExportFailed"), JmriJOptionPane.ERROR_MESSAGE);
302        }
303    }
304
305    // Operation files always use the same directory
306    public static String defaultOperationsFilename() {
307        return OperationsSetupXml.getFileLocation()
308                + OperationsSetupXml.getOperationsDirectoryName()
309                + File.separator
310                + getOperationsFileName();
311    }
312
313    public static void setOperationsFileName(String name) {
314        operationsFileName = name;
315    }
316
317    public static String getOperationsFileName() {
318        return operationsFileName;
319    }
320
321    private static String operationsFileName = "ExportOperationsLocationRoster.csv"; // NOI18N
322
323    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ExportLocations.class);
324
325}