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