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