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}