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