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("HoldCarsWithCustomLoads"),
100                    Bundle.getMessage("DisableLoadChange"),
101                    Bundle.getMessage("SwapCarLoads"),
102                    Bundle.getMessage("EmptyDefaultCarLoads"),
103                    Bundle.getMessage("EmptyCarLoads"),
104                    Bundle.getMessage("LoadCarLoads"),
105                    Bundle.getMessage("LoadAnyCarLoads"),
106                    Bundle.getMessage("LoadsStaging"),
107                    Bundle.getMessage("BlockCars"),
108                    Bundle.getMessage("Comment"),
109                    Bundle.getMessage("CommentBoth"),
110                    Bundle.getMessage("CommentPickup"),
111                    Bundle.getMessage("CommentSetout"));
112
113            List<Location> locations = locationManager.getLocationsByNameList();
114            for (Location location : locations) {
115                for (Track track : location.getTracksByNameList(null)) {
116
117                    StringBuilder trainDirections = new StringBuilder();
118                    String[] directions = Setup.getDirectionStrings(
119                            Setup.getTrainDirection() & location.getTrainDirections() & track.getTrainDirections());
120                    for (String dir : directions) {
121                        if (dir != null) {
122                            trainDirections.append(dir).append("; ");
123                        }
124                    }
125
126                    StringBuilder rollingStockNames = new StringBuilder();
127                    for (String rollingStockName : track.getTypeNames()) {
128                        rollingStockNames.append(rollingStockName).append("; ");
129                    }
130
131                    StringBuilder roadNames = new StringBuilder();
132                    if (!track.getRoadOption().equals(Track.ALL_ROADS)) {
133                        for (String roadName : track.getRoadNames()) {
134                            roadNames.append(roadName).append("; ");
135                        }
136                    }
137
138                    StringBuilder loadNames = new StringBuilder();
139                    if (!track.getLoadOption().equals(Track.ALL_LOADS)) {
140                        for (String loadName : track.getLoadNames()) {
141                            loadNames.append(loadName).append("; ");
142                        }
143                    }
144
145                    StringBuilder shipNames = new StringBuilder();
146                    if (!track.getShipLoadOption().equals(Track.ALL_LOADS)) {
147                        for (String shipName : track.getShipLoadNames()) {
148                            shipNames.append(shipName).append("; ");
149                        }
150                    }
151
152                    String setOutRestriction = Bundle.getMessage("None");
153                    switch (track.getDropOption()) {
154                        case Track.TRAINS:
155                            setOutRestriction = Bundle.getMessage("Trains");
156                            break;
157                        case Track.ROUTES:
158                            setOutRestriction = Bundle.getMessage("Routes");
159                            break;
160                        case Track.EXCLUDE_TRAINS:
161                            setOutRestriction = Bundle.getMessage("ExcludeTrains");
162                            break;
163                        case Track.EXCLUDE_ROUTES:
164                            setOutRestriction = Bundle.getMessage("ExcludeRoutes");
165                            break;
166                        default:
167                            break;
168                    }
169
170                    StringBuilder setOutRestrictions = new StringBuilder();
171                    if (track.getDropOption().equals(Track.TRAINS) || track.getDropOption().equals(Track.EXCLUDE_TRAINS)) {
172                        for (String id : track.getDropIds()) {
173                            Train train = trainManager.getTrainById(id);
174                            if (train != null) {
175                                setOutRestrictions.append(train.getName()).append("; ");
176                            }
177                        }
178                    }
179                    if (track.getDropOption().equals(Track.ROUTES) || track.getDropOption().equals(Track.EXCLUDE_ROUTES)) {
180                        for (String id : track.getDropIds()) {
181                            Route route = routeManager.getRouteById(id);
182                            if (route != null) {
183                                setOutRestrictions.append(route.getName()).append("; ");
184                            }
185                        }
186                    }
187
188                    String pickUpRestriction = Bundle.getMessage("None");
189                    switch (track.getPickupOption()) {
190                        case Track.TRAINS:
191                            pickUpRestriction = Bundle.getMessage("Trains");
192                            break;
193                        case Track.ROUTES:
194                            pickUpRestriction = Bundle.getMessage("Routes");
195                            break;
196                        case Track.EXCLUDE_TRAINS:
197                            pickUpRestriction = Bundle.getMessage("ExcludeTrains");
198                            break;
199                        case Track.EXCLUDE_ROUTES:
200                            pickUpRestriction = Bundle.getMessage("ExcludeRoutes");
201                            break;
202                        default:
203                            break;
204                    }
205
206                    StringBuilder pickUpRestrictions = new StringBuilder();
207                    if (track.getPickupOption().equals(Track.TRAINS)
208                            || track.getPickupOption().equals(Track.EXCLUDE_TRAINS)) {
209                        for (String id : track.getPickupIds()) {
210                            Train train = trainManager.getTrainById(id);
211                            if (train != null) {
212                                pickUpRestrictions.append(train.getName()).append("; ");
213                            }
214                        }
215                    }
216                    if (track.getPickupOption().equals(Track.ROUTES)
217                            || track.getPickupOption().equals(Track.EXCLUDE_ROUTES)) {
218                        for (String id : track.getPickupIds()) {
219                            Route route = routeManager.getRouteById(id);
220                            if (route != null) {
221                                pickUpRestrictions.append(route.getName()).append("; ");
222                            }
223                        }
224                    }
225
226                    String alternateTrackName = "";
227                    if (track.getAlternateTrack() != null) {
228                        alternateTrackName = track.getAlternateTrack().getName();
229                    }
230                    if (track.isAlternate()) {
231                        alternateTrackName = Bundle.getMessage("ButtonYes");
232                    }
233
234                    StringBuilder destinationNames = new StringBuilder();
235                    for (String id : track.getDestinationIds()) {
236                        Location destination = locationManager.getLocationById(id);
237                        if (destination != null) {
238                            destinationNames.append(destination.getName()).append("; ");
239                        }
240                    }
241
242                    fileOut.printRecord(location.getName(),
243                            track.getName(),
244                            track.getTrackTypeName(),
245                            track.getLength(),
246                            track.getUsedLength(),
247                            track.getNumberCars(),
248                            track.getNumberEngines(),
249                            track.getMoves(),
250                            track.getDivision(),
251                            trainDirections.toString(),
252                            rollingStockNames.toString(),
253                            track.getServiceOrder(),
254                            track.getRoadOptionString(),
255                            roadNames.toString(),
256                            track.getLoadOptionString(),
257                            loadNames.toString(),
258                            track.getShipLoadOptionString(),
259                            shipNames.toString(),
260                            setOutRestriction,
261                            setOutRestrictions.toString(),
262                            pickUpRestriction,
263                            pickUpRestrictions.toString(),
264                            track.getScheduleName(),
265                            track.getScheduleModeName(),
266                            track.getReservationFactor(),
267                            alternateTrackName,
268                            track.getPoolName(),
269                            track.getPoolMinimumLength(),
270                            track.getPoolMaximumLength(),
271                            track.getBlockingOrder(),
272                            track.getIgnoreUsedLengthPercentage(),
273                            Bundle.getMessage(track.getDestinationOption().equals(Track.ALL_DESTINATIONS) ? "All" : "Include"),
274                            destinationNames.toString(),
275                            (track.isHoldCarsWithCustomLoadsEnabled() ? Bundle.getMessage("ButtonYes") : ""),
276                            (track.isDisableLoadChangeEnabled() ? Bundle.getMessage("ButtonYes") : ""),
277                            (track.isLoadSwapEnabled() ? Bundle.getMessage("ButtonYes") : ""),
278                            (track.isLoadEmptyEnabled() ? Bundle.getMessage("ButtonYes") : ""),
279                            (track.isRemoveCustomLoadsEnabled() ? Bundle.getMessage("ButtonYes") : ""),
280                            (track.isAddCustomLoadsEnabled() ? Bundle.getMessage("ButtonYes") : ""),
281                            (track.isAddCustomLoadsAnySpurEnabled() ? Bundle.getMessage("ButtonYes") : ""),
282                            (track.isAddCustomLoadsAnyStagingTrackEnabled() ? Bundle.getMessage("ButtonYes") : ""),
283                            (track.isBlockCarsEnabled() ? Bundle.getMessage("ButtonYes") : ""),
284                            // strip line feeds, parse EOL error when importing
285                            track.getComment().replace('\n', ' '),
286                            track.getCommentBoth().replace('\n', ' '),
287                            track.getCommentPickup().replace('\n', ' '),
288                            track.getCommentSetout().replace('\n', ' '));
289                }
290            }
291            log.info("Exported {} locations to file {}", locations.size(), defaultOperationsFilename());
292            JmriJOptionPane.showMessageDialog(null,
293                    Bundle.getMessage("ExportedLocationsToFile", locations.size(), defaultOperationsFilename()),
294                    Bundle.getMessage("ExportComplete"), JmriJOptionPane.INFORMATION_MESSAGE);
295        } catch (IOException e) {
296            log.error("Can not open export locations CSV file: {}", e.getLocalizedMessage());
297            JmriJOptionPane.showMessageDialog(null,
298                    Bundle.getMessage("ExportedLocationsToFile", 0, defaultOperationsFilename()),
299                    Bundle.getMessage("ExportFailed"), JmriJOptionPane.ERROR_MESSAGE);
300        }
301    }
302
303    // Operation files always use the same directory
304    public static String defaultOperationsFilename() {
305        return OperationsSetupXml.getFileLocation()
306                + OperationsSetupXml.getOperationsDirectoryName()
307                + File.separator
308                + getOperationsFileName();
309    }
310
311    public static void setOperationsFileName(String name) {
312        operationsFileName = name;
313    }
314
315    public static String getOperationsFileName() {
316        return operationsFileName;
317    }
318
319    private static String operationsFileName = "ExportOperationsLocationRoster.csv"; // NOI18N
320
321    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ExportLocations.class);
322
323}