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) { 030 this.nameStore = nameStore; 031 } 032 033 OlcbEventNameStore nameStore; 034 /** 035 * The original implementation of this store 036 * was via the IdTagManager class. This is now 037 * viewed as a mistake. This method takes names 038 * stored in the IdTagManager and migrates them 039 * to the dedicated local store. 040 *<p> 041 * Note when this actually migrates, it opens and loads the IdTag Manager 042 * which can result in name confusion when Reporters 043 * are later created by a panel file. If this has already happened, 044 * the internal reporters generated by that have to be removed 045 * before this migration takes place. This means that there's 046 * no real good place to run this as part of JMRI startup. 047 * Instead, it's left as a public method so it can (when needed) 048 * be invoked by e.g. a script after the name clean up has been done. 049 */ 050 public void migrateFromIdTagStore() { 051 // check for whether migration is already done 052 File file = findFile(getDefaultEventNameFileName()); 053 if (file != null) { 054 return; 055 } 056 057 log.info("Checking for IdTag-stored Event Names to migrate"); 058 059 // get the IdTags, find any that are Event Names, and migrate those 060 IdTagManager tagmgr = InstanceManager.getDefault(IdTagManager.class); 061 log.debug("*** Starting event name migration"); 062 063 var tagSet = tagmgr.getNamedBeanSet(); 064 065 var localSet = new HashSet<>(tagSet); // avoid concurrent modifications 066 067 for (var tag : localSet) { 068 log.trace(" Process tag {}", tag); 069 if (tag.getSystemName().startsWith(OlcbConstants.tagPrefix)) { 070 var eid = tag.getSystemName().substring(OlcbConstants.tagPrefix.length()); 071 log.info(" Migrating event name '{}' event ID '{}' from IdTag table", 072 tag.getUserName(), eid); 073 074 // Add to this store 075 nameStore.addMatch(new EventID(eid), tag.getUserName()); 076 077 // Remove from ID tag store 078 tagmgr.deregister(tag); 079 tag.dispose(); 080 } 081 } 082 083 log.debug("*** Ending event name migration"); 084 } 085 086 public void store() throws java.io.IOException { 087 log.debug("Storing using file: {}", getDefaultEventNameFileName()); 088 createFile(getDefaultEventNameFileName(), true); 089 try { 090 writeFile(getDefaultEventNameFileName()); 091 } catch (FileNotFoundException ex) { 092 log.error("File not found while writing Event Name file, may not be complete", ex); 093 } 094 } 095 096 public void load() { 097 log.debug("Loading..."); 098 var wasDirty = nameStore.dirty; 099 try { 100 readFile(getDefaultEventNameFileName()); 101 } catch (JDOMException | IOException ex) { 102 log.error("Exception during IdTag file reading", ex); 103 } 104 nameStore.dirty = wasDirty; 105 } 106 107 private File createFile(String fileName, boolean backup) { 108 if (backup) { 109 makeBackupFile(fileName); 110 } 111 112 File file = null; 113 try { 114 if (!checkFile(fileName)) { 115 // The file does not exist, create it before writing 116 file = new File(fileName); 117 File parentDir = file.getParentFile(); 118 if (!parentDir.exists()) { 119 if (!parentDir.mkdir()) { 120 log.error("Directory wasn't created"); 121 } 122 } 123 if (file.createNewFile()) { 124 log.debug("New file created"); 125 } 126 } else { 127 file = new File(fileName); 128 } 129 } catch (java.io.IOException ex) { 130 log.error("Exception while creating Event Name file, may not be complete", (Object) ex); 131 } 132 return file; 133 } 134 135 private void writeFile(String fileName) throws FileNotFoundException, java.io.IOException { 136 log.debug("writeFile {}", fileName); 137 // This is taken in large part from "Java and XML" page 368 138 File file = findFile(fileName); 139 if (file == null) { 140 file = new File(fileName); 141 } 142 // Create root element 143 Element root = new Element("eventNameStore"); // NOI18N 144 // root.setAttribute("noNamespaceSchemaLocation", // NOI18N 145 // "http://jmri.org/xml/schema/idtags.xsd", // NOI18N 146 // org.jdom2.Namespace.getNamespace("xsi", // NOI18N 147 // "http://www.w3.org/2001/XMLSchema-instance")); // NOI18N 148 Document doc = newDocument(root); 149 150 // add XSLT processing instruction 151 // java.util.Map<String, String> m = new java.util.HashMap<>(); 152 // m.put("type", "text/xsl"); // NOI18N 153 // m.put("href", xsltLocation + "idtags.xsl"); // NOI18N 154 // ProcessingInstruction p = new ProcessingInstruction("xml-stylesheet", m); // NOI18N 155 // doc.addContent(0, p); 156 157 Element values; 158 159 // Loop through event names 160 root.addContent(values = new Element("names")); // NOI18N 161 for (EventID eid : nameStore.getMatches()) { 162 var name = nameStore.getEventName(eid); 163 log.debug("Writing event name: {} event {}", name, eid); 164 var element = new Element("entry"); 165 var nameElement = new Element("name"); 166 nameElement.addContent(name); 167 var eventIdElement = new Element("eventID"); 168 eventIdElement.addContent(eid.toShortString()); 169 element.addContent(eventIdElement); 170 element.addContent(nameElement); 171 values.addContent(element); 172 } 173 writeXML(file, doc); 174 } 175 176 private String getDefaultEventNameFileName() { 177 return getFileLocation() + getEventNameDirectoryName() + File.separator + getEventNameFileName(); 178 } 179 180 private String getFileLocation() { 181 return FileUtil.getUserFilesPath(); 182 } 183 184 private static final String EVENT_NAMES_DIRECTORY_NAME = "eventnames"; // NOI18N 185 186 private String getEventNameDirectoryName() { 187 return EVENT_NAMES_DIRECTORY_NAME; 188 } 189 190 private String getEventNameFileName() { 191 return "eventNames.xml"; 192 } 193 194 private void readFile(String fileName) throws org.jdom2.JDOMException, java.io.IOException, IllegalArgumentException { 195 // Check file exists 196 if (findFile(fileName) == null) { 197 log.debug("{} file could not be found", fileName); 198 return; 199 } 200 201 // Find root 202 Element root = rootFromName(fileName); 203 if (root == null) { 204 log.debug("{} file could not be read", fileName); 205 return; 206 } 207 208 // Now read name-id mapping information 209 if (root.getChild("names") != null) { // NOI18N 210 List<Element> names = root.getChildren("names"); 211 log.debug("readFile sees {} names elements", names.size()); 212 for (Element n : names) { 213 List<Element> l = n.getChildren("entry"); // NOI18N 214 log.debug(" readFile sees {} event names", l.size()); 215 for (Element e : l) { 216 String eid = e.getChild("eventID").getText(); // NOI18N 217 String name = e.getChild("name").getText(); 218 log.debug(" read EventID {}", eid); 219 nameStore.addMatch(new EventID(eid), name); 220 } 221 } 222 } 223 } 224 225 226 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(OlcbEventNameStoreXml.class); 227 228}