- /*
- * @(#)XmlSupport.java 1.7 04/01/12
- *
- * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
- * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
- */
-
- package java.util.prefs;
-
- import java.util.*;
- import java.io.*;
- import javax.xml.parsers.*;
- import javax.xml.transform.*;
- import javax.xml.transform.dom.*;
- import javax.xml.transform.stream.*;
- import org.xml.sax.*;
- import org.w3c.dom.*;
-
- /**
- * XML Support for java.util.prefs. Methods to import and export preference
- * nodes and subtrees.
- *
- * @author Josh Bloch and Mark Reinhold
- * @version 1.7, 01/12/04
- * @see Preferences
- * @since 1.4
- */
- class XmlSupport {
- // The required DTD URI for exported preferences
- private static final String PREFS_DTD_URI =
- "http://java.sun.com/dtd/preferences.dtd";
-
- // The actual DTD corresponding to the URI
- private static final String PREFS_DTD =
- "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
-
- "<!-- DTD for preferences -->" +
-
- "<!ELEMENT preferences (root) >" +
- "<!ATTLIST preferences" +
- " EXTERNAL_XML_VERSION CDATA \"0.0\" >" +
-
- "<!ELEMENT root (map, node*) >" +
- "<!ATTLIST root" +
- " type (system|user) #REQUIRED >" +
-
- "<!ELEMENT node (map, node*) >" +
- "<!ATTLIST node" +
- " name CDATA #REQUIRED >" +
-
- "<!ELEMENT map (entry*) >" +
- "<!ATTLIST map" +
- " MAP_XML_VERSION CDATA \"0.0\" >" +
- "<!ELEMENT entry EMPTY >" +
- "<!ATTLIST entry" +
- " key CDATA #REQUIRED" +
- " value CDATA #REQUIRED >" ;
- /**
- * Version number for the format exported preferences files.
- */
- private static final String EXTERNAL_XML_VERSION = "1.0";
-
- /*
- * Version number for the internal map files.
- */
- private static final String MAP_XML_VERSION = "1.0";
-
- /**
- * Export the specified preferences node and, if subTree is true, all
- * subnodes, to the specified output stream. Preferences are exported as
- * an XML document conforming to the definition in the Preferences spec.
- *
- * @throws IOException if writing to the specified output stream
- * results in an <tt>IOException</tt>.
- * @throws BackingStoreException if preference data cannot be read from
- * backing store.
- * @throws IllegalStateException if this node (or an ancestor) has been
- * removed with the {@link #removeNode()} method.
- */
- static void export(OutputStream os, final Preferences p, boolean subTree)
- throws IOException, BackingStoreException {
- if (((AbstractPreferences)p).isRemoved())
- throw new IllegalStateException("Node has been removed");
- Document doc = createPrefsDoc("preferences");
- Element preferences = doc.getDocumentElement() ;
- preferences.setAttribute("EXTERNAL_XML_VERSION", EXTERNAL_XML_VERSION);
- Element xmlRoot = (Element)
- preferences.appendChild(doc.createElement("root"));
- xmlRoot.setAttribute("type", (p.isUserNode() ? "user" : "system"));
-
- // Get bottom-up list of nodes from p to root, excluding root
- List ancestors = new ArrayList();
-
- for (Preferences kid = p, dad = kid.parent(); dad != null;
- kid = dad, dad = kid.parent()) {
- ancestors.add(kid);
- }
- Element e = xmlRoot;
- for (int i=ancestors.size()-1; i >= 0; i--) {
- e.appendChild(doc.createElement("map"));
- e = (Element) e.appendChild(doc.createElement("node"));
- e.setAttribute("name", ((Preferences)ancestors.get(i)).name());
- }
- putPreferencesInXml(e, doc, p, subTree);
-
- writeDoc(doc, os);
- }
-
- /**
- * Put the preferences in the specified Preferences node into the
- * specified XML element which is assumed to represent a node
- * in the specified XML document which is assumed to conform to
- * PREFS_DTD. If subTree is true, create children of the specified
- * XML node conforming to all of the children of the specified
- * Preferences node and recurse.
- *
- * @throws BackingStoreException if it is not possible to read
- * the preferences or children out of the specified
- * preferences node.
- */
- private static void putPreferencesInXml(Element elt, Document doc,
- Preferences prefs, boolean subTree) throws BackingStoreException
- {
- Preferences[] kidsCopy = null;
- String[] kidNames = null;
-
- // Node is locked to export its contents and get a
- // copy of children, then lock is released,
- // and, if subTree = true, recursive calls are made on children
- synchronized (((AbstractPreferences)prefs).lock) {
- // Check if this node was concurrently removed. If yes
- // remove it from XML Document and return.
- if (((AbstractPreferences)prefs).isRemoved()) {
- elt.getParentNode().removeChild(elt);
- return;
- }
- // Put map in xml element
- String[] keys = prefs.keys();
- Element map = (Element) elt.appendChild(doc.createElement("map"));
- for (int i=0; i<keys.length; i++) {
- Element entry = (Element)
- map.appendChild(doc.createElement("entry"));
- entry.setAttribute("key", keys[i]);
- // NEXT STATEMENT THROWS NULL PTR EXC INSTEAD OF ASSERT FAIL
- entry.setAttribute("value", prefs.get(keys[i], null));
- }
- // Recurse if appropriate
- if (subTree) {
- /* Get a copy of kids while lock is held */
- kidNames = prefs.childrenNames();
- kidsCopy = new Preferences[kidNames.length];
- for (int i = 0; i < kidNames.length; i++)
- kidsCopy[i] = prefs.node(kidNames[i]);
- }
- // release lock
- }
-
- if (subTree) {
- for (int i=0; i < kidNames.length; i++) {
- Element xmlKid = (Element)
- elt.appendChild(doc.createElement("node"));
- xmlKid.setAttribute("name", kidNames[i]);
- putPreferencesInXml(xmlKid, doc, kidsCopy[i], subTree);
- }
- }
- }
-
- /**
- * Import preferences from the specified input stream, which is assumed
- * to contain an XML document in the format described in the Preferences
- * spec.
- *
- * @throws IOException if reading from the specified output stream
- * results in an <tt>IOException</tt>.
- * @throws InvalidPreferencesFormatException Data on input stream does not
- * constitute a valid XML document with the mandated document type.
- */
- static void importPreferences(InputStream is)
- throws IOException, InvalidPreferencesFormatException
- {
- try {
- Document doc = loadPrefsDoc(is);
- String xmlVersion =
- ((Element)doc.getChildNodes().item(1)).getAttribute("EXTERNAL_XML_VERSION");
- if (xmlVersion.compareTo(EXTERNAL_XML_VERSION) > 0)
- throw new InvalidPreferencesFormatException(
- "Exported preferences file format version " + xmlVersion +
- " is not supported. This java installation can read" +
- " versions " + EXTERNAL_XML_VERSION + " or older. You may need" +
- " to install a newer version of JDK.");
-
- Element xmlRoot = (Element) doc.getChildNodes().item(1).
- getChildNodes().item(0);
- Preferences prefsRoot =
- (xmlRoot.getAttribute("type").equals("user") ?
- Preferences.userRoot() : Preferences.systemRoot());
- ImportSubtree(prefsRoot, xmlRoot);
- } catch(SAXException e) {
- throw new InvalidPreferencesFormatException(e);
- }
- }
-
- /**
- * Create a new prefs XML document.
- */
- private static Document createPrefsDoc( String qname ) {
- try {
- DOMImplementation di = DocumentBuilderFactory.newInstance().
- newDocumentBuilder().getDOMImplementation();
- DocumentType dt = di.createDocumentType(qname, null, PREFS_DTD_URI);
- return di.createDocument(null, qname, dt);
- } catch(ParserConfigurationException e) {
- throw new AssertionError(e);
- }
- }
-
- /**
- * Load an XML document from specified input stream, which must
- * have the requisite DTD URI.
- */
- private static Document loadPrefsDoc(InputStream in)
- throws SAXException, IOException
- {
- Resolver r = new Resolver();
- DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
- dbf.setIgnoringElementContentWhitespace(true);
- dbf.setValidating(true);
- dbf.setCoalescing(true);
- dbf.setIgnoringComments(true);
- try {
- DocumentBuilder db = dbf.newDocumentBuilder();
- db.setEntityResolver(new Resolver());
- db.setErrorHandler(new EH());
- return db.parse(new InputSource(in));
- } catch (ParserConfigurationException e) {
- throw new AssertionError(e);
- }
- }
-
- /**
- * Write XML document to the specified output stream.
- */
- private static final void writeDoc(Document doc, OutputStream out)
- throws IOException
- {
- try {
- Transformer t = TransformerFactory.newInstance().newTransformer();
- t.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, doc.getDoctype().getSystemId());
- t.transform(new DOMSource(doc), new StreamResult(out));
- } catch(TransformerException e) {
- throw new AssertionError(e);
- }
- }
-
- /**
- * Recursively traverse the specified preferences node and store
- * the described preferences into the system or current user
- * preferences tree, as appropriate.
- */
- private static void ImportSubtree(Preferences prefsNode, Element xmlNode) {
- NodeList xmlKids = xmlNode.getChildNodes();
- int numXmlKids = xmlKids.getLength();
- /*
- * We first lock the node, import its contents and get
- * child nodes. Then we unlock the node and go to children
- * Since some of the children might have been concurrently
- * deleted we check for this.
- */
- Preferences[] prefsKids;
- /* Lock the node */
- synchronized (((AbstractPreferences)prefsNode).lock) {
- //If removed, return silently
- if (((AbstractPreferences)prefsNode).isRemoved())
- return;
-
- // Import any preferences at this node
- Element firstXmlKid = (Element) xmlKids.item(0);
- ImportPrefs(prefsNode, firstXmlKid);
- prefsKids = new Preferences[numXmlKids - 1];
-
- // Get involved children
- for (int i=1; i < numXmlKids; i++) {
- Element xmlKid = (Element) xmlKids.item(i);
- prefsKids[i-1] = prefsNode.node(xmlKid.getAttribute("name"));
- }
- } // unlocked the node
- // import children
- for (int i=1; i < numXmlKids; i++)
- ImportSubtree(prefsKids[i-1], (Element)xmlKids.item(i));
- }
-
- /**
- * Import the preferences described by the specified XML element
- * (a map from a preferences document) into the specified
- * preferences node.
- */
- private static void ImportPrefs(Preferences prefsNode, Element map) {
- NodeList entries = map.getChildNodes();
- for (int i=0, numEntries = entries.getLength(); i < numEntries; i++) {
- Element entry = (Element) entries.item(i);
- prefsNode.put(entry.getAttribute("key"),
- entry.getAttribute("value"));
- }
- }
-
- /**
- * Export the specified Map<String,String> to a map document on
- * the specified OutputStream as per the prefs DTD. This is used
- * as the internal (undocumented) format for FileSystemPrefs.
- *
- * @throws IOException if writing to the specified output stream
- * results in an <tt>IOException</tt>.
- */
- static void exportMap(OutputStream os, Map map) throws IOException {
- Document doc = createPrefsDoc("map");
- Element xmlMap = doc.getDocumentElement( ) ;
- xmlMap.setAttribute("MAP_XML_VERSION", MAP_XML_VERSION);
-
- for (Iterator i = map.entrySet().iterator(); i.hasNext(); ) {
- Map.Entry e = (Map.Entry) i.next();
- Element xe = (Element)
- xmlMap.appendChild(doc.createElement("entry"));
- xe.setAttribute("key", (String) e.getKey());
- xe.setAttribute("value", (String) e.getValue());
- }
-
- writeDoc(doc, os);
- }
-
- /**
- * Import Map from the specified input stream, which is assumed
- * to contain a map document as per the prefs DTD. This is used
- * as the internal (undocumented) format for FileSystemPrefs. The
- * key-value pairs specified in the XML document will be put into
- * the specified Map. (If this Map is empty, it will contain exactly
- * the key-value pairs int the XML-document when this method returns.)
- *
- * @throws IOException if reading from the specified output stream
- * results in an <tt>IOException</tt>.
- * @throws InvalidPreferencesFormatException Data on input stream does not
- * constitute a valid XML document with the mandated document type.
- */
- static void importMap(InputStream is, Map m)
- throws IOException, InvalidPreferencesFormatException
- {
- try {
- Document doc = loadPrefsDoc(is);
- Element xmlMap = (Element) doc.getChildNodes().item(1);
- // check version
- String mapVersion = xmlMap.getAttribute("MAP_XML_VERSION");
- if (mapVersion.compareTo(MAP_XML_VERSION) > 0)
- throw new InvalidPreferencesFormatException(
- "Preferences map file format version " + mapVersion +
- " is not supported. This java installation can read" +
- " versions " + MAP_XML_VERSION + " or older. You may need" +
- " to install a newer version of JDK.");
-
- NodeList entries = xmlMap.getChildNodes();
- for (int i=0, numEntries=entries.getLength(); i<numEntries; i++) {
- Element entry = (Element) entries.item(i);
- m.put(entry.getAttribute("key"), entry.getAttribute("value"));
- }
- } catch(SAXException e) {
- throw new InvalidPreferencesFormatException(e);
- }
- }
-
- private static class Resolver implements EntityResolver {
- public InputSource resolveEntity(String pid, String sid)
- throws SAXException
- {
- if (sid.equals(PREFS_DTD_URI)) {
- InputSource is;
- is = new InputSource(new StringReader(PREFS_DTD));
- is.setSystemId(PREFS_DTD_URI);
- return is;
- }
- throw new SAXException("Invalid system identifier: " + sid);
- }
- }
-
- private static class EH implements ErrorHandler {
- public void error(SAXParseException x) throws SAXException {
- throw x;
- }
- public void fatalError(SAXParseException x) throws SAXException {
- throw x;
- }
- public void warning(SAXParseException x) throws SAXException {
- throw x;
- }
- }
- }