src/share/classes/sun/util/xml/DefaultPrefsXmlSupport.java
author Jaroslav Tulach <jtulach@netbeans.org>
Wed, 24 Jun 2009 17:50:25 +0200
branchxml-sax-and-dom-2
changeset 1264 601d21ee9aa6
parent 1263 24b6c30fbf71
permissions -rw-r--r--
Reading of exported preferences yields the same results if done via DOM as well as nanoXML parser
jtulach@1263
     1
/*
jtulach@1263
     2
 * Copyright 2002-2006 Sun Microsystems, Inc.  All Rights Reserved.
jtulach@1263
     3
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
jtulach@1263
     4
 *
jtulach@1263
     5
 * This code is free software; you can redistribute it and/or modify it
jtulach@1263
     6
 * under the terms of the GNU General Public License version 2 only, as
jtulach@1263
     7
 * published by the Free Software Foundation.  Sun designates this
jtulach@1263
     8
 * particular file as subject to the "Classpath" exception as provided
jtulach@1263
     9
 * by Sun in the LICENSE file that accompanied this code.
jtulach@1263
    10
 *
jtulach@1263
    11
 * This code is distributed in the hope that it will be useful, but WITHOUT
jtulach@1263
    12
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
jtulach@1263
    13
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
jtulach@1263
    14
 * version 2 for more details (a copy is included in the LICENSE file that
jtulach@1263
    15
 * accompanied this code).
jtulach@1263
    16
 *
jtulach@1263
    17
 * You should have received a copy of the GNU General Public License version
jtulach@1263
    18
 * 2 along with this work; if not, write to the Free Software Foundation,
jtulach@1263
    19
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
jtulach@1263
    20
 *
jtulach@1263
    21
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
jtulach@1263
    22
 * CA 95054 USA or visit www.sun.com if you need additional information or
jtulach@1263
    23
 * have any questions.
jtulach@1263
    24
 */
jtulach@1263
    25
jtulach@1263
    26
package sun.util.xml;
jtulach@1263
    27
jtulach@1263
    28
import java.util.*;
jtulach@1263
    29
import java.util.prefs.*;
jtulach@1263
    30
import java.io.*;
jtulach@1263
    31
jtulach@1263
    32
/**
jtulach@1263
    33
 * Simplified XML Support for java.util.prefs. Methods to import and export preference
jtulach@1263
    34
 * nodes and subtrees.
jtulach@1263
    35
 *
jtulach@1263
    36
 * @author  Jaroslav Tulach
jtulach@1263
    37
 * @since   1.7
jtulach@1263
    38
 */
jtulach@1263
    39
