001package jmri.util.prefs; 002 003import java.io.File; 004import java.io.FileInputStream; 005import java.io.FileOutputStream; 006import java.io.IOException; 007import java.io.InputStream; 008import java.io.OutputStream; 009import java.nio.charset.StandardCharsets; 010 011import jmri.profile.AuxiliaryConfiguration; 012import jmri.util.FileUtil; 013import jmri.util.ThreadingUtil; 014import jmri.util.xml.XMLUtil; 015 016import org.w3c.dom.DOMException; 017import org.w3c.dom.Document; 018import org.w3c.dom.Element; 019import org.w3c.dom.Node; 020import org.w3c.dom.NodeList; 021import org.xml.sax.InputSource; 022import org.xml.sax.SAXException; 023 024/** 025 * 026 * @author Randall Wood 027 */ 028public abstract class JmriConfiguration implements AuxiliaryConfiguration { 029 030 JmriConfiguration() { 031 } 032 033 protected abstract File getConfigurationFile(boolean shared); 034 035 protected abstract boolean isSharedBackedUp(); 036 037 protected abstract void setSharedBackedUp(boolean backedUp); 038 039 protected abstract boolean isPrivateBackedUp(); 040 041 protected abstract void setPrivateBackedUp(boolean backedUp); 042 043 File persistentFile; 044 Document persistentDocument; 045 046 Document getDocumentFromFile(File file) { 047 if (file != null && file.canRead()) { 048 try { 049 try (final InputStream is = new FileInputStream(file)) { 050 InputSource input = new InputSource(is); 051 input.setSystemId(file.toURI().toURL().toString()); 052 053 log.debug("Start read of user-interface.xml"); 054 055 var document = XMLUtil.parse(input, false, true, null, null); 056 log.debug("End read of user-interface.xml"); 057 return document; 058 } 059 } catch (IOException | SAXException | IllegalArgumentException ex) { 060 log.warn("Cannot parse {}", file, ex); 061 return null; 062 } 063 } 064 return null; 065 } 066 067 @Override 068 public Element getConfigurationFragment(final String elementName, final String namespace, final boolean shared) { 069 return ThreadingUtil.runOnGUIwithReturn(() -> { 070 synchronized (this) { 071 File file = this.getConfigurationFile(shared); 072 if (persistentDocument == null || ! file.equals(persistentFile)) { 073 log.debug("getting config for get from {}", file); 074 persistentDocument = getDocumentFromFile(file); 075 persistentFile = file; 076 } 077 if (persistentDocument == null) { 078 return null; 079 } 080 Element root = persistentDocument.getDocumentElement(); 081 return XMLUtil.findElement(root, elementName, namespace); 082 } 083 }); 084 } 085 086 @Override 087 public void putConfigurationFragment(final Element fragment, final boolean shared) throws IllegalArgumentException { 088 ThreadingUtil.runOnGUI(() -> { 089 synchronized (this) { 090 String elementName = fragment.getLocalName(); 091 String namespace = fragment.getNamespaceURI(); 092 if (namespace == null) { 093 throw new IllegalArgumentException(); 094 } 095 File file = this.getConfigurationFile(shared); 096 if (persistentDocument == null || ! file.equals(persistentFile) ) { 097 log.debug("getting config for put from {}", file); 098 persistentDocument = getDocumentFromFile(file); 099 persistentFile = file; 100 } 101 if (persistentDocument == null) { 102 persistentDocument = XMLUtil.createDocument("auxiliary-configuration", JmriConfigurationProvider.NAMESPACE, null, null); // NOI18N 103 } 104 Element root = persistentDocument.getDocumentElement(); 105 Element oldFragment = XMLUtil.findElement(root, elementName, namespace); 106 if (oldFragment != null) { 107 root.removeChild(oldFragment); 108 } 109 Node ref = null; 110 NodeList list = root.getChildNodes(); 111 for (int i = 0; i < list.getLength(); i++) { 112 Node node = list.item(i); 113 if (node.getNodeType() != Node.ELEMENT_NODE) { 114 continue; 115 } 116 int comparison = node.getNodeName().compareTo(elementName); 117 if (comparison == 0) { 118 comparison = node.getNamespaceURI().compareTo(namespace); 119 } 120 if (comparison > 0) { 121 ref = node; 122 break; 123 } 124 } 125 root.insertBefore(root.getOwnerDocument().importNode(fragment, true), ref); 126 try { 127 this.backup(shared); 128 try (final OutputStream os = new FileOutputStream(file)) { 129 log.debug("Start write of user-interface.xml"); 130 try { 131 XMLUtil.write(persistentDocument, os, StandardCharsets.UTF_8.name()); 132 } catch (IOException ex) { 133 log.error("Cannot write {}", file, ex); 134 } 135 log.debug("End write of user-interface.xml"); 136 os.flush(); 137 } 138 } catch (IOException ex) { 139 log.error("Cannot write {}", file, ex); 140 } 141 } 142 }); 143 } 144 145 @Override 146 public boolean removeConfigurationFragment(final String elementName, final String namespace, final boolean shared) throws IllegalArgumentException { 147 return ThreadingUtil.runOnGUIwithReturn(() -> { 148 synchronized (this) { 149 File file = this.getConfigurationFile(shared); 150 if (file.canWrite()) { 151 try { 152 if (persistentDocument == null || ! file.equals(persistentFile)) { 153 log.debug("getting config for remove from {}", file); 154 persistentDocument = getDocumentFromFile(file); 155 persistentFile = file; 156 } 157 Element root = persistentDocument.getDocumentElement(); 158 Element toRemove = XMLUtil.findElement(root, elementName, namespace); 159 if (toRemove != null) { 160 root.removeChild(toRemove); 161 this.backup(shared); 162 if (root.getElementsByTagName("*").getLength() > 0) { 163 // NOI18N 164 try (final OutputStream os = new FileOutputStream(file)) { 165 log.debug("Start write of user-interface.xml"); 166 XMLUtil.write(persistentDocument, os, StandardCharsets.UTF_8.name()); 167 log.debug("End write of user-interface.xml"); 168 } 169 } else if (!file.delete()) { 170 log.warn("Unable to delete {}", file); 171 } 172 return true; 173 } 174 } catch (IOException | DOMException ex) { 175 log.error("Cannot remove {} from {}", elementName, file, ex); 176 } 177 } 178 return false; 179 } 180 }); 181 } 182 183 private void backup(boolean shared) { 184 final File file = this.getConfigurationFile(shared); 185 if (!(shared ? this.isSharedBackedUp() : this.isPrivateBackedUp()) && file.exists()) { 186 log.debug("Backing up {}", file); 187 try { 188 FileUtil.backup(file); 189 if (shared) { 190 this.setSharedBackedUp(true); 191 } else { 192 this.setPrivateBackedUp(true); 193 } 194 } catch (IOException ex) { 195 log.error("Error backing up {}", file, ex); 196 } 197 } 198 } 199 200 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(JmriConfiguration.class); 201}