001package jmri.jmrit.catalog.configurexml;
002
003import java.io.File;
004import java.io.IOException;
005import java.util.Enumeration;
006import java.util.List;
007import java.util.Set;
008import javax.swing.tree.*;
009import jmri.CatalogTree;
010import jmri.CatalogTreeManager;
011import jmri.InstanceManager;
012import jmri.jmrit.XmlFile;
013import jmri.CatalogTreeLeaf;
014import jmri.CatalogTreeNode;
015import jmri.util.FileUtil;
016import org.jdom2.Attribute;
017import org.jdom2.Document;
018import org.jdom2.Element;
019import org.slf4j.Logger;
020import org.slf4j.LoggerFactory;
021
022/**
023 * Provides the abstract base and store functionality for configuring the
024 * CatalogTreeManager.
025 * <p>
026 * Typically, a subclass will just implement the load(Element catalogTree)
027 * class, relying on implementation here to load the individual CatalogTree
028 * objects.
029 *
030 * @author Pete Cressman Copyright: Copyright (c) 2009
031 */
032public class DefaultCatalogTreeManagerXml extends XmlFile {
033
034    private final static String DEFAULT_FILE_NAME = FileUtil.getUserFilesPath() + "catalogTrees.xml";
035
036    public DefaultCatalogTreeManagerXml() {
037    }
038
039    /**
040     * Write out tree values to a file in the user's preferences directory.
041     *
042     * @throws IOException from any I/O issues during write; not handled locally
043     */
044    public void writeCatalogTrees() throws IOException {
045        log.debug("entered writeCatalogTreeValues");
046        CatalogTreeManager manager = InstanceManager.getDefault(jmri.CatalogTreeManager.class);
047        Set<CatalogTree> trees = manager.getNamedBeanSet();
048        boolean found = false;
049        for (CatalogTree tree : manager.getNamedBeanSet()) {
050            String sname = tree.getSystemName();
051            if (log.isDebugEnabled()) {
052                log.debug("Tree: sysName= {}, userName= {}", sname, tree.getUserName());
053                CatalogTreeNode root = tree.getRoot();
054                log.debug("enumerateTree called for root= {}, has {} children", root, root.getChildCount());
055
056                Enumeration<TreeNode> e = root.depthFirstEnumeration();
057                while (e.hasMoreElements()) {
058                    CatalogTreeNode n = (CatalogTreeNode)e.nextElement();
059                    log.debug("nodeName= {} has {} leaves and {} subnodes.", n.getUserObject(), n.getLeaves().size(), n.getChildCount());
060                }
061            }
062            if (sname.charAt(1) == CatalogTree.XML) {
063                found = true;
064                break;
065            }
066        }
067        if (found) {
068            // there are trees defined, create root element
069            Element root = new Element("catalogTrees");
070            Document doc = newDocument(root, dtdLocation + "catalogTree.dtd");
071
072            // add XSLT processing instruction
073            // <?xml-stylesheet type="text/xsl" href="XSLT/tree-values.xsl"?>
074            java.util.Map<String, String> m = new java.util.HashMap<>();
075            m.put("type", "text/xsl");
076            m.put("href", xsltLocation + "panelfile.xsl");
077            org.jdom2.ProcessingInstruction p = new org.jdom2.ProcessingInstruction("xml-stylesheet", m);
078            doc.addContent(0, p);
079
080            store(root, trees);
081
082            try {
083                if (!checkFile(DEFAULT_FILE_NAME)) {
084                    // file does not exist, create it
085                    File file = new File(DEFAULT_FILE_NAME);
086                    if (!file.createNewFile()) {
087                        log.error("createNewFile failed");
088                    }
089                }
090                // write content to file
091                writeXML(findFile(DEFAULT_FILE_NAME), doc);
092                // memory consistent with file
093                InstanceManager.getDefault(CatalogTreeManager.class).indexChanged(false);
094            } catch (IOException ioe) {
095                log.error("IO Exception writing CatalogTrees", ioe);
096                throw (ioe);
097            }
098        }
099    }
100
101    /**
102     * Default implementation for storing the contents of a CatalogTreeManager.
103     *
104     * @param cat   Element to load with contents
105     * @param trees List of contents
106     */
107    public void store(Element cat, Set<CatalogTree> trees) {
108        cat.setAttribute("class", "jmri.jmrit.catalog.DefaultCatalogTreeManagerConfigXML");
109        for (CatalogTree ct : trees) {
110            String sname = ct.getSystemName();
111            log.debug("system name is {}", sname);
112            if (sname.charAt(1) != CatalogTree.XML) {
113                continue;
114            }
115            Element elem = new Element("catalogTree");
116            elem.setAttribute("systemName", sname);
117            String uname = ct.getUserName();
118            if (uname != null) {
119                elem.setAttribute("userName", uname);
120            }
121
122            storeNode(elem, ct.getRoot());
123
124            log.debug("store CatalogTree {}", sname);
125            cat.addContent(elem);
126        }
127    }
128
129    /**
130     * Recursively store a CatalogTree.
131     *
132     * @param parent the element to store node in
133     * @param node   the root node of the tree
134     */
135    public void storeNode(Element parent, CatalogTreeNode node) {
136        log.debug("storeNode {}, has {} leaves.", node, node.getLeaves().size());
137        Element element = new Element("node");
138        element.setAttribute("nodeName", node.toString());
139        List<CatalogTreeLeaf> leaves = node.getLeaves();
140        for (CatalogTreeLeaf leaf : leaves) {
141            Element el = new Element("leaf");
142            el.setAttribute("name", leaf.getName()); // prefer to store non-localized name
143            el.setAttribute("path", leaf.getPath());
144            element.addContent(el);
145        }
146        parent.addContent(element);
147        Enumeration<TreeNode> e = node.children();
148        while (e.hasMoreElements()) {
149            CatalogTreeNode n = (CatalogTreeNode) e.nextElement();
150            storeNode(element, n);
151        }
152    }
153
154    /**
155     * This is invoked as part of the "store all" mechanism, which is not used
156     * for these objects. Hence this is implemented to do nothing.
157     *
158     * @param o the object to store
159     * @return null
160     */
161    public Element store(Object o) {
162        return null;
163    }
164
165    /*
166     * Read CatalogTree values from a file in the user's preferences directory.
167     */
168    public void readCatalogTrees() {
169        log.debug("entered readCatalogTrees");
170        //CatalogTreeManager manager = InstanceManager.getDefault(jmri.CatalogTreeManager.class);
171        try {
172            // check if file exists
173            if (checkFile(DEFAULT_FILE_NAME)) {
174                Element root = rootFromName(DEFAULT_FILE_NAME);
175                if (root != null) {
176                    load(root);
177                }
178            } else {
179                log.debug("File: {} not Found", DEFAULT_FILE_NAME);
180            }
181        } catch (org.jdom2.JDOMException | IOException jde) {
182            log.error("Exception reading CatalogTrees", jde);
183        }
184    }
185
186    /**
187     * Create a CatalogTreeManager object of the correct class, then register
188     * and fill it.
189     *
190     * @param catalogTrees top level Element to unpack
191     * @return true if successful
192     */
193    public boolean load(Element catalogTrees) {
194        loadCatalogTrees(catalogTrees);
195        return true;
196    }
197
198    /**
199     * Utility method to load the individual CatalogTree objects.
200     *
201     * @param catalogTrees element containing trees
202     */
203    public void loadCatalogTrees(Element catalogTrees) {
204        List<Element> catList = catalogTrees.getChildren("catalogTree");
205        log.debug("loadCatalogTrees: found {} CatalogTree objects", catList.size());
206        CatalogTreeManager mgr = InstanceManager.getDefault(jmri.CatalogTreeManager.class);
207
208        for (Element elem : catList) {
209            Attribute attr = elem.getAttribute("systemName");
210            if (attr == null) {
211                log.warn("unexpected null systemName. elem= {}, attrs= {}", elem, elem.getAttributes());
212                continue;
213            }
214            String sysName = attr.getValue();
215            String userName;
216            attr = elem.getAttribute("userName");
217            if (attr == null) {
218                log.warn("unexpected null userName. attrs= {}", elem.getAttributes());
219                continue;
220            } else {
221                userName = attr.getValue();
222            }
223            CatalogTree ct = mgr.getBySystemName(sysName);
224            if (ct != null) {
225                continue;   // tree already registered
226            }
227            try {
228                ct = mgr.newCatalogTree(sysName, userName);
229            }
230            catch (IllegalArgumentException ex){
231                log.error("Could not create CatalogTree: {}",ex.getMessage());
232                continue;
233            }
234            if (ct instanceof DefaultTreeModel) {
235                log.debug("CatalogTree: sysName= {}, userName= {}", sysName, userName);
236                CatalogTreeNode root = ct.getRoot();
237                elem = elem.getChild("node");
238                loadNode(elem, root, (DefaultTreeModel) ct);
239            }
240        }
241    }
242
243    /**
244     * Recursively load a CatalogTree.
245     *
246     * @param element element containing the node to load
247     * @param parent  the parent node of the node in element
248     * @param model   the tree model containing the tree to add the node to
249     */
250    public void loadNode(Element element, CatalogTreeNode parent, DefaultTreeModel model) {
251        List<Element> nodeList = element.getChildren("node");
252        log.debug("Found {} CatalogTreeNode objects", nodeList.size());
253        for (int i = 0; i < nodeList.size(); i++) {
254            Element elem = nodeList.get(i);
255            Attribute attr = elem.getAttribute("nodeName");
256            if (attr == null) {
257                log.warn("unexpected null nodeName. elem= {}, attrs= {}", elem, elem.getAttributes());
258                continue;
259            }
260            String nodeName = attr.getValue();
261            CatalogTreeNode n = new CatalogTreeNode(nodeName);
262            addLeaves(elem, n);
263            model.insertNodeInto(n, parent, parent.getChildCount());
264            loadNode(elem, n, model);
265        }
266    }
267
268    private void addLeaves(Element element, CatalogTreeNode node) {
269        List<Element> leafList = element.getChildren("leaf");
270        for (Element elem : leafList) {
271            Attribute attr = elem.getAttribute("name");
272            if (attr == null) {
273                log.error("unexpected null leaf name. elem= {}, attrs= {}", elem, elem.getAttributes());
274                continue;
275            }
276            String name = attr.getValue();
277            attr = elem.getAttribute("path");
278            if (attr == null) {
279                log.error("unexpected null leaf path. elem= {}, attrs= {}", elem, elem.getAttributes());
280                continue;
281            }
282            String path = attr.getValue();
283            // use the method that maintains the same order
284            node.addLeaf(new CatalogTreeLeaf(name, path, 0));
285        }
286    }
287
288    private final static Logger log = LoggerFactory.getLogger(DefaultCatalogTreeManagerXml.class);
289
290}