001package jmri.configurexml; 002 003import java.util.List; 004import java.util.SortedSet; 005 006import jmri.BeanSetting; 007import jmri.Block; 008import jmri.BlockManager; 009import jmri.InstanceManager; 010import jmri.Path; 011import jmri.Reporter; 012import jmri.Turnout; 013import org.jdom2.Element; 014 015/** 016 * Persistency implementation for BlockManager persistence. 017 * <p> 018 * The Block objects are not yet read in, pending a reliable write out! 019 * <p> 020 * Every block is written twice. First, the list of blocks is written without 021 * contents, so that we're sure they're all created on read-back. Then, they're 022 * written out again with contents, including the block references in the path 023 * elements. 024 * 025 * @author Bob Jacobsen Copyright: Copyright (c) 2008 026 * @since 2.1.2 027 */ 028public class BlockManagerXml extends jmri.managers.configurexml.AbstractMemoryManagerConfigXML { 029 030 public BlockManagerXml() { 031 } 032 033 /** 034 * Subclass provides implementation to create the correct top element, 035 * including the type information. Default implementation is to use the 036 * local class here. 037 * 038 * @param memories The top-level element being created 039 */ 040 @Override 041 public void setStoreElementClass(Element memories) { 042 memories.setAttribute("class", "jmri.configurexml.BlockManagerXml"); 043 } 044 045 /** 046 * Store the contents of a BlockManager. 047 * 048 * @param o Object to store, of type BlockManager 049 * @return Element containing the complete info 050 */ 051 @Override 052 public Element store(Object o) { 053 Element blocks = new Element("blocks"); 054 setStoreElementClass(blocks); 055 BlockManager bm = (BlockManager) o; 056 if (bm != null) { 057 058 SortedSet<Block> blkList = bm.getNamedBeanSet(); 059 // don't return an element if there are no blocks to include 060 if (blkList.isEmpty()) { 061 return null; 062 } 063 064 blocks.addContent(new Element("defaultspeed").addContent(bm.getDefaultSpeed())); 065 //TODO: The block info saved includes paths that at load time might 066 // reference blocks that haven't yet been loaded. In the past the 067 // workaround was to write all the blocks out twice: once with just 068 // the system and user names and then again with everything so that 069 // at load time the first set of (minimum) blocks would create all 070 // the blocks before the second pass loaded the path information. 071 // To remove the necessity of doing this (and having duplicate 072 // blocks in the saved file) we've changed the load routine to make 073 // two passes: once only creating the blocks (with system & user 074 // names) and then a second pass with everything (including the 075 // paths). At some point in the future (after a major release?) we 076 // can remove writing the first set of blocks without contents 077 // (and this (now way overly verbose) comment). 078 if (true) { 079 // write out first set of blocks without contents 080 for (Block b : blkList) { 081 try { 082 String bName = b.getSystemName(); 083 Element elem = new Element("block"); 084 elem.addContent(new Element("systemName").addContent(bName)); 085 086 // As a work-around for backward compatibility, store systemName as attribute. 087 // TODO Remove this in e.g. JMRI 4.11.1 and then update all the loadref comparison files 088 elem.setAttribute("systemName", bName); 089 090 // the following null check is to catch a null pointer exception that sometimes was found to happen 091 String uName = b.getUserName(); 092 if ((uName != null) && (!uName.isEmpty())) { 093 elem.addContent(new Element("userName").addContent(uName)); 094 } 095 log.debug("initial store Block {}", bName); 096 097 // and put this element out 098 blocks.addContent(elem); 099 } catch (Exception e) { 100 log.error("Exception: ", e); 101 } 102 } 103 } 104 105 // write out with contents 106 for (Block b : blkList) { 107 String bName = b.getSystemName(); 108 String uName = b.getUserName(); 109 if (uName == null) { 110 uName = ""; 111 } 112 Element elem = new Element("block"); 113 elem.addContent(new Element("systemName").addContent(bName)); 114 115 // As a work-around for backward compatibility, store systemName as attribute. 116 // TODO Remove this in e.g. JMRI 4.11.1 and then update all the loadref comparison files 117 elem.setAttribute("systemName", bName); 118 log.debug("second store Block {}: {}", bName, uName); 119 120 // add length and curvature attributes 121 elem.setAttribute("length", Float.toString(b.getLengthMm())); 122 elem.setAttribute("curve", Integer.toString(b.getCurvature())); 123 124 // store common parts 125 storeCommon(b, elem); 126 127 if ((!b.getBlockSpeed().isEmpty()) && !b.getBlockSpeed().contains("Global")) { 128 elem.addContent(new Element("speed").addContent(b.getBlockSpeed())); 129 } 130 String perm = "no"; 131 if (b.getPermissiveWorking()) { 132 perm = "yes"; 133 } 134 elem.addContent(new Element("permissive").addContent(perm)); 135 if (b.getIsGhost()) { 136 elem.addContent(new Element("ghost").addContent("yes")); 137 } 138 // Add content. First, the sensor 139 var sensor = b.getNamedSensor(); 140 if ( sensor != null) { 141 elem.addContent(new Element("occupancysensor").addContent(sensor.getName())); 142 } 143 144 if (!b.getDeniedBlocks().isEmpty()) { 145 Element denied = new Element("deniedBlocks"); 146 b.getDeniedBlocks().forEach( deniedBlock -> 147 denied.addContent(new Element("block").addContent(deniedBlock))); 148 elem.addContent(denied); 149 } 150 151 // now the Reporter 152 Reporter r = b.getReporter(); 153 if (r != null) { 154 Element re = new Element("reporter"); 155 re.setAttribute("systemName", r.getSystemName()); 156 re.setAttribute("useCurrent", b.isReportingCurrent() ? "yes" : "no"); 157 elem.addContent(re); 158 } 159 160 if (bm.isSavedPathInfo()) { 161 // then the paths 162 List<Path> paths = b.getPaths(); 163 164 // in sorted order 165 java.util.Collections.sort(paths); 166 167 for (Path p : paths) { 168 addPath(elem, p); 169 } 170 // and put this element out 171 } 172 blocks.addContent(elem); 173 } 174 } 175 return blocks; 176 } 177 178 private void addPath(Element e, Path p) { 179 // for now, persist two directions and a bean setting 180 Element pe = new Element("path"); 181 pe.setAttribute("todir", "" + p.getToBlockDirection()); 182 pe.setAttribute("fromdir", "" + p.getFromBlockDirection()); 183 if (p.getBlock() != null) { 184 pe.setAttribute("block", "" + p.getBlock().getSystemName()); 185 } 186 List<BeanSetting> l = p.getSettings(); 187 if (l != null) { 188 for (BeanSetting bSet : l) { 189 addBeanSetting(pe, bSet); 190 } 191 } 192 e.addContent(pe); 193 } 194 195 private void addBeanSetting(Element e, BeanSetting bs) { 196 // persist bean name, type and value 197 Element bse = new Element("beansetting"); 198 // for now, assume turnout 199 bse.setAttribute("setting", "" + bs.getSetting()); 200 Element be = new Element("turnout"); 201 be.setAttribute("systemName", bs.getBeanName()); 202 bse.addContent(be); 203 e.addContent(bse); 204 } 205 206 /** 207 * Load Blocks into the existing BlockManager. 208 * <p> 209 * The BlockManager in the InstanceManager is created automatically. 210 * 211 * @param sharedBlocks Element containing the block elements to load 212 * @param perNodeBlocks Per-node block elements to load 213 * @return true if successful 214 * @throws jmri.configurexml.JmriConfigureXmlException if error during load 215 */ 216 @Override 217 public boolean load(Element sharedBlocks, Element perNodeBlocks) throws JmriConfigureXmlException { 218 boolean result = true; 219 try { 220 if (sharedBlocks.getChild("defaultspeed") != null) { 221 String speed = sharedBlocks.getChild("defaultspeed").getText(); 222 if (speed != null && !speed.isEmpty()) { 223 InstanceManager.getDefault(BlockManager.class).setDefaultSpeed(speed); 224 } 225 } 226 } catch (IllegalArgumentException ex) { 227 log.error("Ill Argument Exception: ", ex ); 228 } 229 230 List<Element> list = sharedBlocks.getChildren("block"); 231 log.debug("Found {} objects", list.size()); 232 233 InstanceManager.getDefault(BlockManager.class).setPropertyChangesSilenced("beans", true); 234 // first pass don't load full contents (just create all the blocks) 235 for (Element block : list) { 236 loadBlock(block, false); 237 } 238 239 // second pass load full contents 240 for (Element block : list) { 241 loadBlock(block, true); 242 } 243 InstanceManager.getDefault(BlockManager.class).setPropertyChangesSilenced("beans", false); 244 245 return result; 246 } 247 248 /** 249 * Utility method to load the individual Block objects. 250 * 251 * @param element Element containing one block 252 * @throws jmri.configurexml.JmriConfigureXmlException if element contains 253 * malformed or 254 * schematically invalid 255 * XMl 256 */ 257 // default optional contentsFlag parameter to true 258 public void loadBlock(Element element) throws JmriConfigureXmlException { 259 loadBlock(element, true); 260 } 261 262 private void loadBlock(Element element, boolean contentsFlag) throws JmriConfigureXmlException { 263 String sysName = getSystemName(element); 264 String userName = getUserName(element); 265 log.debug("defined Block: ({})({})", sysName, (userName == null ? "<null>" : userName)); 266 267 Block block = InstanceManager.getDefault(BlockManager.class).getBlock(sysName); 268 if (block == null) { // create it if doesn't exist 269 InstanceManager.getDefault(BlockManager.class).createNewBlock(sysName, userName); 270 block = InstanceManager.getDefault(BlockManager.class).getBlock(sysName); 271 } 272 if (block == null) { 273 log.error("Unable to load block with system name {} and username of {}", 274 sysName, (userName == null ? "<null>" : userName)); 275 return; 276 } 277 if (userName != null) { 278 block.setUserName(userName); 279 } 280 if (!contentsFlag) { 281 return; 282 } 283 if (element.getAttribute("length") != null) { 284 // load length in millimeters 285 block.setLength(Float.parseFloat(element.getAttribute("length").getValue())); 286 } 287 if (element.getAttribute("curve") != null) { 288 // load curve attribute 289 block.setCurvature(Integer.parseInt((element.getAttribute("curve")).getValue())); 290 } 291 try { 292 block.setBlockSpeed("Global"); 293 if (element.getChild("speed") != null) { 294 String speed = element.getChild("speed").getText(); 295 if (speed != null && !speed.isEmpty() && !speed.contains("Global")) { 296 block.setBlockSpeed(speed); 297 } 298 } 299 } catch (jmri.JmriException ex) { 300 log.error("Exception setting block speed, ", ex ); 301 } 302 if (element.getChild("permissive") != null) { 303 boolean permissive = false; 304 if ("yes".equals(element.getChild("permissive").getText())) { 305 permissive = true; 306 } 307 block.setPermissiveWorking(permissive); 308 } 309 if (element.getChild("ghost") != null) { 310 block.setIsGhost("yes".equals(element.getChild("ghost").getText())); 311 } 312 Element deniedBlocks = element.getChild("deniedBlocks"); 313 if (deniedBlocks != null) { 314 List<Element> denyBlock = deniedBlocks.getChildren("block"); 315 for (Element deny : denyBlock) { 316 block.addBlockDenyList(deny.getText()); 317 } 318 } 319 // load common parts 320 loadCommon(block, element); 321 322 // load sensor if present 323 List<Element> sensors = element.getChildren("sensor"); 324 if (sensors.size() > 1) { 325 log.error("More than one sensor present: {}", sensors.size()); 326 } 327 if (sensors.size() == 1 && sensors.get(0).getAttribute("systemName") != null) { 328 //Old method of saving sensors 329 String name = sensors.get(0).getAttribute("systemName").getValue(); 330 if (!name.isEmpty()) { 331 block.setSensor(name); 332 } 333 } 334 if (element.getChild("occupancysensor") != null) { 335 String name = element.getChild("occupancysensor").getText(); 336 if (!name.isEmpty()) { 337 block.setSensor(name); 338 } 339 } 340 341 // load Reporter if present 342 List<Element> reporters = element.getChildren("reporter"); 343 if (reporters.size() > 1) { 344 log.error("More than one reporter present: {}", reporters.size()); 345 } 346 if (reporters.size() == 1) { 347 // Reporter 348 String name = reporters.get(0).getAttribute("systemName").getValue(); 349 try { 350 Reporter reporter = InstanceManager.getDefault(jmri.ReporterManager.class).provideReporter(name); 351 block.setReporter(reporter); 352 block.setReportingCurrent("yes".equals(reporters.get(0).getAttribute("useCurrent").getValue())); 353 } catch (IllegalArgumentException ex) { 354 log.warn("failed to create Reporter \"{}\" during Block load", name); 355 } 356 } 357 358 // load paths if present 359 List<Element> paths = element.getChildren("path"); 360 361 int startSize = block.getPaths().size(); 362 int loadCount = 0; 363 364 for (Element path : paths) { 365 if (loadPath(block, path)) { 366 loadCount++; 367 } 368 } 369 370 if (startSize > 0 && loadCount > 0) { 371 log.warn("Added {} paths to block {} that already had {} blocks.", loadCount, sysName, startSize); 372 loadCount++; 373 } 374 375 if (startSize + loadCount != block.getPaths().size()) { 376 log.error("Started with {} paths in block {}, added {} but final count is {}; something not right.", 377 startSize, sysName, loadCount, block.getPaths().size()); 378 } 379 } 380 381 /** 382 * Load path into an existing Block from XML. 383 * 384 * @param block Block to receive path 385 * @param element Element containing path information 386 * @return true if path added to block; false otherwise 387 * @throws jmri.configurexml.JmriConfigureXmlException if element contains 388 * malformed or 389 * schematically invalid 390 * XMl 391 */ 392 public boolean loadPath(Block block, Element element) throws JmriConfigureXmlException { 393 // load individual path 394 int toDir = 0; 395 int fromDir = 0; 396 397 var toElem = element.getAttribute("todir"); 398 var fromElem = element.getAttribute("fromdir"); 399 if ( toElem == null || fromElem == null ) { 400 handleException("Block Path entry in file missing required attribute", 401 null, block.getSystemName(), block.getUserName(), null); 402 } else { 403 try { 404 toDir = toElem.getIntValue(); 405 fromDir = fromElem.getIntValue(); 406 } catch (org.jdom2.DataConversionException e) { 407 log.error("Could not parse Block {} path attribute {}", block.getDisplayName(), e.getMessage()); 408 } 409 } 410 411 Block toBlock = null; 412 if (element.getAttribute("block") != null) { 413 String name = element.getAttribute("block").getValue(); 414 toBlock = InstanceManager.getDefault(BlockManager.class).getBlock(name); 415 } 416 Path path = new Path(toBlock, toDir, fromDir); 417 418 List<Element> settings = element.getChildren("beansetting"); 419 for (Element setting : settings) { 420 loadBeanSetting(path, setting); 421 } 422 423 // check if path already in block 424 if (!block.hasPath(path)) { 425 block.addPath(path); 426 return true; 427 } else { 428 log.debug("Skipping load of duplicate path {}", path); 429 return false; 430 } 431 } 432 433 /** 434 * Load BeanSetting into an existing Path. 435 * 436 * @param path Path to receive BeanSetting 437 * @param element Element containing beansetting information 438 */ 439 public void loadBeanSetting(Path path, Element element) { 440 int setting = 0; 441 try { 442 setting = element.getAttribute("setting").getIntValue(); 443 } catch (org.jdom2.DataConversionException e) { 444 log.error("Could not parse beansetting attribute"); 445 } 446 List<Element> turnouts = element.getChildren("turnout"); 447 if (turnouts.size() != 1) { 448 log.error("invalid number of turnout element children"); 449 } 450 String name = turnouts.get(0).getAttribute("systemName").getValue(); 451 try { 452 Turnout t = InstanceManager.turnoutManagerInstance().provideTurnout(name); 453 BeanSetting bs = new BeanSetting(t, name, setting); 454 path.addSetting(bs); 455 } catch (IllegalArgumentException ex) { 456 log.warn("failed to create Turnout \"{}\" during Block load", name); 457 } 458 } 459 460 @Override 461 public int loadOrder() { 462 return InstanceManager.getDefault(BlockManager.class).getXMLOrder(); 463 } 464 465 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(BlockManagerXml.class); 466 467}