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.error("Locations Import load option was not recognized: {} ", loadOptions);
249                    }
250                    thisTrack.setLoadOption(optionValue);
251                }
252            }
253            if (inputLine.length >= FIELD_LOADS) {
254                // process names of loads, again, don't trim first
255                if (inputLine[FIELD_LOADS].length() > 0) {
256                    String[] loads = inputLine[FIELD_LOADS].split("; ");
257                    log.debug("This location is surviced by {} loads", loads.length);
258                    for (String load : loads) {
259                        thisTrack.addLoadName(load);
260                    }
261                }
262            }
263            if (inputLine.length >= FIELD_SHIP_LOAD_OPTION) {
264                String loadOptions = inputLine[FIELD_SHIP_LOAD_OPTION].trim();
265                String optionValue = "";
266                if (loadOptions.length() > 0) {
267                    if (loadOptions.startsWith(Bundle.getMessage("ShipsAllLoads"))) {
268                        optionValue = Track.ALL_LOADS;
269                    } else if (loadOptions.startsWith(Bundle.getMessage("ShipOnly"))) {
270                        optionValue = Track.INCLUDE_ROADS;
271                    } else if (loadOptions.startsWith(Bundle.getMessage("Exclude"))) {
272                        optionValue = Track.EXCLUDE_LOADS;
273                    } else {
274                        log.error("Locations Import ship load option was not recognized: {} ", loadOptions);
275                    }
276                    thisTrack.setShipLoadOption(optionValue);
277                }
278            }
279            if (inputLine.length >= FIELD_SHIPS) {
280                // process names of loads, again, don't trim first
281                if (inputLine[FIELD_SHIPS].length() > 0) {
282                    String[] loads = inputLine[FIELD_SHIPS].split("; ");
283                    log.debug("This location ships {} loads", loads.length);
284                    for (String load : loads) {
285                        thisTrack.addShipLoadName(load);
286                    }
287                }
288            }
289
290            // TODO import fields 14 through 22
291
292            if (inputLine.length >= FIELD_IGNORE_MINIMUM) {
293                String ignoreMin = inputLine[FIELD_IGNORE_MINIMUM].trim();
294                if (ignoreMin.length() > 0) {
295                    log.debug("setting the ignore minimum to {}", ignoreMin);
296                    Integer ignoreValue = null;
297                    try {
298                        ignoreValue = Integer.valueOf(ignoreMin);
299                        thisTrack.setBlockingOrder(ignoreValue);
300                    } catch (NumberFormatException exception) {
301                        log.debug("Exception converting the ignore minimum to a number - value was {}", ignoreMin);
302                    }
303                }
304            }
305            if (inputLine.length >= FIELD_TRACK_BLOCKING_ORDER) {
306                String fieldTrackBlockingOrder = inputLine[FIELD_TRACK_BLOCKING_ORDER].trim();
307                if (fieldTrackBlockingOrder.length() > 0) {
308                    log.debug("setting the blocking order to {}", fieldTrackBlockingOrder);
309                    Integer blockingOrder = null;
310                    try {
311                        blockingOrder = Integer.valueOf(fieldTrackBlockingOrder);
312                        thisTrack.setBlockingOrder(blockingOrder);
313                    } catch (NumberFormatException exception) {
314                        log.debug("Exception converting the track blocking order to a number - value was {}", fieldTrackBlockingOrder);
315                    }
316                }
317            }
318            if (inputLine.length >= FIELD_PLANNED_PICK_UPS) {
319                String ignoreUsedLength = inputLine[FIELD_PLANNED_PICK_UPS].trim();
320                if (ignoreUsedLength.length() > 0) {
321                    try {
322                        Integer ignorePercentage = Integer.valueOf(ignoreUsedLength);
323                        thisTrack.setIgnoreUsedLengthPercentage( ignorePercentage);
324                    } catch (NumberFormatException exception) {
325                        log.debug("Exception converting field Ignore Used track Percentage - value was {}", ignoreUsedLength);
326                    }
327                }
328            }
329            // TODO import fields 26 though 35
330            
331            if (inputLine.length >= FIELD_COMMENT) {
332                String fieldComment = inputLine[FIELD_COMMENT].trim();
333                if (fieldComment.length() > 0) {
334                    log.debug("setting the location comment to: {}", fieldComment);
335                    thisTrack.setComment(fieldComment);
336                }
337            }
338            if (inputLine.length >= FIELD_COMMENT_BOTH) {
339                String commentBoth = inputLine[FIELD_COMMENT_BOTH].trim();
340                thisTrack.setCommentBoth(commentBoth);
341            }
342            if (inputLine.length >= FIELD_COMMENT_PICKUPS) {
343                String commentPickups = inputLine[FIELD_COMMENT_PICKUPS].trim();
344                thisTrack.setCommentPickup(commentPickups);
345            }
346            if (inputLine.length >= FIELD_COMMENT_SETOUTS) {
347                String commentSetouts = inputLine[FIELD_COMMENT_SETOUTS].trim();
348                thisTrack.setCommentSetout(commentSetouts);
349            }
350        }
351        if (importOkay) {
352            JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("ImportTracksAdded", tracksAdded),
353                Bundle.getMessage("SuccessfulImport"), JmriJOptionPane.INFORMATION_MESSAGE);
354        } else {
355            JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("ImportTracksAdded", tracksAdded),
356                Bundle.getMessage("ImportFailed"), JmriJOptionPane.ERROR_MESSAGE);
357        }
358        fstatus.dispose();
359    }
360
361    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ImportLocations.class);
362
363}