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