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