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}