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