001package jmri.jmrit.operations.locations.tools; 002 003import java.io.BufferedReader; 004import java.io.File; 005import java.util.Locale; 006 007import jmri.InstanceManager; 008import jmri.jmrit.operations.locations.*; 009import jmri.jmrit.operations.locations.divisions.Division; 010import jmri.jmrit.operations.locations.divisions.DivisionManager; 011import jmri.jmrit.operations.locations.schedules.Schedule; 012import jmri.jmrit.operations.locations.schedules.ScheduleManager; 013import jmri.jmrit.operations.rollingstock.ImportCommon; 014import jmri.jmrit.operations.setup.Setup; 015import jmri.util.ThreadingUtil; 016import jmri.util.swing.JmriJOptionPane; 017 018/** 019 * This routine will import Locations from a CSV file into the operations 020 * database. The field order is: Location, Track, Type, Length, Used, Cars, 021 * Locos, Moves, Division, Serviced by Trains Traveling, Rolling Stock, Track 022 * Service Order, Road Option, Roads, Load Option, Loads, Ship Load Option, 023 * Ships, Set Out Restrictions, Restrictions, Pick up Restrictions, 024 * Restrictions, Schedule Name, Mode, Alternate Track, Pool name, Minimum, Track 025 * Blocking Order, Planned Pick Ups, Track Destinations, Destinations, Quick 026 * Service, Hold Cars, Disable Load Change, Swap default loads and empties, 027 * Empty cars with default loads, Generate custom loads for spurs serviced by 028 * this train, Generate custom loads for any spur (multiple trains), Generate 029 * custom loads for any staging track, Block cars by pick up location, Comment, 030 * Comment when there is only pick ups, Comment when there is only set outs 031 */ 032public class ImportLocations extends ImportCommon { 033 034 LocationManager locationManager = InstanceManager.getDefault(LocationManager.class); 035 DivisionManager divisionManager = InstanceManager.getDefault(DivisionManager.class); 036 037 int tracksAdded = 0; 038 039 protected static final int FIELD_LOCATION = 0; 040 protected static final int FIELD_TRACK = 1; 041 protected static final int FIELD_TYPE = 2; 042 protected static final int FIELD_LENGTH = 3; 043 protected static final int FIELD_USED = 4; // not imported 044 protected static final int FIELD_CARS = 5; // not imported 045 protected static final int FIELD_LOCOS = 6; // not imported 046 protected static final int FIELD_MOVES = 7; // not imported 047 protected static final int FIELD_DIVISION = 8; 048 protected static final int FIELD_SERVICED_BY = 9; 049 protected static final int FIELD_ROLLING_STOCK = 10; 050 protected static final int FIELD_ORDER = 11; 051 protected static final int FIELD_ROAD_OPTION = 12; 052 protected static final int FIELD_ROADS = 13; 053 protected static final int FIELD_LOAD_OPTION = 14; 054 protected static final int FIELD_LOADS = 15; 055 protected static final int FIELD_SHIP_LOAD_OPTION = 16; 056 protected static final int FIELD_SHIPS = 17; 057 // 18 - 21 import not implemented 058 protected static final int FIELD_SET_OUT_RESTRICTIONS = 18; 059 protected static final int FIELD_RESTRICTIONS_1 = 19; 060 protected static final int FIELD_PICK_UP_RESTRICTIONS = 20; 061 protected static final int FIELD_RESTRICTIONS_2 = 21; 062 063 protected static final int FIELD_SCHEDULE_NAME = 22; 064 protected static final int FIELD_SCHEDULE_MODE = 23; 065 protected static final int FIELD_PERCENT_STAGING = 24; 066 protected static final int FIELD_ALTERNATE_TRACK = 25; 067 protected static final int FIELD_POOL_NAME = 26; 068 protected static final int FIELD_TRACK_MINIMUM_POOL = 27; 069 protected static final int FIELD_TRACK_MAXIMUM_POOL = 28; 070 protected static final int FIELD_TRACK_BLOCKING_ORDER = 29; 071 protected static final int FIELD_PLANNED_PICK_UPS = 30; 072 // 31 - 42 import not implemented 073 protected static final int FIELD_TRACK_DESTINATIONS = 31; 074 protected static final int FIELD_DESTINATIONS = 32; 075 protected static final int FIELD_QUICK_SERVICE = 33; 076 protected static final int FIELD_HOLD_CARS_CUSTOM_LOADS = 34; 077 protected static final int FIELD_DISABLE_LOAD_CHANGE = 35; 078 protected static final int FIELD_SWAP_DEFAULT = 36; 079 protected static final int FIELD_EMPTY_DEFAULT_LOADS = 37; 080 protected static final int FIELD_EMPTY_CUSTOM_LOADS = 38; 081 protected static final int FIELD_GENERATE_SPUR = 39; 082 protected static final int FIELD_GENERATE_ANY_SPUR = 40; 083 protected static final int FIELD_GENERATE_STAGING = 41; 084 protected static final int FIELD_BLOCK_CARS_BY_PICKUP = 42; 085 086 protected static final int FIELD_COMMENT = 43; 087 protected static final int FIELD_COMMENT_BOTH = 44; 088 protected static final int FIELD_COMMENT_PICKUPS = 45; 089 protected static final int FIELD_COMMENT_SETOUTS = 46; 090 091 @Override 092 public void run() { 093 File file = getFile(); 094 if (file == null) { 095 return; 096 } 097 BufferedReader rdr = getBufferedReader(file); 098 if (rdr == null) { 099 return; 100 } 101 createStatusFrame(Bundle.getMessage("ImportLocations")); 102 103 // read the import (CSV) file 104 String[] inputLine; 105 boolean headerFound = false; 106 107 while (true) { 108 inputLine = readNextLine(rdr); 109 if (inputLine == BREAK) { 110 log.debug("Done"); 111 break; 112 } 113 if (inputLine.length < 1) { 114 log.debug("Skipping blank line"); 115 continue; 116 } 117 String fieldLocation = ""; 118 String fieldTrack = ""; 119 String fieldType = ""; 120 String fieldLength = ""; 121 // header? 122 if (!headerFound && inputLine[FIELD_LOCATION].equals(Bundle.getMessage("Location"))) { 123 headerFound = true; 124 int elementNum = 0; 125 for (String lineElement : inputLine) { 126 log.debug("Header {} is: {}", elementNum++, lineElement); 127 } 128 continue; // skip header 129 } 130 if (inputLine.length < 4) { 131 log.info("Skipping row {} as we need at least 4 fields (Location, Track, Type and Length)", 132 Integer.toString(lineNum)); 133 continue; 134 } 135 fieldLocation = inputLine[FIELD_LOCATION]; 136 Location location = locationManager.getLocationByName(fieldLocation); 137 if (location == null) { 138 log.debug("adding location - {}", fieldLocation); 139 location = locationManager.newLocation(fieldLocation); 140 } 141 fieldTrack = inputLine[FIELD_TRACK]; 142 fieldLength = inputLine[FIELD_LENGTH].trim(); 143 fieldType = inputLine[FIELD_TYPE].trim(); 144 String typeValue = null; 145 if (fieldType.length() > 0) { 146 if (fieldType.equals(Bundle.getMessage("Spur").toLowerCase(Locale.ROOT))) { 147 typeValue = Track.SPUR; 148 } else if (fieldType.equals(Bundle.getMessage("Yard").toLowerCase(Locale.ROOT))) { 149 typeValue = Track.YARD; 150 } else if (fieldType.equals(Bundle.getMessage("Class/Interchange"))) { 151 typeValue = Track.INTERCHANGE; 152 } else if (fieldType.equals(Bundle.getMessage("Staging").toLowerCase(Locale.ROOT))) { 153 typeValue = Track.STAGING; 154 } else { 155 typeValue = "unknown"; 156 } 157 } 158 Track thisTrack = location.getTrackByName(fieldTrack, null); 159 Integer trackLength = null; 160 try { 161 trackLength = Integer.parseInt(fieldLength); 162 } catch (NumberFormatException exception) { 163 log.info( 164 "Import caught an exception converting the length field of the new track - value was {} at line number {}", 165 fieldLength, Integer.toString(lineNum)); 166 } 167 if (thisTrack != null) { 168 if (!thisTrack.getTrackType().equals(typeValue)) { 169 log.debug("Import is changing type of track for Location {} track {} to {}", location.getName(), 170 thisTrack.getName(), typeValue); 171 thisTrack.setTrackType(typeValue); 172 } 173 } else { 174 log.debug("Import is adding location {} new track {} of type {}", location.getName(), fieldTrack, 175 typeValue); 176 thisTrack = location.addTrack(fieldTrack, typeValue); 177 ++tracksAdded; 178 } 179 if (trackLength != null) { 180 thisTrack.setLength(trackLength); 181 } 182 183 // ignore FIELD_USED 184 // ignore FIELD_CARS 185 // ignore FIELD_LOCOS 186 // ignore FIELD_MOVES 187 188 if (inputLine.length >= FIELD_DIVISION) { 189 // division was included in import 190 String fieldDivision = inputLine[FIELD_DIVISION].trim(); 191 if (fieldDivision.length() > 0) { 192 Division division = divisionManager.newDivision(fieldDivision); 193 location.setDivision(division); 194 log.debug("Setting this location to division {}", division); 195 } 196 } 197 if (inputLine.length >= FIELD_SERVICED_BY) { 198 // process direction string (a list of directions each ending with a semicolon) 199 String[] directions = inputLine[FIELD_SERVICED_BY].split("; "); 200 log.debug("this track is serviced by {} directions", directions.length); 201 int trackDir = 0; // no direction yet 202 for (String dir : directions) { 203 trackDir += Setup.getDirectionInt(dir); 204 } 205 thisTrack.setTrainDirections(trackDir); 206 log.debug("setting this location to directions {}", trackDir); 207 } 208 if (inputLine.length >= FIELD_ROLLING_STOCK) { 209 // process rolling stock accepted 210 if (inputLine[FIELD_ROLLING_STOCK].length() > 0) { 211 log.debug("Setting track to accepting the following rolling stock: {}", 212 inputLine[FIELD_ROLLING_STOCK]); 213 // first we need to remove all rolling stock types 214 for (String typeName : thisTrack.getTypeNames()) { 215 thisTrack.deleteTypeName(typeName); 216 } 217 String[] rollingStock = inputLine[FIELD_ROLLING_STOCK].split("; "); 218 for (String typeName : rollingStock) { 219 thisTrack.addTypeName(typeName); 220 } 221 } 222 } 223 if (inputLine.length >= FIELD_ORDER) { 224 // process service order (Normal, FIFO or LIFO - Track handles the bundling 225 String fieldServiceOrder = inputLine[FIELD_ORDER].trim(); 226 if (fieldServiceOrder.length() > 0) { 227 thisTrack.setServiceOrder(fieldServiceOrder); 228 log.debug("Setting the service order to {}", fieldServiceOrder); 229 } 230 } 231 if (inputLine.length >= FIELD_ROADS) { 232 log.debug("setting the road names to: {}", inputLine[FIELD_ROADS]); 233 // note -- don't trim so the final semi-colon space remains on the last field 234 if (inputLine[FIELD_ROADS].length() > 0) { 235 String[] roads = inputLine[FIELD_ROADS].split("; "); 236 for (String road : roads) { 237 thisTrack.addRoadName(road); 238 } 239 } 240 } 241 if (inputLine.length >= FIELD_ROAD_OPTION) { 242 // process road option - again use the words imported 243 String roadOptions = inputLine[FIELD_ROAD_OPTION].trim(); 244 String optionValue = ""; 245 if (roadOptions.length() > 0) { 246 if (roadOptions.startsWith(Bundle.getMessage("AcceptsAllRoads"))) { 247 optionValue = Track.ALL_ROADS; 248 } else if (roadOptions.startsWith(Bundle.getMessage("AcceptOnly"))) { 249 optionValue = Track.INCLUDE_ROADS; 250 } else if (roadOptions.startsWith(Bundle.getMessage("Exclude"))) { 251 optionValue = Track.EXCLUDE_ROADS; 252 } 253 thisTrack.setRoadOption(optionValue); 254 log.debug("setting the road options to {}", optionValue); 255 } 256 } 257 if (inputLine.length >= FIELD_LOAD_OPTION) { 258 String loadOptions = inputLine[FIELD_LOAD_OPTION].trim(); 259 String optionValue = ""; 260 if (loadOptions.length() > 0) { 261 if (loadOptions.startsWith(Bundle.getMessage("AcceptsAllLoads"))) { 262 optionValue = Track.ALL_LOADS; 263 } else if (loadOptions.startsWith(Bundle.getMessage("AcceptOnly"))) { 264 optionValue = Track.INCLUDE_ROADS; 265 } else if (loadOptions.startsWith(Bundle.getMessage("Exclude"))) { 266 optionValue = Track.EXCLUDE_LOADS; 267 } else { 268 log.error("Locations Import load option was not recognized: {} ", loadOptions); 269 } 270 thisTrack.setLoadOption(optionValue); 271 } 272 } 273 if (inputLine.length >= FIELD_LOADS) { 274 // process names of loads, again, don't trim first 275 if (inputLine[FIELD_LOADS].length() > 0) { 276 String[] loads = inputLine[FIELD_LOADS].split("; "); 277 log.debug("This location is surviced by {} loads", loads.length); 278 for (String load : loads) { 279 thisTrack.addLoadName(load); 280 } 281 } 282 } 283 if (inputLine.length >= FIELD_SHIP_LOAD_OPTION) { 284 String loadOptions = inputLine[FIELD_SHIP_LOAD_OPTION].trim(); 285 String optionValue = ""; 286 if (loadOptions.length() > 0) { 287 if (loadOptions.startsWith(Bundle.getMessage("ShipsAllLoads"))) { 288 optionValue = Track.ALL_LOADS; 289 } else if (loadOptions.startsWith(Bundle.getMessage("ShipOnly"))) { 290 optionValue = Track.INCLUDE_ROADS; 291 } else if (loadOptions.startsWith(Bundle.getMessage("Exclude"))) { 292 optionValue = Track.EXCLUDE_LOADS; 293 } else { 294 log.error("Locations Import ship load option was not recognized: {} ", loadOptions); 295 } 296 thisTrack.setShipLoadOption(optionValue); 297 } 298 } 299 if (inputLine.length >= FIELD_SHIPS) { 300 // process names of loads, again, don't trim first 301 if (inputLine[FIELD_SHIPS].length() > 0) { 302 String[] loads = inputLine[FIELD_SHIPS].split("; "); 303 log.debug("This location ships {} loads", loads.length); 304 for (String load : loads) { 305 thisTrack.addShipLoadName(load); 306 } 307 } 308 } 309 310 // TODO import fields 18 through 21 311 312 if (inputLine.length >= FIELD_SCHEDULE_NAME) { 313 String scheduleName = inputLine[FIELD_SCHEDULE_NAME].trim(); 314 Schedule schedule = InstanceManager.getDefault(ScheduleManager.class).newSchedule(scheduleName); 315 thisTrack.setSchedule(schedule); 316 } 317 if (inputLine.length >= FIELD_SCHEDULE_MODE) { 318 String scheduleMode = inputLine[FIELD_SCHEDULE_MODE].trim(); 319 // default is match mode 320 if (scheduleMode.equals(Bundle.getMessage("Sequential"))) { 321 thisTrack.setScheduleMode(Track.SEQUENTIAL); 322 } 323 } 324 if (inputLine.length >= FIELD_PERCENT_STAGING) { 325 String percentStaging = inputLine[FIELD_PERCENT_STAGING].trim(); 326 try { 327 thisTrack.setReservationFactor(Integer.parseInt(percentStaging)); 328 } catch (NumberFormatException exception) { 329 log.debug("Exception converting percentage from staging - value was {}", percentStaging); 330 } 331 } 332 if (inputLine.length >= FIELD_ALTERNATE_TRACK) { 333 String alternateTrackName = inputLine[FIELD_ALTERNATE_TRACK].trim(); 334 if (!alternateTrackName.isBlank() && !alternateTrackName.equals(Bundle.getMessage("ButtonYes"))) { 335 Track altTrack = location.getTrackByName(alternateTrackName, null); 336 if (altTrack == null) { 337 altTrack = location.addTrack(alternateTrackName, Track.YARD); 338 ++tracksAdded; 339 } 340 thisTrack.setAlternateTrack(altTrack); 341 } 342 } 343 if (inputLine.length >= FIELD_POOL_NAME) { 344 String poolName = inputLine[FIELD_POOL_NAME].trim(); 345 Pool pool = location.addPool(poolName); 346 thisTrack.setPool(pool); 347 } 348 if (inputLine.length >= FIELD_TRACK_MINIMUM_POOL) { 349 String minPool = inputLine[FIELD_TRACK_MINIMUM_POOL].trim(); 350 if (minPool.length() > 0) { 351 log.debug("setting track pool minimum: {}", minPool); 352 try { 353 thisTrack.setPoolMinimumLength(Integer.parseInt(minPool)); 354 } catch (NumberFormatException exception) { 355 log.debug("Exception converting the ignore minimum to a number - value was {}", minPool); 356 } 357 } 358 } 359 if (inputLine.length >= FIELD_TRACK_BLOCKING_ORDER) { 360 String fieldTrackBlockingOrder = inputLine[FIELD_TRACK_BLOCKING_ORDER].trim(); 361 if (fieldTrackBlockingOrder.length() > 0) { 362 log.debug("setting the blocking order to {}", fieldTrackBlockingOrder); 363 Integer blockingOrder = null; 364 try { 365 blockingOrder = Integer.parseInt(fieldTrackBlockingOrder); 366 thisTrack.setBlockingOrder(blockingOrder); 367 } catch (NumberFormatException exception) { 368 log.debug("Exception converting the track blocking order to a number - value was {}", 369 fieldTrackBlockingOrder); 370 } 371 } 372 } 373 if (inputLine.length >= FIELD_PLANNED_PICK_UPS) { 374 String ignoreUsedLength = inputLine[FIELD_PLANNED_PICK_UPS].trim(); 375 if (ignoreUsedLength.length() > 0) { 376 try { 377 Integer ignorePercentage = Integer.parseInt(ignoreUsedLength); 378 thisTrack.setIgnoreUsedLengthPercentage(ignorePercentage); 379 } catch (NumberFormatException exception) { 380 log.debug("Exception converting field Ignore Used track Percentage - value was {}", 381 ignoreUsedLength); 382 } 383 } 384 } 385 386 // TODO import fields 31 though 42 387 388 if (inputLine.length >= FIELD_COMMENT) { 389 String fieldComment = inputLine[FIELD_COMMENT].trim(); 390 if (fieldComment.length() > 0) { 391 log.debug("setting the location comment to: {}", fieldComment); 392 thisTrack.setComment(fieldComment); 393 } 394 } 395 if (inputLine.length >= FIELD_COMMENT_BOTH) { 396 String commentBoth = inputLine[FIELD_COMMENT_BOTH].trim(); 397 thisTrack.setCommentBoth(commentBoth); 398 } 399 if (inputLine.length >= FIELD_COMMENT_PICKUPS) { 400 String commentPickups = inputLine[FIELD_COMMENT_PICKUPS].trim(); 401 thisTrack.setCommentPickup(commentPickups); 402 } 403 if (inputLine.length >= FIELD_COMMENT_SETOUTS) { 404 String commentSetouts = inputLine[FIELD_COMMENT_SETOUTS].trim(); 405 thisTrack.setCommentSetout(commentSetouts); 406 } 407 } 408 ThreadingUtil.runOnGUI(() -> { 409 if (importOkay) { 410 JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("ImportTracksAdded", tracksAdded), 411 Bundle.getMessage("SuccessfulImport"), JmriJOptionPane.INFORMATION_MESSAGE); 412 } else { 413 JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("ImportTracksAdded", tracksAdded), 414 Bundle.getMessage("ImportFailed"), JmriJOptionPane.ERROR_MESSAGE); 415 } 416 }); 417 fstatus.dispose(); 418 } 419 420 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ImportLocations.class); 421 422}