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}