XMLUtils.java revision 2c87ad3a45cecf9e344487cad1abfdebe79f2c7c
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 *
6 * This code is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License version 2 only, as
8 * published by the Free Software Foundation.  Oracle designates this
9 * particular file as subject to the "Classpath" exception as provided
10 * by Oracle in the LICENSE file that accompanied this code.
11 *
12 * This code is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15 * version 2 for more details (a copy is included in the LICENSE file that
16 * accompanied this code).
17 *
18 * You should have received a copy of the GNU General Public License version
19 * 2 along with this work; if not, write to the Free Software Foundation,
20 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21 *
22 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
23 * or visit www.oracle.com if you need additional information or have any
24 * questions.
25 */
26
27package java.util;
28
29import java.io.*;
30import org.xml.sax.*;
31import org.xml.sax.helpers.*;
32import org.w3c.dom.*;
33import javax.xml.parsers.*;
34import javax.xml.transform.*;
35import javax.xml.transform.dom.*;
36import javax.xml.transform.stream.*;
37
38/**
39 * A class used to aid in Properties load and save in XML. Keeping this
40 * code outside of Properties helps reduce the number of classes loaded
41 * when Properties is loaded.
42 *
43 * @author  Michael McCloskey
44 * @since   1.3
45 */
46class XMLUtils {
47
48    // XML loading and saving methods for Properties
49
50    // The required DTD URI for exported properties
51    private static final String PROPS_DTD_URI =
52    "http://java.sun.com/dtd/properties.dtd";
53
54    private static final String PROPS_DTD =
55    "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
56    "<!-- DTD for properties -->"                +
57    "<!ELEMENT properties ( comment?, entry* ) >"+
58    "<!ATTLIST properties"                       +
59        " version CDATA #FIXED \"1.0\">"         +
60    "<!ELEMENT comment (#PCDATA) >"              +
61    "<!ELEMENT entry (#PCDATA) >"                +
62    "<!ATTLIST entry "                           +
63        " key CDATA #REQUIRED>";
64
65    /**
66     * Version number for the format of exported properties files.
67     */
68    private static final String EXTERNAL_XML_VERSION = "1.0";
69
70    static void load(Properties props, InputStream in)
71        throws IOException, InvalidPropertiesFormatException
72    {
73        Document doc = null;
74        try {
75            doc = getLoadingDoc(in);
76        } catch (SAXException saxe) {
77            throw new InvalidPropertiesFormatException(saxe);
78        }
79        Element propertiesElement = doc.getDocumentElement();
80        String xmlVersion = propertiesElement.getAttribute("version");
81        if (xmlVersion.compareTo(EXTERNAL_XML_VERSION) > 0)
82            throw new InvalidPropertiesFormatException(
83                "Exported Properties file format version " + xmlVersion +
84                " is not supported. This java installation can read" +
85                " versions " + EXTERNAL_XML_VERSION + " or older. You" +
86                " may need to install a newer version of JDK.");
87        importProperties(props, propertiesElement);
88    }
89
90    static Document getLoadingDoc(InputStream in)
91        throws SAXException, IOException
92    {
93        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
94        dbf.setIgnoringElementContentWhitespace(true);
95        // Android-chanaged: We don't currently have a validating document builder.
96        // Revert this if the situation changes.
97        //
98        // dbf.setValidating(true);
99        dbf.setCoalescing(true);
100        dbf.setIgnoringComments(true);
101        try {
102            DocumentBuilder db = dbf.newDocumentBuilder();
103            db.setEntityResolver(new Resolver());
104            db.setErrorHandler(new EH());
105            InputSource is = new InputSource(in);
106            return db.parse(is);
107        } catch (ParserConfigurationException x) {
108            throw new Error(x);
109        }
110    }
111
112    static void importProperties(Properties props, Element propertiesElement) {
113        NodeList entries = propertiesElement.getChildNodes();
114        int numEntries = entries.getLength();
115        int start = numEntries > 0 &&
116            entries.item(0).getNodeName().equals("comment") ? 1 : 0;
117        for (int i=start; i<numEntries; i++) {
118            // Android-changed: Exclude CDATA nodes and the like.
119            if (!(entries.item(i) instanceof Element)) {
120                continue;
121            }
122            Element entry = (Element)entries.item(i);
123            if (entry.hasAttribute("key")) {
124                Node n = entry.getFirstChild();
125                String val = (n == null) ? "" : n.getNodeValue();
126                props.setProperty(entry.getAttribute("key"), val);
127            }
128        }
129    }
130
131    static void save(Properties props, OutputStream os, String comment,
132                     String encoding)
133        throws IOException
134    {
135        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
136        DocumentBuilder db = null;
137        try {
138            db = dbf.newDocumentBuilder();
139        } catch (ParserConfigurationException pce) {
140            assert(false);
141        }
142        Document doc = db.newDocument();
143        Element properties =  (Element)
144            doc.appendChild(doc.createElement("properties"));
145
146        if (comment != null) {
147            Element comments = (Element)properties.appendChild(
148                doc.createElement("comment"));
149            comments.appendChild(doc.createTextNode(comment));
150        }
151
152        synchronized (props) {
153            for (String key : props.stringPropertyNames()) {
154                Element entry = (Element)properties.appendChild(
155                    doc.createElement("entry"));
156                entry.setAttribute("key", key);
157                entry.appendChild(doc.createTextNode(props.getProperty(key)));
158            }
159        }
160        emitDocument(doc, os, encoding);
161    }
162
163    static void emitDocument(Document doc, OutputStream os, String encoding)
164        throws IOException
165    {
166        TransformerFactory tf = TransformerFactory.newInstance();
167        Transformer t = null;
168        try {
169            t = tf.newTransformer();
170            t.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, PROPS_DTD_URI);
171            t.setOutputProperty(OutputKeys.INDENT, "yes");
172            t.setOutputProperty(OutputKeys.METHOD, "xml");
173            t.setOutputProperty(OutputKeys.ENCODING, encoding);
174        } catch (TransformerConfigurationException tce) {
175            assert(false);
176        }
177        DOMSource doms = new DOMSource(doc);
178        StreamResult sr = new StreamResult(os);
179        try {
180            t.transform(doms, sr);
181        } catch (TransformerException te) {
182            IOException ioe = new IOException();
183            ioe.initCause(te);
184            throw ioe;
185        }
186    }
187
188    private static class Resolver implements EntityResolver {
189        public InputSource resolveEntity(String pid, String sid)
190            throws SAXException
191        {
192            if (sid.equals(PROPS_DTD_URI)) {
193                InputSource is;
194                is = new InputSource(new StringReader(PROPS_DTD));
195                is.setSystemId(PROPS_DTD_URI);
196                return is;
197            }
198            throw new SAXException("Invalid system identifier: " + sid);
199        }
200    }
201
202    private static class EH implements ErrorHandler {
203        public void error(SAXParseException x) throws SAXException {
204            throw x;
205        }
206        public void fatalError(SAXParseException x) throws SAXException {
207            throw x;
208        }
209        public void warning(SAXParseException x) throws SAXException {
210            throw x;
211        }
212    }
213
214}
215