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