001/*
002 * Copyright 2010-2015 Institut Pasteur.
003 * 
004 * This file is part of Icy.
005 * 
006 * Icy is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 * 
011 * Icy is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014 * GNU General Public License for more details.
015 * 
016 * You should have received a copy of the GNU General Public License
017 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
018 */
019package icy.preferences;
020
021import icy.util.ClassUtil;
022import icy.util.StringUtil;
023import icy.util.XMLUtil;
024
025import java.io.File;
026import java.util.ArrayList;
027import java.util.List;
028
029import org.w3c.dom.Document;
030import org.w3c.dom.Element;
031import org.w3c.dom.Node;
032
033/**
034 * @author Stephane
035 */
036public class XMLPreferences
037{
038    public static class XMLPreferencesRoot
039    {
040        private final String filename;
041        private Document doc;
042
043        // cached
044        Element element;
045        XMLPreferences preferences;
046
047        public XMLPreferencesRoot(String filename)
048        {
049            this.filename = filename;
050
051            load();
052        }
053
054        /**
055         * Load preferences from file
056         */
057        public void load()
058        {
059            load(filename);
060        }
061
062        /**
063         * Load preferences from file
064         */
065        public void load(String filename)
066        {
067            try
068            {
069                // get document
070                doc = XMLUtil.loadDocument(new File(filename));
071            }
072            catch (Throwable t)
073            {
074                System.err.println("Error: " + filename + " preferences file is corrupted, cannot recover settings.");
075                // corrupted XML file
076                doc = null;
077            }
078
079            // create it if not existing
080            if (doc == null)
081                doc = XMLUtil.createDocument(false);
082
083            // create root element
084            element = XMLUtil.createRootElement(doc);
085            // create our root XMLPreference object
086            preferences = new XMLPreferences(this, element);
087            preferences.clean();
088        }
089
090        /**
091         * Save preferences to file
092         */
093        public void save()
094        {
095            save(filename);
096        }
097
098        /**
099         * Save preferences to file
100         */
101        public void save(String filename)
102        {
103            if (doc != null)
104                XMLUtil.saveDocument(doc, new File(filename));
105        }
106
107        /**
108         * @return the element
109         */
110        public Element getElement()
111        {
112            return element;
113        }
114
115        /**
116         * @return the preferences
117         */
118        public XMLPreferences getPreferences()
119        {
120            return preferences;
121        }
122    }
123
124    private final static String TYPE_SECTION = "section";
125    private final static String TYPE_KEY = "key";
126
127    private final XMLPreferencesRoot root;
128    private final Element currentElement;
129
130    /**
131     * Use:<br>
132     * <code>new XMLPreferencesRoot(filename).getPreferences()</code><br>
133     * to load preferences from file.
134     */
135    XMLPreferences(XMLPreferencesRoot root, Element element)
136    {
137        super();
138
139        this.root = root;
140        currentElement = element;
141    }
142
143    public String absolutePath()
144    {
145        String result = "/" + name();
146
147        synchronized (root)
148        {
149            Element parent = XMLUtil.getParentElement(currentElement);
150            while ((parent != null) && (parent != root.element))
151            {
152                result = "/" + XMLUtil.getGenericElementName(parent) + result;
153                parent = XMLUtil.getParentElement(parent);
154            }
155        }
156
157        return result;
158    }
159
160    public String name()
161    {
162        synchronized (root)
163        {
164            return XMLUtil.getGenericElementName(currentElement);
165        }
166    }
167
168    public XMLPreferences getParent()
169    {
170        final Element parent;
171
172        synchronized (root)
173        {
174            parent = XMLUtil.getParentElement(currentElement);
175        }
176
177        if (parent != null)
178            return new XMLPreferences(root, parent);
179
180        return null;
181    }
182
183    public ArrayList<XMLPreferences> getChildren()
184    {
185        final ArrayList<XMLPreferences> result = new ArrayList<XMLPreferences>();
186        final List<Element> elements;
187
188        synchronized (root)
189        {
190            elements = XMLUtil.getGenericElements(currentElement, TYPE_SECTION);
191        }
192
193        for (Element element : elements)
194            result.add(new XMLPreferences(root, element));
195
196        return result;
197    }
198
199    public ArrayList<String> childrenNames()
200    {
201        final ArrayList<String> result = new ArrayList<String>();
202
203        synchronized (root)
204        {
205            for (Element element : XMLUtil.getGenericElements(currentElement, TYPE_SECTION))
206                result.add(XMLUtil.getGenericElementName(element));
207        }
208
209        return result;
210    }
211
212    private Element getSection(String name)
213    {
214        if (StringUtil.isEmpty(name))
215            return currentElement;
216
217        Element element;
218
219        // absolute path
220        if (name.startsWith("/"))
221            element = root.element;
222        else
223        {
224            // we test first current node is still existing
225            if (!exists())
226                return null;
227
228            element = currentElement;
229        }
230
231        synchronized (root)
232        {
233            for (String subName : name.split("/"))
234                if (!subName.isEmpty())
235                    element = XMLUtil.getGenericElement(element, TYPE_SECTION, subName);
236        }
237
238        return element;
239    }
240
241    private Element setSection(String name)
242    {
243        if (StringUtil.isEmpty(name))
244            return currentElement;
245
246        Element element;
247
248        // absolute path
249        if (name.startsWith("/"))
250            element = root.element;
251        else
252        {
253            // we test first current node is still existing
254            if (!exists())
255                return null;
256
257            element = currentElement;
258        }
259
260        synchronized (root)
261        {
262            for (String subName : name.split("/"))
263                if (!subName.isEmpty())
264                    element = XMLUtil.setGenericElement(element, TYPE_SECTION, subName);
265        }
266
267        return element;
268    }
269
270    /**
271     * Return XMLPreferences of specified node.<br>
272     */
273    public XMLPreferences node(String name)
274    {
275        final Element element = setSection(name);
276
277        if (element != null)
278            return new XMLPreferences(root, element);
279
280        return null;
281    }
282
283    /**
284     * Return XMLPreferences of specified node using class name of specified object.<br>
285     * <code>nodeForClass(object) == node(object.getClass().getName())</code><br>
286     * Ex : <code>nodeForClass("text") == node("java.lang.String")</code>
287     */
288    public XMLPreferences nodeForClass(Object object)
289    {
290        if (object != null)
291            return node(ClassUtil.getPathFromQualifiedName(object.getClass().getName()));
292
293        return null;
294    }
295
296    /**
297     * Return the {@link XMLPreferences} node as an XML node.
298     */
299    public Element getXMLNode()
300    {
301        return currentElement;
302    }
303
304    /**
305     * Return true if current node is existing
306     */
307    public boolean exists()
308    {
309        // root element, always exists
310        if (currentElement == root.element)
311            return true;
312
313        synchronized (root)
314        {
315            // try to reach root from current element
316            Element parent = XMLUtil.getParentElement(currentElement);
317            while (parent != null)
318            {
319                // we reached root so the element still exist
320                if (parent == root.element)
321                    return true;
322
323                parent = XMLUtil.getParentElement(parent);
324            }
325        }
326
327        // can't reach root, element is no more existing
328        return false;
329    }
330
331    /**
332     * Return true if specified node exists
333     */
334    public boolean nodeExists(String name)
335    {
336        return getSection(name) != null;
337    }
338
339    /**
340     * Return true if node for specified object exists.<br>
341     * <code>nodeForClassExists(object) == nodeExists(object.getClass().getName())</code><br>
342     * Ex : <code>nodeForClassExists("text") == nodeExists("java.lang.String")</code>
343     */
344    public boolean nodeForClassExists(Object object)
345    {
346        if (object != null)
347            return nodeExists(ClassUtil.getPathFromQualifiedName(object.getClass().getName()));
348
349        return false;
350    }
351
352    public ArrayList<String> keys()
353    {
354        final ArrayList<String> result = new ArrayList<String>();
355
356        synchronized (root)
357        {
358            for (Element element : XMLUtil.getGenericElements(currentElement, TYPE_KEY))
359                result.add(XMLUtil.getGenericElementName(element));
360        }
361
362        return result;
363    }
364
365    /**
366     * Remove all non element nodes
367     */
368    public void clean()
369    {
370        synchronized (root)
371        {
372            final List<Node> nodes = XMLUtil.getChildren(currentElement);
373
374            for (Node node : nodes)
375            {
376                final String nodeName = node.getNodeName();
377
378                if (!(nodeName.equals(TYPE_KEY) || nodeName.equals(TYPE_SECTION)))
379                    XMLUtil.removeNode(currentElement, node);
380            }
381        }
382    }
383
384    /**
385     * Remove all direct children of this node
386     */
387    public void clear()
388    {
389        synchronized (root)
390        {
391            XMLUtil.removeChildren(currentElement, TYPE_KEY);
392        }
393    }
394
395    /**
396     * Remove specified element
397     */
398    private void remove(Element element)
399    {
400        if (element != null)
401        {
402            synchronized (root)
403            {
404                final Element parent = XMLUtil.getParentElement(element);
405
406                if (parent != null)
407                    XMLUtil.removeNode(parent, element);
408            }
409        }
410    }
411
412    /**
413     * Remove current section
414     */
415    public void remove()
416    {
417        remove(currentElement);
418    }
419
420    /**
421     * Remove specified section
422     */
423    public void remove(String name)
424    {
425        remove(getSection(name));
426    }
427
428    /**
429     * Remove all sections
430     */
431    public void removeChildren()
432    {
433        synchronized (root)
434        {
435            XMLUtil.removeChildren(currentElement, TYPE_SECTION);
436        }
437    }
438
439    public String get(String key, String def)
440    {
441        synchronized (root)
442        {
443            return XMLUtil.getGenericElementValue(currentElement, TYPE_KEY, key, def);
444        }
445    }
446
447    public boolean getBoolean(String key, boolean def)
448    {
449        synchronized (root)
450        {
451            return XMLUtil.getGenericElementBooleanValue(currentElement, TYPE_KEY, key, def);
452        }
453    }
454
455    public byte[] getBytes(String key, byte[] def)
456    {
457        synchronized (root)
458        {
459            return XMLUtil.getGenericElementBytesValue(currentElement, TYPE_KEY, key, def);
460        }
461    }
462
463    public double getDouble(String key, double def)
464    {
465        synchronized (root)
466        {
467            return XMLUtil.getGenericElementDoubleValue(currentElement, TYPE_KEY, key, def);
468        }
469    }
470
471    public float getFloat(String key, float def)
472    {
473        synchronized (root)
474        {
475            return XMLUtil.getGenericElementFloatValue(currentElement, TYPE_KEY, key, def);
476        }
477    }
478
479    public int getInt(String key, int def)
480    {
481        synchronized (root)
482        {
483            return XMLUtil.getGenericElementIntValue(currentElement, TYPE_KEY, key, def);
484        }
485    }
486
487    public long getLong(String key, long def)
488    {
489        synchronized (root)
490        {
491            return XMLUtil.getGenericElementLongValue(currentElement, TYPE_KEY, key, def);
492        }
493    }
494
495    public void put(String key, String value)
496    {
497        synchronized (root)
498        {
499            XMLUtil.setGenericElementValue(currentElement, TYPE_KEY, key, value);
500        }
501    }
502
503    public void putBoolean(String key, boolean value)
504    {
505        synchronized (root)
506        {
507            XMLUtil.setGenericElementBooleanValue(currentElement, TYPE_KEY, key, value);
508        }
509    }
510
511    public void putBytes(String key, byte[] value)
512    {
513        synchronized (root)
514        {
515            XMLUtil.setGenericElementBytesValue(currentElement, TYPE_KEY, key, value.clone());
516        }
517    }
518
519    public void putDouble(String key, double value)
520    {
521        synchronized (root)
522        {
523            XMLUtil.setGenericElementDoubleValue(currentElement, TYPE_KEY, key, value);
524        }
525    }
526
527    public void putFloat(String key, float value)
528    {
529        synchronized (root)
530        {
531            XMLUtil.setGenericElementFloatValue(currentElement, TYPE_KEY, key, value);
532        }
533    }
534
535    public void putInt(String key, int value)
536    {
537        synchronized (root)
538        {
539            XMLUtil.setGenericElementIntValue(currentElement, TYPE_KEY, key, value);
540        }
541    }
542
543    public void putLong(String key, long value)
544    {
545        synchronized (root)
546        {
547            XMLUtil.setGenericElementLongValue(currentElement, TYPE_KEY, key, value);
548        }
549    }
550}