Properties.java revision b4acb463582a510894aeb85f4fa8f35b339449c8
13c827367444ee418f129b2c238299f49d3264554Jarkko Poyry/*
23c827367444ee418f129b2c238299f49d3264554Jarkko Poyry *  Licensed to the Apache Software Foundation (ASF) under one or more
33c827367444ee418f129b2c238299f49d3264554Jarkko Poyry *  contributor license agreements.  See the NOTICE file distributed with
43c827367444ee418f129b2c238299f49d3264554Jarkko Poyry *  this work for additional information regarding copyright ownership.
53c827367444ee418f129b2c238299f49d3264554Jarkko Poyry *  The ASF licenses this file to You under the Apache License, Version 2.0
63c827367444ee418f129b2c238299f49d3264554Jarkko Poyry *  (the "License"); you may not use this file except in compliance with
73c827367444ee418f129b2c238299f49d3264554Jarkko Poyry *  the License.  You may obtain a copy of the License at
83c827367444ee418f129b2c238299f49d3264554Jarkko Poyry *
93c827367444ee418f129b2c238299f49d3264554Jarkko Poyry *     http://www.apache.org/licenses/LICENSE-2.0
103c827367444ee418f129b2c238299f49d3264554Jarkko Poyry *
113c827367444ee418f129b2c238299f49d3264554Jarkko Poyry *  Unless required by applicable law or agreed to in writing, software
123c827367444ee418f129b2c238299f49d3264554Jarkko Poyry *  distributed under the License is distributed on an "AS IS" BASIS,
133c827367444ee418f129b2c238299f49d3264554Jarkko Poyry *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
143c827367444ee418f129b2c238299f49d3264554Jarkko Poyry *  See the License for the specific language governing permissions and
153c827367444ee418f129b2c238299f49d3264554Jarkko Poyry *  limitations under the License.
163c827367444ee418f129b2c238299f49d3264554Jarkko Poyry */
173c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
183c827367444ee418f129b2c238299f49d3264554Jarkko Poyrypackage java.util;
193c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
203c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport java.io.BufferedReader;
213c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport java.io.IOException;
223c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport java.io.InputStream;
233c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport java.io.InputStreamReader;
243c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport java.io.OutputStream;
253c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport java.io.OutputStreamWriter;
263c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport java.io.PrintStream;
273c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport java.io.PrintWriter;
283c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport java.io.Reader;
293c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport java.io.StringReader;
303c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport java.io.Writer;
313c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport java.nio.charset.Charset;
323c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport java.nio.charset.IllegalCharsetNameException;
333c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport java.nio.charset.UnsupportedCharsetException;
343c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport javax.xml.parsers.DocumentBuilder;
353c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport javax.xml.parsers.DocumentBuilderFactory;
363c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport javax.xml.parsers.ParserConfigurationException;
373c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport org.w3c.dom.Document;
383c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport org.w3c.dom.Element;
393c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport org.w3c.dom.Node;
403c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport org.w3c.dom.NodeList;
413c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport org.w3c.dom.Text;
423c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport org.xml.sax.EntityResolver;
433c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport org.xml.sax.ErrorHandler;
443c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport org.xml.sax.InputSource;
453c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport org.xml.sax.SAXException;
463c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport org.xml.sax.SAXParseException;
47bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry
483c827367444ee418f129b2c238299f49d3264554Jarkko Poyry/**
493c827367444ee418f129b2c238299f49d3264554Jarkko Poyry * A {@code Properties} object is a {@code Hashtable} where the keys and values
503c827367444ee418f129b2c238299f49d3264554Jarkko Poyry * must be {@code String}s. Each property can have a default
513c827367444ee418f129b2c238299f49d3264554Jarkko Poyry * {@code Properties} list which specifies the default
523c827367444ee418f129b2c238299f49d3264554Jarkko Poyry * values to be used when a given key is not found in this {@code Properties}
533c827367444ee418f129b2c238299f49d3264554Jarkko Poyry * instance.
543c827367444ee418f129b2c238299f49d3264554Jarkko Poyry *
553c827367444ee418f129b2c238299f49d3264554Jarkko Poyry * <a name="character_encoding"><h3>Character Encoding</h3></a>
563c827367444ee418f129b2c238299f49d3264554Jarkko Poyry * <p>Note that in some cases {@code Properties} uses ISO-8859-1 instead of UTF-8.
573c827367444ee418f129b2c238299f49d3264554Jarkko Poyry * ISO-8859-1 is only capable of representing a tiny subset of Unicode.
583c827367444ee418f129b2c238299f49d3264554Jarkko Poyry * Use either the {@code loadFromXML}/{@code storeToXML} methods (which use UTF-8 by
593c827367444ee418f129b2c238299f49d3264554Jarkko Poyry * default) or the {@code load}/{@code store} overloads that take
603c827367444ee418f129b2c238299f49d3264554Jarkko Poyry * an {@code OutputStreamWriter} (so you can supply a UTF-8 instance) instead.
613c827367444ee418f129b2c238299f49d3264554Jarkko Poyry *
623c827367444ee418f129b2c238299f49d3264554Jarkko Poyry * @see Hashtable
633c827367444ee418f129b2c238299f49d3264554Jarkko Poyry * @see java.lang.System#getProperties
643c827367444ee418f129b2c238299f49d3264554Jarkko Poyry */
653c827367444ee418f129b2c238299f49d3264554Jarkko Poyrypublic class Properties extends Hashtable<Object, Object> {
663c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
673c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    private static final long serialVersionUID = 4112578634029874840L;
683c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
693c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    private transient DocumentBuilder builder = null;
703c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
713c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    private static final String PROP_DTD_NAME = "http://java.sun.com/dtd/properties.dtd";
723c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
733c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    private static final String PROP_DTD = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
743c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            + "    <!ELEMENT properties (comment?, entry*) >"
753c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            + "    <!ATTLIST properties version CDATA #FIXED \"1.0\" >"
763c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            + "    <!ELEMENT comment (#PCDATA) >"
773c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            + "    <!ELEMENT entry (#PCDATA) >"
783c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            + "    <!ATTLIST entry key CDATA #REQUIRED >";
793c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
803c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    /**
813c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * The default values for keys not found in this {@code Properties}
823c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * instance.
833c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     */
843c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    protected Properties defaults;
853c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
863c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    private static final int NONE = 0, SLASH = 1, UNICODE = 2, CONTINUE = 3,
873c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            KEY_DONE = 4, IGNORE = 5;
883c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
893c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    /**
903c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * Constructs a new {@code Properties} object.
913c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     */
923c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    public Properties() {
933c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        super();
943c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    }
953c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
963c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    /**
973c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * Constructs a new {@code Properties} object using the specified default
983c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * {@code Properties}.
993c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     *
1003c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * @param properties
1013c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     *            the default {@code Properties}.
1023c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     */
1033c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    public Properties(Properties properties) {
1043c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        defaults = properties;
1053c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    }
1063c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
1073c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    private void dumpString(StringBuilder buffer, String string, boolean key) {
1083c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        int i = 0;
1093c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        if (!key && i < string.length() && string.charAt(i) == ' ') {
1103c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            buffer.append("\\ ");
1113c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            i++;
1123c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        }
1133c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
1143c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        for (; i < string.length(); i++) {
1153c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            char ch = string.charAt(i);
1163c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            switch (ch) {
1173c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            case '\t':
1183c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                buffer.append("\\t");
1193c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                break;
1203c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            case '\n':
1213c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                buffer.append("\\n");
1223c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                break;
1233c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            case '\f':
1243c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                buffer.append("\\f");
1253c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                break;
1263c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            case '\r':
1273c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                buffer.append("\\r");
1283c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                break;
1293c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            default:
1303c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                if ("\\#!=:".indexOf(ch) >= 0 || (key && ch == ' ')) {
1313c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    buffer.append('\\');
1323c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                }
1333c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                if (ch >= ' ' && ch <= '~') {
1343c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    buffer.append(ch);
1353c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                } else {
1363c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    String hex = Integer.toHexString(ch);
1373c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    buffer.append("\\u");
1383c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    for (int j = 0; j < 4 - hex.length(); j++) {
1393c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                        buffer.append("0");
1403c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    }
1413c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    buffer.append(hex);
1423c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                }
1433c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            }
1443c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        }
1453c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    }
1463c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
1473c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    /**
1483c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * Searches for the property with the specified name. If the property is not
1493c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * found, the default {@code Properties} are checked. If the property is not
1503c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * found in the default {@code Properties}, {@code null} is returned.
1513c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     *
1523c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * @param name
1533c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     *            the name of the property to find.
1543c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * @return the named property value, or {@code null} if it can't be found.
1553c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     */
1563c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    public String getProperty(String name) {
1573c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        Object result = super.get(name);
1583c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        String property = result instanceof String ? (String) result : null;
1593c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        if (property == null && defaults != null) {
1603c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            property = defaults.getProperty(name);
1613c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        }
1623c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        return property;
1633c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    }
1643c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
1653c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    /**
1663c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * Searches for the property with the specified name. If the property is not
1673c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * found, it looks in the default {@code Properties}. If the property is not
1683c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * found in the default {@code Properties}, it returns the specified
1693c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * default.
1703c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     *
1713c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * @param name
1723c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     *            the name of the property to find.
1733c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * @param defaultValue
1743c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     *            the default value.
1753c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * @return the named property value.
1763c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     */
1773c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    public String getProperty(String name, String defaultValue) {
1783c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        Object result = super.get(name);
1793c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        String property = result instanceof String ? (String) result : null;
1803c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        if (property == null && defaults != null) {
1813c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            property = defaults.getProperty(name);
1823c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        }
1833c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        if (property == null) {
1843c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            return defaultValue;
1853c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        }
1863c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        return property;
1873c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    }
1883c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
1893c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    /**
1903c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * Lists the mappings in this {@code Properties} to {@code out} in a human-readable form.
1913c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * Note that values are truncated to 37 characters, so this method is rarely useful.
1923c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     */
1933c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    public void list(PrintStream out) {
1943c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        listToAppendable(out);
1953c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    }
1963c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
1973c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    /**
1983c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * Lists the mappings in this {@code Properties} to {@code out} in a human-readable form.
1993c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * Note that values are truncated to 37 characters, so this method is rarely useful.
2003c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     */
2013c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    public void list(PrintWriter out) {
2023c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        listToAppendable(out);
2033c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    }
2043c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
2053c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    private void listToAppendable(Appendable out) {
2063c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        try {
2073c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            if (out == null) {
2083c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                throw new NullPointerException("out == null");
2093c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            }
2103c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            StringBuilder sb = new StringBuilder(80);
2113c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            Enumeration<?> keys = propertyNames();
2123c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            while (keys.hasMoreElements()) {
2133c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                String key = (String) keys.nextElement();
2143c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                sb.append(key);
2153c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                sb.append('=');
2163c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                String property = (String) super.get(key);
2173c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                Properties def = defaults;
2183c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                while (property == null) {
2193c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    property = (String) def.get(key);
2203c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    def = def.defaults;
2213c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                }
222bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                if (property.length() > 40) {
223bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                    sb.append(property.substring(0, 37));
224bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                    sb.append("...");
225bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                } else {
226bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                    sb.append(property);
227bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                }
228bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                sb.append(System.lineSeparator());
229bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                out.append(sb.toString());
230bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                sb.setLength(0);
231bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            }
232bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        } catch (IOException ex) {
233bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            // Appendable.append throws IOException, but PrintStream and PrintWriter don't.
234bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            throw new AssertionError(ex);
235bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        }
236bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry    }
237bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry
238bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry    /**
239bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * Loads properties from the specified {@code InputStream}, assumed to be ISO-8859-1.
240bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * See "<a href="#character_encoding">Character Encoding</a>".
241bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     *
242bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * @param in the {@code InputStream}
243bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * @throws IOException
244bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     */
245bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry    public synchronized void load(InputStream in) throws IOException {
246bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        if (in == null) {
247bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            throw new NullPointerException();
248bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        }
249bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        load(new InputStreamReader(in, "ISO-8859-1"));
250bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry    }
251bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry
252bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry    /**
253bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * Loads properties from the specified {@code Reader}.
254bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * The properties file is interpreted according to the following rules:
255bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * <ul>
256bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * <li>Empty lines are ignored.</li>
257bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * <li>Lines starting with either a "#" or a "!" are comment lines and are
258bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * ignored.</li>
259bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * <li>A backslash at the end of the line escapes the following newline
260bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * character ("\r", "\n", "\r\n"). If there's whitespace after the
261bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * backslash it will just escape that whitespace instead of concatenating
262bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * the lines. This does not apply to comment lines.</li>
263bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * <li>A property line consists of the key, the space between the key and
264bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * the value, and the value. The key goes up to the first whitespace, "=" or
265bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * ":" that is not escaped. The space between the key and the value contains
266bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * either one whitespace, one "=" or one ":" and any amount of additional
267bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * whitespace before and after that character. The value starts with the
268bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * first character after the space between the key and the value.</li>
269bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * <li>Following escape sequences are recognized: "\ ", "\\", "\r", "\n",
270bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * "\!", "\#", "\t", "\b", "\f", and "&#92;uXXXX" (unicode character).</li>
271bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * </ul>
272bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     *
273bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * @param in the {@code Reader}
274bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * @throws IOException
275bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * @since 1.6
276bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     */
277bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry    @SuppressWarnings("fallthrough")
278bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry    public synchronized void load(Reader in) throws IOException {
279bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        if (in == null) {
280bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            throw new NullPointerException();
281bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        }
282bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        int mode = NONE, unicode = 0, count = 0;
283bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        char nextChar, buf[] = new char[40];
284bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        int offset = 0, keyLength = -1, intVal;
285bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        boolean firstChar = true;
286bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry
287bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        BufferedReader br = new BufferedReader(in);
288bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry
289bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        while (true) {
290bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            intVal = br.read();
291bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            if (intVal == -1) {
292bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                break;
293bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            }
294bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            nextChar = (char) intVal;
295bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry
296bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            if (offset == buf.length) {
297bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                char[] newBuf = new char[buf.length * 2];
298bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                System.arraycopy(buf, 0, newBuf, 0, offset);
299bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                buf = newBuf;
300bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            }
301bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            if (mode == UNICODE) {
302bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                int digit = Character.digit(nextChar, 16);
303bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                if (digit >= 0) {
304bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                    unicode = (unicode << 4) + digit;
305bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                    if (++count < 4) {
306bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                        continue;
307bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                    }
308bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                } else if (count <= 4) {
309bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                    throw new IllegalArgumentException("Invalid Unicode sequence: illegal character");
310bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                }
311bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                mode = NONE;
312bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                buf[offset++] = (char) unicode;
313bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                if (nextChar != '\n') {
314bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                    continue;
315bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                }
316bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            }
317bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            if (mode == SLASH) {
318bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                mode = NONE;
319bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                switch (nextChar) {
320bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                case '\r':
321bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                    mode = CONTINUE; // Look for a following \n
322bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                    continue;
323bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                case '\n':
324bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                    mode = IGNORE; // Ignore whitespace on the next line
325bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                    continue;
3263c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                case 'b':
3273c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    nextChar = '\b';
328bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                    break;
3293c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                case 'f':
3303c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    nextChar = '\f';
3313c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    break;
332bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                case 'n':
3333c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    nextChar = '\n';
3343c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    break;
3353c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                case 'r':
3363c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    nextChar = '\r';
3373c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    break;
3383c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                case 't':
3393c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    nextChar = '\t';
3403c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    break;
3413c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                case 'u':
3423c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    mode = UNICODE;
3433c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    unicode = count = 0;
3443c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    continue;
3453c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                }
3463c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            } else {
3473c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                switch (nextChar) {
3483c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                case '#':
3493c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                case '!':
3503c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    if (firstChar) {
3513c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                        while (true) {
3523c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                            intVal = br.read();
3533c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                            if (intVal == -1) {
3543c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                                break;
3553c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                            }
3563c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                            nextChar = (char) intVal;
3573c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                            if (nextChar == '\r' || nextChar == '\n') {
3583c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                                break;
3593c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                            }
3603c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                        }
3613c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                        continue;
3623c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    }
3633c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    break;
3643c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                case '\n':
3653c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    if (mode == CONTINUE) { // Part of a \r\n sequence
366bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                        mode = IGNORE; // Ignore whitespace on the next line
367bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                        continue;
368bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                    }
369bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                    // fall into the next case
370bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                case '\r':
371bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                    mode = NONE;
3723c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    firstChar = true;
3733c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    if (offset > 0 || (offset == 0 && keyLength == 0)) {
3743c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                        if (keyLength == -1) {
3753c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                            keyLength = offset;
3763c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                        }
3773c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                        String temp = new String(buf, 0, offset);
3783c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                        put(temp.substring(0, keyLength), temp
379bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                                .substring(keyLength));
380bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                    }
3813c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    keyLength = -1;
3823c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    offset = 0;
3833c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    continue;
3843c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                case '\\':
3853c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    if (mode == KEY_DONE) {
386bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                        keyLength = offset;
387bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                    }
3883c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    mode = SLASH;
3893c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    continue;
3903c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                case ':':
3913c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                case '=':
3923c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    if (keyLength == -1) { // if parsing the key
3933c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                        mode = NONE;
3943c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                        keyLength = offset;
3953c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                        continue;
3963c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    }
3973c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    break;
398bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                }
3993c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                if (Character.isWhitespace(nextChar)) {
4003c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    if (mode == CONTINUE) {
4013c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                        mode = IGNORE;
4023c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    }
4033c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    // if key length == 0 or value length == 0
4043c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    if (offset == 0 || offset == keyLength || mode == IGNORE) {
4053c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                        continue;
4063c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    }
4073c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    if (keyLength == -1) { // if parsing the key
4083c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                        mode = KEY_DONE;
409bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                        continue;
410bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                    }
411bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                }
412bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                if (mode == IGNORE || mode == CONTINUE) {
413bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                    mode = NONE;
414bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                }
415bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            }
416bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            firstChar = false;
417bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            if (mode == KEY_DONE) {
418bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                keyLength = offset;
419bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                mode = NONE;
420bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            }
421bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            buf[offset++] = nextChar;
422bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        }
423bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        if (mode == UNICODE && count <= 4) {
424bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            throw new IllegalArgumentException("Invalid Unicode sequence: expected format \\uxxxx");
425bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        }
426bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        if (keyLength == -1 && offset > 0) {
427bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            keyLength = offset;
428bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        }
429bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        if (keyLength >= 0) {
430bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            String temp = new String(buf, 0, offset);
431bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            String key = temp.substring(0, keyLength);
432bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            String value = temp.substring(keyLength);
433bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            if (mode == SLASH) {
434bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                value += "\u0000";
435bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            }
436bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            put(key, value);
437bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        }
438bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry    }
439bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry
440bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry    /**
441bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * Returns all of the property names (keys) in this {@code Properties} object.
442bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     */
443bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry    public Enumeration<?> propertyNames() {
444bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        Hashtable<Object, Object> selected = new Hashtable<Object, Object>();
445bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        selectProperties(selected, false);
446bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        return selected.keys();
447bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry    }
448bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry
449bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry    /**
450bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * Returns those property names (keys) in this {@code Properties} object for which
451bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * both key and value are strings.
452bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     *
453bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * @return a set of keys in the property list
454bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * @since 1.6
455bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     */
456bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry    public Set<String> stringPropertyNames() {
457bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        Hashtable<String, Object> stringProperties = new Hashtable<String, Object>();
458bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        selectProperties(stringProperties, true);
459bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        return Collections.unmodifiableSet(stringProperties.keySet());
460bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry    }
461bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry
462bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry    private <K> void selectProperties(Hashtable<K, Object> selectProperties, final boolean isStringOnly) {
463bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        if (defaults != null) {
464bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            defaults.selectProperties(selectProperties, isStringOnly);
465bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        }
466bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        Enumeration<Object> keys = keys();
467bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        while (keys.hasMoreElements()) {
468bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            @SuppressWarnings("unchecked")
469bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            K key = (K) keys.nextElement();
470bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            if (isStringOnly && !(key instanceof String)) {
471bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                // Only select property with string key and value
472bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry                continue;
473bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            }
474bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            Object value = get(key);
475bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            selectProperties.put(key, value);
476bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        }
477bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry    }
478bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry
479bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry    /**
480bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * Saves the mappings in this {@code Properties} to the specified {@code
481bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * OutputStream}, putting the specified comment at the beginning. The output
482bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * from this method is suitable for being read by the
483bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * {@link #load(InputStream)} method.
484bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     *
485bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * @param out the {@code OutputStream} to write to.
486bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * @param comment the comment to add at the beginning.
487bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * @throws ClassCastException if the key or value of a mapping is not a
488bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     *                String.
489bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * @deprecated This method ignores any {@code IOException} thrown while
490bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     *             writing -- use {@link #store} instead for better exception
491bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     *             handling.
492bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     */
493bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry    @Deprecated
494bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry    public void save(OutputStream out, String comment) {
495bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        try {
496bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            store(out, comment);
497bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        } catch (IOException e) {
498bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        }
499bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry    }
500bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry
501bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry    /**
502bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * Maps the specified key to the specified value. If the key already exists,
503bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * the old value is replaced. The key and value cannot be {@code null}.
504bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     *
505bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * @param name
506bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     *            the key.
507bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * @param value
508bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     *            the value.
509bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * @return the old value mapped to the key, or {@code null}.
510bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     */
511bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry    public Object setProperty(String name, String value) {
512bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        return put(name, value);
513bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry    }
514bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry
515bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry    /**
516bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * Stores properties to the specified {@code OutputStream}, using ISO-8859-1.
517bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * See "<a href="#character_encoding">Character Encoding</a>".
518bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     *
519bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * @param out the {@code OutputStream}
520bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * @param comment an optional comment to be written, or null
521bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * @throws IOException
522bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * @throws ClassCastException if a key or value is not a string
523bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     */
524bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry    public synchronized void store(OutputStream out, String comment) throws IOException {
525bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        store(new OutputStreamWriter(out, "ISO-8859-1"), comment);
526bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry    }
527bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry
528bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry    /**
529bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * Stores the mappings in this {@code Properties} object to {@code out},
530bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * putting the specified comment at the beginning.
531bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     *
532bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * @param writer the {@code Writer}
533bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * @param comment an optional comment to be written, or null
534bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * @throws IOException
535bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * @throws ClassCastException if a key or value is not a string
536bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * @since 1.6
537bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     */
538bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry    public synchronized void store(Writer writer, String comment) throws IOException {
539bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        if (comment != null) {
540bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            writer.write("#");
541bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            writer.write(comment);
542bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry            writer.write(System.lineSeparator());
543bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        }
544bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        writer.write("#");
545bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        writer.write(new Date().toString());
546bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        writer.write(System.lineSeparator());
547bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry
5483c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        StringBuilder sb = new StringBuilder(200);
5493c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        for (Map.Entry<Object, Object> entry : entrySet()) {
5503c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            String key = (String) entry.getKey();
5513c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            dumpString(sb, key, true);
5523c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            sb.append('=');
5533c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            dumpString(sb, (String) entry.getValue(), false);
5543c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            sb.append(System.lineSeparator());
5553c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            writer.write(sb.toString());
5563c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            sb.setLength(0);
5573c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        }
5583c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        writer.flush();
5593c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    }
5603c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
5613c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    /**
5623c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * Loads the properties from an {@code InputStream} containing the
5633c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * properties in XML form. The XML document must begin with (and conform to)
5643c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * following DOCTYPE:
5653c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     *
5663c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * <pre>
5673c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * &lt;!DOCTYPE properties SYSTEM &quot;http://java.sun.com/dtd/properties.dtd">;
5683c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * </pre>
5693c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     *
5703c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * Also the content of the XML data must satisfy the DTD but the xml is not
5713c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * validated against it. The DTD is not loaded from the SYSTEM ID. After
5723c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * this method returns the InputStream is not closed.
5733c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     *
5743c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * @param in the InputStream containing the XML document.
5753c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * @throws IOException in case an error occurs during a read operation.
5763c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * @throws InvalidPropertiesFormatException if the XML data is not a valid
5773c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     *             properties file.
5783c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     */
5793c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    public synchronized void loadFromXML(InputStream in) throws IOException,
5803c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            InvalidPropertiesFormatException {
5813c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        if (in == null) {
5823c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            throw new NullPointerException();
5833c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        }
5843c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
5853c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        if (builder == null) {
5863c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
5873c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            // BEGIN android-removed: we still don't support validation.
5883c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            // factory.setValidating(true);
5893c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            // END android-removed
5903c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
5913c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            try {
5923c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                builder = factory.newDocumentBuilder();
5933c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            } catch (ParserConfigurationException e) {
5943c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                throw new Error(e);
5953c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            }
5963c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
5973c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            builder.setErrorHandler(new ErrorHandler() {
5983c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                public void warning(SAXParseException e) throws SAXException {
5993c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    throw e;
6003c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                }
6013c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
6023c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                public void error(SAXParseException e) throws SAXException {
6033c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    throw e;
6043c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                }
6053c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
6063c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                public void fatalError(SAXParseException e) throws SAXException {
6073c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    throw e;
6083c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                }
6093c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            });
6103c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
6113c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            builder.setEntityResolver(new EntityResolver() {
6123c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                public InputSource resolveEntity(String publicId,
6133c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                        String systemId) throws SAXException, IOException {
6143c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    if (systemId.equals(PROP_DTD_NAME)) {
6153c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                        InputSource result = new InputSource(new StringReader(
6163c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                                PROP_DTD));
6173c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                        result.setSystemId(PROP_DTD_NAME);
6183c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                        return result;
6193c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    }
6203c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    throw new SAXException("Invalid DOCTYPE declaration: "
6213c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                            + systemId);
6223c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                }
6233c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            });
6243c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        }
6253c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
6263c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        try {
6273c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            Document doc = builder.parse(in);
6283c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            NodeList entries = doc.getElementsByTagName("entry");
6293c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            if (entries == null) {
6303c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                return;
6313c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            }
6323c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            int entriesListLength = entries.getLength();
6333c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
6343c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            for (int i = 0; i < entriesListLength; i++) {
6353c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                Element entry = (Element) entries.item(i);
6363c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                String key = entry.getAttribute("key");
6373c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                String value = entry.getTextContent();
6383c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
6393c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                /*
6403c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                 * key != null & value != null but key or(and) value can be
6413c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                 * empty String
6423c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                 */
6433c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                put(key, value);
6443c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            }
6453c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        } catch (IOException e) {
6463c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            throw e;
6473c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        } catch (SAXException e) {
6483c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            throw new InvalidPropertiesFormatException(e);
6493c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        }
6503c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    }
6513c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
6523c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    /**
6533c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * Writes all properties stored in this instance into the {@code
6543c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * OutputStream} in XML representation. The DOCTYPE is
6553c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     *
6563c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * <pre>
6573c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * &lt;!DOCTYPE properties SYSTEM &quot;http://java.sun.com/dtd/properties.dtd">;
6583c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * </pre>
6593c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     *
6603c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * If the comment is null, no comment is added to the output. UTF-8 is used
6613c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * as the encoding. The {@code OutputStream} is not closed at the end. A
6623c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * call to this method is the same as a call to {@code storeToXML(os,
6633c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * comment, "UTF-8")}.
6643c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     *
6653c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * @param os the {@code OutputStream} to write to.
6663c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * @param comment the comment to add. If null, no comment is added.
6673c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * @throws IOException if an error occurs during writing to the output.
6683c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     */
6693c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    public void storeToXML(OutputStream os, String comment) throws IOException {
6703c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        storeToXML(os, comment, "UTF-8");
6713c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    }
6723c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
6733c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    /**
6743c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * Writes all properties stored in this instance into the {@code
6753c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * OutputStream} in XML representation. The DOCTYPE is
6763c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     *
6773c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * <pre>
6783c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * &lt;!DOCTYPE properties SYSTEM &quot;http://java.sun.com/dtd/properties.dtd">;
6793c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     * </pre>
6803c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     *
681bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * If the comment is null, no comment is added to the output. The parameter
682bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * {@code encoding} defines which encoding should be used. The {@code
683bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * OutputStream} is not closed at the end.
684bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     *
685bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * @param os the {@code OutputStream} to write to.
686bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * @param comment the comment to add. If null, no comment is added.
687bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * @param encoding the code identifying the encoding that should be used to
688bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     *            write into the {@code OutputStream}.
689bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry     * @throws IOException if an error occurs during writing to the output.
6903c827367444ee418f129b2c238299f49d3264554Jarkko Poyry     */
6913c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    public synchronized void storeToXML(OutputStream os, String comment,
6923c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            String encoding) throws IOException {
6933c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
6943c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        if (os == null || encoding == null) {
6953c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            throw new NullPointerException();
6963c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        }
6973c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
6983c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        /*
6993c827367444ee418f129b2c238299f49d3264554Jarkko Poyry         * We can write to XML file using encoding parameter but note that some
7003c827367444ee418f129b2c238299f49d3264554Jarkko Poyry         * aliases for encodings are not supported by the XML parser. Thus we
7013c827367444ee418f129b2c238299f49d3264554Jarkko Poyry         * have to know canonical name for encoding used to store data in XML
7023c827367444ee418f129b2c238299f49d3264554Jarkko Poyry         * since the XML parser must recognize encoding name used to store data.
7033c827367444ee418f129b2c238299f49d3264554Jarkko Poyry         */
7043c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
7053c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        String encodingCanonicalName;
7063c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        try {
7073c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            encodingCanonicalName = Charset.forName(encoding).name();
7083c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        } catch (IllegalCharsetNameException e) {
7093c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            System.out.println("Warning: encoding name " + encoding
7103c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    + " is illegal, using UTF-8 as default encoding");
7113c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            encodingCanonicalName = "UTF-8";
7123c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        } catch (UnsupportedCharsetException e) {
7133c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            System.out.println("Warning: encoding " + encoding
7143c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                    + " is not supported, using UTF-8 as default encoding");
7153c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            encodingCanonicalName = "UTF-8";
7163c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        }
7173c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
7183c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        PrintStream printStream = new PrintStream(os, false,
7193c827367444ee418f129b2c238299f49d3264554Jarkko Poyry                encodingCanonicalName);
7203c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
7213c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        printStream.print("<?xml version=\"1.0\" encoding=\"");
7223c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        printStream.print(encodingCanonicalName);
7233c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        printStream.println("\"?>");
7243c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
7253c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        printStream.print("<!DOCTYPE properties SYSTEM \"");
726bd4d098088a5b205c7e7d8dc0bec140d2fa55446Jarkko Pöyry        printStream.print(PROP_DTD_NAME);
7273c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        printStream.println("\">");
7283c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
7293c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        printStream.println("<properties>");
7303c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
7313c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        if (comment != null) {
7323c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            printStream.print("<comment>");
7333c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            printStream.print(substitutePredefinedEntries(comment));
7343c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            printStream.println("</comment>");
7353c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        }
7363c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
7373c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        for (Map.Entry<Object, Object> entry : entrySet()) {
7383c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            String keyValue = (String) entry.getKey();
7393c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            String entryValue = (String) entry.getValue();
7403c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            printStream.print("<entry key=\"");
7413c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            printStream.print(substitutePredefinedEntries(keyValue));
7423c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            printStream.print("\">");
7433c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            printStream.print(substitutePredefinedEntries(entryValue));
7443c827367444ee418f129b2c238299f49d3264554Jarkko Poyry            printStream.println("</entry>");
7453c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        }
7463c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        printStream.println("</properties>");
7473c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        printStream.flush();
7483c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    }
7493c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
7503c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    private String substitutePredefinedEntries(String s) {
7513c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        // substitution for predefined character entities to use them safely in XML.
7523c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        s = s.replaceAll("&", "&amp;");
7533c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        s = s.replaceAll("<", "&lt;");
7543c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        s = s.replaceAll(">", "&gt;");
7553c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        s = s.replaceAll("'", "&apos;");
7563c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        s = s.replaceAll("\"", "&quot;");
7573c827367444ee418f129b2c238299f49d3264554Jarkko Poyry        return s;
7583c827367444ee418f129b2c238299f49d3264554Jarkko Poyry    }
7593c827367444ee418f129b2c238299f49d3264554Jarkko Poyry}
7603c827367444ee418f129b2c238299f49d3264554Jarkko Poyry