001package jmri.jmrix.openlcb.configurexml;
002
003import java.io.File;
004import java.io.FileNotFoundException;
005import java.io.IOException;
006import java.util.List;
007import java.util.HashSet;
008
009import org.jdom2.Document;
010import org.jdom2.Element;
011import org.jdom2.JDOMException;
012
013import jmri.IdTagManager;
014import jmri.InstanceManager;
015import jmri.jmrit.XmlFile;
016import jmri.jmrix.openlcb.OlcbConstants;
017import jmri.jmrix.openlcb.OlcbEventNameStore;
018import jmri.util.FileUtil;
019
020import org.openlcb.EventID;
021
022/**
023 * JMRI's implementation of part of the OpenLcb EventNameStore interface persistance
024 *
025 * @author Bob Jacobsen Copyright (C) 2025
026 */
027public final class OlcbEventNameStoreXml extends XmlFile {
028
029    public OlcbEventNameStoreXml(OlcbEventNameStore nameStore, String baseFileName) {
030        this.nameStore = nameStore;
031        this.baseFileName = baseFileName;
032        
033        migrateFromIdTagStore();
034    }
035
036    OlcbEventNameStore nameStore;
037    String baseFileName;
038    /**
039     * The original implementation of this store
040     * was via the IdTagManager class.  This is now
041     * viewed as a mistake.  This method takes names
042     * stored in the IdTagManager and migrates them
043     * to the dedicated local store.
044     */
045    private void migrateFromIdTagStore() {
046        // check for whether migration is already done
047        File file = findFile(getDefaultEventNameFileName());
048        if (file != null) {
049           return;
050        }
051
052        IdTagManager tagmgr = InstanceManager.getDefault(IdTagManager.class); 
053        log.debug("*** Starting event name migration");
054        
055        var tagSet = tagmgr.getNamedBeanSet();
056        
057        var localSet = new HashSet<>(tagSet); // avoid concurrent modifications
058        
059        for (var tag : localSet) {
060            log.debug("  Process tag {}", tag);
061            if (tag.getSystemName().startsWith(OlcbConstants.tagPrefix)) {
062                var eid = tag.getSystemName().substring(OlcbConstants.tagPrefix.length());
063                log.info("    Migrating event name '{}' event ID '{}' from IdTag table", 
064                        tag.getUserName(), eid);
065                
066                // Add to this store
067                nameStore.addMatch(new EventID(eid), tag.getUserName());
068                
069                
070                // Remove from ID tag store
071                tagmgr.deregister(tag);
072                tag.dispose();
073            }
074        }
075        
076        log.debug("*** Ending event name migration");
077    }
078
079    public void store() throws java.io.IOException {
080        log.debug("Storing using file: {}", getDefaultEventNameFileName());
081        createFile(getDefaultEventNameFileName(), true);
082        try {
083            writeFile(getDefaultEventNameFileName());
084        } catch (FileNotFoundException ex) {
085            log.error("File not found while writing Event Name file, may not be complete", ex);
086        }
087    }
088
089    public void load() {
090        log.debug("Loading...");
091        var wasDirty = nameStore.dirty;
092        try {
093            readFile(getDefaultEventNameFileName());
094        } catch (JDOMException | IOException ex) {
095            log.error("Exception during IdTag file reading", ex);
096        }
097        nameStore.dirty = wasDirty;
098    }
099
100    private File createFile(String fileName, boolean backup) {
101        if (backup) {
102            makeBackupFile(fileName);
103        }
104
105        File file = null;
106        try {
107            if (!checkFile(fileName)) {
108                // The file does not exist, create it before writing
109                file = new File(fileName);
110                File parentDir = file.getParentFile();
111                if (!parentDir.exists()) {
112                    if (!parentDir.mkdir()) {
113                        log.error("Directory wasn't created");
114                    }
115                }
116                if (file.createNewFile()) {
117                    log.debug("New file created");
118                }
119           } else {
120              file = new File(fileName);
121           }
122       } catch (java.io.IOException ex) {
123          log.error("Exception while creating Event Name file, may not be complete", (Object) ex);
124       }
125       return file;
126   }
127
128   private void writeFile(String fileName) throws FileNotFoundException, java.io.IOException {
129        log.debug("writeFile {}", fileName);
130        // This is taken in large part from "Java and XML" page 368
131        File file = findFile(fileName);
132        if (file == null) {
133           file = new File(fileName);
134        }
135        // Create root element
136        Element root = new Element("eventNameStore");              // NOI18N
137        // root.setAttribute("noNamespaceSchemaLocation", // NOI18N
138        //      "http://jmri.org/xml/schema/idtags.xsd", // NOI18N
139        //      org.jdom2.Namespace.getNamespace("xsi", // NOI18N
140        //      "http://www.w3.org/2001/XMLSchema-instance")); // NOI18N
141        Document doc = newDocument(root);
142
143        // add XSLT processing instruction
144        // java.util.Map<String, String> m = new java.util.HashMap<>();
145        // m.put("type", "text/xsl"); // NOI18N
146        // m.put("href", xsltLocation + "idtags.xsl"); // NOI18N
147        // ProcessingInstruction p = new ProcessingInstruction("xml-stylesheet", m); // NOI18N
148        // doc.addContent(0, p);
149
150        Element values;
151
152        // Loop through event names
153        root.addContent(values = new Element("names")); // NOI18N
154        for (EventID eid : nameStore.getMatches()) {
155            var name = nameStore.getEventName(eid);
156            log.debug("Writing event name: {} event {}", name, eid);
157            var element = new Element("entry");
158            var nameElement = new Element("name");
159            nameElement.addContent(name);
160            var eventIdElement = new Element("eventID");
161            eventIdElement.addContent(eid.toShortString());
162            element.addContent(eventIdElement);
163            element.addContent(nameElement);
164            values.addContent(element);
165        }
166        writeXML(file, doc);
167    }
168
169    private String getDefaultEventNameFileName() {
170        return getFileLocation() + getEventNameDirectoryName() + File.separator + getEventNameFileName();
171    }
172    
173    private String getFileLocation() {
174        return FileUtil.getUserFilesPath();
175    }
176
177    private static final String EVENT_NAMES_DIRECTORY_NAME = "eventnames"; // NOI18N
178
179    private String getEventNameDirectoryName() {
180        return EVENT_NAMES_DIRECTORY_NAME;
181    }
182
183    private String getEventNameFileName() {
184        return "eventNames.xml";
185    }
186    
187    private void readFile(String fileName) throws org.jdom2.JDOMException, java.io.IOException, IllegalArgumentException {
188        // Check file exists
189        if (findFile(fileName) == null) {
190            log.debug("{} file could not be found", fileName);
191            return;
192        }
193
194        // Find root
195        Element root = rootFromName(fileName);
196        if (root == null) {
197            log.debug("{} file could not be read", fileName);
198            return;
199        }
200
201        // Now read name-id mapping information
202        if (root.getChild("names") != null) { // NOI18N
203            List<Element> l = root.getChild("names").getChildren("entry"); // NOI18N
204            log.debug("readFile sees {} event names", l.size());
205            for (Element e : l) {
206                String eid = e.getChild("eventID").getText(); // NOI18N
207                String name = e.getChild("name").getText();
208                log.debug("read EventID {}", eid);
209                nameStore.addMatch(new EventID(eid), name);
210            }
211        }
212    }
213
214
215    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(OlcbEventNameStoreXml.class);
216
217}