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