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}