|
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 } |