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