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