001package jmri.jmrix.loconet.configurexml; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004 005import java.io.File; 006import java.io.IOException; 007import java.io.FileNotFoundException; 008 009import jmri.jmrit.XmlFile; 010import jmri.jmrix.loconet.LnConstants; 011import jmri.jmrix.loconet.Ln7gAccyRoutesManager; 012import jmri.jmrix.loconet.alm.LnSimple7thGenDeviceRoutes; 013import jmri.jmrix.loconet.alm.LnSimpleRouteEntry; 014import jmri.jmrix.loconet.alm.RouteSwitchPositionEnum; 015import jmri.util.FileUtil; 016 017import org.jdom2.Document; 018import org.jdom2.Element; 019import org.jdom2.JDOMException; 020 021import org.slf4j.Logger; 022import org.slf4j.LoggerFactory; 023 024/** 025 * Handle XML configuration for sevgen.Accy7thGenRoutes objects. 026 * 027 * @author B. Milhaupt; Copyright(c) 2024 028 */ 029 030public class Digitrax7thGenAccyRoutesXML { 031 private final Ln7gAccyRoutesManager l7garm; 032 033 public Digitrax7thGenAccyRoutesXML(Ln7gAccyRoutesManager l7garm) { 034 this.l7garm = l7garm; 035 } 036 037 /** 038 * Load the Routes info from file. 039 * @return true if loaded 040 */ 041 public boolean loadXML() { 042 if (l7garm.getCountOfDevicesWithRoutes() > 0) { 043 log.warn("loadXML: already loaded. Quitting."); 044 return true; 045 } 046 DigitraxRoutesXmlFile xmlFile = new DigitraxRoutesXmlFile(); 047 File file = xmlFile.getLoadFile(); 048 049 if (file == null) { 050 doStore(); 051 file = xmlFile.getStoreFile(); 052 } 053 054 if (!file.exists()) { 055 log.warn("loadXML: Note: File does not exist."); 056 return false; 057 } 058 059 // Find digitraxRoutesPreferencesElement 060 Element root; 061 try { 062 root = xmlFile.rootFromFile(file); 063 if (root == null) { 064 log.warn("loadXML: File could not be read"); // NOI18N 065 return false; 066 } 067 if (!root.getName().equals("DigitraxRoutesPreferences") ) { // NOI18N 068 log.warn("loadXML: Wrong root name: {}",root.getName()); 069 return false; 070 } 071 072 for (Element routes: root.getChildren()) { 073 if (!routes.getAttributeValue("class").equals("Accy7thGenRoutes.configurexml.RoutesManagerXml")) { 074 log.debug("loadXML: routes ignored at element {}, class {}", 075 routes.getName(), routes.getAttribute("class")); 076 } else { 077 for (Element device: routes.getChildren("device")) { 078 String type=device.getAttributeValue("type"); 079 String serNum = device.getAttributeValue("serNum"); 080 String baseAddr = device.getAttributeValue("baseAddr"); 081 log.debug("loadXML: device {}, serNum {}, baseAddr {}", 082 type, serNum, baseAddr); 083 084 LnSimple7thGenDeviceRoutes dev = 085 new LnSimple7thGenDeviceRoutes( 086 LnSimple7thGenDeviceRoutes.getDeviceType(type), 087 Integer.parseInt(serNum)); 088 dev.setBaseAddr(Integer.parseInt(baseAddr)); 089 log.debug("loadXML: add {}", dev.getDeviceType()); 090 091 int numRoutes = ((dev.getDeviceType() == LnConstants.RE_IPL_DIGITRAX_HOST_DS78V) ? 16:8); 092 for (int i = 0; i < numRoutes; ++i) { 093 for (int j =0; j<8; ++j) { 094 dev.setOneEntry(i, j, -1, RouteSwitchPositionEnum.UNUSED); 095 } 096 } 097 098 for (Element route: device.getChildren()) { 099 log.debug("\tdevice's route {}", route.getAttributeValue("number")); 100 int rn = Integer.parseInt(route.getAttributeValue("number")) - 1; 101 102 int entryNum = 0; 103 104 for (Element entry: route.getChildren()) { 105 if (entry.getName().equals("routeTop")) { 106 log.debug("\t\trouteTop: {}, {}", 107 entry.getAttributeValue("controlTurnout"), 108 entry.getAttributeValue("controlTurnoutState")); 109 int t = Integer.parseInt(entry.getAttributeValue("controlTurnout")); 110 dev.setOneEntry(rn, entryNum, t, 111 RouteSwitchPositionEnum.valueOf(entry.getAttributeValue("controlTurnoutState"))); 112 } else { 113 log.debug("\t\tsubsequent Entry: {}, {}", 114 entry.getAttributeValue("turnout"), 115 entry.getAttributeValue("state")); 116 dev.setOneEntry(rn, entryNum, Integer.parseInt(entry.getAttributeValue("turnout")), 117 RouteSwitchPositionEnum.valueOf(entry.getAttributeValue("state"))); 118 } 119 entryNum++; 120 } 121 } 122 // Register the device/serNum/BaseAddr and routes 123 // to the Ln7gAccyRoutesManager 124 l7garm.addDevice(dev); 125 } 126 } 127 } 128 } catch (JDOMException ex) { 129 log.error("loadXML: File invalid", ex); // NOI18N 130 return false; 131 } catch (IOException ex) { 132 log.error("loadXML: Error reading file", ex); // NOI18N 133 return false; 134 } 135 136 log.debug("loadXML: Finished reading the 'DigitraxRoutes' file."); 137 return false; 138 } 139 140 141 /** 142 * Store the Routes info. 143 * @return true if stored 144 */ 145 @SuppressFBWarnings(value="RV_RETURN_VALUE_IGNORED_BAD_PRACTICE") 146 public boolean doStore() { 147 // Create digitraxRoutesPreferencesElement element 148 Element digitraxRoutesPreferencesElement = new Element("DigitraxRoutesPreferences"); // NOI18N 149 digitraxRoutesPreferencesElement.setAttribute("noNamespaceSchemaLocation", // NOI18N 150 "http://jmri.org/xml/schema/DigitraxRoutes.xsd", // NOI18N 151 org.jdom2.Namespace.getNamespace("xsi", // NOI18N 152 "http://www.w3.org/2001/XMLSchema-instance")); // NOI18N 153 154 Element routesEntity = new Element("routes"); // NOI18N 155 digitraxRoutesPreferencesElement.addContent(routesEntity); 156 routesEntity.setAttribute("class", "jmri.jmrix.loconet.configurexml.CmdStnRoutesManagerXml"); // NOI18N 157 158 routesEntity = new Element("routes"); // NOI18N 159 digitraxRoutesPreferencesElement.addContent(routesEntity); // NOI18N 160 routesEntity.setAttribute("class", "Accy7thGenRoutes.configurexml.RoutesManagerXml"); // NOI18N 161 162 int countOfDevices = l7garm.getCountOfDevicesWithRoutes(); 163 164 log.debug("Saving {} devices", l7garm.getCountOfDevicesWithRoutes()); 165 if (countOfDevices < 1) { 166 log.debug("No Digitrax 7th-gen Accessory devices with stored/storable routes."); 167 } else { 168 StringBuilder loggingString = new StringBuilder("Known Digitrax 7th-gen Accessory devices with " 169 + "stored/storable routes:\n"); // NOI18N 170 for (int aDevice = 0; aDevice < l7garm.getCountOfDevicesWithRoutes(); aDevice++) { 171 LnSimple7thGenDeviceRoutes device = l7garm.getDevice(aDevice); 172 loggingString.append("\tDevice "); // NOI18N 173 loggingString.append(l7garm.getDevName(device.getDeviceType())); 174 loggingString.append(", ser. num. "); // NOI18N 175 loggingString.append(device.getSerNum()); 176 loggingString.append(", base addr. "); // NOI18N 177 loggingString.append(device.getBaseAddr()); 178 loggingString.append(".\n"); 179 log.debug("doStore: saving {}\n",loggingString.toString()); 180 Element deviceEntity = new Element("device"); // NOI18N 181 routesEntity.addContent(deviceEntity); 182 183 deviceEntity.setAttribute("type", // NOI18N 184 l7garm.getDevName(device.getDeviceType())); 185 deviceEntity.setAttribute("serNum", // NOI18N 186 Integer.toString(device.getSerNum())); 187 deviceEntity.setAttribute("baseAddr", // NOI18N 188 Integer.toString(device.getBaseAddr())); 189 190 // number of routes for the device 191 int numAvailRoutes = 192 (device.getDeviceType() == LnConstants.RE_IPL_DIGITRAX_HOST_DS78V) 193 ? 16 : 8; 194 195 for (int routeNumber = 0; routeNumber < numAvailRoutes; ++routeNumber) { 196 log.debug("doStore: device {} route {} (of {} possible device routes)", 197 aDevice, routeNumber+1, numAvailRoutes); 198 // get route'loggingString "top" entry, to see if it is "used" 199 if (device. 200 getRoutes(routeNumber) != null) { 201 LnSimpleRouteEntry re = device.getRoutes(routeNumber).getRouteEntry(0); 202 if (re != null) { 203 int turn = device.getRoutes(routeNumber).getRouteEntry(0).getNumber(); 204 log.debug("\tdoStore: route {} entry 0 turn = {}", routeNumber, turn); 205 if ((turn >= 0) && (turn <= 2043)) { 206 log.debug("\tdoStore: Continued at route {} with" 207 + " 1<=address<=2044: {}.", 208 routeNumber, turn); 209 210 Element routeElement = new Element("route"); // NOI18N 211 routeElement.setAttribute("number", // NOI18N 212 Integer.toString((routeNumber+1))); 213 loggingString.append("\t\tRoute "); // NOI18N 214 loggingString.append(routeNumber+1); 215 loggingString.append(":\n"); 216 boolean anUndefined = false; 217 for (int entryNumber = 0; (entryNumber < 8) && 218 (!anUndefined); ++entryNumber) { 219 int addr = device.getRoutes(routeNumber). 220 getRouteEntry(entryNumber).getNumber(); 221 RouteSwitchPositionEnum state = device.getRoutes(routeNumber). 222 getRouteEntry(entryNumber).getPosition(); 223 if (state == null) { 224 state = RouteSwitchPositionEnum.UNUSED; 225 anUndefined = true; 226 log.debug("\tDevice {} Route {} loop entry {}" 227 + " has state {} ", 228 aDevice, routeNumber+1, entryNumber, 229 state.toString()); 230 } else { 231 log.debug("\tDevice {} Route {} loop entry {} " 232 + "address {} has state {} ", 233 aDevice, routeNumber+1, entryNumber, 234 addr, state.toString()); 235 Element routeEntryElement; 236 if (entryNumber == 0) { 237 routeEntryElement = new Element("routeTop"); // NOI18N 238 routeEntryElement.setAttribute("controlTurnout", // NOI18N 239 Integer.toString(addr+1)); 240 routeEntryElement.setAttribute("controlTurnoutState", // NOI18N 241 state.toString() ); 242 routeElement.addContent(routeEntryElement); 243 loggingString.append("\t\tTop Addr:"); // NOI18N 244 loggingString.append(Integer.toString(addr+1)); 245 } else { 246 routeEntryElement = new Element("routeOutputTurnout"); // NOI18N 247 routeEntryElement.setAttribute("turnout", // NOI18N 248 Integer.toString(addr+1)); 249 routeEntryElement.setAttribute("state", // NOI18N 250 state.toString()); 251 routeElement.addContent(routeEntryElement); 252 loggingString.append("\tAddr:"); // NOI18N 253 loggingString.append(Integer.toString(addr+1)); 254 } 255 loggingString.append(", State:"); // NOI18N 256 loggingString.append(state); 257 } 258 if ((entryNumber == 0) && (state != RouteSwitchPositionEnum.UNUSED)) { 259 deviceEntity.addContent(routeElement); 260 261 } 262 } 263 loggingString.append("\n"); 264 } else { 265 log.debug("\tdoStore: no entries because top address is -1."); 266 } 267 } 268 } 269 } 270 } 271 log.debug("doStore: saving string as: {}", loggingString.toString()); 272 } 273 274 DigitraxRoutesXmlFile dtxXmlFil = new DigitraxRoutesXmlFile(); 275 File fileFileFile = dtxXmlFil.getStoreFile(); 276 Document xmlDoc = new Document(digitraxRoutesPreferencesElement); 277 278 log.debug("doStore: checking canwrite()"); 279 if (!fileFileFile.canWrite()) { 280 log.debug("doStore: canwrite is false; Unable to create writable local Digitrax routes file"); 281 try { 282 fileFileFile.createNewFile(); 283 } catch (IOException e) { 284 log.error("IOError trying to create file!"); 285 return false; 286 } 287 if (!fileFileFile.canWrite()) { 288 return false; 289 } 290 } 291 292 try { 293 dtxXmlFil.writeXML(fileFileFile, xmlDoc); 294 } catch (FileNotFoundException ex) { 295 log.error("File not found when writing", ex); // NOI18N 296 return false; 297 } catch (IOException ex) { 298 log.error("IO Exception when writing", ex); // NOI18N 299 return false; 300 } 301 return true; 302 } 303 304 public static class DigitraxRoutesXmlFile extends XmlFile { 305 private static final String PRODUCTIONFILEPATH = FileUtil.getProgramPath() 306 + "resources/digitraxRoutes/"; // NOI18N 307 private static final String USERFILEPATH = FileUtil.getUserFilesPath() 308 + "resources/digitraxRoutes/"; // NOI18N 309 private static final String TARGETFILEPATH = "DigitraxRoutes.xml"; // NOI18N 310 311 /** 312 * Getter. 313 * @return store file name including path 314 */ 315 public static String getStoreFileName() { 316 return USERFILEPATH + TARGETFILEPATH; 317 } 318 319 /** 320 * Getter. 321 * @return load file name including path 322 */ 323 public static String getLoadFileName() { 324 return getStoreFileName(); 325 } 326 327 /** 328 * Getter 329 * @return store file 330 */ 331 public File getStoreFile() { 332 333 log.debug("getStoreFile: check directory exists - {}", USERFILEPATH); 334 File chkdir = new File(USERFILEPATH); 335 log.debug("getStoreFile: check directory exists result: chkdir {}", chkdir); 336 if ((!chkdir.exists()) && (!chkdir.mkdirs())) { 337 log.warn("getStoreFile: check directory does not exist & make directory failed!"); 338 return null; 339 } 340 File file = findFile(getStoreFileName()); 341 log.debug("getStoreFile part n/2: file is {}.", file); 342 343 if (file == null) { 344 log.debug("getStoreFile: Try to Create new DigitraxRoutes.xml file"); // NOI18N 345 file = new File(getStoreFileName()); 346 log.debug("getStoreFile part n: file is {}.", file); 347 } else { 348 try { 349 FileUtil.rotate(file, 4, "bup"); // NOI18N 350 } catch (IOException ex) { 351 log.debug("Rotate failed, reverting to xml backup"); // NOI18N 352 makeBackupFile(getStoreFileName()); 353 } 354 } 355 return file; 356 } 357 358 /** 359 * Getter. 360 * @return load file 361 */ 362 public File getLoadFile() { 363 log.debug("getLoadFile: USERFILEPATH = {}, TARGETFILEPATH = {}", 364 USERFILEPATH , TARGETFILEPATH); 365 File file = findFile(USERFILEPATH + TARGETFILEPATH); 366 367 if (file == null) { 368 file = findFile(PRODUCTIONFILEPATH + TARGETFILEPATH); 369 log.debug("getLoadFile: try2 - file = {}", file); 370 } 371 return file; 372 } 373 } 374 375 private final static Logger log = LoggerFactory.getLogger(Digitrax7thGenAccyRoutesXML.class); 376}