Reading of exported preferences yields the same results if done via DOM as well as nanoXML parser
2 * Copyright 2002-2006 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
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.
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).
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.
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
29 import java.util.prefs.*;
33 * Simplified XML Support for java.util.prefs. Methods to import and export preference
36 * @author Jaroslav Tulach
39 public class DefaultPrefsXmlSupport extends sun.util.xml.PrefsXmlSupport {
40 public void export(OutputStream os, final Preferences p, boolean subTree)
41 throws IOException, BackingStoreException {
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");
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);
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("\">");
63 putPreferencesInXml(w, indent + " ", p, subTree);
64 for (Preferences pref : ancestors) {
65 w.print(indent); w.println("</node>");
66 indent = indent.substring(2);
69 w.println(" </root>");
70 w.println("</preferences>");
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;
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
86 if (isRemoved(prefs)) {
89 // Put map in xml element
90 String[] keys = prefs.keys();
91 if (keys.length == 0) {
92 w.print(indent); w.println("<map/>");
94 w.print(indent); w.println("<map>");
95 for (int i=0; i<keys.length; i++) {
96 w.print(indent); w.print(" <entry key=\"");
98 w.print("\" value=\"");
99 w.print(prefs.get(keys[i], null));
102 w.print(indent); w.println("</map>");
104 // Recurse if appropriate
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]);
116 for (int i=0; i < kidNames.length; i++) {
117 w.print(indent); w.print("<node name=\"");
118 w.print(kidNames[i]);
120 putPreferencesInXml(w, " " + indent, kidsCopy[i], subTree);
121 w.print(indent); w.println("</node>");
127 * Import preferences from the specified input stream, which is assumed
128 * to contain an XML document in the format described in the Preferences
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.
136 public void importPreferences(InputStream is)
137 throws IOException, InvalidPreferencesFormatException
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.");
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);
161 * Create a new prefs XML document.
163 private static Document createPrefsDoc( String qname ) {
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);
176 * Write XML document to the specified output stream.
178 private static final void writeDoc(Document doc, OutputStream out)
182 TransformerFactory tf = TransformerFactory.newInstance();
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".
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);
203 * Recursively traverse the specified preferences node and store
204 * the described preferences into the system or current user
205 * preferences tree, as appropriate.
207 private static void ImportSubtree(Preferences prefsNode, XMLElement xmlNode) {
208 Vector xmlKids = xmlNode.getChildren();
209 int numXmlKids = xmlKids.size();
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.
216 Preferences[] prefsKids;
218 synchronized (lock(prefsNode)) {
219 //If removed, return silently
220 if (isRemoved(prefsNode))
223 // Import any preferences at this node
224 XMLElement firstXmlKid = (XMLElement) xmlKids.get(0);
225 ImportPrefs(prefsNode, firstXmlKid);
226 prefsKids = new Preferences[numXmlKids - 1];
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"));
233 } // unlocked the node
235 for (int i=1; i < numXmlKids; i++)
236 ImportSubtree(prefsKids[i-1], (XMLElement)xmlKids.get(i));
240 * Import the preferences described by the specified XML element
241 * (a map from a preferences document) into the specified
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"));
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.
258 * @throws IOException if writing to the specified output stream
259 * results in an <tt>IOException</tt>.
261 public void exportMap(OutputStream os, Map map) throws IOException {
263 Document doc = createPrefsDoc("map");
264 Element xmlMap = doc.getDocumentElement( ) ;
265 xmlMap.setAttribute("MAP_XML_VERSION", MAP_XML_VERSION);
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());
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.)
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.
292 public void importMap(InputStream is, Map m)
293 throws IOException, InvalidPreferencesFormatException
297 Document doc = loadPrefsDoc(is);
298 Element xmlMap = doc.getDocumentElement();
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.");
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"));
313 } catch(SAXException e) {
314 throw new InvalidPreferencesFormatException(e);
319 private static class Resolver implements EntityResolver {
320 public InputSource resolveEntity(String pid, String sid)
323 if (sid.equals(PREFS_DTD_URI)) {
325 is = new InputSource(new StringReader(PREFS_DTD));
326 is.setSystemId(PREFS_DTD_URI);
329 throw new SAXException("Invalid system identifier: " + sid);
333 private static class EH implements ErrorHandler {
334 public void error(SAXParseException x) throws SAXException {
337 public void fatalError(SAXParseException x) throws SAXException {
340 public void warning(SAXParseException x) throws SAXException {
345 private static boolean isRemoved(Preferences p) {
347 p.parent(); // throws IllegalStateException if removed;
349 } catch (IllegalStateException ex) {
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;