001package jmri.jmrit.logix.configurexml;
002
003import java.awt.GraphicsEnvironment;
004import java.util.HashMap;
005import java.util.List;
006import java.util.SortedSet;
007
008import jmri.BeanSetting;
009import jmri.InstanceManager;
010import jmri.NamedBean;
011import jmri.Path;
012import jmri.Reporter;
013import jmri.Turnout;
014import jmri.jmrit.logix.OBlock;
015import jmri.jmrit.logix.OBlockManager;
016import jmri.jmrit.logix.OPath;
017import jmri.jmrit.logix.Portal;
018import jmri.jmrit.logix.PortalManager;
019import jmri.util.swing.JmriJOptionPane;
020
021import org.jdom2.Attribute;
022import org.jdom2.Element;
023
024/**
025 * Provides the abstract base and store functionality for configuring the
026 * OBlockManager.
027 * <p>
028 * Typically, a subclass will just implement the load(Element oblocks)
029 * class, relying on implementation here to load the individual oblock
030 * objects.
031 *
032 * @author Pete Cressman Copyright: Copyright (c) 2009
033 */
034public class OBlockManagerXml // extends XmlFile
035        extends jmri.configurexml.AbstractXmlAdapter {
036
037    public OBlockManagerXml() {
038    }
039
040    /**
041     * Store the contents of a OBlockManager.
042     *
043     * @param o Object to store, of type BlockManager
044     * @return Element containing the complete info
045     */
046    @Override
047    public Element store(Object o) {
048        Element blocks = new Element("oblocks");
049        blocks.setAttribute("class", "jmri.jmrit.logix.configurexml.OBlockManagerXml");
050        OBlockManager obm = (OBlockManager) o;
051        if (obm != null) {
052            SortedSet<OBlock> oblockList = obm.getNamedBeanSet();
053            // don't return an element if there are no oblocks to include
054            if (oblockList.isEmpty()) {
055                return null;
056            }
057            for (OBlock block : oblockList) {
058                String sName = block.getSystemName();
059                String uName = block.getUserName();
060                log.debug("OBlock: sysName= {}, userName= {}", sName, uName);
061                Element elem = new Element("oblock");
062                elem.setAttribute("systemName", sName);
063                if (uName != null && !uName.isEmpty()) {
064                    elem.setAttribute("userName", uName); // doing this for compatibility during 2.9.* series
065                    elem.addContent(new Element("userName").addContent(uName));
066                }
067                String comment = block.getComment();
068                if (comment != null && !comment.isEmpty()) {
069                    Element c = new Element("comment");
070                    c.addContent(comment);
071                    elem.addContent(c);
072                }
073                elem.setAttribute("length", "" + block.getLengthMm());
074                elem.setAttribute("units", block.isMetric() ? "true" : "false");
075                elem.setAttribute("curve", "" + block.getCurvature());
076                if (block.getNamedSensor() != null) {
077                    Element se = new Element("sensor");
078                    se.setAttribute("systemName", block.getNamedSensor().getName());
079                    elem.addContent(se);
080                }
081                if (block.getNamedErrorSensor() != null) {
082                    Element se = new Element("errorSensor");
083                    se.setAttribute("systemName", block.getNamedErrorSensor().getName());
084                    elem.addContent(se);
085                }
086                var reporter = block.getReporter();
087                if ( reporter != null) {
088                    Element se = new Element("reporter");
089                    se.setAttribute("systemName", reporter.getSystemName());
090                    se.setAttribute("reportCurrent", block.isReportingCurrent() ? "true" : "false");
091                    elem.addContent(se);
092                }
093                elem.setAttribute("permissive", block.getPermissiveWorking() ? "true" : "false");
094                elem.setAttribute("speedNotch", block.getBlockSpeed());
095
096                List<Path> paths = block.getPaths();
097                for (Path op : paths) {
098                    if ( op instanceof OPath ) {
099                        elem.addContent(storePath((OPath) op));
100                    }
101                }
102                List<Portal> portals = block.getPortals();
103                for (Portal po : portals) {
104                    elem.addContent(storePortal(po));
105                }
106                // and put this element out
107                blocks.addContent(elem);
108            }
109        }
110        return blocks;
111    }
112
113    static private Element storePortal(Portal portal) {
114        Element elem = new Element("portal");
115        elem.setAttribute("portalName", portal.getName());
116        OBlock block = portal.getFromBlock();
117        if (block != null) {
118            Element fromElem = new Element("fromBlock");
119            fromElem.setAttribute("blockName", block.getSystemName());
120            List<OPath> paths = portal.getFromPaths();
121            if (paths != null) {
122                for (OPath path : paths) {
123                    fromElem.addContent(storePathKey(path));
124                }
125            }
126            elem.addContent(fromElem);
127        } else {
128            log.error("Portal \"{}\" has no fromBlock!", portal.getName());
129        }
130        NamedBean signal = portal.getFromSignal();
131        if (signal != null) {
132            Element fromElem = new Element("fromSignal");
133            fromElem.setAttribute("signalName", signal.getSystemName());
134            fromElem.setAttribute("signalDelay", "" + portal.getFromSignalOffset()); // actually a Distance/Offset
135            elem.addContent(fromElem);
136        }
137        block = portal.getToBlock();
138        if (block != null) {
139            Element toElem = new Element("toBlock");
140            toElem.setAttribute("blockName", block.getSystemName());
141            List<OPath> paths = portal.getToPaths();
142            if (paths != null) {
143                for (OPath path : paths) {
144                    toElem.addContent(storePathKey(path));
145                }
146            }
147            elem.addContent(toElem);
148        } else {
149            log.error("Portal \"{}\" has no toBlock!", portal.getName());
150        }
151        signal = portal.getToSignal();
152        if (signal != null) {
153            Element toElem = new Element("toSignal");
154            toElem.setAttribute("signalName", signal.getSystemName());
155            toElem.setAttribute("signalDelay", "" + portal.getToSignalOffset());
156            elem.addContent(toElem);
157        }
158        return elem;
159    }   // storePortal
160
161    /**
162     * Key is sufficient to mark the Portal's knowledge of the path. Full path
163     * info will get loaded from the HashMap.
164     */
165    static private Element storePathKey(OPath path) {
166        Element elem = new Element("path");
167        elem.setAttribute("pathName", path.getName());
168        elem.setAttribute("blockName", "" + path.getBlock().getSystemName());
169        return elem;
170    }
171
172    static private Element storePath(OPath path) {
173        Element elem = new Element("path");
174        elem.setAttribute("pathName", path.getName());
175        elem.setAttribute("blockName", "" + path.getBlock().getSystemName());
176        Portal portal = path.getFromPortal();
177        if (portal != null) {
178            elem.setAttribute("fromPortal", portal.getName());
179        }
180        portal = path.getToPortal();
181        if (portal != null) {
182            elem.setAttribute("toPortal", portal.getName());
183        }
184        List<BeanSetting> list = path.getSettings();
185        for (BeanSetting bs : list) {
186            Element e = new Element("setting");
187            e.setAttribute("turnout", bs.getBeanName());
188            e.setAttribute("set", "" + bs.getSetting());
189            elem.addContent(e);
190        }
191        elem.setAttribute("fromDirection", "" + path.getFromBlockDirection());
192        elem.setAttribute("toDirection", "" + path.getToBlockDirection());
193        // get actual object stored length.
194        elem.setAttribute("length", "" + path.getLength());
195        return elem;
196    }
197
198    /**
199     * Due to the forward and backward referencing among OBlock, OPath and
200     * Portal no precedence order exists to fully create these objects in one
201     * pass. The unique naming of these objects allows the use of Hashmaps to
202     * hold them for update.
203     */
204    private HashMap<String, OBlock> _blockMap;
205    private HashMap<String, OPath> _pathMap;
206    private OBlockManager _manager;
207    private PortalManager _portalMgr;
208
209    private OBlock getBlock(String sysName) {
210        OBlock block = _blockMap.get(sysName);
211        if (block == null) {
212            try {
213                block = _manager.provideOBlock(sysName);
214                log.debug("found OBlock: ({}) {}", sysName, block);
215            } catch (IllegalArgumentException ex) {
216                block = _manager.createNewOBlock(sysName, null);
217                log.debug("create OBlock: ({})", sysName);
218            }
219            _blockMap.put(sysName, block);
220        }
221        return block;
222    }
223
224    private OPath getPath(OBlock block, String name) {
225        String key = block.getSystemName() + name;
226        OPath path = _pathMap.get(key);
227        if (path == null) {
228            path = new OPath(block, name);
229            _pathMap.put(key, path);
230            log.debug("create OPath: \"{}\" in block {}", name, block.getSystemName());
231        }
232        return path;
233    }
234
235    @Override
236    public boolean load(Element shared, Element perNode) {
237        _blockMap = new HashMap<>();
238        _pathMap = new HashMap<>();
239        _manager = InstanceManager.getDefault(OBlockManager.class);
240        _portalMgr = InstanceManager.getDefault(PortalManager.class);
241        List<Element> blockList = shared.getChildren("oblock");
242        log.debug("Found {} OBlock objects", blockList.size());
243        for (Element bl : blockList) {
244            loadBlock(bl);
245        }
246        return true;
247    }
248
249    @Override
250    public void load(Element element, Object o) {
251        log.error("load called. Invalid method.");
252    }
253
254    private void loadBlock(Element elem) {
255        if (elem.getAttribute("systemName") == null) {
256            log.error("unexpected null for block systemName elem = {}", elem);
257            return;
258        }
259        String systemName = elem.getAttribute("systemName").getValue();
260        String userName = null;
261        if (elem.getAttribute("userName") != null) {
262            userName = elem.getAttribute("userName").getValue();
263        }
264        log.debug("Load block sysName= {}, userName= {}", systemName, userName);
265        // Portal may have already created a skeleton of this block
266        OBlock block = getBlock(systemName); // never null (for a valid systemName)
267        block.setUserName(userName);
268        String c = elem.getChildText("comment");
269        if (c != null) {
270            block.setComment(c);
271        }
272        if (elem.getAttribute("units") != null) {
273            block.setMetricUnits(elem.getAttribute("units").getValue().equals("true"));
274        } else {
275            block.setMetricUnits(false);
276        }
277        if (elem.getAttribute("length") != null) {
278            block.setLength(Float.parseFloat(elem.getAttribute("length").getValue()));
279        }
280        if (elem.getAttribute("curve") != null) {
281            block.setCurvature(Integer.parseInt((elem.getAttribute("curve")).getValue()));
282        }
283        List<Element> sensors = elem.getChildren("sensor");
284        if (sensors.size() > 1) {
285            log.error("More than one sensor present: {}", sensors.size());
286        }
287        if (sensors.size() > 0) {
288            // sensor
289            String name = sensors.get(0).getAttribute("systemName").getValue();
290            block.setSensor(name);
291        }
292        Element errSensor = elem.getChild("errorSensor");
293        if (errSensor != null) {
294            // sensor
295            String name = errSensor.getAttribute("systemName").getValue();
296            block.setErrorSensor(name);
297        }
298        Element reporter = elem.getChild("reporter");
299        if (reporter != null) {
300            // sensor
301            String name = reporter.getAttribute("systemName").getValue();
302            try {
303                Reporter rep = InstanceManager.getDefault(jmri.ReporterManager.class).getReporter(name);
304                if (rep != null) {
305                    block.setReporter(rep);
306                }
307            } catch (Exception ex) {
308                log.error("No Reporter named \"{}\" found. threw exception", name,  ex);
309            }
310            if (reporter.getAttribute("reportCurrent") != null) {
311                block.setReportingCurrent(reporter.getAttribute("reportCurrent").getValue().equals("true"));
312            } else {
313                block.setReportingCurrent(false);
314            }
315        }
316        if (elem.getAttribute("permissive") != null) {
317            block.setPermissiveWorking(elem.getAttribute("permissive").getValue().equals("true"));
318        } else {
319            block.setPermissiveWorking(false);
320        }
321        if (elem.getAttribute("speedNotch") != null) {
322            try {
323                block.setBlockSpeed(elem.getAttribute("speedNotch").getValue());
324            } catch (jmri.JmriException ex) {
325                log.error("Error setting SpeedNotch {} threw exception", elem.getAttribute("speedNotch").getValue(),  ex);
326                if (!GraphicsEnvironment.isHeadless()) {
327                    JmriJOptionPane.showMessageDialog(null, ex.getMessage() + "\n" + elem.getAttribute("speedNotch").getValue());
328                }
329            }
330        }
331
332        List<Element> portals = elem.getChildren("portal");
333        for (Element po : portals) {
334            Portal portal = loadPortal(po);
335            if (portal != null) {
336                block.addPortal(portal);
337            }
338        }
339
340        List<Element> paths = elem.getChildren("path");
341        for (Element pa : paths) {
342            if (!block.addPath(loadPath(pa, block))) {
343                log.error("load: block \"{}\" failed to add path \"{}\" in block \"{}\"",
344                        systemName, pa.getName(), block.getSystemName());
345            }
346        }
347    }   // loadBlock
348
349    private Portal loadPortal(Element elem) {
350        String userName = elem.getAttribute("portalName").getValue();
351        String fromBlockName = null;
352        String toBlockName = null;
353        // Portals must have user names.
354        Portal portal = _portalMgr.getPortal(userName);
355        if (portal != null) {
356            OBlock block = portal.getFromBlock();
357            if (block != null) {
358                fromBlockName = block.getSystemName();
359            }
360            block = portal.getToBlock();
361            if (block != null) {
362                toBlockName = block.getSystemName();
363            }
364        } else {
365            portal = _portalMgr.providePortal(userName);
366        }
367        if (portal == null) {
368            log.error("unable to create Portal ({}) elem attrs= {}",
369                    userName, elem.getAttributes());
370            return null;
371        }
372        log.debug("create Portal: ({})", userName);
373
374        OBlock fromBlock = null;
375        Element eFromBlk = elem.getChild("fromBlock");
376        if (eFromBlk != null && eFromBlk.getAttribute("blockName") != null) {
377            String name = eFromBlk.getAttribute("blockName").getValue();
378            if (fromBlockName != null && !fromBlockName.equals(name)) {
379                log.error("Portal user name \"{}\" has conflicting fromBlock \"{}\". Should be \"{}\"",
380                        userName, fromBlockName, name);
381            } else {
382                fromBlock = getBlock(name);
383                if (fromBlock != null) {
384                    portal.setFromBlock(fromBlock, false);
385                    fromBlock.addPortal(portal);
386
387                    List<Element> ePathsFromBlock = eFromBlk.getChildren("path");
388                    for (Element e : ePathsFromBlock) {
389                        String pathName = e.getAttribute("pathName").getValue();
390                        String blockName = e.getAttribute("blockName").getValue();
391                        log.debug("Load portal= \"{}\" fromBlock= {}, pathName= {}, blockName= {}",
392                                userName, fromBlock.getSystemName(), pathName, blockName);
393                        OPath path = getPath(fromBlock, pathName);
394                        portal.addPath(path);
395                    }
396                }
397            }
398        } else {
399            log.error("Portal \"{}\" has no fromBlock!", userName);
400        }
401
402        OBlock toBlock = null;
403        Element eToBlk = elem.getChild("toBlock");
404        if (eToBlk != null && eToBlk.getAttribute("blockName") != null) {
405            String name = eToBlk.getAttribute("blockName").getValue();
406            if (toBlockName != null && !toBlockName.equals(name)) {
407                log.error("Portal user name \"{}\" has conflicting toBlock \"{}\". Should be \"{}\"",
408                        userName, toBlockName, name);
409            } else {
410                toBlock = getBlock(name);
411                if (toBlock != null) {
412                    portal.setToBlock(toBlock, false);
413                    toBlock.addPortal(portal);
414
415                    List<Element> ePathsToBlock = eToBlk.getChildren("path");
416                    for (Element ePath : ePathsToBlock) {
417                        String pathName = ePath.getAttribute("pathName").getValue();
418                        String blockName = ePath.getAttribute("blockName").getValue();
419                        log.debug("Load portal= \"{}\" toBlock= {}, pathName= {}, blockName= {}", userName, toBlock.getSystemName(), pathName, blockName);
420                        // path is in the toBlock
421                        OPath path = getPath(toBlock, pathName);
422                        portal.addPath(path);
423                    }
424                }
425            }
426        } else {
427            log.error("Portal \"{}\" has no toBlock!",  userName);
428        }
429        Element eSignal = elem.getChild("fromSignal");
430        if (eSignal != null) {
431            String name = eSignal.getAttribute("signalName").getValue();
432            float length = 0.0f;
433            try {
434                Attribute attr = eSignal.getAttribute("signalDelay"); // actually a Distance/Offset
435                if (attr != null) {
436                    length = attr.getFloatValue();
437                }
438            } catch (org.jdom2.DataConversionException e) {
439                log.error("Could not parse signalDelay fromSignal ({}) in portal ({})", name, userName);
440            }
441            portal.setProtectSignal(Portal.getSignal(name), length, toBlock);
442        }
443        eSignal = elem.getChild("toSignal");
444        if (eSignal != null) {
445            String name = eSignal.getAttribute("signalName").getValue();
446            float length = 0.0f;
447            try {
448                Attribute attr = eSignal.getAttribute("signalDelay"); // actually a Distance/Offset
449                if (attr != null) {
450                    length = attr.getFloatValue();
451                }
452            } catch (org.jdom2.DataConversionException e) {
453                log.error("Could not parse signalDelay toSignal ({}) in portal ({})", name, userName);
454            }
455            portal.setProtectSignal(Portal.getSignal(name), length, fromBlock);
456        }
457
458        log.debug("End Load portal {}", userName);
459        return portal;
460    }   // loadPortal
461
462    OPath loadPath(Element elem, OBlock block) {
463        String pName = elem.getAttribute("pathName").getValue();
464        OPath path = getPath(block, pName);
465        try {
466            Attribute attr = elem.getAttribute("fromDirection");
467            if (attr != null) {
468                path.setFromBlockDirection(attr.getIntValue());
469            }
470            attr = elem.getAttribute("toDirection");
471            if (attr != null) {
472                path.setToBlockDirection(attr.getIntValue());
473            }
474            attr =  elem.getAttribute("length");
475            if (attr != null) {
476                path.setLength(attr.getFloatValue());
477            }
478        } catch (org.jdom2.DataConversionException e) {
479            log.error("Could not parse attribute of path \"{}\" in block \"{}\")",
480                    pName, block.getSystemName());
481        }
482
483        Attribute attr = elem.getAttribute("fromPortal");
484        if (attr != null) {
485            Portal portal = _portalMgr.providePortal(attr.getValue());
486            if (portal != null) {
487                path.setFromPortal(portal);
488                portal.addPath(path);
489            }
490        }
491        attr = elem.getAttribute("toPortal");
492        if (attr != null) {
493            Portal portal = _portalMgr.providePortal(attr.getValue());
494            if (portal != null) {
495                path.setToPortal(portal);
496                portal.addPath(path);
497            }
498        }
499
500        List<Element> settings = elem.getChildren("setting");
501        log.debug("Path \"{}\" has {} settings.", pName, settings.size());
502        java.util.HashSet<String> turnouts = new java.util.HashSet<>();
503        int dups = 0;
504        for (Element setElem : settings) {
505            int setting = 0;
506            try {
507                setting = setElem.getAttribute("set").getIntValue();
508            } catch (org.jdom2.DataConversionException e) {
509                log.error("Could not parse 'set' attribute for path path \"{}\" in block \"{}\"",
510                        pName,  block.getSystemName());
511            }
512            String sysName = setElem.getAttribute("turnout").getValue();
513            if (!turnouts.contains(sysName)) {
514                Turnout to = InstanceManager.turnoutManagerInstance().provideTurnout(sysName);
515                turnouts.add(sysName);
516                BeanSetting bs = new BeanSetting(to, sysName, setting);
517                path.addSetting(bs);
518            } else {
519                dups++;
520            }
521        }
522        if (dups > 0) {
523            log.warn("{} duplicate settings not loaded for path \"{}\"", dups, pName);
524        }
525        return path;
526    }   // loadPath
527
528    @Override
529    public int loadOrder() {
530        return InstanceManager.getDefault(OBlockManager.class).getXMLOrder();
531    }
532
533    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(OBlockManagerXml.class);
534
535}