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("TitleTrackBlockingOrder"),
095                    Bundle.getMessage("MenuItemPlannedPickups"),
096                    Bundle.getMessage("MenuItemDestinations"),
097                    Bundle.getMessage("Destinations"),
098                    Bundle.getMessage("HoldCarsWithCustomLoads"),
099                    Bundle.getMessage("DisableLoadChange"),
100                    Bundle.getMessage("SwapCarLoads"),
101                    Bundle.getMessage("EmptyDefaultCarLoads"),
102                    Bundle.getMessage("EmptyCarLoads"),
103                    Bundle.getMessage("LoadCarLoads"),
104                    Bundle.getMessage("LoadAnyCarLoads"),
105                    Bundle.getMessage("LoadsStaging"),
106                    Bundle.getMessage("BlockCars"),
107                    Bundle.getMessage("Comment"),
108                    Bundle.getMessage("CommentBoth"),
109                    Bundle.getMessage("CommentPickup"),
110                    Bundle.getMessage("CommentSetout"));
111
112            List<Location> locations = locationManager.getLocationsByNameList();
113            for (Location location : locations) {
114                for (Track track : location.getTracksByNameList(null)) {
115
116                    StringBuilder trainDirections = new StringBuilder();
117                    String[] directions = Setup.getDirectionStrings(
118                            Setup.getTrainDirection() & location.getTrainDirections() & track.getTrainDirections());
119                    for (String dir : directions) {
120                        if (dir != null) {
121                            trainDirections.append(dir).append("; ");
122                        }
123                    }
124
125                    StringBuilder rollingStockNames = new StringBuilder();
126                    for (String rollingStockName : track.getTypeNames()) {
127                        rollingStockNames.append(rollingStockName).append("; ");
128                    }
129
130                    StringBuilder roadNames = new StringBuilder();
131                    if (!track.getRoadOption().equals(Track.ALL_ROADS)) {
132                        for (String roadName : track.getRoadNames()) {
133                            roadNames.append(roadName).append("; ");
134                        }
135                    }
136
137                    StringBuilder loadNames = new StringBuilder();
138                    if (!track.getLoadOption().equals(Track.ALL_LOADS)) {
139                        for (String loadName : track.getLoadNames()) {
140                            loadNames.append(loadName).append("; ");
141                        }
142                    }
143
144                    StringBuilder shipNames = new StringBuilder();
145                    if (!track.getShipLoadOption().equals(Track.ALL_LOADS)) {
146                        for (String shipName : track.getShipLoadNames()) {
147                            shipNames.append(shipName).append("; ");
148                        }
149                    }
150
151                    String setOutRestriction = Bundle.getMessage("None");
152                    switch (track.getDropOption()) {
153                        case Track.TRAINS:
154                            setOutRestriction = Bundle.getMessage("Trains");
155                            break;
156                        case Track.ROUTES:
157                            setOutRestriction = Bundle.getMessage("Routes");
158                            break;
159                        case Track.EXCLUDE_TRAINS:
160                            setOutRestriction = Bundle.getMessage("ExcludeTrains");
161                            break;
162                        case Track.EXCLUDE_ROUTES:
163                            setOutRestriction = Bundle.getMessage("ExcludeRoutes");
164                            break;
165                        default:
166                            break;
167                    }
168
169                    StringBuilder setOutRestrictions = new StringBuilder();
170                    if (track.getDropOption().equals(Track.TRAINS) || track.getDropOption().equals(Track.EXCLUDE_TRAINS)) {
171                        for (String id : track.getDropIds()) {
172                            Train train = trainManager.getTrainById(id);
173                            if (train != null) {
174                                setOutRestrictions.append(train.getName()).append("; ");
175                            }
176                        }
177                    }
178                    if (track.getDropOption().equals(Track.ROUTES) || track.getDropOption().equals(Track.EXCLUDE_ROUTES)) {
179                        for (String id : track.getDropIds()) {
180                            Route route = routeManager.getRouteById(id);
181                            if (route != null) {
182                                setOutRestrictions.append(route.getName()).append("; ");
183                            }
184                        }
185                    }
186
187                    String pickUpRestriction = Bundle.getMessage("None");
188                    switch (track.getPickupOption()) {
189                        case Track.TRAINS:
190                            pickUpRestriction = Bundle.getMessage("Trains");
191                            break;
192                        case Track.ROUTES:
193                            pickUpRestriction = Bundle.getMessage("Routes");
194                            break;
195                        case Track.EXCLUDE_TRAINS:
196                            pickUpRestriction = Bundle.getMessage("ExcludeTrains");
197                            break;
198                        case Track.EXCLUDE_ROUTES:
199                            pickUpRestriction = Bundle.getMessage("ExcludeRoutes");
200                            break;
201                        default:
202                            break;
203                    }
204
205                    StringBuilder pickUpRestrictions = new StringBuilder();
206                    if (track.getPickupOption().equals(Track.TRAINS)
207                            || track.getPickupOption().equals(Track.EXCLUDE_TRAINS)) {
208                        for (String id : track.getPickupIds()) {
209                            Train train = trainManager.getTrainById(id);
210                            if (train != null) {
211                                pickUpRestrictions.append(train.getName()).append("; ");
212                            }
213                        }
214                    }
215                    if (track.getPickupOption().equals(Track.ROUTES)
216                            || track.getPickupOption().equals(Track.EXCLUDE_ROUTES)) {
217                        for (String id : track.getPickupIds()) {
218                            Route route = routeManager.getRouteById(id);
219                            if (route != null) {
220                                pickUpRestrictions.append(route.getName()).append("; ");
221                            }
222                        }
223                    }
224
225                    String alternateTrackName = "";
226                    if (track.getAlternateTrack() != null) {
227                        alternateTrackName = track.getAlternateTrack().getName();
228                    }
229                    if (track.isAlternate()) {
230                        alternateTrackName = Bundle.getMessage("ButtonYes");
231                    }
232
233                    StringBuilder destinationNames = new StringBuilder();
234                    for (String id : track.getDestinationIds()) {
235                        Location destination = locationManager.getLocationById(id);
236                        if (destination != null) {
237                            destinationNames.append(destination.getName()).append("; ");
238                        }
239                    }
240
241                    fileOut.printRecord(location.getName(),
242                            track.getName(),
243                            track.getTrackTypeName(),
244                            track.getLength(),
245                            track.getUsedLength(),
246                            track.getNumberCars(),
247                            track.getNumberEngines(),
248                            track.getMoves(),
249                            track.getDivision(),
250                            trainDirections.toString(),
251                            rollingStockNames.toString(),
252                            track.getServiceOrder(),
253                            track.getRoadOptionString(),
254                            roadNames.toString(),
255                            track.getLoadOptionString(),
256                            loadNames.toString(),
257                            track.getShipLoadOptionString(),
258                            shipNames.toString(),
259                            setOutRestriction,
260                            setOutRestrictions.toString(),
261                            pickUpRestriction,
262                            pickUpRestrictions.toString(),
263                            track.getScheduleName(),
264                            track.getScheduleModeName(),
265                            track.getReservationFactor(),
266                            alternateTrackName,
267                            track.getPoolName(),
268                            track.getMinimumLength(),
269                            track.getBlockingOrder(),
270                            track.getIgnoreUsedLengthPercentage(),
271                            Bundle.getMessage(track.getDestinationOption().equals(Track.ALL_DESTINATIONS) ? "All" : "Include"),
272                            destinationNames.toString(),
273                            (track.isHoldCarsWithCustomLoadsEnabled() ? Bundle.getMessage("ButtonYes") : ""),
274                            (track.isDisableLoadChangeEnabled() ? Bundle.getMessage("ButtonYes") : ""),
275                            (track.isLoadSwapEnabled() ? Bundle.getMessage("ButtonYes") : ""),
276                            (track.isLoadEmptyEnabled() ? Bundle.getMessage("ButtonYes") : ""),
277                            (track.isRemoveCustomLoadsEnabled() ? Bundle.getMessage("ButtonYes") : ""),
278                            (track.isAddCustomLoadsEnabled() ? Bundle.getMessage("ButtonYes") : ""),
279                            (track.isAddCustomLoadsAnySpurEnabled() ? Bundle.getMessage("ButtonYes") : ""),
280                            (track.isAddCustomLoadsAnyStagingTrackEnabled() ? Bundle.getMessage("ButtonYes") : ""),
281                            (track.isBlockCarsEnabled() ? Bundle.getMessage("ButtonYes") : ""),
282                            // strip line feeds, parse EOL error when importing
283                            track.getComment().replace('\n', ' '),
284                            track.getCommentBoth().replace('\n', ' '),
285                            track.getCommentPickup().replace('\n', ' '),
286                            track.getCommentSetout().replace('\n', ' '));
287                }
288            }
289            fileOut.flush();
290            fileOut.close();
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}