0N/A/*
2362N/A * Copyright (c) 2002, 2006, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
2362N/A * published by the Free Software Foundation. Oracle designates this
0N/A * particular file as subject to the "Classpath" exception as provided
2362N/A * by Oracle in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
0N/A */
0N/A
0N/Apackage java.util.prefs;
0N/A
0N/Aimport java.util.*;
0N/Aimport java.io.*;
0N/Aimport javax.xml.parsers.*;
0N/Aimport javax.xml.transform.*;
0N/Aimport javax.xml.transform.dom.*;
0N/Aimport javax.xml.transform.stream.*;
0N/Aimport org.xml.sax.*;
0N/Aimport org.w3c.dom.*;
0N/A
0N/A/**
0N/A * XML Support for java.util.prefs. Methods to import and export preference
0N/A * nodes and subtrees.
0N/A *
0N/A * @author Josh Bloch and Mark Reinhold
0N/A * @see Preferences
0N/A * @since 1.4
0N/A */
0N/Aclass XmlSupport {
0N/A // The required DTD URI for exported preferences
0N/A private static final String PREFS_DTD_URI =
0N/A "http://java.sun.com/dtd/preferences.dtd";
0N/A
0N/A // The actual DTD corresponding to the URI
0N/A private static final String PREFS_DTD =
0N/A "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
0N/A
0N/A "<!-- DTD for preferences -->" +
0N/A
0N/A "<!ELEMENT preferences (root) >" +
0N/A "<!ATTLIST preferences" +
0N/A " EXTERNAL_XML_VERSION CDATA \"0.0\" >" +
0N/A
0N/A "<!ELEMENT root (map, node*) >" +
0N/A "<!ATTLIST root" +
0N/A " type (system|user) #REQUIRED >" +
0N/A
0N/A "<!ELEMENT node (map, node*) >" +
0N/A "<!ATTLIST node" +
0N/A " name CDATA #REQUIRED >" +
0N/A
0N/A "<!ELEMENT map (entry*) >" +
0N/A "<!ATTLIST map" +
0N/A " MAP_XML_VERSION CDATA \"0.0\" >" +
0N/A "<!ELEMENT entry EMPTY >" +
0N/A "<!ATTLIST entry" +
0N/A " key CDATA #REQUIRED" +
0N/A " value CDATA #REQUIRED >" ;
0N/A /**
0N/A * Version number for the format exported preferences files.
0N/A */
0N/A private static final String EXTERNAL_XML_VERSION = "1.0";
0N/A
0N/A /*
0N/A * Version number for the internal map files.
0N/A */
0N/A private static final String MAP_XML_VERSION = "1.0";
0N/A
0N/A /**
0N/A * Export the specified preferences node and, if subTree is true, all
0N/A * subnodes, to the specified output stream. Preferences are exported as
0N/A * an XML document conforming to the definition in the Preferences spec.
0N/A *
0N/A * @throws IOException if writing to the specified output stream
0N/A * results in an <tt>IOException</tt>.
0N/A * @throws BackingStoreException if preference data cannot be read from
0N/A * backing store.
0N/A * @throws IllegalStateException if this node (or an ancestor) has been
0N/A * removed with the {@link #removeNode()} method.
0N/A */
0N/A static void export(OutputStream os, final Preferences p, boolean subTree)
0N/A throws IOException, BackingStoreException {
0N/A if (((AbstractPreferences)p).isRemoved())
0N/A throw new IllegalStateException("Node has been removed");
0N/A Document doc = createPrefsDoc("preferences");
0N/A Element preferences = doc.getDocumentElement() ;
0N/A preferences.setAttribute("EXTERNAL_XML_VERSION", EXTERNAL_XML_VERSION);
0N/A Element xmlRoot = (Element)
0N/A preferences.appendChild(doc.createElement("root"));
0N/A xmlRoot.setAttribute("type", (p.isUserNode() ? "user" : "system"));
0N/A
0N/A // Get bottom-up list of nodes from p to root, excluding root
0N/A List ancestors = new ArrayList();
0N/A
0N/A for (Preferences kid = p, dad = kid.parent(); dad != null;
0N/A kid = dad, dad = kid.parent()) {
0N/A ancestors.add(kid);
0N/A }
0N/A Element e = xmlRoot;
0N/A for (int i=ancestors.size()-1; i >= 0; i--) {
0N/A e.appendChild(doc.createElement("map"));
0N/A e = (Element) e.appendChild(doc.createElement("node"));
0N/A e.setAttribute("name", ((Preferences)ancestors.get(i)).name());
0N/A }
0N/A putPreferencesInXml(e, doc, p, subTree);
0N/A
0N/A writeDoc(doc, os);
0N/A }
0N/A
0N/A /**
0N/A * Put the preferences in the specified Preferences node into the
0N/A * specified XML element which is assumed to represent a node
0N/A * in the specified XML document which is assumed to conform to
0N/A * PREFS_DTD. If subTree is true, create children of the specified
0N/A * XML node conforming to all of the children of the specified
0N/A * Preferences node and recurse.
0N/A *
0N/A * @throws BackingStoreException if it is not possible to read
0N/A * the preferences or children out of the specified
0N/A * preferences node.
0N/A */
0N/A private static void putPreferencesInXml(Element elt, Document doc,
0N/A Preferences prefs, boolean subTree) throws BackingStoreException
0N/A {
0N/A Preferences[] kidsCopy = null;
0N/A String[] kidNames = null;
0N/A
0N/A // Node is locked to export its contents and get a
0N/A // copy of children, then lock is released,
0N/A // and, if subTree = true, recursive calls are made on children
0N/A synchronized (((AbstractPreferences)prefs).lock) {
0N/A // Check if this node was concurrently removed. If yes
0N/A // remove it from XML Document and return.
0N/A if (((AbstractPreferences)prefs).isRemoved()) {
0N/A elt.getParentNode().removeChild(elt);
0N/A return;
0N/A }
0N/A // Put map in xml element
0N/A String[] keys = prefs.keys();
0N/A Element map = (Element) elt.appendChild(doc.createElement("map"));
0N/A for (int i=0; i<keys.length; i++) {
0N/A Element entry = (Element)
0N/A map.appendChild(doc.createElement("entry"));
0N/A entry.setAttribute("key", keys[i]);
0N/A // NEXT STATEMENT THROWS NULL PTR EXC INSTEAD OF ASSERT FAIL
0N/A entry.setAttribute("value", prefs.get(keys[i], null));
0N/A }
0N/A // Recurse if appropriate
0N/A if (subTree) {
0N/A /* Get a copy of kids while lock is held */
0N/A kidNames = prefs.childrenNames();
0N/A kidsCopy = new Preferences[kidNames.length];
0N/A for (int i = 0; i < kidNames.length; i++)
0N/A kidsCopy[i] = prefs.node(kidNames[i]);
0N/A }
0N/A // release lock
0N/A }
0N/A
0N/A if (subTree) {
0N/A for (int i=0; i < kidNames.length; i++) {
0N/A Element xmlKid = (Element)
0N/A elt.appendChild(doc.createElement("node"));
0N/A xmlKid.setAttribute("name", kidNames[i]);
0N/A putPreferencesInXml(xmlKid, doc, kidsCopy[i], subTree);
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Import preferences from the specified input stream, which is assumed
0N/A * to contain an XML document in the format described in the Preferences
0N/A * spec.
0N/A *
0N/A * @throws IOException if reading from the specified output stream
0N/A * results in an <tt>IOException</tt>.
0N/A * @throws InvalidPreferencesFormatException Data on input stream does not
0N/A * constitute a valid XML document with the mandated document type.
0N/A */
0N/A static void importPreferences(InputStream is)
0N/A throws IOException, InvalidPreferencesFormatException
0N/A {
0N/A try {
0N/A Document doc = loadPrefsDoc(is);
0N/A String xmlVersion =
0N/A doc.getDocumentElement().getAttribute("EXTERNAL_XML_VERSION");
0N/A if (xmlVersion.compareTo(EXTERNAL_XML_VERSION) > 0)
0N/A throw new InvalidPreferencesFormatException(
0N/A "Exported preferences file format version " + xmlVersion +
0N/A " is not supported. This java installation can read" +
0N/A " versions " + EXTERNAL_XML_VERSION + " or older. You may need" +
0N/A " to install a newer version of JDK.");
0N/A
0N/A Element xmlRoot = (Element) doc.getDocumentElement().
0N/A getChildNodes().item(0);
0N/A Preferences prefsRoot =
0N/A (xmlRoot.getAttribute("type").equals("user") ?
0N/A Preferences.userRoot() : Preferences.systemRoot());
0N/A ImportSubtree(prefsRoot, xmlRoot);
0N/A } catch(SAXException e) {
0N/A throw new InvalidPreferencesFormatException(e);
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Create a new prefs XML document.
0N/A */
0N/A private static Document createPrefsDoc( String qname ) {
0N/A try {
0N/A DOMImplementation di = DocumentBuilderFactory.newInstance().
0N/A newDocumentBuilder().getDOMImplementation();
0N/A DocumentType dt = di.createDocumentType(qname, null, PREFS_DTD_URI);
0N/A return di.createDocument(null, qname, dt);
0N/A } catch(ParserConfigurationException e) {
0N/A throw new AssertionError(e);
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Load an XML document from specified input stream, which must
0N/A * have the requisite DTD URI.
0N/A */
0N/A private static Document loadPrefsDoc(InputStream in)
0N/A throws SAXException, IOException
0N/A {
0N/A DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
0N/A dbf.setIgnoringElementContentWhitespace(true);
0N/A dbf.setValidating(true);
0N/A dbf.setCoalescing(true);
0N/A dbf.setIgnoringComments(true);
0N/A try {
0N/A DocumentBuilder db = dbf.newDocumentBuilder();
0N/A db.setEntityResolver(new Resolver());
0N/A db.setErrorHandler(new EH());
0N/A return db.parse(new InputSource(in));
0N/A } catch (ParserConfigurationException e) {
0N/A throw new AssertionError(e);
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Write XML document to the specified output stream.
0N/A */
0N/A private static final void writeDoc(Document doc, OutputStream out)
0N/A throws IOException
0N/A {
0N/A try {
0N/A TransformerFactory tf = TransformerFactory.newInstance();
0N/A try {
0N/A tf.setAttribute("indent-number", new Integer(2));
0N/A } catch (IllegalArgumentException iae) {
0N/A //Ignore the IAE. Should not fail the writeout even the
0N/A //transformer provider does not support "indent-number".
0N/A }
0N/A Transformer t = tf.newTransformer();
0N/A t.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, doc.getDoctype().getSystemId());
0N/A t.setOutputProperty(OutputKeys.INDENT, "yes");
0N/A //Transformer resets the "indent" info if the "result" is a StreamResult with
0N/A //an OutputStream object embedded, creating a Writer object on top of that
0N/A //OutputStream object however works.
0N/A t.transform(new DOMSource(doc),
0N/A new StreamResult(new BufferedWriter(new OutputStreamWriter(out, "UTF-8"))));
0N/A } catch(TransformerException e) {
0N/A throw new AssertionError(e);
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Recursively traverse the specified preferences node and store
0N/A * the described preferences into the system or current user
0N/A * preferences tree, as appropriate.
0N/A */
0N/A private static void ImportSubtree(Preferences prefsNode, Element xmlNode) {
0N/A NodeList xmlKids = xmlNode.getChildNodes();
0N/A int numXmlKids = xmlKids.getLength();
0N/A /*
0N/A * We first lock the node, import its contents and get
0N/A * child nodes. Then we unlock the node and go to children
0N/A * Since some of the children might have been concurrently
0N/A * deleted we check for this.
0N/A */
0N/A Preferences[] prefsKids;
0N/A /* Lock the node */
0N/A synchronized (((AbstractPreferences)prefsNode).lock) {
0N/A //If removed, return silently
0N/A if (((AbstractPreferences)prefsNode).isRemoved())
0N/A return;
0N/A
0N/A // Import any preferences at this node
0N/A Element firstXmlKid = (Element) xmlKids.item(0);
0N/A ImportPrefs(prefsNode, firstXmlKid);
0N/A prefsKids = new Preferences[numXmlKids - 1];
0N/A
0N/A // Get involved children
0N/A for (int i=1; i < numXmlKids; i++) {
0N/A Element xmlKid = (Element) xmlKids.item(i);
0N/A prefsKids[i-1] = prefsNode.node(xmlKid.getAttribute("name"));
0N/A }
0N/A } // unlocked the node
0N/A // import children
0N/A for (int i=1; i < numXmlKids; i++)
0N/A ImportSubtree(prefsKids[i-1], (Element)xmlKids.item(i));
0N/A }
0N/A
0N/A /**
0N/A * Import the preferences described by the specified XML element
0N/A * (a map from a preferences document) into the specified
0N/A * preferences node.
0N/A */
0N/A private static void ImportPrefs(Preferences prefsNode, Element map) {
0N/A NodeList entries = map.getChildNodes();
0N/A for (int i=0, numEntries = entries.getLength(); i < numEntries; i++) {
0N/A Element entry = (Element) entries.item(i);
0N/A prefsNode.put(entry.getAttribute("key"),
0N/A entry.getAttribute("value"));
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Export the specified Map<String,String> to a map document on
0N/A * the specified OutputStream as per the prefs DTD. This is used
0N/A * as the internal (undocumented) format for FileSystemPrefs.
0N/A *
0N/A * @throws IOException if writing to the specified output stream
0N/A * results in an <tt>IOException</tt>.
0N/A */
0N/A static void exportMap(OutputStream os, Map map) throws IOException {
0N/A Document doc = createPrefsDoc("map");
0N/A Element xmlMap = doc.getDocumentElement( ) ;
0N/A xmlMap.setAttribute("MAP_XML_VERSION", MAP_XML_VERSION);
0N/A
0N/A for (Iterator i = map.entrySet().iterator(); i.hasNext(); ) {
0N/A Map.Entry e = (Map.Entry) i.next();
0N/A Element xe = (Element)
0N/A xmlMap.appendChild(doc.createElement("entry"));
0N/A xe.setAttribute("key", (String) e.getKey());
0N/A xe.setAttribute("value", (String) e.getValue());
0N/A }
0N/A
0N/A writeDoc(doc, os);
0N/A }
0N/A
0N/A /**
0N/A * Import Map from the specified input stream, which is assumed
0N/A * to contain a map document as per the prefs DTD. This is used
0N/A * as the internal (undocumented) format for FileSystemPrefs. The
0N/A * key-value pairs specified in the XML document will be put into
0N/A * the specified Map. (If this Map is empty, it will contain exactly
0N/A * the key-value pairs int the XML-document when this method returns.)
0N/A *
0N/A * @throws IOException if reading from the specified output stream
0N/A * results in an <tt>IOException</tt>.
0N/A * @throws InvalidPreferencesFormatException Data on input stream does not
0N/A * constitute a valid XML document with the mandated document type.
0N/A */
0N/A static void importMap(InputStream is, Map m)
0N/A throws IOException, InvalidPreferencesFormatException
0N/A {
0N/A try {
0N/A Document doc = loadPrefsDoc(is);
0N/A Element xmlMap = doc.getDocumentElement();
0N/A // check version
0N/A String mapVersion = xmlMap.getAttribute("MAP_XML_VERSION");
0N/A if (mapVersion.compareTo(MAP_XML_VERSION) > 0)
0N/A throw new InvalidPreferencesFormatException(
0N/A "Preferences map file format version " + mapVersion +
0N/A " is not supported. This java installation can read" +
0N/A " versions " + MAP_XML_VERSION + " or older. You may need" +
0N/A " to install a newer version of JDK.");
0N/A
0N/A NodeList entries = xmlMap.getChildNodes();
0N/A for (int i=0, numEntries=entries.getLength(); i<numEntries; i++) {
0N/A Element entry = (Element) entries.item(i);
0N/A m.put(entry.getAttribute("key"), entry.getAttribute("value"));
0N/A }
0N/A } catch(SAXException e) {
0N/A throw new InvalidPreferencesFormatException(e);
0N/A }
0N/A }
0N/A
0N/A private static class Resolver implements EntityResolver {
0N/A public InputSource resolveEntity(String pid, String sid)
0N/A throws SAXException
0N/A {
0N/A if (sid.equals(PREFS_DTD_URI)) {
0N/A InputSource is;
0N/A is = new InputSource(new StringReader(PREFS_DTD));
0N/A is.setSystemId(PREFS_DTD_URI);
0N/A return is;
0N/A }
0N/A throw new SAXException("Invalid system identifier: " + sid);
0N/A }
0N/A }
0N/A
0N/A private static class EH implements ErrorHandler {
0N/A public void error(SAXParseException x) throws SAXException {
0N/A throw x;
0N/A }
0N/A public void fatalError(SAXParseException x) throws SAXException {
0N/A throw x;
0N/A }
0N/A public void warning(SAXParseException x) throws SAXException {
0N/A throw x;
0N/A }
0N/A }
0N/A}