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 // Add content. First, the sensor 136 if (b.getNamedSensor() != null) { 137 elem.addContent(new Element("occupancysensor").addContent(b.getNamedSensor().getName())); 138 } 139 140 if (!b.getDeniedBlocks().isEmpty()) { 141 Element denied = new Element("deniedBlocks"); 142 b.getDeniedBlocks().forEach((deniedBlock) -> { 143 denied.addContent(new Element("block").addContent(deniedBlock)); 144 }); 145 elem.addContent(denied); 146 } 147 148 // now the Reporter 149 Reporter r = b.getReporter(); 150 if (r != null) { 151 Element re = new Element("reporter"); 152 re.setAttribute("systemName", r.getSystemName()); 153 re.setAttribute("useCurrent", b.isReportingCurrent() ? "yes" : "no"); 154 elem.addContent(re); 155 } 156 157 if (bm.isSavedPathInfo()) { 158 // then the paths 159 List<Path> paths = b.getPaths(); 160 161 // in sorted order 162 java.util.Collections.sort(paths); 163 164 for (Path p : paths) { 165 addPath(elem, p); 166 } 167 // and put this element out 168 } 169 blocks.addContent(elem); 170 } 171 } 172 return blocks; 173 } 174 175 private void addPath(Element e, Path p) { 176 // for now, persist two directions and a bean setting 177 Element pe = new Element("path"); 178 pe.setAttribute("todir", "" + p.getToBlockDirection()); 179 pe.setAttribute("fromdir", "" + p.getFromBlockDirection()); 180 if (p.getBlock() != null) { 181 pe.setAttribute("block", "" + p.getBlock().getSystemName()); 182 } 183 List<BeanSetting> l = p.getSettings(); 184 if (l != null) { 185 for (BeanSetting bSet : l) { 186 addBeanSetting(pe, bSet); 187 } 188 } 189 e.addContent(pe); 190 } 191 192 private void addBeanSetting(Element e, BeanSetting bs) { 193 // persist bean name, type and value 194 Element bse = new Element("beansetting"); 195 // for now, assume turnout 196 bse.setAttribute("setting", "" + bs.getSetting()); 197 Element be = new Element("turnout"); 198 be.setAttribute("systemName", bs.getBeanName()); 199 bse.addContent(be); 200 e.addContent(bse); 201 } 202 203 /** 204 * Load Blocks into the existing BlockManager. 205 * <p> 206 * The BlockManager in the InstanceManager is created automatically. 207 * 208 * @param sharedBlocks Element containing the block elements to load 209 * @param perNodeBlocks Per-node block elements to load 210 * @return true if successful 211 * @throws jmri.configurexml.JmriConfigureXmlException if error during load 212 */ 213 @Override 214 public boolean load(Element sharedBlocks, Element perNodeBlocks) throws JmriConfigureXmlException { 215 boolean result = true; 216 try { 217 if (sharedBlocks.getChild("defaultspeed") != null) { 218 String speed = sharedBlocks.getChild("defaultspeed").getText(); 219 if (speed != null && !speed.isEmpty()) { 220 InstanceManager.getDefault(jmri.BlockManager.class).setDefaultSpeed(speed); 221 } 222 } 223 } catch (IllegalArgumentException ex) { 224 log.error("Ill Argument Exception: ", ex ); 225 } 226 227 List<Element> list = sharedBlocks.getChildren("block"); 228 log.debug("Found {} objects", list.size()); 229 230 InstanceManager.getDefault(jmri.BlockManager.class).setPropertyChangesSilenced("beans", true); 231 // first pass don't load full contents (just create all the blocks) 232 for (Element block : list) { 233 loadBlock(block, false); 234 } 235 236 // second pass load full contents 237 for (Element block : list) { 238 loadBlock(block, true); 239 } 240 InstanceManager.getDefault(jmri.BlockManager.class).setPropertyChangesSilenced("beans", false); 241 242 return result; 243 } 244 245 /** 246 * Utility method to load the individual Block objects. 247 * 248 * @param element Element containing one block 249 * @throws jmri.configurexml.JmriConfigureXmlException if element contains 250 * malformed or 251 * schematically invalid 252 * XMl 253 */ 254 // default optional contentsFlag parameter to true 255 public void loadBlock(Element element) throws JmriConfigureXmlException { 256 loadBlock(element, true); 257 } 258 259 private void loadBlock(Element element, boolean contentsFlag) throws JmriConfigureXmlException { 260 String sysName = getSystemName(element); 261 String userName = getUserName(element); 262 log.debug("defined Block: ({})({})", sysName, (userName == null ? "<null>" : userName)); 263 264 Block block = InstanceManager.getDefault(jmri.BlockManager.class).getBlock(sysName); 265 if (block == null) { // create it if doesn't exist 266 InstanceManager.getDefault(jmri.BlockManager.class).createNewBlock(sysName, userName); 267 block = InstanceManager.getDefault(jmri.BlockManager.class).getBlock(sysName); 268 } 269 if (block == null) { 270 log.error("Unable to load block with system name {} and username of {}", sysName, (userName == null ? "<null>" : userName)); 271 return; 272 } 273 if (userName != null) { 274 block.setUserName(userName); 275 } 276 if (!contentsFlag) { 277 return; 278 } 279 if (element.getAttribute("length") != null) { 280 // load length in millimeters 281 block.setLength(Float.parseFloat(element.getAttribute("length").getValue())); 282 } 283 if (element.getAttribute("curve") != null) { 284 // load curve attribute 285 block.setCurvature(Integer.parseInt((element.getAttribute("curve")).getValue())); 286 } 287 try { 288 block.setBlockSpeed("Global"); 289 if (element.getChild("speed") != null) { 290 String speed = element.getChild("speed").getText(); 291 if (speed != null && !speed.isEmpty() && !speed.contains("Global")) { 292 block.setBlockSpeed(speed); 293 } 294 } 295 } catch (jmri.JmriException ex) { 296 log.error("Exception setting block speed, ", ex ); 297 } 298 if (element.getChild("permissive") != null) { 299 boolean permissive = false; 300 if (element.getChild("permissive").getText().equals("yes")) { 301 permissive = true; 302 } 303 block.setPermissiveWorking(permissive); 304 } 305 Element deniedBlocks = element.getChild("deniedBlocks"); 306 if (deniedBlocks != null) { 307 List<Element> denyBlock = deniedBlocks.getChildren("block"); 308 for (Element deny : denyBlock) { 309 block.addBlockDenyList(deny.getText()); 310 } 311 } 312 // load common parts 313 loadCommon(block, element); 314 315 // load sensor if present 316 List<Element> sensors = element.getChildren("sensor"); 317 if (sensors.size() > 1) { 318 log.error("More than one sensor present: {}", sensors.size()); 319 } 320 if (sensors.size() == 1) { 321 //Old method of saving sensors 322 if (sensors.get(0).getAttribute("systemName") != null) { 323 String name = sensors.get(0).getAttribute("systemName").getValue(); 324 if (!name.isEmpty()) { 325 block.setSensor(name); 326 } 327 } 328 } 329 if (element.getChild("occupancysensor") != null) { 330 String name = element.getChild("occupancysensor").getText(); 331 if (!name.isEmpty()) { 332 block.setSensor(name); 333 } 334 } 335 336 // load Reporter if present 337 List<Element> reporters = element.getChildren("reporter"); 338 if (reporters.size() > 1) { 339 log.error("More than one reporter present: {}", reporters.size()); 340 } 341 if (reporters.size() == 1) { 342 // Reporter 343 String name = reporters.get(0).getAttribute("systemName").getValue(); 344 try { 345 Reporter reporter = InstanceManager.getDefault(jmri.ReporterManager.class).provideReporter(name); 346 block.setReporter(reporter); 347 block.setReportingCurrent(reporters.get(0).getAttribute("useCurrent").getValue().equals("yes")); 348 } catch (IllegalArgumentException ex) { 349 log.warn("failed to create Reporter \"{}\" during Block load", name); 350 } 351 } 352 353 // load paths if present 354 List<Element> paths = element.getChildren("path"); 355 356 int startSize = block.getPaths().size(); 357 int loadCount = 0; 358 359 for (Element path : paths) { 360 if (loadPath(block, path)) { 361 loadCount++; 362 } 363 } 364 365 if ((startSize > 0) && (loadCount > 0)) { 366 log.warn("Added {} paths to block {} that already had {} blocks.", loadCount++, sysName, startSize); 367 } 368 369 if (startSize + loadCount != block.getPaths().size()) { 370 log.error("Started with {} paths in block {}, added {} but final count is {}; something not right.", 371 startSize, sysName, loadCount, block.getPaths().size()); 372 } 373 } 374 375 /** 376 * Load path into an existing Block from XML. 377 * 378 * @param block Block to receive path 379 * @param element Element containing path information 380 * @return true if path added to block; false otherwise 381 * @throws jmri.configurexml.JmriConfigureXmlException if element contains 382 * malformed or 383 * schematically invalid 384 * XMl 385 */ 386 public boolean loadPath(Block block, Element element) throws JmriConfigureXmlException { 387 // load individual path 388 int toDir = 0; 389 int fromDir = 0; 390 try { 391 toDir = element.getAttribute("todir").getIntValue(); 392 fromDir = element.getAttribute("fromdir").getIntValue(); 393 } catch (org.jdom2.DataConversionException e) { 394 log.error("Could not parse path attribute"); 395 } catch (NullPointerException e) { 396 handleException("Block Path entry in file missing required attribute", 397 null, block.getSystemName(), block.getUserName(), null); 398 } 399 400 Block toBlock = null; 401 if (element.getAttribute("block") != null) { 402 String name = element.getAttribute("block").getValue(); 403 toBlock = InstanceManager.getDefault(jmri.BlockManager.class).getBlock(name); 404 } 405 Path path = new Path(toBlock, toDir, fromDir); 406 407 List<Element> settings = element.getChildren("beansetting"); 408 for (Element setting : settings) { 409 loadBeanSetting(path, setting); 410 } 411 412 // check if path already in block 413 if (!block.hasPath(path)) { 414 block.addPath(path); 415 return true; 416 } else { 417 log.debug("Skipping load of duplicate path {}", path); 418 return false; 419 } 420 } 421 422 /** 423 * Load BeanSetting into an existing Path. 424 * 425 * @param path Path to receive BeanSetting 426 * @param element Element containing beansetting information 427 */ 428 public void loadBeanSetting(Path path, Element element) { 429 int setting = 0; 430 try { 431 setting = element.getAttribute("setting").getIntValue(); 432 } catch (org.jdom2.DataConversionException e) { 433 log.error("Could not parse beansetting attribute"); 434 } 435 List<Element> turnouts = element.getChildren("turnout"); 436 if (turnouts.size() != 1) { 437 log.error("invalid number of turnout element children"); 438 } 439 String name = turnouts.get(0).getAttribute("systemName").getValue(); 440 try { 441 Turnout t = InstanceManager.turnoutManagerInstance().provideTurnout(name); 442 BeanSetting bs = new BeanSetting(t, name, setting); 443 path.addSetting(bs); 444 } catch (IllegalArgumentException ex) { 445 log.warn("failed to create Turnout \"{}\" during Block load", name); 446 } 447 } 448 449 @Override 450 public int loadOrder() { 451 return InstanceManager.getDefault(jmri.BlockManager.class).getXMLOrder(); 452 } 453 454 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(BlockManagerXml.class); 455 456}