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