public class DefaultPrefsXmlSupport extends sun.util.xml.PrefsXmlSupport {
jtulach@1263
    40
    public void export(OutputStream os, final Preferences p, boolean subTree)
jtulach@1263
    41
        throws IOException, BackingStoreException {
jtulach@1263
    42
        if (isRemoved(p))
jtulach@1263
    43
            throw new IllegalStateException("Node has been removed");
jtulach@1263
    44
        PrintWriter w = new PrintWriter(new OutputStreamWriter(os, "UTF-8"));
jtulach@1263
    45
        w.println("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>");
jtulach@1263
    46
        w.println("<!DOCTYPE preferences SYSTEM \"http://java.sun.com/dtd/preferences.dtd\">");
jtulach@1263
    47
        w.println("<preferences EXTERNAL_XML_VERSION=\"1.0\">");
jtulach@1263
    48
        w.print("  <root type=\"");
jtulach@1263
    49
        w.print(p.isUserNode() ? "user" : "system");
jtulach@1263
    50
        w.println("\">");
jtulach@1263
    51
        LinkedList<Preferences> ancestors = new LinkedList<Preferences>();
jtulach@1263
    52
        for (Preferences kid = p, dad = kid.parent(); dad != null;
jtulach@1263
    53
                kid = dad, dad = kid.parent()) {
jtulach@1263
    54
            ancestors.addFirst(kid);
jtulach@1263
    55
        }
jtulach@1263
    56
        String indent = "  ";
jtulach@1263
    57
        for (Preferences pref : ancestors) {
jtulach@1263
    58
            indent = "  " + indent;
jtulach@1263
    59
            w.print(indent); w.println("<map/>");
jtulach@1263
    60
            w.print(indent); w.print("<node name=\""); w.print(pref.name()); w.println("\">");
jtulach@1263
    61
        }
jtulach@1263
    62
jtulach@1263
    63
        putPreferencesInXml(w, indent + "  ", p, subTree);
jtulach@1263
    64
        for (Preferences pref : ancestors) {
jtulach@1263
    65
            w.print(indent); w.println("</node>");
jtulach@1263
    66
            indent = indent.substring(2);
jtulach@1263
    67
        }
jtulach@1263
    68
jtulach@1263
    69
        w.println("  </root>");
jtulach@1263
    70
        w.println("</preferences>");
jtulach@1263
    71
        w.flush();
jtulach@1263
    72
    }
jtulach@1263
    73
jtulach@1263
    74
    private static void putPreferencesInXml(
jtulach@1263
    75
        PrintWriter w, String indent, Preferences prefs, boolean subTree
jtulach@1263
    76
    ) throws BackingStoreException {
jtulach@1263
    77
        Preferences[] kidsCopy = null;
jtulach@1263
    78
        String[] kidNames = null;
jtulach@1263
    79
jtulach@1263
    80
        // Node is locked to export its contents and get a
jtulach@1263
    81
        // copy of children, then lock is released,
jtulach@1263
    82
        // and, if subTree = true, recursive calls are made on children
jtulach@1263
    83
        synchronized (lock(prefs)) {
jtulach@1263
    84
            // Check if this node was concurrently removed. If yes
jtulach@1263
    85
            // don't print it
jtulach@1263
    86
            if (isRemoved(prefs)) {
jtulach@1263
    87
                return;
jtulach@1263
    88
            }
jtulach@1263
    89
            // Put map in xml element
jtulach@1263
    90
            String[] keys = prefs.keys();
jtulach@1263
    91
            if (keys.length == 0) {
jtulach@1263
    92
                w.print(indent); w.println("<map/>");
jtulach@1263
    93
            } else {
jtulach@1263
    94
                w.print(indent); w.println("<map>");
jtulach@1263
    95
                for (int i=0; i<keys.length; i++) {
jtulach@1263
    96
                    w.print(indent); w.print("  <entry key=\"");
jtulach@1263
    97
                    w.print(keys[i]);
jtulach@1263
    98
                    w.print("\" value=\"");
jtulach@1263
    99
                    w.print(prefs.get(keys[i], null));
jtulach@1263
   100
                    w.println("\"/>");
jtulach@1263
   101
                }
jtulach@1263
   102
                w.print(indent); w.println("</map>");
jtulach@1263
   103
            }
jtulach@1263
   104
            // Recurse if appropriate
jtulach@1263
   105
            if (subTree) {
jtulach@1263
   106
                /* Get a copy of kids while lock is held */
jtulach@1263
   107
                kidNames = prefs.childrenNames();
jtulach@1263
   108
                kidsCopy = new Preferences[kidNames.length];
jtulach@1263
   109
                for (int i = 0; i <  kidNames.length; i++)
jtulach@1263
   110
                    kidsCopy[i] = prefs.node(kidNames[i]);
jtulach@1263
   111
            }
jtulach@1263
   112
            // release lock
jtulach@1263
   113
        }
jtulach@1263
   114
jtulach@1263
   115
        if (subTree) {
jtulach@1263
   116
            for (int i=0; i < kidNames.length; i++) {
jtulach@1263
   117
                w.print(indent); w.print("<node name=\"");
jtulach@1263
   118
                w.print(kidNames[i]);
jtulach@1263
   119
                w.println("\">");
jtulach@1263
   120
                putPreferencesInXml(w, "  " + indent, kidsCopy[i], subTree);
jtulach@1263
   121
                w.print(indent); w.println("</node>");
jtulach@1263
   122
            }
jtulach@1263
   123
        }
jtulach@1263
   124
    }
jtulach@1263
   125
jtulach@1263
   126
    /**
jtulach@1263
   127
     * Import preferences from the specified input stream, which is assumed
jtulach@1263
   128
     * to contain an XML document in the format described in the Preferences
jtulach@1263
   129
     * spec.
jtulach@1263
   130
     *
jtulach@1263
   131
     * @throws IOException if reading from the specified output stream
jtulach@1263
   132
     *         results in an <tt>IOException</tt>.
jtulach@1263
   133
     * @throws InvalidPreferencesFormatException Data on input stream does not
jtulach@1263
   134
     *         constitute a valid XML document with the mandated document type.
jtulach@1263
   135
     */
jtulach@1263
   136
    public void importPreferences(InputStream is)
jtulach@1263
   137
        throws IOException, InvalidPreferencesFormatException
jtulach@1263
   138
    {
jtulach@1263
   139
        try {
jtulach@1264
   140
            XMLElement doc = new XMLElement();
jtulach@1264
   141
            doc.parseFromReader(new InputStreamReader(is, "UTF-8"));
jtulach@1264
   142
            String xmlVersion = (String)doc.getAttribute("EXTERNAL_XML_VERSION");
jtulach@1264
   143
            if (xmlVersion.compareTo("1.0") > 0)
jtulach@1263
   144
                throw new InvalidPreferencesFormatException(
jtulach@1263
   145
                "Exported preferences file format version " + xmlVersion +
jtulach@1263
   146
                " is not supported. This java installation can read" +
jtulach@1264
   147
                " versions " + "1.0" + " or older. You may need" +
jtulach@1263
   148
                " to install a newer version of JDK.");
jtulach@1263
   149
jtulach@1264
   150
            XMLElement xmlRoot = (XMLElement) doc.getChildren().get(0);
jtulach@1263
   151
            Preferences prefsRoot =
jtulach@1263
   152
                (xmlRoot.getAttribute("type").equals("user") ?
jtulach@1263
   153
                            Preferences.userRoot() : Preferences.systemRoot());
jtulach@1263
   154
            ImportSubtree(prefsRoot, xmlRoot);
jtulach@1264
   155
        } catch(XMLParseException e) {
jtulach@1263
   156
            throw new InvalidPreferencesFormatException(e);
jtulach@1263
   157
        }
jtulach@1263
   158
    }
jtulach@1263
   159
jtulach@1263
   160
    /**
jtulach@1263
   161
     * Create a new prefs XML document.
jtulach@1263
   162
     *
jtulach@1263
   163
    private static Document createPrefsDoc( String qname ) {
jtulach@1263
   164
        try {
jtulach@1263
   165
            DOMImplementation di = DocumentBuilderFactory.newInstance().
jtulach@1263
   166
                newDocumentBuilder().getDOMImplementation();
jtulach@1263
   167
            DocumentType dt = di.createDocumentType(qname, null, PREFS_DTD_URI);
jtulach@1263
   168
            return di.createDocument(null, qname, dt);
jtulach@1263
   169
        } catch(ParserConfigurationException e) {
jtulach@1263
   170
            throw new AssertionError(e);
jtulach@1263
   171
        }
jtulach@1263
   172
    }
jtulach@1263
   173
jtulach@1263
   174
jtulach@1263
   175
    /**
jtulach@1263
   176
     * Write XML document to the specified output stream.
jtulach@1263
   177
     *
jtulach@1263
   178
    private static final void writeDoc(Document doc, OutputStream out)
jtulach@1263
   179
        throws IOException
jtulach@1263
   180
    {
jtulach@1263
   181
        try {
jtulach@1263
   182
            TransformerFactory tf = TransformerFactory.newInstance();
jtulach@1263
   183
            try {
jtulach@1263
   184
                tf.setAttribute("indent-number", new Integer(2));
jtulach@1263
   185
            } catch (IllegalArgumentException iae) {
jtulach@1263
   186
                //Ignore the IAE. Should not fail the writeout even the
jtulach@1263
   187
                //transformer provider does not support "indent-number".
jtulach@1263
   188
            }
jtulach@1263
   189
            Transformer t = tf.newTransformer();
jtulach@1263
   190
            t.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, doc.getDoctype().getSystemId());
jtulach@1263
   191
            t.setOutputProperty(OutputKeys.INDENT, "yes");
jtulach@1263
   192
            //Transformer resets the "indent" info if the "result" is a StreamResult with
jtulach@1263
   193
            //an OutputStream object embedded, creating a Writer object on top of that
jtulach@1263
   194
            //OutputStream object however works.
jtulach@1263
   195
            t.transform(new DOMSource(doc),
jtulach@1263
   196
                        new StreamResult(new BufferedWriter(new OutputStreamWriter(out, "UTF-8"))));
jtulach@1263
   197
        } catch(TransformerException e) {
jtulach@1263
   198
            throw new AssertionError(e);
jtulach@1263
   199
        }
jtulach@1263
   200
    }
jtulach@1263
   201
jtulach@1263
   202
    /**
jtulach@1263
   203
     * Recursively traverse the specified preferences node and store
jtulach@1263
   204
     * the described preferences into the system or current user
jtulach@1263
   205
     * preferences tree, as appropriate.
jtulach@1264
   206
     */
jtulach@1264
   207
    private static void ImportSubtree(Preferences prefsNode, XMLElement xmlNode) {
jtulach@1264
   208
        Vector xmlKids = xmlNode.getChildren();
jtulach@1264
   209
        int numXmlKids = xmlKids.size();
jtulach@1263
   210
        /*
jtulach@1263
   211
         * We first lock the node, import its contents and get
jtulach@1263
   212
         * child nodes. Then we unlock the node and go to children
jtulach@1263
   213
         * Since some of the children might have been concurrently
jtulach@1263
   214
         * deleted we check for this.
jtulach@1264
   215
         */
jtulach@1263
   216
        Preferences[] prefsKids;
jtulach@1264
   217
        /* Lock the node */
jtulach@1263
   218
        synchronized (lock(prefsNode)) {
jtulach@1263
   219
            //If removed, return silently
jtulach@1263
   220
            if (isRemoved(prefsNode))
jtulach@1263
   221
                return;
jtulach@1263
   222
jtulach@1263
   223
            // Import any preferences at this node
jtulach@1264
   224
            XMLElement firstXmlKid = (XMLElement) xmlKids.get(0);
jtulach@1263
   225
            ImportPrefs(prefsNode, firstXmlKid);
jtulach@1263
   226
            prefsKids = new Preferences[numXmlKids - 1];
jtulach@1263
   227
jtulach@1263
   228
            // Get involved children
jtulach@1263
   229
            for (int i=1; i < numXmlKids; i++) {
jtulach@1264
   230
                XMLElement xmlKid = (XMLElement) xmlKids.get(i);
jtulach@1264
   231
                prefsKids[i-1] = prefsNode.node((String)xmlKid.getAttribute("name"));
jtulach@1263
   232
            }
jtulach@1263
   233
        } // unlocked the node
jtulach@1263
   234
        // import children
jtulach@1263
   235
        for (int i=1; i < numXmlKids; i++)
jtulach@1264
   236
            ImportSubtree(prefsKids[i-1], (XMLElement)xmlKids.get(i));
jtulach@1263
   237
    }
jtulach@1263
   238
jtulach@1263
   239
    /**
jtulach@1263
   240
     * Import the preferences described by the specified XML element
jtulach@1263
   241
     * (a map from a preferences document) into the specified
jtulach@1263
   242
     * preferences node.
jtulach@1264
   243
     */
jtulach@1264
   244
    private static void ImportPrefs(Preferences prefsNode, XMLElement map) {
jtulach@1264
   245
        Vector entries = map.getChildren();
jtulach@1264
   246
        for (int i=0, numEntries = entries.size(); i < numEntries; i++) {
jtulach@1264
   247
            XMLElement entry = (XMLElement) entries.get(i);
jtulach@1264
   248
            prefsNode.put((String)entry.getAttribute("key"),
jtulach@1264
   249
                          (String)entry.getAttribute("value"));
jtulach@1263
   250
        }
jtulach@1263
   251
    }
jtulach@1263
   252
jtulach@1263
   253
    /**
jtulach@1263
   254
     * Export the specified Map<String,String> to a map document on
jtulach@1263
   255
     * the specified OutputStream as per the prefs DTD.  This is used
jtulach@1263
   256
     * as the internal (undocumented) format for FileSystemPrefs.
jtulach@1263
   257
     *
jtulach@1263
   258
     * @throws IOException if writing to the specified output stream
jtulach@1263
   259
     *         results in an <tt>IOException</tt>.
jtulach@1263
   260
     */
jtulach@1263
   261
    public void exportMap(OutputStream os, Map map) throws IOException {
jtulach@1263
   262
        /*
jtulach@1263
   263
        Document doc = createPrefsDoc("map");
jtulach@1263
   264
        Element xmlMap = doc.getDocumentElement( ) ;
jtulach@1263
   265
        xmlMap.setAttribute("MAP_XML_VERSION", MAP_XML_VERSION);
jtulach@1263
   266
jtulach@1263
   267
        for (Iterator i = map.entrySet().iterator(); i.hasNext(); ) {
jtulach@1263
   268
            Map.Entry e = (Map.Entry) i.next();
jtulach@1263
   269
            Element xe = (Element)
jtulach@1263
   270
                xmlMap.appendChild(doc.createElement("entry"));
jtulach@1263
   271
            xe.setAttribute("key",   (String) e.getKey());
jtulach@1263
   272
            xe.setAttribute("value", (String) e.getValue());
jtulach@1263
   273
        }
jtulach@1263
   274
jtulach@1263
   275
        writeDoc(doc, os);
jtulach@1263
   276
         */
jtulach@1263
   277
    }
jtulach@1263
   278
jtulach@1263
   279
    /**
jtulach@1263
   280
     * Import Map from the specified input stream, which is assumed
jtulach@1263
   281
     * to contain a map document as per the prefs DTD.  This is used
jtulach@1263
   282
     * as the internal (undocumented) format for FileSystemPrefs.  The
jtulach@1263
   283
     * key-value pairs specified in the XML document will be put into
jtulach@1263
   284
     * the specified Map.  (If this Map is empty, it will contain exactly
jtulach@1263
   285
     * the key-value pairs int the XML-document when this method returns.)
jtulach@1263
   286
     *
jtulach@1263
   287
     * @throws IOException if reading from the specified output stream
jtulach@1263
   288
     *         results in an <tt>IOException</tt>.
jtulach@1263
   289
     * @throws InvalidPreferencesFormatException Data on input stream does not
jtulach@1263
   290
     *         constitute a valid XML document with the mandated document type.
jtulach@1263
   291
     */
jtulach@1263
   292
    public void importMap(InputStream is, Map m)
jtulach@1263
   293
        throws IOException, InvalidPreferencesFormatException
jtulach@1263
   294
    {
jtulach@1263
   295
        /*
jtulach@1263
   296
        try {
jtulach@1263
   297
            Document doc = loadPrefsDoc(is);
jtulach@1263
   298
            Element xmlMap = doc.getDocumentElement();
jtulach@1263
   299
            // check version
jtulach@1263
   300
            String mapVersion = xmlMap.getAttribute("MAP_XML_VERSION");
jtulach@1263
   301
            if (mapVersion.compareTo(MAP_XML_VERSION) > 0)
jtulach@1263
   302
                throw new InvalidPreferencesFormatException(
jtulach@1263
   303
                "Preferences map file format version " + mapVersion +
jtulach@1263
   304
                " is not supported. This java installation can read" +
jtulach@1263
   305
                " versions " + MAP_XML_VERSION + " or older. You may need" +
jtulach@1263
   306
                " to install a newer version of JDK.");
jtulach@1263
   307
jtulach@1263
   308
            NodeList entries = xmlMap.getChildNodes();
jtulach@1263
   309
            for (int i=0, numEntries=entries.getLength(); i<numEntries; i++) {
jtulach@1263
   310
                Element entry = (Element) entries.item(i);
jtulach@1263
   311
                m.put(entry.getAttribute("key"), entry.getAttribute("value"));
jtulach@1263
   312
            }
jtulach@1263
   313
        } catch(SAXException e) {
jtulach@1263
   314
            throw new InvalidPreferencesFormatException(e);
jtulach@1263
   315
        }
jtulach@1263
   316
         */
jtulach@1263
   317
    }
jtulach@1263
   318
/*
jtulach@1263
   319
    private static class Resolver implements EntityResolver {
jtulach@1263
   320
        public InputSource resolveEntity(String pid, String sid)
jtulach@1263
   321
            throws SAXException
jtulach@1263
   322
        {
jtulach@1263
   323
            if (sid.equals(PREFS_DTD_URI)) {
jtulach@1263
   324
                InputSource is;
jtulach@1263
   325
                is = new InputSource(new StringReader(PREFS_DTD));
jtulach@1263
   326
                is.setSystemId(PREFS_DTD_URI);
jtulach@1263
   327
                return is;
jtulach@1263
   328
            }
jtulach@1263
   329
            throw new SAXException("Invalid system identifier: " + sid);
jtulach@1263
   330
        }
jtulach@1263
   331
    }
jtulach@1263
   332
jtulach@1263
   333
    private static class EH implements ErrorHandler {
jtulach@1263
   334
        public void error(SAXParseException x) throws SAXException {
jtulach@1263
   335
            throw x;
jtulach@1263
   336
        }
jtulach@1263
   337
        public void fatalError(SAXParseException x) throws SAXException {
jtulach@1263
   338
            throw x;
jtulach@1263
   339
        }
jtulach@1263
   340
        public void warning(SAXParseException x) throws SAXException {
jtulach@1263
   341
            throw x;
jtulach@1263
   342
        }
jtulach@1263
   343
    }
jtulach@1263
   344
*/
jtulach@1263
   345
    private static boolean isRemoved(Preferences p) {
jtulach@1263
   346
        try {
jtulach@1263
   347
            p.parent(); // throws IllegalStateException if removed;
jtulach@1263
   348
            return false;
jtulach@1263
   349
        } catch (IllegalStateException ex) {
jtulach@1263
   350
            return true;
jtulach@1263
   351
        }
jtulach@1263
   352
    }
jtulach@1263
   353
jtulach@1263
   354
    private static Object lock(Preferences p) {
jtulach@1263
   355
        /** JST-XXX: Needs reflection or accessor:
jtulach@1263
   356
         * http://wiki.apidesign.org/wiki/FriendPackages
jtulach@1263
   357
        return ((AbstractPreferences)prefs).lock;
jtulach@1263
   358
         */
jtulach@1263
   359
        return p;
jtulach@1263
   360
    }
jtulach@1263
   361
}