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}