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