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