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