001package jmri.jmrit.dispatcher;
002
003import java.io.File;
004import java.util.Set;
005
006import jmri.InstanceManager;
007import jmri.InstanceManagerAutoDefault;
008import jmri.ScaleManager;
009import jmri.configurexml.AbstractXmlAdapter.EnumIO;
010import jmri.configurexml.AbstractXmlAdapter.EnumIoNamesNumbers;
011import jmri.jmrit.dispatcher.DispatcherFrame.TrainsFrom;
012import jmri.jmrit.display.EditorManager;
013import jmri.jmrit.display.layoutEditor.LayoutEditor;
014import jmri.util.FileUtil;
015
016import org.jdom2.Document;
017import org.jdom2.Element;
018import org.slf4j.Logger;
019import org.slf4j.LoggerFactory;
020
021/**
022 * Handles reading and writing of Dispatcher options to disk as an XML file
023 * called "dispatcher-options.xml" in the user's preferences area.
024 * <p>
025 * This class manipulates the files conforming to the dispatcher-options DTD
026 * <p>
027 * The file is written when the user requests that options be saved. If the
028 * dispatcheroptions.xml file is present when Dispatcher is started, it is read
029 * and options set accordingly
030 * <p>
031 * This file is part of JMRI.
032 * <p>
033 * JMRI is open source software; you can redistribute it and/or modify it under
034 * the terms of version 2 of the GNU General Public License as published by the
035 * Free Software Foundation. See the "COPYING" file for a copy of this license.
036 * <p>
037 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
038 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
039 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
040 *
041 * @author Dave Duchamp Copyright (C) 2008
042 */
043public class OptionsFile extends jmri.jmrit.XmlFile implements InstanceManagerAutoDefault {
044
045    public OptionsFile() {
046        super();
047    }
048
049    static final EnumIO<DispatcherFrame.TrainsFrom> trainsFromEnumMap = new EnumIoNamesNumbers<>(DispatcherFrame.TrainsFrom.class);
050
051    // operational variables
052    protected DispatcherFrame dispatcher = null;
053    private static String defaultFileName = FileUtil.getUserFilesPath() + "dispatcher" + FileUtil.SEPARATOR + "dispatcheroptions.xml";
054    private static String oldFileName = FileUtil.getUserFilesPath() + "dispatcheroptions.xml";
055
056    public static void setDefaultFileName(String testLocation) {
057        defaultFileName = testLocation;
058    }
059    private Document doc = null;
060    private Element root = null;
061
062    /**
063     * Read Dispatcher Options from a file in the user's preferences directory.  The default
064     * location is within the dispatcher directory.  The previous location is also supported.
065     * If the file containing Dispatcher Options does not exist, this routine returns quietly.
066     * <p>The lename attribute is deprecated at 5.1.3. The current value will be retained.
067     *
068     * @param f   The dispatcher instance.
069     * @throws org.jdom2.JDOMException  if dispatcher parameter logically incorrect
070     * @throws java.io.IOException    if dispatcher parameter not found
071     */
072    public void readDispatcherOptions(DispatcherFrame f) throws org.jdom2.JDOMException, java.io.IOException {
073        // check if a file exists
074        String optionsFileName = getOptionsFileLocation();
075        if (optionsFileName != null) {
076            // file is present,
077            log.debug("Reading Dispatcher options from file {}", optionsFileName);
078            root = rootFromName(optionsFileName);
079            dispatcher = f;
080            if (root != null) {
081                // there is a file
082                Element options = root.getChild("options");
083                if (options != null) {
084                    // there are options defined, read and set Dispatcher options
085                    if (options.getAttribute("lename") != null) {
086                        // there is a layout editor name selected
087                        String leName = options.getAttribute("lename").getValue();
088
089                        // get list of Layout Editor panels
090                        // Note: While editor is deprecated, retain the value for backward compatibility.
091                        Set<LayoutEditor> layoutEditorList = InstanceManager.getDefault(EditorManager.class).getAll(LayoutEditor.class);
092                        if (layoutEditorList.isEmpty()) {
093                            log.warn("Dispatcher options specify a Layout Editor panel that is not present.");
094                        } else {
095                            boolean found = false;
096                            for (LayoutEditor editor : layoutEditorList) {
097                                if (leName.equals(editor.getTitle())) {
098                                    found = true;
099                                    dispatcher.setLayoutEditor(editor);
100                                }
101                            }
102                            if (!found) {
103                                log.warn("Layout Editor panel - {} - not found.", leName);
104                            }
105                        }
106                    }
107                    if (options.getAttribute("usesignaltype") != null) {
108                        switch (options.getAttribute("usesignaltype").getValue()) {
109                            case "signalmast":
110                                dispatcher.setSignalType(DispatcherFrame.SIGNALMAST);
111                                break;
112                            case "sectionsallocated":
113                                dispatcher.setSignalType(DispatcherFrame.SECTIONSALLOCATED);
114                                break;
115                            default:
116                                dispatcher.setSignalType(DispatcherFrame.SIGNALHEAD);
117                        }
118                    }
119                    if (options.getAttribute("useconnectivity") != null) {
120                        dispatcher.setUseConnectivity(true);
121                        if (options.getAttribute("useconnectivity").getValue().equals("no")) {
122                            dispatcher.setUseConnectivity(false);
123                        }
124                    }
125                    if (options.getAttribute("trainsfrom") != null) {
126                        dispatcher.setTrainsFrom(trainsFromEnumMap.inputFromAttribute(options.getAttribute("trainsfrom")));
127                    } else {
128                        log.warn("Old Style dispatcheroptions file found - will be converted when saved");
129                    if (options.getAttribute("trainsfromroster") != null &&
130                            options.getAttribute("trainsfromroster").getValue().equals("yes")) {
131                        dispatcher.setTrainsFrom(TrainsFrom.TRAINSFROMROSTER);
132                    } else if (options.getAttribute("trainsfromtrains") != null &&
133                            options.getAttribute("trainsfromtrains").getValue().equals("no")) {
134                        dispatcher.setTrainsFrom(TrainsFrom.TRAINSFROMOPS);
135                    } else if (options.getAttribute("trainsfromuser") != null &&
136                            options.getAttribute("trainsfromuser").getValue().equals("no")) {
137                        dispatcher.setTrainsFrom(TrainsFrom.TRAINSFROMUSER);
138                    }
139                    }
140                    if (options.getAttribute("autoallocate") != null) {
141                        dispatcher.setAutoAllocate(false);
142                        if (options.getAttribute("autoallocate").getValue().equals("yes")) {
143                            dispatcher.setAutoAllocate(true);
144                        }
145                    }
146                    if (options.getAttribute("autorelease") != null) {
147                        dispatcher.setAutoRelease(false);
148                        if (options.getAttribute("autorelease").getValue().equals("yes")) {
149                            dispatcher.setAutoRelease(true);
150                        }
151                    }
152                    if (options.getAttribute("autoturnouts") != null) {
153                        dispatcher.setAutoTurnouts(true);
154                        if (options.getAttribute("autoturnouts").getValue().equals("no")) {
155                            dispatcher.setAutoTurnouts(false);
156                        }
157                    }
158                    if (options.getAttribute("trustknownturnouts") != null) {
159                        dispatcher.setTrustKnownTurnouts(false);
160                        if (options.getAttribute("trustknownturnouts").getValue().equals("yes")) {
161                            dispatcher.setTrustKnownTurnouts(true);
162                        }
163                    }
164                    if (options.getAttribute("useturnoutconnectiondelay") != null) {
165                        dispatcher.setUseTurnoutConnectionDelay(false);
166                        if (options.getAttribute("useturnoutconnectiondelay").getValue().equals("yes")) {
167                            dispatcher.setUseTurnoutConnectionDelay(true);
168                        }
169                    }
170                    if (options.getAttribute("useoccupiedtrackspeed") != null) {
171                        dispatcher.setUseOccupiedTrackSpeed(false);
172                        if (options.getAttribute("useoccupiedtrackspeed").getValue().equals("yes")) {
173                            dispatcher.setUseOccupiedTrackSpeed(true);
174                        }
175                    }
176                    if (options.getAttribute("usestricttraintracking") != null) {
177                        dispatcher.setUseStrictTrainTracking(true);
178                        if (options.getAttribute("usestricttraintracking").getValue().equals("no")) {
179                            dispatcher.setUseStrictTrainTracking(false);
180                        }
181                    }
182                    if (options.getAttribute("minthrottleinterval") != null) {
183                        String s = (options.getAttribute("minthrottleinterval")).getValue();
184                        dispatcher.setMinThrottleInterval(Integer.parseInt(s));
185                    }
186                    if (options.getAttribute("fullramptime") != null) {
187                        String s = (options.getAttribute("fullramptime")).getValue();
188                        dispatcher.setFullRampTime(Integer.parseInt(s));
189                    }
190                    if (options.getAttribute("hasoccupancydetection") != null) {
191                        dispatcher.setHasOccupancyDetection(true);
192                        if (options.getAttribute("hasoccupancydetection").getValue().equals("no")) {
193                            dispatcher.setHasOccupancyDetection(false);
194                        }
195                    }
196                    if (options.getAttribute("sslcheckdirectionsensors") != null) {
197                        dispatcher.setSetSSLDirectionalSensors(true);
198                        if (options.getAttribute("sslcheckdirectionsensors").getValue().equals("no")) {
199                            dispatcher.setSetSSLDirectionalSensors(false);
200                        }
201                    }
202                    if (options.getAttribute("shortactivetrainnames") != null) {
203                        dispatcher.setShortActiveTrainNames(true);
204                        if (options.getAttribute("shortactivetrainnames").getValue().equals("no")) {
205                            dispatcher.setShortActiveTrainNames(false);
206                        }
207                    }
208                    if (options.getAttribute("shortnameinblock") != null) {
209                        dispatcher.setShortNameInBlock(true);
210                        if (options.getAttribute("shortnameinblock").getValue().equals("no")) {
211                            dispatcher.setShortNameInBlock(false);
212                        }
213                    }
214                    if (options.getAttribute("extracolorforallocated") != null) {
215                        dispatcher.setExtraColorForAllocated(true);
216                        if (options.getAttribute("extracolorforallocated").getValue().equals("no")) {
217                            dispatcher.setExtraColorForAllocated(false);
218                        }
219                    }
220                    if (options.getAttribute("nameinallocatedblock") != null) {
221                        dispatcher.setNameInAllocatedBlock(true);
222                        if (options.getAttribute("nameinallocatedblock").getValue().equals("no")) {
223                            dispatcher.setNameInAllocatedBlock(false);
224                        }
225                    }
226                    if (options.getAttribute("supportvsdecoder") != null) {
227                        dispatcher.setSupportVSDecoder(true);
228                        if (options.getAttribute("supportvsdecoder").getValue().equals("no")) {
229                            dispatcher.setSupportVSDecoder(false);
230                        }
231                    }
232                    if (options.getAttribute("layoutscale") != null) {
233                        String s = (options.getAttribute("layoutscale")).getValue();
234                        dispatcher.setScale(ScaleManager.getScale(s));
235                    }
236                    if (options.getAttribute("usescalemeters") != null) {
237                        dispatcher.setUseScaleMeters(true);
238                        if (options.getAttribute("usescalemeters").getValue().equals("no")) {
239                            dispatcher.setUseScaleMeters(false);
240                        }
241                    }
242                    if (options.getAttribute("userosterentryinblock") != null) {
243                        dispatcher.setRosterEntryInBlock(false);
244                        if (options.getAttribute("userosterentryinblock").getValue().equals("yes")) {
245                            dispatcher.setRosterEntryInBlock(true);
246                        }
247                    }
248                    if (options.getAttribute("stoppingspeedname") != null) {
249                        dispatcher.setStoppingSpeedName((options.getAttribute("stoppingspeedname")).getValue());
250                    }
251
252                    log.debug("  Options: {}, Detection={}, AutoAllocate={}, AutoTurnouts={}, SetSSLDirectionSensors={}",
253                            (dispatcher.getSignalTypeString()),
254                            (dispatcher.getAutoAllocate()?"yes":"no"),
255                            (dispatcher.getAutoTurnouts()?"yes":"no"),
256                            (dispatcher.getSetSSLDirectionalSensors()?"yes":"no"));
257                }
258            }
259        } else {
260            log.debug("No Dispatcher options file found at {} or {}, using defaults", defaultFileName, oldFileName);
261        }
262    }
263
264    /**
265     * Select a file location.
266     * @return which location to use or null if none.
267     */
268    private String getOptionsFileLocation() {
269        if (checkFile(defaultFileName)) {
270            if (checkFile(oldFileName)) {
271                // Both files exist, check file modification times
272                try {
273                    long newFileTime = FileUtil.getFile(defaultFileName).lastModified();
274                    long oldFileTime = FileUtil.getFile(oldFileName).lastModified();
275                    if (oldFileTime > newFileTime) {
276                        return oldFileName;
277                    }
278                } catch (java.io.IOException ioe) {
279                    log.error("IO Exception while selecting which file to load :: {}", ioe.getMessage());
280                }
281            }
282            return defaultFileName;
283        } else if (checkFile(oldFileName)) {
284            return oldFileName;
285        }
286        return null;
287    }
288
289    /**
290     * Write out Dispatcher options to a file in the user's preferences directory.
291     * <p>The lename attribute is deprecated at 5.1.3.  The current value will be retained.
292     * @param f Dispatcher instance.
293     * @throws java.io.IOException Thrown if dispatcher option file not found
294     */
295    public void writeDispatcherOptions(DispatcherFrame f) throws java.io.IOException {
296        log.debug("Saving Dispatcher options to file {}", defaultFileName);
297        dispatcher = f;
298        root = new Element("dispatcheroptions");
299        doc = newDocument(root, dtdLocation + "dispatcher-options.dtd");
300        // add XSLT processing instruction
301        // <?xml-stylesheet type="text/xsl" href="XSLT/block-values.xsl"?>
302        java.util.Map<String, String> m = new java.util.HashMap<>();
303        m.put("type", "text/xsl");
304        m.put("href", xsltLocation + "dispatcheroptions.xsl");
305        org.jdom2.ProcessingInstruction p = new org.jdom2.ProcessingInstruction("xml-stylesheet", m);
306        doc.addContent(0, p);
307
308        // save Dispatcher Options in xml format
309        Element options = new Element("options");
310        LayoutEditor le = dispatcher.getLayoutEditor();
311        if (le != null) {
312            options.setAttribute("lename", le.getTitle());
313        }
314        options.setAttribute("useconnectivity", "" + (dispatcher.getUseConnectivity() ? "yes" : "no"));
315        options.setAttribute("trainsfrom", trainsFromEnumMap.outputFromEnum(dispatcher.getTrainsFrom()));
316        options.setAttribute("autoallocate", "" + (dispatcher.getAutoAllocate() ? "yes" : "no"));
317        options.setAttribute("autorelease", "" + (dispatcher.getAutoRelease() ? "yes" : "no"));
318        options.setAttribute("autoturnouts", "" + (dispatcher.getAutoTurnouts() ? "yes" : "no"));
319        options.setAttribute("trustknownturnouts", "" + (dispatcher.getTrustKnownTurnouts() ? "yes" : "no"));
320        options.setAttribute("useturnoutconnectiondelay", "" + (dispatcher.getUseTurnoutConnectionDelay() ? "yes" : "no"));
321        options.setAttribute("useoccupiedtrackspeed", "" + (dispatcher.getUseOccupiedTrackSpeed() ? "yes" : "no"));
322        options.setAttribute("setStrictTrainTracking", "" + (dispatcher.getUseStrictTrainTracking() ? "yes" : "no"));
323        options.setAttribute("minthrottleinterval", "" + (dispatcher.getMinThrottleInterval()));
324        options.setAttribute("fullramptime", "" + (dispatcher.getFullRampTime()));
325        options.setAttribute("hasoccupancydetection", "" + (dispatcher.getHasOccupancyDetection() ? "yes" : "no"));
326        options.setAttribute("sslcheckdirectionsensors", "" + (dispatcher.getSetSSLDirectionalSensors() ? "yes" : "no"));
327        options.setAttribute("shortactivetrainnames", "" + (dispatcher.getShortActiveTrainNames() ? "yes" : "no"));
328        options.setAttribute("shortnameinblock", "" + (dispatcher.getShortNameInBlock() ? "yes" : "no"));
329        options.setAttribute("extracolorforallocated", "" + (dispatcher.getExtraColorForAllocated() ? "yes" : "no"));
330        options.setAttribute("nameinallocatedblock", "" + (dispatcher.getNameInAllocatedBlock() ? "yes" : "no"));
331        options.setAttribute("supportvsdecoder", "" + (dispatcher.getSupportVSDecoder() ? "yes" : "no"));
332        options.setAttribute("layoutscale", dispatcher.getScale().getScaleName());
333        options.setAttribute("usescalemeters", "" + (dispatcher.getUseScaleMeters() ? "yes" : "no"));
334        options.setAttribute("userosterentryinblock", "" + (dispatcher.getRosterEntryInBlock() ? "yes" : "no"));
335        options.setAttribute("stoppingspeedname", dispatcher.getStoppingSpeedName());
336        switch (dispatcher.getSignalType()) {
337            case DispatcherFrame.SIGNALMAST:
338                options.setAttribute("usesignaltype", "signalmast");
339                break;
340            case DispatcherFrame.SECTIONSALLOCATED:
341                options.setAttribute("usesignaltype", "sectionsallocated");
342                break;
343            default:
344                options.setAttribute("usesignaltype", "signalhead");
345        }
346        root.addContent(options);
347
348        // write out the file
349        try {
350            if (!checkFile(defaultFileName)) {
351                // file does not exist, create it
352                File file = new File(defaultFileName);
353                if (!file.createNewFile()) // create new file and check result
354                {
355                    log.error("createNewFile failed");
356                }
357            }
358            // write content to file
359            writeXML(findFile(defaultFileName), doc);
360            updateOldLocation();
361        } catch (java.io.IOException ioe) {
362            log.error("IO Exception {}", ioe.getMessage());
363            throw (ioe);
364        }
365    }
366
367    /**
368     * To maintain backward support, the updated options file is copied to the old location.
369     */
370    private void updateOldLocation() {
371        if (checkFile(oldFileName)) {
372            log.debug("Replace {} with a copy of {}", oldFileName, defaultFileName);
373            try {
374                File source = FileUtil.getFile(defaultFileName);
375                File dest = FileUtil.getFile(oldFileName);
376                FileUtil.copy(source, dest);
377            } catch (java.io.IOException ioe) {
378                log.error("IO Exception while updating old file :: {}", ioe.getMessage());
379            }
380        }
381    }
382
383    private final static Logger log = LoggerFactory.getLogger(OptionsFile.class);
384}