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
141 Document doc = loadPrefsDoc(is);
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.");
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);
164 * Create a new prefs XML document.
166 private static Document createPrefsDoc( String qname ) {
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);
178 * Load an XML document from specified input stream, which must
179 * have the requisite DTD URI.
181 private static Document loadPrefsDoc(InputStream in)
182 throws SAXException, IOException
184 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
185 dbf.setIgnoringElementContentWhitespace(true);
186 dbf.setValidating(true);
187 dbf.setCoalescing(true);
188 dbf.setIgnoringComments(true);
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);
200 * Write XML document to the specified output stream.
202 private static final void writeDoc(Document doc, OutputStream out)
206 TransformerFactory tf = TransformerFactory.newInstance();
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".
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);
227 * Recursively traverse the specified preferences node and store
228 * the described preferences into the system or current user
229 * preferences tree, as appropriate.
231 private static void ImportSubtree(Preferences prefsNode, Element xmlNode) {
232 NodeList xmlKids = xmlNode.getChildNodes();
233 int numXmlKids = xmlKids.getLength();
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.
240 Preferences[] prefsKids;
242 synchronized (lock(prefsNode)) {
243 //If removed, return silently
244 if (isRemoved(prefsNode))
247 // Import any preferences at this node
248 Element firstXmlKid = (Element) xmlKids.item(0);
249 ImportPrefs(prefsNode, firstXmlKid);
250 prefsKids = new Preferences[numXmlKids - 1];
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"));
257 } // unlocked the node
259 for (int i=1; i < numXmlKids; i++)
260 ImportSubtree(prefsKids[i-1], (Element)xmlKids.item(i));
264 * Import the preferences described by the specified XML element
265 * (a map from a preferences document) into the specified
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"));
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.
282 * @throws IOException if writing to the specified output stream
283 * results in an <tt>IOException</tt>.
285 public void exportMap(OutputStream os, Map map) throws IOException {
287 Document doc = createPrefsDoc("map");
288 Element xmlMap = doc.getDocumentElement( ) ;
289 xmlMap.setAttribute("MAP_XML_VERSION", MAP_XML_VERSION);
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());
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.)
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.
316 public void importMap(InputStream is, Map m)
317 throws IOException, InvalidPreferencesFormatException
321 Document doc = loadPrefsDoc(is);
322 Element xmlMap = doc.getDocumentElement();
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.");
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"));
337 } catch(SAXException e) {
338 throw new InvalidPreferencesFormatException(e);
343 private static class Resolver implements EntityResolver {
344 public InputSource resolveEntity(String pid, String sid)
347 if (sid.equals(PREFS_DTD_URI)) {
349 is = new InputSource(new StringReader(PREFS_DTD));
350 is.setSystemId(PREFS_DTD_URI);
353 throw new SAXException("Invalid system identifier: " + sid);
357 private static class EH implements ErrorHandler {
358 public void error(SAXParseException x) throws SAXException {
361 public void fatalError(SAXParseException x) throws SAXException {
364 public void warning(SAXParseException x) throws SAXException {
369 private static boolean isRemoved(Preferences p) {
371 p.parent(); // throws IllegalStateException if removed;
373 } catch (IllegalStateException ex) {
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;