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("minthrottleinterval") != null) {
171                        String s = (options.getAttribute("minthrottleinterval")).getValue();
172                        dispatcher.setMinThrottleInterval(Integer.parseInt(s));
173                    }
174                    if (options.getAttribute("fullramptime") != null) {
175                        String s = (options.getAttribute("fullramptime")).getValue();
176                        dispatcher.setFullRampTime(Integer.parseInt(s));
177                    }
178                    if (options.getAttribute("hasoccupancydetection") != null) {
179                        dispatcher.setHasOccupancyDetection(true);
180                        if (options.getAttribute("hasoccupancydetection").getValue().equals("no")) {
181                            dispatcher.setHasOccupancyDetection(false);
182                        }
183                    }
184                    if (options.getAttribute("sslcheckdirectionsensors") != null) {
185                        dispatcher.setSetSSLDirectionalSensors(true);
186                        if (options.getAttribute("sslcheckdirectionsensors").getValue().equals("no")) {
187                            dispatcher.setSetSSLDirectionalSensors(false);
188                        }
189                    }
190                    if (options.getAttribute("shortactivetrainnames") != null) {
191                        dispatcher.setShortActiveTrainNames(true);
192                        if (options.getAttribute("shortactivetrainnames").getValue().equals("no")) {
193                            dispatcher.setShortActiveTrainNames(false);
194                        }
195                    }
196                    if (options.getAttribute("shortnameinblock") != null) {
197                        dispatcher.setShortNameInBlock(true);
198                        if (options.getAttribute("shortnameinblock").getValue().equals("no")) {
199                            dispatcher.setShortNameInBlock(false);
200                        }
201                    }
202                    if (options.getAttribute("extracolorforallocated") != null) {
203                        dispatcher.setExtraColorForAllocated(true);
204                        if (options.getAttribute("extracolorforallocated").getValue().equals("no")) {
205                            dispatcher.setExtraColorForAllocated(false);
206                        }
207                    }
208                    if (options.getAttribute("nameinallocatedblock") != null) {
209                        dispatcher.setNameInAllocatedBlock(true);
210                        if (options.getAttribute("nameinallocatedblock").getValue().equals("no")) {
211                            dispatcher.setNameInAllocatedBlock(false);
212                        }
213                    }
214                    if (options.getAttribute("supportvsdecoder") != null) {
215                        dispatcher.setSupportVSDecoder(true);
216                        if (options.getAttribute("supportvsdecoder").getValue().equals("no")) {
217                            dispatcher.setSupportVSDecoder(false);
218                        }
219                    }
220                    if (options.getAttribute("layoutscale") != null) {
221                        String s = (options.getAttribute("layoutscale")).getValue();
222                        dispatcher.setScale(ScaleManager.getScale(s));
223                    }
224                    if (options.getAttribute("usescalemeters") != null) {
225                        dispatcher.setUseScaleMeters(true);
226                        if (options.getAttribute("usescalemeters").getValue().equals("no")) {
227                            dispatcher.setUseScaleMeters(false);
228                        }
229                    }
230                    if (options.getAttribute("userosterentryinblock") != null) {
231                        dispatcher.setRosterEntryInBlock(false);
232                        if (options.getAttribute("userosterentryinblock").getValue().equals("yes")) {
233                            dispatcher.setRosterEntryInBlock(true);
234                        }
235                    }
236                    if (options.getAttribute("stoppingspeedname") != null) {
237                        dispatcher.setStoppingSpeedName((options.getAttribute("stoppingspeedname")).getValue());
238                    }
239
240                    log.debug("  Options: {}, Detection={}, AutoAllocate={}, AutoTurnouts={}, SetSSLDirectionSensors={}",
241                            (dispatcher.getSignalTypeString()),
242                            (dispatcher.getAutoAllocate()?"yes":"no"),
243                            (dispatcher.getAutoTurnouts()?"yes":"no"),
244                            (dispatcher.getSetSSLDirectionalSensors()?"yes":"no"));
245                }
246            }
247        } else {
248            log.debug("No Dispatcher options file found at {} or {}, using defaults", defaultFileName, oldFileName);
249        }
250    }
251
252    /**
253     * Select a file location.
254     * @return which location to use or null if none.
255     */
256    private String getOptionsFileLocation() {
257        if (checkFile(defaultFileName)) {
258            if (checkFile(oldFileName)) {
259                // Both files exist, check file modification times
260                try {
261                    long newFileTime = FileUtil.getFile(defaultFileName).lastModified();
262                    long oldFileTime = FileUtil.getFile(oldFileName).lastModified();
263                    if (oldFileTime > newFileTime) {
264                        return oldFileName;
265                    }
266                } catch (java.io.IOException ioe) {
267                    log.error("IO Exception while selecting which file to load :: {}", ioe.getMessage());
268                }
269            }
270            return defaultFileName;
271        } else if (checkFile(oldFileName)) {
272            return oldFileName;
273        }
274        return null;
275    }
276
277    /**
278     * Write out Dispatcher options to a file in the user's preferences directory.
279     * <p>The lename attribute is deprecated at 5.1.3.  The current value will be retained.
280     * @param f Dispatcher instance.
281     * @throws java.io.IOException Thrown if dispatcher option file not found
282     */
283    public void writeDispatcherOptions(DispatcherFrame f) throws java.io.IOException {
284        log.debug("Saving Dispatcher options to file {}", defaultFileName);
285        dispatcher = f;
286        root = new Element("dispatcheroptions");
287        doc = newDocument(root, dtdLocation + "dispatcher-options.dtd");
288        // add XSLT processing instruction
289        // <?xml-stylesheet type="text/xsl" href="XSLT/block-values.xsl"?>
290        java.util.Map<String, String> m = new java.util.HashMap<>();
291        m.put("type", "text/xsl");
292        m.put("href", xsltLocation + "dispatcheroptions.xsl");
293        org.jdom2.ProcessingInstruction p = new org.jdom2.ProcessingInstruction("xml-stylesheet", m);
294        doc.addContent(0, p);
295
296        // save Dispatcher Options in xml format
297        Element options = new Element("options");
298        LayoutEditor le = dispatcher.getLayoutEditor();
299        if (le != null) {
300            options.setAttribute("lename", le.getTitle());
301        }
302        options.setAttribute("useconnectivity", "" + (dispatcher.getUseConnectivity() ? "yes" : "no"));
303        options.setAttribute("trainsfrom", trainsFromEnumMap.outputFromEnum(dispatcher.getTrainsFrom()));
304        options.setAttribute("autoallocate", "" + (dispatcher.getAutoAllocate() ? "yes" : "no"));
305        options.setAttribute("autorelease", "" + (dispatcher.getAutoRelease() ? "yes" : "no"));
306        options.setAttribute("autoturnouts", "" + (dispatcher.getAutoTurnouts() ? "yes" : "no"));
307        options.setAttribute("trustknownturnouts", "" + (dispatcher.getTrustKnownTurnouts() ? "yes" : "no"));
308        options.setAttribute("useturnoutconnectiondelay", "" + (dispatcher.getUseTurnoutConnectionDelay() ? "yes" : "no"));
309        options.setAttribute("minthrottleinterval", "" + (dispatcher.getMinThrottleInterval()));
310        options.setAttribute("fullramptime", "" + (dispatcher.getFullRampTime()));
311        options.setAttribute("hasoccupancydetection", "" + (dispatcher.getHasOccupancyDetection() ? "yes" : "no"));
312        options.setAttribute("sslcheckdirectionsensors", "" + (dispatcher.getSetSSLDirectionalSensors() ? "yes" : "no"));
313        options.setAttribute("shortactivetrainnames", "" + (dispatcher.getShortActiveTrainNames() ? "yes" : "no"));
314        options.setAttribute("shortnameinblock", "" + (dispatcher.getShortNameInBlock() ? "yes" : "no"));
315        options.setAttribute("extracolorforallocated", "" + (dispatcher.getExtraColorForAllocated() ? "yes" : "no"));
316        options.setAttribute("nameinallocatedblock", "" + (dispatcher.getNameInAllocatedBlock() ? "yes" : "no"));
317        options.setAttribute("supportvsdecoder", "" + (dispatcher.getSupportVSDecoder() ? "yes" : "no"));
318        options.setAttribute("layoutscale", dispatcher.getScale().getScaleName());
319        options.setAttribute("usescalemeters", "" + (dispatcher.getUseScaleMeters() ? "yes" : "no"));
320        options.setAttribute("userosterentryinblock", "" + (dispatcher.getRosterEntryInBlock() ? "yes" : "no"));
321        options.setAttribute("stoppingspeedname", dispatcher.getStoppingSpeedName());
322        switch (dispatcher.getSignalType()) {
323            case DispatcherFrame.SIGNALMAST:
324                options.setAttribute("usesignaltype", "signalmast");
325                break;
326            case DispatcherFrame.SECTIONSALLOCATED:
327                options.setAttribute("usesignaltype", "sectionsallocated");
328                break;
329            default:
330                options.setAttribute("usesignaltype", "signalhead");
331        }
332        root.addContent(options);
333
334        // write out the file
335        try {
336            if (!checkFile(defaultFileName)) {
337                // file does not exist, create it
338                File file = new File(defaultFileName);
339                if (!file.createNewFile()) // create new file and check result
340                {
341                    log.error("createNewFile failed");
342                }
343            }
344            // write content to file
345            writeXML(findFile(defaultFileName), doc);
346            updateOldLocation();
347        } catch (java.io.IOException ioe) {
348            log.error("IO Exception {}", ioe.getMessage());
349            throw (ioe);
350        }
351    }
352
353    /**
354     * To maintain backward support, the updated options file is copied to the old location.
355     */
356    private void updateOldLocation() {
357        if (checkFile(oldFileName)) {
358            log.debug("Replace {} with a copy of {}", oldFileName, defaultFileName);
359            try {
360                File source = FileUtil.getFile(defaultFileName);
361                File dest = FileUtil.getFile(oldFileName);
362                FileUtil.copy(source, dest);
363            } catch (java.io.IOException ioe) {
364                log.error("IO Exception while updating old file :: {}", ioe.getMessage());
365            }
366        }
367    }
368
369    private final static Logger log = LoggerFactory.getLogger(OptionsFile.class);
370}