src/share/classes/sun/util/xml/DefaultPrefsXmlSupport.java
author Jaroslav Tulach <jtulach@netbeans.org>
Wed, 24 Jun 2009 17:29:29 +0200
branchxml-sax-and-dom-2
changeset 1263 24b6c30fbf71
child 1264 601d21ee9aa6
permissions -rw-r--r--
Simple output of Preferences is the same as the original one via DOM
     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         /*
   140         try {
   141             Document doc = loadPrefsDoc(is);
   142             String xmlVersion =
   143                 doc.getDocumentElement().getAttribute("EXTERNAL_XML_VERSION");
   144             if (xmlVersion.compareTo(EXTERNAL_XML_VERSION) > 0)
   145                 throw new InvalidPreferencesFormatException(
   146                 "Exported preferences file format version " + xmlVersion +
   147                 " is not supported. This java installation can read" +
   148                 " versions " + EXTERNAL_XML_VERSION + " or older. You may need" +
   149                 " to install a newer version of JDK.");
   150 
   151             Element xmlRoot = (Element) doc.getDocumentElement().
   152                                                getChildNodes().item(0);
   153             Preferences prefsRoot =
   154                 (xmlRoot.getAttribute("type").equals("user") ?
   155                             Preferences.userRoot() : Preferences.systemRoot());
   156             ImportSubtree(prefsRoot, xmlRoot);
   157         } catch(SAXException e) {
   158             throw new InvalidPreferencesFormatException(e);
   159         }
   160          */
   161     }
   162 
   163     /**
   164      * Create a new prefs XML document.
   165      *
   166     private static Document createPrefsDoc( String qname ) {
   167         try {
   168             DOMImplementation di = DocumentBuilderFactory.newInstance().
   169                 newDocumentBuilder().getDOMImplementation();
   170             DocumentType dt = di.createDocumentType(qname, null, PREFS_DTD_URI);
   171             return di.createDocument(null, qname, dt);
   172         } catch(ParserConfigurationException e) {
   173             throw new AssertionError(e);
   174         }
   175     }
   176 
   177     /**
   178      * Load an XML document from specified input stream, which must
   179      * have the requisite DTD URI.
   180      *
   181     private static Document loadPrefsDoc(InputStream in)
   182         throws SAXException, IOException
   183     {
   184         DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
   185         dbf.setIgnoringElementContentWhitespace(true);
   186         dbf.setValidating(true);
   187         dbf.setCoalescing(true);
   188         dbf.setIgnoringComments(true);
   189         try {
   190             DocumentBuilder db = dbf.newDocumentBuilder();
   191             db.setEntityResolver(new Resolver());
   192             db.setErrorHandler(new EH());
   193             return db.parse(new InputSource(in));
   194         } catch (ParserConfigurationException e) {
   195             throw new AssertionError(e);
   196         }
   197     }
   198 
   199     /**
   200      * Write XML document to the specified output stream.
   201      *
   202     private static final void writeDoc(Document doc, OutputStream out)
   203         throws IOException
   204     {
   205         try {
   206             TransformerFactory tf = TransformerFactory.newInstance();
   207             try {
   208                 tf.setAttribute("indent-number", new Integer(2));
   209             } catch (IllegalArgumentException iae) {
   210                 //Ignore the IAE. Should not fail the writeout even the
   211                 //transformer provider does not support "indent-number".
   212             }
   213             Transformer t = tf.newTransformer();
   214             t.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, doc.getDoctype().getSystemId());
   215             t.setOutputProperty(OutputKeys.INDENT, "yes");
   216             //Transformer resets the "indent" info if the "result" is a StreamResult with
   217             //an OutputStream object embedded, creating a Writer object on top of that
   218             //OutputStream object however works.
   219             t.transform(new DOMSource(doc),
   220                         new StreamResult(new BufferedWriter(new OutputStreamWriter(out, "UTF-8"))));
   221         } catch(TransformerException e) {
   222             throw new AssertionError(e);
   223         }
   224     }
   225 
   226     /**
   227      * Recursively traverse the specified preferences node and store
   228      * the described preferences into the system or current user
   229      * preferences tree, as appropriate.
   230      *
   231     private static void ImportSubtree(Preferences prefsNode, Element xmlNode) {
   232         NodeList xmlKids = xmlNode.getChildNodes();
   233         int numXmlKids = xmlKids.getLength();
   234         /*
   235          * We first lock the node, import its contents and get
   236          * child nodes. Then we unlock the node and go to children
   237          * Since some of the children might have been concurrently
   238          * deleted we check for this.
   239          * /
   240         Preferences[] prefsKids;
   241         /* Lock the node * /
   242         synchronized (lock(prefsNode)) {
   243             //If removed, return silently
   244             if (isRemoved(prefsNode))
   245                 return;
   246 
   247             // Import any preferences at this node
   248             Element firstXmlKid = (Element) xmlKids.item(0);
   249             ImportPrefs(prefsNode, firstXmlKid);
   250             prefsKids = new Preferences[numXmlKids - 1];
   251 
   252             // Get involved children
   253             for (int i=1; i < numXmlKids; i++) {
   254                 Element xmlKid = (Element) xmlKids.item(i);
   255                 prefsKids[i-1] = prefsNode.node(xmlKid.getAttribute("name"));
   256             }
   257         } // unlocked the node
   258         // import children
   259         for (int i=1; i < numXmlKids; i++)
   260             ImportSubtree(prefsKids[i-1], (Element)xmlKids.item(i));
   261     }
   262 
   263     /**
   264      * Import the preferences described by the specified XML element
   265      * (a map from a preferences document) into the specified
   266      * preferences node.
   267      * /
   268     private static void ImportPrefs(Preferences prefsNode, Element map) {
   269         NodeList entries = map.getChildNodes();
   270         for (int i=0, numEntries = entries.getLength(); i < numEntries; i++) {
   271             Element entry = (Element) entries.item(i);
   272             prefsNode.put(entry.getAttribute("key"),
   273                           entry.getAttribute("value"));
   274         }
   275     }
   276 
   277     /**
   278      * Export the specified Map<String,String> to a map document on
   279      * the specified OutputStream as per the prefs DTD.  This is used
   280      * as the internal (undocumented) format for FileSystemPrefs.
   281      *
   282      * @throws IOException if writing to the specified output stream
   283      *         results in an <tt>IOException</tt>.
   284      */
   285     public void exportMap(OutputStream os, Map map) throws IOException {
   286         /*
   287         Document doc = createPrefsDoc("map");
   288         Element xmlMap = doc.getDocumentElement( ) ;
   289         xmlMap.setAttribute("MAP_XML_VERSION", MAP_XML_VERSION);
   290 
   291         for (Iterator i = map.entrySet().iterator(); i.hasNext(); ) {
   292             Map.Entry e = (Map.Entry) i.next();
   293             Element xe = (Element)
   294                 xmlMap.appendChild(doc.createElement("entry"));
   295             xe.setAttribute("key",   (String) e.getKey());
   296             xe.setAttribute("value", (String) e.getValue());
   297         }
   298 
   299         writeDoc(doc, os);
   300          */
   301     }
   302 
   303     /**
   304      * Import Map from the specified input stream, which is assumed
   305      * to contain a map document as per the prefs DTD.  This is used
   306      * as the internal (undocumented) format for FileSystemPrefs.  The
   307      * key-value pairs specified in the XML document will be put into
   308      * the specified Map.  (If this Map is empty, it will contain exactly
   309      * the key-value pairs int the XML-document when this method returns.)
   310      *
   311      * @throws IOException if reading from the specified output stream
   312      *         results in an <tt>IOException</tt>.
   313      * @throws InvalidPreferencesFormatException Data on input stream does not
   314      *         constitute a valid XML document with the mandated document type.
   315      */
   316     public void importMap(InputStream is, Map m)
   317         throws IOException, InvalidPreferencesFormatException
   318     {
   319         /*
   320         try {
   321             Document doc = loadPrefsDoc(is);
   322             Element xmlMap = doc.getDocumentElement();
   323             // check version
   324             String mapVersion = xmlMap.getAttribute("MAP_XML_VERSION");
   325             if (mapVersion.compareTo(MAP_XML_VERSION) > 0)
   326                 throw new InvalidPreferencesFormatException(
   327                 "Preferences map file format version " + mapVersion +
   328                 " is not supported. This java installation can read" +
   329                 " versions " + MAP_XML_VERSION + " or older. You may need" +
   330                 " to install a newer version of JDK.");
   331 
   332             NodeList entries = xmlMap.getChildNodes();
   333             for (int i=0, numEntries=entries.getLength(); i<numEntries; i++) {
   334                 Element entry = (Element) entries.item(i);
   335                 m.put(entry.getAttribute("key"), entry.getAttribute("value"));
   336             }
   337         } catch(SAXException e) {
   338             throw new InvalidPreferencesFormatException(e);
   339         }
   340          */
   341     }
   342 /*
   343     private static class Resolver implements EntityResolver {
   344         public InputSource resolveEntity(String pid, String sid)
   345             throws SAXException
   346         {
   347             if (sid.equals(PREFS_DTD_URI)) {
   348                 InputSource is;
   349                 is = new InputSource(new StringReader(PREFS_DTD));
   350                 is.setSystemId(PREFS_DTD_URI);
   351                 return is;
   352             }
   353             throw new SAXException("Invalid system identifier: " + sid);
   354         }
   355     }
   356 
   357     private static class EH implements ErrorHandler {
   358         public void error(SAXParseException x) throws SAXException {
   359             throw x;
   360         }
   361         public void fatalError(SAXParseException x) throws SAXException {
   362             throw x;
   363         }
   364         public void warning(SAXParseException x) throws SAXException {
   365             throw x;
   366         }
   367     }
   368 */
   369     private static boolean isRemoved(Preferences p) {
   370         try {
   371             p.parent(); // throws IllegalStateException if removed;
   372             return false;
   373         } catch (IllegalStateException ex) {
   374             return true;
   375         }
   376     }
   377 
   378     private static Object lock(Preferences p) {
   379         /** JST-XXX: Needs reflection or accessor:
   380          * http://wiki.apidesign.org/wiki/FriendPackages
   381         return ((AbstractPreferences)prefs).lock;
   382          */
   383         return p;
   384     }
   385 }