001package jmri.jmrit.dispatcher;
002
003import java.io.File;
004import java.util.ArrayList;
005import java.util.Arrays;
006import java.util.List;
007import java.util.regex.Matcher;
008import java.util.regex.Pattern;
009import jmri.util.FileUtil;
010import jmri.util.XmlFilenameFilter;
011import org.jdom2.Document;
012import org.jdom2.Element;
013import org.slf4j.Logger;
014import org.slf4j.LoggerFactory;
015import jmri.configurexml.AbstractXmlAdapter.EnumIO;
016import jmri.configurexml.AbstractXmlAdapter.EnumIoNamesNumbers;
017import jmri.jmrit.dispatcher.ActiveTrain.TrainDetection;
018
019/**
020 * Handles reading and writing of TrainInfo files to disk as an XML file to/from
021 * the dispatcher/traininfo/ directory in the user's preferences area
022 * <p>
023 * This class manipulates the files conforming to the dispatcher-traininfo DTD
024 * <p>
025 * The file is written when the user requests that train information be saved. A
026 * TrainInfo file is read when the user request it in the Activate New Train
027 * window
028 *
029 * <p>
030 * This file is part of JMRI.
031 * <p>
032 * JMRI is open source software; you can redistribute it and/or modify it under
033 * the terms of version 2 of the GNU General Public License as published by the
034 * Free Software Foundation. See the "COPYING" file for a copy of this license.
035 * <p>
036 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
037 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
038 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
039 *
040 * @author Dave Duchamp Copyright (C) 2009
041 */
042public class TrainInfoFile extends jmri.jmrit.XmlFile {
043
044    public TrainInfoFile() {
045        super();
046    }
047    // operational variables
048    private String fileLocation = FileUtil.getUserFilesPath()
049            + "dispatcher" + File.separator + "traininfo" + File.separator;
050
051    public void setFileLocation(String testLocation) {
052        fileLocation = testLocation;
053    }
054    private Document doc = null;
055    private Element root = null;
056
057    static final EnumIO<ActiveTrain.TrainDetection> trainsdectionFromEnumMap = new EnumIoNamesNumbers<>(ActiveTrain.TrainDetection.class);
058
059    /*
060     *  Reads Dispatcher TrainInfo from a file in the user's preferences directory
061     *  If the file containing Dispatcher TrainInfo does not exist this routine returns quietly.
062     *  "name" is assumed to have the .xml or .XML extension already included
063     */
064    public TrainInfo readTrainInfo(String name) throws org.jdom2.JDOMException, java.io.IOException {
065        log.debug("entered readTrainInfo for {}", name);
066        TrainInfo tInfo = null;
067        int version  = 1;
068        // check if file exists
069        if (checkFile(fileLocation + name)) {
070            // file is present.
071            tInfo = new TrainInfo();
072            root = rootFromName(fileLocation + name);
073            if (root != null) {
074                // there is a file
075                Element traininfo = root.getChild("traininfo");
076                if (traininfo != null) {
077                    // get version so we dont look for missing fields
078                    if (traininfo.getAttribute("version") != null ) {
079                        try {
080                            version = traininfo.getAttribute("version").getIntValue();
081                        }
082                        catch(Exception ex) {
083                            log.error("Traininfo file version number not an integer: assuming version 1");
084                            version = 1;
085                        }
086                    } else {
087                        version = 1;
088                    }
089                    tInfo.setVersion(version);
090                    // there are train info options defined, read them
091                    if (traininfo.getAttribute("transitname") != null) {
092                        // there is a transit name selected
093                        tInfo.setTransitName(traininfo.getAttribute("transitname").getValue());
094                    } else {
095                        log.error("Transit name missing when reading TrainInfoFile {}", name);
096                    }
097                    if (traininfo.getAttribute("trainname") != null) {
098                        // there is a transit name selected
099                        tInfo.setTrainName(traininfo.getAttribute("trainname").getValue());
100                    } else {
101                        log.error("Train name missing when reading TrainInfoFile {}", name);
102                    }
103                    if (traininfo.getAttribute("dccaddress") != null) {
104                        tInfo.setDccAddress(traininfo.getAttribute("dccaddress").getValue());
105                    } else {
106                        log.error("DCC Address missing when reading TrainInfoFile {}", name);
107                    }
108                    if (traininfo.getAttribute("trainintransit") != null) {
109                        tInfo.setTrainInTransit(true);
110                        if (traininfo.getAttribute("trainintransit").getValue().equals("no")) {
111                            tInfo.setTrainInTransit(false);
112                        }
113                    } else {
114                        log.error("Train in Transit check box missing  when reading TrainInfoFile {}", name);
115                    }
116                    if (traininfo.getAttribute("startblockname") != null) {
117                        // there is a transit name selected
118                        tInfo.setStartBlockName(traininfo.getAttribute("startblockname").getValue());
119                    } else {
120                        log.error("Start block name missing when reading TrainInfoFile {}", name);
121                    }
122                    if (traininfo.getAttribute("endblockname") != null) {
123                        // there is a transit name selected
124                        tInfo.setDestinationBlockName(traininfo.getAttribute("endblockname").getValue());
125                    } else {
126                        log.error("Destination block name missing when reading TrainInfoFile {}", name);
127                    }
128
129                    if (traininfo.getAttribute("trainfromroster") != null) {
130                        tInfo.setTrainFromRoster(true);
131                        if (traininfo.getAttribute("trainfromroster").getValue().equals("no")) {
132                            tInfo.setTrainFromRoster(false);
133                        }
134                    }
135                    if (traininfo.getAttribute("trainfromtrains") != null) {
136                        tInfo.setTrainFromTrains(true);
137                        if (traininfo.getAttribute("trainfromtrains").getValue().equals("no")) {
138                            tInfo.setTrainFromTrains(false);
139                        }
140                    }
141                    if (traininfo.getAttribute("trainfromuser") != null) {
142                        tInfo.setTrainFromUser(true);
143                        if (traininfo.getAttribute("trainfromuser").getValue().equals("no")) {
144                            tInfo.setTrainFromUser(false);
145                        }
146                    }
147                    if (traininfo.getAttribute("trainfromsetlater") != null) {
148                        tInfo.setTrainFromSetLater(true);
149                        if (traininfo.getAttribute("trainfromsetlater").getValue().equals("no")) {
150                            tInfo.setTrainFromSetLater(false);
151                        }
152                    }
153                    if (traininfo.getAttribute("priority") != null) {
154                        tInfo.setPriority(Integer.parseInt(traininfo.getAttribute("priority").getValue()));
155                    } else {
156                        log.error("Priority missing when reading TrainInfoFile {}", name);
157                    }
158                    if (traininfo.getAttribute("allocatealltheway") != null) {
159                        if (traininfo.getAttribute("allocatealltheway").getValue().equals("yes")) {
160                            tInfo.setAllocateAllTheWay(true);
161                        }
162                    }
163                    if (traininfo.getAttribute("allocationmethod") != null) {
164                        tInfo.setAllocationMethod(traininfo.getAttribute("allocationmethod").getIntValue());
165                    }
166                    if (traininfo.getAttribute("nexttrain") != null) {
167                        tInfo.setNextTrain(traininfo.getAttribute("nexttrain").getValue());
168                    }
169                    if (traininfo.getAttribute("resetwhendone") != null) {
170                        if (traininfo.getAttribute("resetwhendone").getValue().equals("yes")) {
171                            tInfo.setResetWhenDone(true);
172                        }
173                        if (traininfo.getAttribute("delayedrestart") != null) {
174                            // for older files that didnot have seperate restart details for to and fro
175                            // we default that data to this data.
176                            switch (traininfo.getAttribute("delayedrestart").getValue()) {
177                                case "no":
178                                    tInfo.setDelayedRestart(ActiveTrain.NODELAY);
179                                    tInfo.setReverseDelayedRestart(ActiveTrain.NODELAY);
180                                    break;
181                                case "sensor":
182                                    tInfo.setDelayedRestart(ActiveTrain.SENSORDELAY);
183                                    tInfo.setReverseDelayedRestart(ActiveTrain.SENSORDELAY);
184                                    if (traininfo.getAttribute("delayedrestartsensor") != null) {
185                                        tInfo.setRestartSensorName(traininfo.getAttribute("delayedrestartsensor").getValue());
186                                        tInfo.setReverseRestartSensorName(traininfo.getAttribute("delayedrestartsensor").getValue());
187                                    }
188                                    if (traininfo.getAttribute("resetrestartsensor") != null) {
189                                        tInfo.setResetRestartSensor(traininfo.getAttribute("resetrestartsensor").getValue().equals("yes"));
190                                        tInfo.setReverseResetRestartSensor(traininfo.getAttribute("resetrestartsensor").getValue().equals("yes"));
191                                    }
192                                    break;
193                                case "timed":
194                                    tInfo.setDelayedRestart(ActiveTrain.TIMEDDELAY);
195                                    tInfo.setReverseDelayedRestart(ActiveTrain.TIMEDDELAY);
196                                    if (traininfo.getAttribute("delayedrestarttime") != null) {
197                                        tInfo.setRestartDelayMin((int) traininfo.getAttribute("delayedrestarttime").getLongValue());
198                                        tInfo.setReverseRestartDelayMin((int) traininfo.getAttribute("delayedrestarttime").getLongValue());
199                                    }   break;
200                                default:
201                                    break;
202                            }
203                        }
204                    }
205                    if (traininfo.getAttribute("reverseatend") != null) {
206                        tInfo.setReverseAtEnd(true);
207                        if (traininfo.getAttribute("reverseatend").getValue().equals("no")) {
208                            tInfo.setReverseAtEnd(false);
209                        }
210                        if (version > 3) {
211                            // fro delays are independent from to delays
212                            if (traininfo.getAttribute("reversedelayedrestart") != null) {
213                                switch (traininfo.getAttribute("reversedelayedrestart").getValue()) {
214                                    case "no":
215                                        tInfo.setReverseDelayedRestart(ActiveTrain.NODELAY);
216                                        break;
217                                    case "sensor":
218                                        tInfo.setReverseDelayedRestart(ActiveTrain.SENSORDELAY);
219                                        if (traininfo.getAttribute("reversedelayedrestartsensor") != null) {
220                                            tInfo.setReverseRestartSensorName(
221                                                    traininfo.getAttribute("reversedelayedrestartsensor").getValue());
222                                        }
223                                        if (traininfo.getAttribute("reverseresetrestartsensor") != null) {
224                                            tInfo.setReverseResetRestartSensor(
225                                                    traininfo.getAttribute("reverseresetrestartsensor").getValue()
226                                                            .equals("yes"));
227                                        }
228                                        break;
229                                    case "timed":
230                                        tInfo.setReverseDelayedRestart(ActiveTrain.TIMEDDELAY);
231                                        if (traininfo.getAttribute("reversedelayedrestarttime") != null) {
232                                            tInfo.setReverseRestartDelayMin((int) traininfo
233                                                    .getAttribute("reversedelayedrestarttime").getLongValue());
234                                        }
235                                        break;
236                                    default:
237                                        break;
238                                }
239                            }
240                        }
241                    }
242                    if (traininfo.getAttribute("delayedstart") != null) {
243                        switch (traininfo.getAttribute("delayedstart").getValue()) {
244                            case "no":
245                                tInfo.setDelayedStart(ActiveTrain.NODELAY);
246                                break;
247                            case "sensor":
248                                tInfo.setDelayedStart(ActiveTrain.SENSORDELAY);
249                                break;
250                            default:
251                                //This covers the old versions of the file with "yes"
252                                tInfo.setDelayedStart(ActiveTrain.TIMEDDELAY);
253                                break;
254                        }
255                    }
256                    if (traininfo.getAttribute("departuretimehr") != null) {
257                        tInfo.setDepartureTimeHr(Integer.parseInt(traininfo.getAttribute("departuretimehr").getValue()));
258                    }
259                    if (traininfo.getAttribute("departuretimemin") != null) {
260                        tInfo.setDepartureTimeMin(Integer.parseInt(traininfo.getAttribute("departuretimemin").getValue()));
261                    }
262                    if (traininfo.getAttribute("delayedSensor") != null) {
263                        tInfo.setDelaySensorName(traininfo.getAttribute("delayedSensor").getValue());
264                    }
265                    if (traininfo.getAttribute("resetstartsensor") != null) {
266                        tInfo.setResetStartSensor(traininfo.getAttribute("resetstartsensor").getValue().equals("yes"));
267                    }
268                    if (traininfo.getAttribute("traintype") != null) {
269                        tInfo.setTrainType(traininfo.getAttribute("traintype").getValue());
270                    }
271                    if (traininfo.getAttribute("autorun") != null) {
272                        tInfo.setAutoRun(true);
273                        if (traininfo.getAttribute("autorun").getValue().equals("no")) {
274                            tInfo.setAutoRun(false);
275                        }
276                    }
277                    if (traininfo.getAttribute("loadatstartup") != null) {
278                        tInfo.setLoadAtStartup(true);
279                        if (traininfo.getAttribute("loadatstartup").getValue().equals("no")) {
280                            tInfo.setLoadAtStartup(false);
281                        }
282                    }
283                    // here retrieve items related only to automatically run trains if present
284                    if (traininfo.getAttribute("speedfactor") != null) {
285                        tInfo.setSpeedFactor(Float.parseFloat(traininfo.getAttribute("speedfactor").getValue()));
286                    }
287                    if (traininfo.getAttribute("maxspeed") != null) {
288                        tInfo.setMaxSpeed(Float.parseFloat(traininfo.getAttribute("maxspeed").getValue()));
289                    }
290                    if (traininfo.getAttribute("ramprate") != null) {
291                        tInfo.setRampRate(traininfo.getAttribute("ramprate").getValue());
292                    }
293                    tInfo.setTrainDetection(TrainDetection.TRAINDETECTION_WHOLETRAIN);
294                    if (version > 4) {
295                        if (traininfo.getAttribute("traindetection") != null) {
296                            tInfo.setTrainDetection(trainsdectionFromEnumMap.inputFromAttribute(traininfo.getAttribute("traindetection")));
297                        }
298                    }
299                    else {
300                        if (traininfo.getAttribute("resistancewheels").getValue().equals("no")) {
301                            tInfo.setTrainDetection(TrainDetection.TRAINDETECTION_HEADONLY);
302                        }
303                    }
304                    if (traininfo.getAttribute("runinreverse") != null) {
305                        tInfo.setRunInReverse(true);
306                        if (traininfo.getAttribute("runinreverse").getValue().equals("no")) {
307                            tInfo.setRunInReverse(false);
308                        }
309                    }
310                    if (traininfo.getAttribute("sounddecoder") != null) {
311                        tInfo.setSoundDecoder(true);
312                        if (traininfo.getAttribute("sounddecoder").getValue().equals("no")) {
313                            tInfo.setSoundDecoder(false);
314                        }
315                    }
316                    if (traininfo.getAttribute("maxtrainlength") != null) {
317                        tInfo.setMaxTrainLength(Float.parseFloat(traininfo.getAttribute("maxtrainlength").getValue()));
318                    }
319                    if (traininfo.getAttribute("terminatewhendone") != null) {
320                        tInfo.setTerminateWhenDone(false);
321                        if (traininfo.getAttribute("terminatewhendone").getValue().equals("yes")) {
322                            tInfo.setTerminateWhenDone(true);
323                        }
324                    }
325                    if (traininfo.getAttribute("usespeedprofile") != null) {
326                        tInfo.setUseSpeedProfile(false);
327                        if (traininfo.getAttribute("usespeedprofile").getValue().equals("yes")) {
328                            tInfo.setUseSpeedProfile(true);
329                        }
330                    }
331                    if (traininfo.getAttribute("stopbyspeedprofile") != null) {
332                        tInfo.setStopBySpeedProfile(false);
333                        if (traininfo.getAttribute("stopbyspeedprofile").getValue().equals("yes")) {
334                            tInfo.setStopBySpeedProfile(true);
335                        }
336                    }
337                    if (traininfo.getAttribute("stopbyspeedprofileadjust") != null) {
338                        tInfo.setStopBySpeedProfileAdjust(traininfo.getAttribute("stopbyspeedprofileadjust").getFloatValue());
339                    }
340                    if (traininfo.getAttribute("waittime") != null) {
341                        tInfo.setWaitTime(traininfo.getAttribute("waittime").getFloatValue());
342                    }
343                    if (traininfo.getAttribute("blockname") != null) {
344                        tInfo.setBlockName(traininfo.getAttribute("blockname").getValue());
345                    }
346
347                    if (version == 1) {
348                        String parseArray[];
349                        // If you only have a systemname then its everything before the dash
350                        tInfo.setStartBlockId(tInfo.getStartBlockName().split("-")[0]);
351                        // If you have a systemname and username you want everything before the open bracket
352                        tInfo.setStartBlockId(tInfo.getStartBlockId().split("\\(")[0]);
353                        // to guard against a dash in the names, we need the last part, not just [1]
354                        parseArray = tInfo.getStartBlockName().split("-");
355                        tInfo.setStartBlockSeq(-1); // default value
356                        if (parseArray.length > 0) {
357                            try {
358                                tInfo.setStartBlockSeq(Integer.parseInt(parseArray[parseArray.length -1]));
359                            }
360                            catch (Exception Ex) {
361                                log.error("Invalid StartBlockSequence{}",parseArray[parseArray.length -1]);
362                            }
363                        }
364                        // repeat for destination
365                        tInfo.setDestinationBlockId(tInfo.getDestinationBlockName().split("-")[0]);
366                        tInfo.setDestinationBlockId(tInfo.getDestinationBlockId().split("\\(")[0]);
367                        parseArray = tInfo.getDestinationBlockName().split("-");
368                        tInfo.setDestinationBlockSeq(-1);
369                        if (parseArray.length > 0) {
370                            try {
371                                tInfo.setDestinationBlockSeq(Integer.parseInt(parseArray[parseArray.length -1]));
372                            }
373                            catch (Exception Ex) {
374                                log.error("Invalid StartBlockSequence{}",parseArray[parseArray.length -1]);
375                            }
376                        }
377                        // Transit we need the whole thing or the bit before the first open bracket
378                        tInfo.setTransitId(tInfo.getTransitName().split("\\(")[0]);
379                        log.debug("v1: t = {}, bs = {}, be = {}", tInfo.getTransitName(), tInfo.getStartBlockName(), tInfo.getDestinationBlockName());
380                    }
381                    if ( version > 1 ) {
382                        if (traininfo.getAttribute("transitid") != null) {
383                            // there is a transit name selected
384                            tInfo.setTransitId(traininfo.getAttribute("transitid").getValue());
385                        } else {
386                            log.error("Transit id missing when reading TrainInfoFile {}", name);
387                        }
388                        if (traininfo.getAttribute("startblockid") != null) {
389                            // there is a transit name selected
390                            tInfo.setStartBlockId(traininfo.getAttribute("startblockid").getValue());
391                        } else {
392                            log.error("Start block Id missing when reading TrainInfoFile {}", name);
393                        }
394                        if (traininfo.getAttribute("endblockid") != null) {
395                            // there is a transit name selected
396                            tInfo.setDestinationBlockId(traininfo.getAttribute("endblockid").getValue());
397                        } else {
398                            log.error("Destination block Id missing when reading TrainInfoFile {}", name);
399                        }
400                        if (traininfo.getAttribute("startblockseq") != null) {
401                            // there is a transit name selected
402                            try {
403                                tInfo.setStartBlockSeq(traininfo.getAttribute("startblockseq").getIntValue());
404                            }
405                            catch (Exception ex) {
406                                log.error("Start block sequence invalid when reading TrainInfoFile");
407                            }
408                        } else {
409                            log.error("Start block sequence missing when reading TrainInfoFile {}", name);
410                        }
411                        if (traininfo.getAttribute("endblockseq") != null) {
412                            // there is a transit name selected
413                            try {
414                                tInfo.setDestinationBlockSeq(traininfo.getAttribute("endblockseq").getIntValue());
415                            }
416                            catch (Exception ex) {
417                                log.error("Destination block sequence invalid when reading TrainInfoFile {}", name);
418                            }
419                        } else {
420                            log.error("Destination block sequence missing when reading TrainInfoFile {}", name);
421                        }
422                    }
423                    if ( version == 1 || version == 2) {
424                        // Change transit and block names from sysName(userName) to displayName
425                        tInfo.setTransitName(convertName(tInfo.getTransitName()));
426                        tInfo.setStartBlockName(convertName(tInfo.getStartBlockName()));
427                        tInfo.setDestinationBlockName(convertName(tInfo.getDestinationBlockName()));
428                    }
429               }
430            }
431        }
432        return tInfo;
433    }
434
435    public String convertName(String name) {
436        // transit: sys(user), block: sys(user)-n
437        String newName = name;
438
439        Pattern p = Pattern.compile(".+\\((.+)\\)(-\\d+)*");
440        Matcher m = p.matcher(name);
441        if (m.matches()) {
442            log.debug("regex: name = '{}', group 1 = '{}', group 2 = '{}'", name, m.group(1), m.group(2));
443            if (m.group(1) != null) {
444                newName = m.group(1).trim();
445                if (m.group(2) != null) {
446                    newName = newName + m.group(2).trim();
447                }
448            }
449        }
450
451        log.debug("convertName: old = '{}', new = '{}'", name, newName);
452        return newName;
453    }
454
455    /*
456     *  Writes out Dispatcher options to a file in the user's preferences directory
457     */
458    public void writeTrainInfo(TrainInfo tf, String name) throws java.io.IOException {
459        log.debug("entered writeTrainInfo");
460        root = new Element("traininfofile");
461        doc = newDocument(root, dtdLocation + "dispatcher-traininfo.dtd");
462        // add XSLT processing instruction
463        // <?xml-stylesheet type="text/xsl" href="XSLT/block-values.xsl"?>
464        java.util.Map<String, String> m = new java.util.HashMap<>();
465        m.put("type", "text/xsl");
466        m.put("href", xsltLocation + "dispatcher-traininfo.xsl");
467        org.jdom2.ProcessingInstruction p = new org.jdom2.ProcessingInstruction("xml-stylesheet", m);
468        doc.addContent(0, p);
469
470        // save Dispatcher TrainInfo in xml format
471        Element traininfo = new Element("traininfo");
472        // write version number
473        traininfo.setAttribute("version", "5");
474        traininfo.setAttribute("transitname", tf.getTransitName());
475        traininfo.setAttribute("transitid", tf.getTransitId());
476        traininfo.setAttribute("trainname", tf.getTrainName());
477        traininfo.setAttribute("dccaddress", tf.getDccAddress());
478        traininfo.setAttribute("trainintransit", "" + (tf.getTrainInTransit() ? "yes" : "no"));
479        traininfo.setAttribute("startblockname", tf.getStartBlockName());
480        traininfo.setAttribute("startblockid", tf.getStartBlockId());
481        traininfo.setAttribute("startblockseq", Integer.toString(tf.getStartBlockSeq()));
482        traininfo.setAttribute("endblockname", tf.getDestinationBlockName());
483        traininfo.setAttribute("endblockid", tf.getDestinationBlockId());
484        traininfo.setAttribute("endblockseq", Integer.toString(tf.getDestinationBlockSeq()));
485        traininfo.setAttribute("trainfromroster", "" + (tf.getTrainFromRoster() ? "yes" : "no"));
486        traininfo.setAttribute("trainfromtrains", "" + (tf.getTrainFromTrains() ? "yes" : "no"));
487        traininfo.setAttribute("trainfromuser", "" + (tf.getTrainFromUser() ? "yes" : "no"));
488        traininfo.setAttribute("trainfromsetlater", "" + (tf.getTrainFromSetLater() ? "yes" : "no"));
489        traininfo.setAttribute("priority", Integer.toString(tf.getPriority()));
490        traininfo.setAttribute("traindetection", trainsdectionFromEnumMap.outputFromEnum(tf.getTrainDetection()));
491        traininfo.setAttribute("resetwhendone", "" + (tf.getResetWhenDone() ? "yes" : "no"));
492        switch (tf.getDelayedRestart()) {
493            case ActiveTrain.SENSORDELAY:
494                traininfo.setAttribute("delayedrestart", "sensor");
495                traininfo.setAttribute("delayedrestartsensor", tf.getRestartSensorName());
496                traininfo.setAttribute("resetrestartsensor", "" + (tf.getResetRestartSensor() ? "yes" : "no"));
497                break;
498            case ActiveTrain.TIMEDDELAY:
499                traininfo.setAttribute("delayedrestart", "timed");
500                traininfo.setAttribute("delayedrestarttime", Integer.toString(tf.getRestartDelayMin()));
501                break;
502            default:
503                traininfo.setAttribute("delayedrestart", "no");
504                break;
505        }
506
507        traininfo.setAttribute("reverseatend", "" + (tf.getReverseAtEnd() ? "yes" : "no"));
508        switch (tf.getReverseDelayedRestart()) {
509            case ActiveTrain.SENSORDELAY:
510                traininfo.setAttribute("reversedelayedrestart", "sensor");
511                traininfo.setAttribute("reversedelayedrestartsensor", tf.getReverseRestartSensorName());
512                traininfo.setAttribute("reverseresetrestartsensor", "" + (tf.getReverseResetRestartSensor() ? "yes" : "no"));
513                break;
514            case ActiveTrain.TIMEDDELAY:
515                traininfo.setAttribute("reversedelayedrestart", "timed");
516                traininfo.setAttribute("reversedelayedrestarttime", Integer.toString(tf.getReverseRestartDelayMin()));
517                break;
518            default:
519                traininfo.setAttribute("reversedelayedrestart", "no");
520                break;
521        }
522        if (tf.getDelayedStart() == ActiveTrain.TIMEDDELAY) {
523            traininfo.setAttribute("delayedstart", "timed");
524        } else if (tf.getDelayedStart() == ActiveTrain.SENSORDELAY) {
525            traininfo.setAttribute("delayedstart", "sensor");
526            if (tf.getDelaySensorName() != null) {
527                traininfo.setAttribute("delayedSensor", tf.getDelaySensorName());
528                traininfo.setAttribute("resetstartsensor", "" + (tf.getResetStartSensor() ? "yes" : "no"));
529            }
530        }
531
532        traininfo.setAttribute("terminatewhendone", (tf.getTerminateWhenDone() ? "yes" : "no"));
533        traininfo.setAttribute("departuretimehr", Integer.toString(tf.getDepartureTimeHr()));
534        traininfo.setAttribute("departuretimemin", Integer.toString(tf.getDepartureTimeMin()));
535        traininfo.setAttribute("traintype", tf.getTrainType());
536        traininfo.setAttribute("autorun", "" + (tf.getAutoRun() ? "yes" : "no"));
537        traininfo.setAttribute("loadatstartup", "" + (tf.getLoadAtStartup() ? "yes" : "no"));
538        traininfo.setAttribute("allocatealltheway", "" + (tf.getAllocateAllTheWay() ? "yes" : "no"));
539        traininfo.setAttribute("allocationmethod", Integer.toString(tf.getAllocationMethod()));
540        traininfo.setAttribute("nexttrain", tf.getNextTrain());
541        // here save items related to automatically running active trains
542        traininfo.setAttribute("speedfactor", Float.toString(tf.getSpeedFactor()));
543        traininfo.setAttribute("maxspeed", Float.toString(tf.getMaxSpeed()));
544        traininfo.setAttribute("ramprate", tf.getRampRate());
545        traininfo.setAttribute("runinreverse", "" + (tf.getRunInReverse() ? "yes" : "no"));
546        traininfo.setAttribute("sounddecoder", "" + (tf.getSoundDecoder() ? "yes" : "no"));
547        traininfo.setAttribute("maxtrainlength", Float.toString(tf.getMaxTrainLength()));
548        traininfo.setAttribute("usespeedprofile", "" + (tf.getUseSpeedProfile() ? "yes" : "no"));
549        traininfo.setAttribute("stopbyspeedprofile", "" + (tf.getStopBySpeedProfile() ? "yes" : "no"));
550        traininfo.setAttribute("stopbyspeedprofileadjust", Float.toString(tf.getStopBySpeedProfileAdjust()));
551        traininfo.setAttribute("waittime", Float.toString(tf.getWaitTime()));
552        traininfo.setAttribute("blockname", tf.getBlockName());
553
554        root.addContent(traininfo);
555
556        // write out the file
557        try {
558            if (!checkFile(fileLocation + name)) {
559                // file does not exist, create it
560                File file = new File(fileLocation + name);
561                if (!file.createNewFile()) // create file and check result
562                {
563                    log.error("createNewFile failed");
564                }
565            }
566            // write content to file
567            writeXML(findFile(fileLocation + name), doc);
568        } catch (java.io.IOException ioe) {
569            log.error("IO Exception writing", ioe);
570            throw (ioe);
571        }
572    }
573
574    /**
575     * Get the names of all current TrainInfo files. Returns names as an array
576     * of Strings. Returns an empty array if no files are present. Note: Fill
577     * names still end with .xml or .XML. (Modeled after a method in
578     * RecreateRosterAction.java by Bob Jacobsen)
579     *
580     * @return names as an array or an empty array if none present
581     */
582    public String[] getTrainInfoFileNames() {
583        // ensure preferences will be found for read
584        FileUtil.createDirectory(fileLocation);
585        // create an array of file names from roster dir in preferences, count entries
586        List<String> names = new ArrayList<>();
587        log.debug("directory of TrainInfoFiles is {}", fileLocation);
588        File fp = new File(fileLocation);
589        if (fp.exists()) {
590            String[] xmlList = fp.list(new XmlFilenameFilter());
591            if (xmlList!=null) {
592                names.addAll(Arrays.asList(xmlList));
593            }
594        }
595        // Sort the resulting array
596        names.sort((s1, s2) -> {
597            return s1.compareTo(s2);
598        });
599        return names.toArray(new String[names.size()]);
600    }
601
602    /**
603     * Delete a specified TrainInfo file.
604     *
605     * @param name the file to delete
606     */
607    public void deleteTrainInfoFile(String name) {
608        // locate the file and delete it if it exists
609        File f = new File(fileLocation + name);
610        if (!f.delete()) { // delete file and check success
611            log.error("failed to delete TrainInfo file - {}", name);
612        }
613    }
614
615    private final static Logger log = LoggerFactory.getLogger(TrainInfoFile.class);
616}