rt/emul/compact/src/main/java/java/util/Properties.java
branchjdk7-b147
changeset 1334 588d5bf7a560
child 1337 c794024954b5
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/rt/emul/compact/src/main/java/java/util/Properties.java	Thu Oct 03 15:40:35 2013 +0200
     1.3 @@ -0,0 +1,1114 @@
     1.4 +/*
     1.5 + * Copyright (c) 1995, 2010, Oracle and/or its affiliates. All rights reserved.
     1.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     1.7 + *
     1.8 + * This code is free software; you can redistribute it and/or modify it
     1.9 + * under the terms of the GNU General Public License version 2 only, as
    1.10 + * published by the Free Software Foundation.  Oracle designates this
    1.11 + * particular file as subject to the "Classpath" exception as provided
    1.12 + * by Oracle in the LICENSE file that accompanied this code.
    1.13 + *
    1.14 + * This code is distributed in the hope that it will be useful, but WITHOUT
    1.15 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    1.16 + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
    1.17 + * version 2 for more details (a copy is included in the LICENSE file that
    1.18 + * accompanied this code).
    1.19 + *
    1.20 + * You should have received a copy of the GNU General Public License version
    1.21 + * 2 along with this work; if not, write to the Free Software Foundation,
    1.22 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
    1.23 + *
    1.24 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
    1.25 + * or visit www.oracle.com if you need additional information or have any
    1.26 + * questions.
    1.27 + */
    1.28 +
    1.29 +package java.util;
    1.30 +
    1.31 +import java.io.IOException;
    1.32 +import java.io.PrintStream;
    1.33 +import java.io.PrintWriter;
    1.34 +import java.io.InputStream;
    1.35 +import java.io.OutputStream;
    1.36 +import java.io.Reader;
    1.37 +import java.io.Writer;
    1.38 +import java.io.OutputStreamWriter;
    1.39 +import java.io.BufferedWriter;
    1.40 +
    1.41 +/**
    1.42 + * The <code>Properties</code> class represents a persistent set of
    1.43 + * properties. The <code>Properties</code> can be saved to a stream
    1.44 + * or loaded from a stream. Each key and its corresponding value in
    1.45 + * the property list is a string.
    1.46 + * <p>
    1.47 + * A property list can contain another property list as its
    1.48 + * "defaults"; this second property list is searched if
    1.49 + * the property key is not found in the original property list.
    1.50 + * <p>
    1.51 + * Because <code>Properties</code> inherits from <code>Hashtable</code>, the
    1.52 + * <code>put</code> and <code>putAll</code> methods can be applied to a
    1.53 + * <code>Properties</code> object.  Their use is strongly discouraged as they
    1.54 + * allow the caller to insert entries whose keys or values are not
    1.55 + * <code>Strings</code>.  The <code>setProperty</code> method should be used
    1.56 + * instead.  If the <code>store</code> or <code>save</code> method is called
    1.57 + * on a "compromised" <code>Properties</code> object that contains a
    1.58 + * non-<code>String</code> key or value, the call will fail. Similarly,
    1.59 + * the call to the <code>propertyNames</code> or <code>list</code> method
    1.60 + * will fail if it is called on a "compromised" <code>Properties</code>
    1.61 + * object that contains a non-<code>String</code> key.
    1.62 + *
    1.63 + * <p>
    1.64 + * The {@link #load(java.io.Reader) load(Reader)} <tt>/</tt>
    1.65 + * {@link #store(java.io.Writer, java.lang.String) store(Writer, String)}
    1.66 + * methods load and store properties from and to a character based stream
    1.67 + * in a simple line-oriented format specified below.
    1.68 + *
    1.69 + * The {@link #load(java.io.InputStream) load(InputStream)} <tt>/</tt>
    1.70 + * {@link #store(java.io.OutputStream, java.lang.String) store(OutputStream, String)}
    1.71 + * methods work the same way as the load(Reader)/store(Writer, String) pair, except
    1.72 + * the input/output stream is encoded in ISO 8859-1 character encoding.
    1.73 + * Characters that cannot be directly represented in this encoding can be written using
    1.74 + * Unicode escapes as defined in section 3.3 of
    1.75 + * <cite>The Java&trade; Language Specification</cite>;
    1.76 + * only a single 'u' character is allowed in an escape
    1.77 + * sequence. The native2ascii tool can be used to convert property files to and
    1.78 + * from other character encodings.
    1.79 + *
    1.80 + * <p> The {@link #loadFromXML(InputStream)} and {@link
    1.81 + * #storeToXML(OutputStream, String, String)} methods load and store properties
    1.82 + * in a simple XML format.  By default the UTF-8 character encoding is used,
    1.83 + * however a specific encoding may be specified if required.  An XML properties
    1.84 + * document has the following DOCTYPE declaration:
    1.85 + *
    1.86 + * <pre>
    1.87 + * &lt;!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"&gt;
    1.88 + * </pre>
    1.89 + * Note that the system URI (http://java.sun.com/dtd/properties.dtd) is
    1.90 + * <i>not</i> accessed when exporting or importing properties; it merely
    1.91 + * serves as a string to uniquely identify the DTD, which is:
    1.92 + * <pre>
    1.93 + *    &lt;?xml version="1.0" encoding="UTF-8"?&gt;
    1.94 + *
    1.95 + *    &lt;!-- DTD for properties --&gt;
    1.96 + *
    1.97 + *    &lt;!ELEMENT properties ( comment?, entry* ) &gt;
    1.98 + *
    1.99 + *    &lt;!ATTLIST properties version CDATA #FIXED "1.0"&gt;
   1.100 + *
   1.101 + *    &lt;!ELEMENT comment (#PCDATA) &gt;
   1.102 + *
   1.103 + *    &lt;!ELEMENT entry (#PCDATA) &gt;
   1.104 + *
   1.105 + *    &lt;!ATTLIST entry key CDATA #REQUIRED&gt;
   1.106 + * </pre>
   1.107 + *
   1.108 + * <p>This class is thread-safe: multiple threads can share a single
   1.109 + * <tt>Properties</tt> object without the need for external synchronization.
   1.110 + *
   1.111 + * @see <a href="../../../technotes/tools/solaris/native2ascii.html">native2ascii tool for Solaris</a>
   1.112 + * @see <a href="../../../technotes/tools/windows/native2ascii.html">native2ascii tool for Windows</a>
   1.113 + *
   1.114 + * @author  Arthur van Hoff
   1.115 + * @author  Michael McCloskey
   1.116 + * @author  Xueming Shen
   1.117 + * @since   JDK1.0
   1.118 + */
   1.119 +public
   1.120 +class Properties extends Hashtable<Object,Object> {
   1.121 +    /**
   1.122 +     * use serialVersionUID from JDK 1.1.X for interoperability
   1.123 +     */
   1.124 +     private static final long serialVersionUID = 4112578634029874840L;
   1.125 +
   1.126 +    /**
   1.127 +     * A property list that contains default values for any keys not
   1.128 +     * found in this property list.
   1.129 +     *
   1.130 +     * @serial
   1.131 +     */
   1.132 +    protected Properties defaults;
   1.133 +
   1.134 +    /**
   1.135 +     * Creates an empty property list with no default values.
   1.136 +     */
   1.137 +    public Properties() {
   1.138 +        this(null);
   1.139 +    }
   1.140 +
   1.141 +    /**
   1.142 +     * Creates an empty property list with the specified defaults.
   1.143 +     *
   1.144 +     * @param   defaults   the defaults.
   1.145 +     */
   1.146 +    public Properties(Properties defaults) {
   1.147 +        this.defaults = defaults;
   1.148 +    }
   1.149 +
   1.150 +    /**
   1.151 +     * Calls the <tt>Hashtable</tt> method <code>put</code>. Provided for
   1.152 +     * parallelism with the <tt>getProperty</tt> method. Enforces use of
   1.153 +     * strings for property keys and values. The value returned is the
   1.154 +     * result of the <tt>Hashtable</tt> call to <code>put</code>.
   1.155 +     *
   1.156 +     * @param key the key to be placed into this property list.
   1.157 +     * @param value the value corresponding to <tt>key</tt>.
   1.158 +     * @return     the previous value of the specified key in this property
   1.159 +     *             list, or <code>null</code> if it did not have one.
   1.160 +     * @see #getProperty
   1.161 +     * @since    1.2
   1.162 +     */
   1.163 +    public synchronized Object setProperty(String key, String value) {
   1.164 +        return put(key, value);
   1.165 +    }
   1.166 +
   1.167 +
   1.168 +    /**
   1.169 +     * Reads a property list (key and element pairs) from the input
   1.170 +     * character stream in a simple line-oriented format.
   1.171 +     * <p>
   1.172 +     * Properties are processed in terms of lines. There are two
   1.173 +     * kinds of line, <i>natural lines</i> and <i>logical lines</i>.
   1.174 +     * A natural line is defined as a line of
   1.175 +     * characters that is terminated either by a set of line terminator
   1.176 +     * characters (<code>\n</code> or <code>\r</code> or <code>\r\n</code>)
   1.177 +     * or by the end of the stream. A natural line may be either a blank line,
   1.178 +     * a comment line, or hold all or some of a key-element pair. A logical
   1.179 +     * line holds all the data of a key-element pair, which may be spread
   1.180 +     * out across several adjacent natural lines by escaping
   1.181 +     * the line terminator sequence with a backslash character
   1.182 +     * <code>\</code>.  Note that a comment line cannot be extended
   1.183 +     * in this manner; every natural line that is a comment must have
   1.184 +     * its own comment indicator, as described below. Lines are read from
   1.185 +     * input until the end of the stream is reached.
   1.186 +     *
   1.187 +     * <p>
   1.188 +     * A natural line that contains only white space characters is
   1.189 +     * considered blank and is ignored.  A comment line has an ASCII
   1.190 +     * <code>'#'</code> or <code>'!'</code> as its first non-white
   1.191 +     * space character; comment lines are also ignored and do not
   1.192 +     * encode key-element information.  In addition to line
   1.193 +     * terminators, this format considers the characters space
   1.194 +     * (<code>' '</code>, <code>'&#92;u0020'</code>), tab
   1.195 +     * (<code>'\t'</code>, <code>'&#92;u0009'</code>), and form feed
   1.196 +     * (<code>'\f'</code>, <code>'&#92;u000C'</code>) to be white
   1.197 +     * space.
   1.198 +     *
   1.199 +     * <p>
   1.200 +     * If a logical line is spread across several natural lines, the
   1.201 +     * backslash escaping the line terminator sequence, the line
   1.202 +     * terminator sequence, and any white space at the start of the
   1.203 +     * following line have no affect on the key or element values.
   1.204 +     * The remainder of the discussion of key and element parsing
   1.205 +     * (when loading) will assume all the characters constituting
   1.206 +     * the key and element appear on a single natural line after
   1.207 +     * line continuation characters have been removed.  Note that
   1.208 +     * it is <i>not</i> sufficient to only examine the character
   1.209 +     * preceding a line terminator sequence to decide if the line
   1.210 +     * terminator is escaped; there must be an odd number of
   1.211 +     * contiguous backslashes for the line terminator to be escaped.
   1.212 +     * Since the input is processed from left to right, a
   1.213 +     * non-zero even number of 2<i>n</i> contiguous backslashes
   1.214 +     * before a line terminator (or elsewhere) encodes <i>n</i>
   1.215 +     * backslashes after escape processing.
   1.216 +     *
   1.217 +     * <p>
   1.218 +     * The key contains all of the characters in the line starting
   1.219 +     * with the first non-white space character and up to, but not
   1.220 +     * including, the first unescaped <code>'='</code>,
   1.221 +     * <code>':'</code>, or white space character other than a line
   1.222 +     * terminator. All of these key termination characters may be
   1.223 +     * included in the key by escaping them with a preceding backslash
   1.224 +     * character; for example,<p>
   1.225 +     *
   1.226 +     * <code>\:\=</code><p>
   1.227 +     *
   1.228 +     * would be the two-character key <code>":="</code>.  Line
   1.229 +     * terminator characters can be included using <code>\r</code> and
   1.230 +     * <code>\n</code> escape sequences.  Any white space after the
   1.231 +     * key is skipped; if the first non-white space character after
   1.232 +     * the key is <code>'='</code> or <code>':'</code>, then it is
   1.233 +     * ignored and any white space characters after it are also
   1.234 +     * skipped.  All remaining characters on the line become part of
   1.235 +     * the associated element string; if there are no remaining
   1.236 +     * characters, the element is the empty string
   1.237 +     * <code>&quot;&quot;</code>.  Once the raw character sequences
   1.238 +     * constituting the key and element are identified, escape
   1.239 +     * processing is performed as described above.
   1.240 +     *
   1.241 +     * <p>
   1.242 +     * As an example, each of the following three lines specifies the key
   1.243 +     * <code>"Truth"</code> and the associated element value
   1.244 +     * <code>"Beauty"</code>:
   1.245 +     * <p>
   1.246 +     * <pre>
   1.247 +     * Truth = Beauty
   1.248 +     *  Truth:Beauty
   1.249 +     * Truth                    :Beauty
   1.250 +     * </pre>
   1.251 +     * As another example, the following three lines specify a single
   1.252 +     * property:
   1.253 +     * <p>
   1.254 +     * <pre>
   1.255 +     * fruits                           apple, banana, pear, \
   1.256 +     *                                  cantaloupe, watermelon, \
   1.257 +     *                                  kiwi, mango
   1.258 +     * </pre>
   1.259 +     * The key is <code>"fruits"</code> and the associated element is:
   1.260 +     * <p>
   1.261 +     * <pre>"apple, banana, pear, cantaloupe, watermelon, kiwi, mango"</pre>
   1.262 +     * Note that a space appears before each <code>\</code> so that a space
   1.263 +     * will appear after each comma in the final result; the <code>\</code>,
   1.264 +     * line terminator, and leading white space on the continuation line are
   1.265 +     * merely discarded and are <i>not</i> replaced by one or more other
   1.266 +     * characters.
   1.267 +     * <p>
   1.268 +     * As a third example, the line:
   1.269 +     * <p>
   1.270 +     * <pre>cheeses
   1.271 +     * </pre>
   1.272 +     * specifies that the key is <code>"cheeses"</code> and the associated
   1.273 +     * element is the empty string <code>""</code>.<p>
   1.274 +     * <p>
   1.275 +     *
   1.276 +     * <a name="unicodeescapes"></a>
   1.277 +     * Characters in keys and elements can be represented in escape
   1.278 +     * sequences similar to those used for character and string literals
   1.279 +     * (see sections 3.3 and 3.10.6 of
   1.280 +     * <cite>The Java&trade; Language Specification</cite>).
   1.281 +     *
   1.282 +     * The differences from the character escape sequences and Unicode
   1.283 +     * escapes used for characters and strings are:
   1.284 +     *
   1.285 +     * <ul>
   1.286 +     * <li> Octal escapes are not recognized.
   1.287 +     *
   1.288 +     * <li> The character sequence <code>\b</code> does <i>not</i>
   1.289 +     * represent a backspace character.
   1.290 +     *
   1.291 +     * <li> The method does not treat a backslash character,
   1.292 +     * <code>\</code>, before a non-valid escape character as an
   1.293 +     * error; the backslash is silently dropped.  For example, in a
   1.294 +     * Java string the sequence <code>"\z"</code> would cause a
   1.295 +     * compile time error.  In contrast, this method silently drops
   1.296 +     * the backslash.  Therefore, this method treats the two character
   1.297 +     * sequence <code>"\b"</code> as equivalent to the single
   1.298 +     * character <code>'b'</code>.
   1.299 +     *
   1.300 +     * <li> Escapes are not necessary for single and double quotes;
   1.301 +     * however, by the rule above, single and double quote characters
   1.302 +     * preceded by a backslash still yield single and double quote
   1.303 +     * characters, respectively.
   1.304 +     *
   1.305 +     * <li> Only a single 'u' character is allowed in a Uniocde escape
   1.306 +     * sequence.
   1.307 +     *
   1.308 +     * </ul>
   1.309 +     * <p>
   1.310 +     * The specified stream remains open after this method returns.
   1.311 +     *
   1.312 +     * @param   reader   the input character stream.
   1.313 +     * @throws  IOException  if an error occurred when reading from the
   1.314 +     *          input stream.
   1.315 +     * @throws  IllegalArgumentException if a malformed Unicode escape
   1.316 +     *          appears in the input.
   1.317 +     * @since   1.6
   1.318 +     */
   1.319 +    public synchronized void load(Reader reader) throws IOException {
   1.320 +        load0(new LineReader(reader));
   1.321 +    }
   1.322 +
   1.323 +    /**
   1.324 +     * Reads a property list (key and element pairs) from the input
   1.325 +     * byte stream. The input stream is in a simple line-oriented
   1.326 +     * format as specified in
   1.327 +     * {@link #load(java.io.Reader) load(Reader)} and is assumed to use
   1.328 +     * the ISO 8859-1 character encoding; that is each byte is one Latin1
   1.329 +     * character. Characters not in Latin1, and certain special characters,
   1.330 +     * are represented in keys and elements using Unicode escapes as defined in
   1.331 +     * section 3.3 of
   1.332 +     * <cite>The Java&trade; Language Specification</cite>.
   1.333 +     * <p>
   1.334 +     * The specified stream remains open after this method returns.
   1.335 +     *
   1.336 +     * @param      inStream   the input stream.
   1.337 +     * @exception  IOException  if an error occurred when reading from the
   1.338 +     *             input stream.
   1.339 +     * @throws     IllegalArgumentException if the input stream contains a
   1.340 +     *             malformed Unicode escape sequence.
   1.341 +     * @since 1.2
   1.342 +     */
   1.343 +    public synchronized void load(InputStream inStream) throws IOException {
   1.344 +        load0(new LineReader(inStream));
   1.345 +    }
   1.346 +
   1.347 +    private void load0 (LineReader lr) throws IOException {
   1.348 +        char[] convtBuf = new char[1024];
   1.349 +        int limit;
   1.350 +        int keyLen;
   1.351 +        int valueStart;
   1.352 +        char c;
   1.353 +        boolean hasSep;
   1.354 +        boolean precedingBackslash;
   1.355 +
   1.356 +        while ((limit = lr.readLine()) >= 0) {
   1.357 +            c = 0;
   1.358 +            keyLen = 0;
   1.359 +            valueStart = limit;
   1.360 +            hasSep = false;
   1.361 +
   1.362 +            //System.out.println("line=<" + new String(lineBuf, 0, limit) + ">");
   1.363 +            precedingBackslash = false;
   1.364 +            while (keyLen < limit) {
   1.365 +                c = lr.lineBuf[keyLen];
   1.366 +                //need check if escaped.
   1.367 +                if ((c == '=' ||  c == ':') && !precedingBackslash) {
   1.368 +                    valueStart = keyLen + 1;
   1.369 +                    hasSep = true;
   1.370 +                    break;
   1.371 +                } else if ((c == ' ' || c == '\t' ||  c == '\f') && !precedingBackslash) {
   1.372 +                    valueStart = keyLen + 1;
   1.373 +                    break;
   1.374 +                }
   1.375 +                if (c == '\\') {
   1.376 +                    precedingBackslash = !precedingBackslash;
   1.377 +                } else {
   1.378 +                    precedingBackslash = false;
   1.379 +                }
   1.380 +                keyLen++;
   1.381 +            }
   1.382 +            while (valueStart < limit) {
   1.383 +                c = lr.lineBuf[valueStart];
   1.384 +                if (c != ' ' && c != '\t' &&  c != '\f') {
   1.385 +                    if (!hasSep && (c == '=' ||  c == ':')) {
   1.386 +                        hasSep = true;
   1.387 +                    } else {
   1.388 +                        break;
   1.389 +                    }
   1.390 +                }
   1.391 +                valueStart++;
   1.392 +            }
   1.393 +            String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf);
   1.394 +            String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, convtBuf);
   1.395 +            put(key, value);
   1.396 +        }
   1.397 +    }
   1.398 +
   1.399 +    /* Read in a "logical line" from an InputStream/Reader, skip all comment
   1.400 +     * and blank lines and filter out those leading whitespace characters
   1.401 +     * (\u0020, \u0009 and \u000c) from the beginning of a "natural line".
   1.402 +     * Method returns the char length of the "logical line" and stores
   1.403 +     * the line in "lineBuf".
   1.404 +     */
   1.405 +    class LineReader {
   1.406 +        public LineReader(InputStream inStream) {
   1.407 +            this.inStream = inStream;
   1.408 +            inByteBuf = new byte[8192];
   1.409 +        }
   1.410 +
   1.411 +        public LineReader(Reader reader) {
   1.412 +            this.reader = reader;
   1.413 +            inCharBuf = new char[8192];
   1.414 +        }
   1.415 +
   1.416 +        byte[] inByteBuf;
   1.417 +        char[] inCharBuf;
   1.418 +        char[] lineBuf = new char[1024];
   1.419 +        int inLimit = 0;
   1.420 +        int inOff = 0;
   1.421 +        InputStream inStream;
   1.422 +        Reader reader;
   1.423 +
   1.424 +        int readLine() throws IOException {
   1.425 +            int len = 0;
   1.426 +            char c = 0;
   1.427 +
   1.428 +            boolean skipWhiteSpace = true;
   1.429 +            boolean isCommentLine = false;
   1.430 +            boolean isNewLine = true;
   1.431 +            boolean appendedLineBegin = false;
   1.432 +            boolean precedingBackslash = false;
   1.433 +            boolean skipLF = false;
   1.434 +
   1.435 +            while (true) {
   1.436 +                if (inOff >= inLimit) {
   1.437 +                    inLimit = (inStream==null)?reader.read(inCharBuf)
   1.438 +                                              :inStream.read(inByteBuf);
   1.439 +                    inOff = 0;
   1.440 +                    if (inLimit <= 0) {
   1.441 +                        if (len == 0 || isCommentLine) {
   1.442 +                            return -1;
   1.443 +                        }
   1.444 +                        return len;
   1.445 +                    }
   1.446 +                }
   1.447 +                if (inStream != null) {
   1.448 +                    //The line below is equivalent to calling a
   1.449 +                    //ISO8859-1 decoder.
   1.450 +                    c = (char) (0xff & inByteBuf[inOff++]);
   1.451 +                } else {
   1.452 +                    c = inCharBuf[inOff++];
   1.453 +                }
   1.454 +                if (skipLF) {
   1.455 +                    skipLF = false;
   1.456 +                    if (c == '\n') {
   1.457 +                        continue;
   1.458 +                    }
   1.459 +                }
   1.460 +                if (skipWhiteSpace) {
   1.461 +                    if (c == ' ' || c == '\t' || c == '\f') {
   1.462 +                        continue;
   1.463 +                    }
   1.464 +                    if (!appendedLineBegin && (c == '\r' || c == '\n')) {
   1.465 +                        continue;
   1.466 +                    }
   1.467 +                    skipWhiteSpace = false;
   1.468 +                    appendedLineBegin = false;
   1.469 +                }
   1.470 +                if (isNewLine) {
   1.471 +                    isNewLine = false;
   1.472 +                    if (c == '#' || c == '!') {
   1.473 +                        isCommentLine = true;
   1.474 +                        continue;
   1.475 +                    }
   1.476 +                }
   1.477 +
   1.478 +                if (c != '\n' && c != '\r') {
   1.479 +                    lineBuf[len++] = c;
   1.480 +                    if (len == lineBuf.length) {
   1.481 +                        int newLength = lineBuf.length * 2;
   1.482 +                        if (newLength < 0) {
   1.483 +                            newLength = Integer.MAX_VALUE;
   1.484 +                        }
   1.485 +                        char[] buf = new char[newLength];
   1.486 +                        System.arraycopy(lineBuf, 0, buf, 0, lineBuf.length);
   1.487 +                        lineBuf = buf;
   1.488 +                    }
   1.489 +                    //flip the preceding backslash flag
   1.490 +                    if (c == '\\') {
   1.491 +                        precedingBackslash = !precedingBackslash;
   1.492 +                    } else {
   1.493 +                        precedingBackslash = false;
   1.494 +                    }
   1.495 +                }
   1.496 +                else {
   1.497 +                    // reached EOL
   1.498 +                    if (isCommentLine || len == 0) {
   1.499 +                        isCommentLine = false;
   1.500 +                        isNewLine = true;
   1.501 +                        skipWhiteSpace = true;
   1.502 +                        len = 0;
   1.503 +                        continue;
   1.504 +                    }
   1.505 +                    if (inOff >= inLimit) {
   1.506 +                        inLimit = (inStream==null)
   1.507 +                                  ?reader.read(inCharBuf)
   1.508 +                                  :inStream.read(inByteBuf);
   1.509 +                        inOff = 0;
   1.510 +                        if (inLimit <= 0) {
   1.511 +                            return len;
   1.512 +                        }
   1.513 +                    }
   1.514 +                    if (precedingBackslash) {
   1.515 +                        len -= 1;
   1.516 +                        //skip the leading whitespace characters in following line
   1.517 +                        skipWhiteSpace = true;
   1.518 +                        appendedLineBegin = true;
   1.519 +                        precedingBackslash = false;
   1.520 +                        if (c == '\r') {
   1.521 +                            skipLF = true;
   1.522 +                        }
   1.523 +                    } else {
   1.524 +                        return len;
   1.525 +                    }
   1.526 +                }
   1.527 +            }
   1.528 +        }
   1.529 +    }
   1.530 +
   1.531 +    /*
   1.532 +     * Converts encoded &#92;uxxxx to unicode chars
   1.533 +     * and changes special saved chars to their original forms
   1.534 +     */
   1.535 +    private String loadConvert (char[] in, int off, int len, char[] convtBuf) {
   1.536 +        if (convtBuf.length < len) {
   1.537 +            int newLen = len * 2;
   1.538 +            if (newLen < 0) {
   1.539 +                newLen = Integer.MAX_VALUE;
   1.540 +            }
   1.541 +            convtBuf = new char[newLen];
   1.542 +        }
   1.543 +        char aChar;
   1.544 +        char[] out = convtBuf;
   1.545 +        int outLen = 0;
   1.546 +        int end = off + len;
   1.547 +
   1.548 +        while (off < end) {
   1.549 +            aChar = in[off++];
   1.550 +            if (aChar == '\\') {
   1.551 +                aChar = in[off++];
   1.552 +                if(aChar == 'u') {
   1.553 +                    // Read the xxxx
   1.554 +                    int value=0;
   1.555 +                    for (int i=0; i<4; i++) {
   1.556 +                        aChar = in[off++];
   1.557 +                        switch (aChar) {
   1.558 +                          case '0': case '1': case '2': case '3': case '4':
   1.559 +                          case '5': case '6': case '7': case '8': case '9':
   1.560 +                             value = (value << 4) + aChar - '0';
   1.561 +                             break;
   1.562 +                          case 'a': case 'b': case 'c':
   1.563 +                          case 'd': case 'e': case 'f':
   1.564 +                             value = (value << 4) + 10 + aChar - 'a';
   1.565 +                             break;
   1.566 +                          case 'A': case 'B': case 'C':
   1.567 +                          case 'D': case 'E': case 'F':
   1.568 +                             value = (value << 4) + 10 + aChar - 'A';
   1.569 +                             break;
   1.570 +                          default:
   1.571 +                              throw new IllegalArgumentException(
   1.572 +                                           "Malformed \\uxxxx encoding.");
   1.573 +                        }
   1.574 +                     }
   1.575 +                    out[outLen++] = (char)value;
   1.576 +                } else {
   1.577 +                    if (aChar == 't') aChar = '\t';
   1.578 +                    else if (aChar == 'r') aChar = '\r';
   1.579 +                    else if (aChar == 'n') aChar = '\n';
   1.580 +                    else if (aChar == 'f') aChar = '\f';
   1.581 +                    out[outLen++] = aChar;
   1.582 +                }
   1.583 +            } else {
   1.584 +                out[outLen++] = aChar;
   1.585 +            }
   1.586 +        }
   1.587 +        return new String (out, 0, outLen);
   1.588 +    }
   1.589 +
   1.590 +    /*
   1.591 +     * Converts unicodes to encoded &#92;uxxxx and escapes
   1.592 +     * special characters with a preceding slash
   1.593 +     */
   1.594 +    private String saveConvert(String theString,
   1.595 +                               boolean escapeSpace,
   1.596 +                               boolean escapeUnicode) {
   1.597 +        int len = theString.length();
   1.598 +        int bufLen = len * 2;
   1.599 +        if (bufLen < 0) {
   1.600 +            bufLen = Integer.MAX_VALUE;
   1.601 +        }
   1.602 +        StringBuffer outBuffer = new StringBuffer(bufLen);
   1.603 +
   1.604 +        for(int x=0; x<len; x++) {
   1.605 +            char aChar = theString.charAt(x);
   1.606 +            // Handle common case first, selecting largest block that
   1.607 +            // avoids the specials below
   1.608 +            if ((aChar > 61) && (aChar < 127)) {
   1.609 +                if (aChar == '\\') {
   1.610 +                    outBuffer.append('\\'); outBuffer.append('\\');
   1.611 +                    continue;
   1.612 +                }
   1.613 +                outBuffer.append(aChar);
   1.614 +                continue;
   1.615 +            }
   1.616 +            switch(aChar) {
   1.617 +                case ' ':
   1.618 +                    if (x == 0 || escapeSpace)
   1.619 +                        outBuffer.append('\\');
   1.620 +                    outBuffer.append(' ');
   1.621 +                    break;
   1.622 +                case '\t':outBuffer.append('\\'); outBuffer.append('t');
   1.623 +                          break;
   1.624 +                case '\n':outBuffer.append('\\'); outBuffer.append('n');
   1.625 +                          break;
   1.626 +                case '\r':outBuffer.append('\\'); outBuffer.append('r');
   1.627 +                          break;
   1.628 +                case '\f':outBuffer.append('\\'); outBuffer.append('f');
   1.629 +                          break;
   1.630 +                case '=': // Fall through
   1.631 +                case ':': // Fall through
   1.632 +                case '#': // Fall through
   1.633 +                case '!':
   1.634 +                    outBuffer.append('\\'); outBuffer.append(aChar);
   1.635 +                    break;
   1.636 +                default:
   1.637 +                    if (((aChar < 0x0020) || (aChar > 0x007e)) & escapeUnicode ) {
   1.638 +                        outBuffer.append('\\');
   1.639 +                        outBuffer.append('u');
   1.640 +                        outBuffer.append(toHex((aChar >> 12) & 0xF));
   1.641 +                        outBuffer.append(toHex((aChar >>  8) & 0xF));
   1.642 +                        outBuffer.append(toHex((aChar >>  4) & 0xF));
   1.643 +                        outBuffer.append(toHex( aChar        & 0xF));
   1.644 +                    } else {
   1.645 +                        outBuffer.append(aChar);
   1.646 +                    }
   1.647 +            }
   1.648 +        }
   1.649 +        return outBuffer.toString();
   1.650 +    }
   1.651 +
   1.652 +    private static void writeComments(BufferedWriter bw, String comments)
   1.653 +        throws IOException {
   1.654 +        bw.write("#");
   1.655 +        int len = comments.length();
   1.656 +        int current = 0;
   1.657 +        int last = 0;
   1.658 +        char[] uu = new char[6];
   1.659 +        uu[0] = '\\';
   1.660 +        uu[1] = 'u';
   1.661 +        while (current < len) {
   1.662 +            char c = comments.charAt(current);
   1.663 +            if (c > '\u00ff' || c == '\n' || c == '\r') {
   1.664 +                if (last != current)
   1.665 +                    bw.write(comments.substring(last, current));
   1.666 +                if (c > '\u00ff') {
   1.667 +                    uu[2] = toHex((c >> 12) & 0xf);
   1.668 +                    uu[3] = toHex((c >>  8) & 0xf);
   1.669 +                    uu[4] = toHex((c >>  4) & 0xf);
   1.670 +                    uu[5] = toHex( c        & 0xf);
   1.671 +                    bw.write(new String(uu));
   1.672 +                } else {
   1.673 +                    bw.newLine();
   1.674 +                    if (c == '\r' &&
   1.675 +                        current != len - 1 &&
   1.676 +                        comments.charAt(current + 1) == '\n') {
   1.677 +                        current++;
   1.678 +                    }
   1.679 +                    if (current == len - 1 ||
   1.680 +                        (comments.charAt(current + 1) != '#' &&
   1.681 +                        comments.charAt(current + 1) != '!'))
   1.682 +                        bw.write("#");
   1.683 +                }
   1.684 +                last = current + 1;
   1.685 +            }
   1.686 +            current++;
   1.687 +        }
   1.688 +        if (last != current)
   1.689 +            bw.write(comments.substring(last, current));
   1.690 +        bw.newLine();
   1.691 +    }
   1.692 +
   1.693 +    /**
   1.694 +     * Calls the <code>store(OutputStream out, String comments)</code> method
   1.695 +     * and suppresses IOExceptions that were thrown.
   1.696 +     *
   1.697 +     * @deprecated This method does not throw an IOException if an I/O error
   1.698 +     * occurs while saving the property list.  The preferred way to save a
   1.699 +     * properties list is via the <code>store(OutputStream out,
   1.700 +     * String comments)</code> method or the
   1.701 +     * <code>storeToXML(OutputStream os, String comment)</code> method.
   1.702 +     *
   1.703 +     * @param   out      an output stream.
   1.704 +     * @param   comments   a description of the property list.
   1.705 +     * @exception  ClassCastException  if this <code>Properties</code> object
   1.706 +     *             contains any keys or values that are not
   1.707 +     *             <code>Strings</code>.
   1.708 +     */
   1.709 +    @Deprecated
   1.710 +    public void save(OutputStream out, String comments)  {
   1.711 +        try {
   1.712 +            store(out, comments);
   1.713 +        } catch (IOException e) {
   1.714 +        }
   1.715 +    }
   1.716 +
   1.717 +    /**
   1.718 +     * Writes this property list (key and element pairs) in this
   1.719 +     * <code>Properties</code> table to the output character stream in a
   1.720 +     * format suitable for using the {@link #load(java.io.Reader) load(Reader)}
   1.721 +     * method.
   1.722 +     * <p>
   1.723 +     * Properties from the defaults table of this <code>Properties</code>
   1.724 +     * table (if any) are <i>not</i> written out by this method.
   1.725 +     * <p>
   1.726 +     * If the comments argument is not null, then an ASCII <code>#</code>
   1.727 +     * character, the comments string, and a line separator are first written
   1.728 +     * to the output stream. Thus, the <code>comments</code> can serve as an
   1.729 +     * identifying comment. Any one of a line feed ('\n'), a carriage
   1.730 +     * return ('\r'), or a carriage return followed immediately by a line feed
   1.731 +     * in comments is replaced by a line separator generated by the <code>Writer</code>
   1.732 +     * and if the next character in comments is not character <code>#</code> or
   1.733 +     * character <code>!</code> then an ASCII <code>#</code> is written out
   1.734 +     * after that line separator.
   1.735 +     * <p>
   1.736 +     * Next, a comment line is always written, consisting of an ASCII
   1.737 +     * <code>#</code> character, the current date and time (as if produced
   1.738 +     * by the <code>toString</code> method of <code>Date</code> for the
   1.739 +     * current time), and a line separator as generated by the <code>Writer</code>.
   1.740 +     * <p>
   1.741 +     * Then every entry in this <code>Properties</code> table is
   1.742 +     * written out, one per line. For each entry the key string is
   1.743 +     * written, then an ASCII <code>=</code>, then the associated
   1.744 +     * element string. For the key, all space characters are
   1.745 +     * written with a preceding <code>\</code> character.  For the
   1.746 +     * element, leading space characters, but not embedded or trailing
   1.747 +     * space characters, are written with a preceding <code>\</code>
   1.748 +     * character. The key and element characters <code>#</code>,
   1.749 +     * <code>!</code>, <code>=</code>, and <code>:</code> are written
   1.750 +     * with a preceding backslash to ensure that they are properly loaded.
   1.751 +     * <p>
   1.752 +     * After the entries have been written, the output stream is flushed.
   1.753 +     * The output stream remains open after this method returns.
   1.754 +     * <p>
   1.755 +     *
   1.756 +     * @param   writer      an output character stream writer.
   1.757 +     * @param   comments   a description of the property list.
   1.758 +     * @exception  IOException if writing this property list to the specified
   1.759 +     *             output stream throws an <tt>IOException</tt>.
   1.760 +     * @exception  ClassCastException  if this <code>Properties</code> object
   1.761 +     *             contains any keys or values that are not <code>Strings</code>.
   1.762 +     * @exception  NullPointerException  if <code>writer</code> is null.
   1.763 +     * @since 1.6
   1.764 +     */
   1.765 +    public void store(Writer writer, String comments)
   1.766 +        throws IOException
   1.767 +    {
   1.768 +        store0((writer instanceof BufferedWriter)?(BufferedWriter)writer
   1.769 +                                                 : new BufferedWriter(writer),
   1.770 +               comments,
   1.771 +               false);
   1.772 +    }
   1.773 +
   1.774 +    /**
   1.775 +     * Writes this property list (key and element pairs) in this
   1.776 +     * <code>Properties</code> table to the output stream in a format suitable
   1.777 +     * for loading into a <code>Properties</code> table using the
   1.778 +     * {@link #load(InputStream) load(InputStream)} method.
   1.779 +     * <p>
   1.780 +     * Properties from the defaults table of this <code>Properties</code>
   1.781 +     * table (if any) are <i>not</i> written out by this method.
   1.782 +     * <p>
   1.783 +     * This method outputs the comments, properties keys and values in
   1.784 +     * the same format as specified in
   1.785 +     * {@link #store(java.io.Writer, java.lang.String) store(Writer)},
   1.786 +     * with the following differences:
   1.787 +     * <ul>
   1.788 +     * <li>The stream is written using the ISO 8859-1 character encoding.
   1.789 +     *
   1.790 +     * <li>Characters not in Latin-1 in the comments are written as
   1.791 +     * <code>&#92;u</code><i>xxxx</i> for their appropriate unicode
   1.792 +     * hexadecimal value <i>xxxx</i>.
   1.793 +     *
   1.794 +     * <li>Characters less than <code>&#92;u0020</code> and characters greater
   1.795 +     * than <code>&#92;u007E</code> in property keys or values are written
   1.796 +     * as <code>&#92;u</code><i>xxxx</i> for the appropriate hexadecimal
   1.797 +     * value <i>xxxx</i>.
   1.798 +     * </ul>
   1.799 +     * <p>
   1.800 +     * After the entries have been written, the output stream is flushed.
   1.801 +     * The output stream remains open after this method returns.
   1.802 +     * <p>
   1.803 +     * @param   out      an output stream.
   1.804 +     * @param   comments   a description of the property list.
   1.805 +     * @exception  IOException if writing this property list to the specified
   1.806 +     *             output stream throws an <tt>IOException</tt>.
   1.807 +     * @exception  ClassCastException  if this <code>Properties</code> object
   1.808 +     *             contains any keys or values that are not <code>Strings</code>.
   1.809 +     * @exception  NullPointerException  if <code>out</code> is null.
   1.810 +     * @since 1.2
   1.811 +     */
   1.812 +    public void store(OutputStream out, String comments)
   1.813 +        throws IOException
   1.814 +    {
   1.815 +        store0(new BufferedWriter(new OutputStreamWriter(out, "8859_1")),
   1.816 +               comments,
   1.817 +               true);
   1.818 +    }
   1.819 +
   1.820 +    private void store0(BufferedWriter bw, String comments, boolean escUnicode)
   1.821 +        throws IOException
   1.822 +    {
   1.823 +        if (comments != null) {
   1.824 +            writeComments(bw, comments);
   1.825 +        }
   1.826 +        bw.write("#" + new Date().toString());
   1.827 +        bw.newLine();
   1.828 +        synchronized (this) {
   1.829 +            for (Enumeration e = keys(); e.hasMoreElements();) {
   1.830 +                String key = (String)e.nextElement();
   1.831 +                String val = (String)get(key);
   1.832 +                key = saveConvert(key, true, escUnicode);
   1.833 +                /* No need to escape embedded and trailing spaces for value, hence
   1.834 +                 * pass false to flag.
   1.835 +                 */
   1.836 +                val = saveConvert(val, false, escUnicode);
   1.837 +                bw.write(key + "=" + val);
   1.838 +                bw.newLine();
   1.839 +            }
   1.840 +        }
   1.841 +        bw.flush();
   1.842 +    }
   1.843 +
   1.844 +    /**
   1.845 +     * Loads all of the properties represented by the XML document on the
   1.846 +     * specified input stream into this properties table.
   1.847 +     *
   1.848 +     * <p>The XML document must have the following DOCTYPE declaration:
   1.849 +     * <pre>
   1.850 +     * &lt;!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"&gt;
   1.851 +     * </pre>
   1.852 +     * Furthermore, the document must satisfy the properties DTD described
   1.853 +     * above.
   1.854 +     *
   1.855 +     * <p>The specified stream is closed after this method returns.
   1.856 +     *
   1.857 +     * @param in the input stream from which to read the XML document.
   1.858 +     * @throws IOException if reading from the specified input stream
   1.859 +     *         results in an <tt>IOException</tt>.
   1.860 +     * @throws InvalidPropertiesFormatException Data on input stream does not
   1.861 +     *         constitute a valid XML document with the mandated document type.
   1.862 +     * @throws NullPointerException if <code>in</code> is null.
   1.863 +     * @see    #storeToXML(OutputStream, String, String)
   1.864 +     * @since 1.5
   1.865 +     */
   1.866 +    public synchronized void loadFromXML(InputStream in)
   1.867 +        throws IOException, InvalidPropertiesFormatException
   1.868 +    {
   1.869 +        if (in == null)
   1.870 +            throw new NullPointerException();
   1.871 +        XMLUtils.load(this, in);
   1.872 +        in.close();
   1.873 +    }
   1.874 +
   1.875 +    /**
   1.876 +     * Emits an XML document representing all of the properties contained
   1.877 +     * in this table.
   1.878 +     *
   1.879 +     * <p> An invocation of this method of the form <tt>props.storeToXML(os,
   1.880 +     * comment)</tt> behaves in exactly the same way as the invocation
   1.881 +     * <tt>props.storeToXML(os, comment, "UTF-8");</tt>.
   1.882 +     *
   1.883 +     * @param os the output stream on which to emit the XML document.
   1.884 +     * @param comment a description of the property list, or <code>null</code>
   1.885 +     *        if no comment is desired.
   1.886 +     * @throws IOException if writing to the specified output stream
   1.887 +     *         results in an <tt>IOException</tt>.
   1.888 +     * @throws NullPointerException if <code>os</code> is null.
   1.889 +     * @throws ClassCastException  if this <code>Properties</code> object
   1.890 +     *         contains any keys or values that are not
   1.891 +     *         <code>Strings</code>.
   1.892 +     * @see    #loadFromXML(InputStream)
   1.893 +     * @since 1.5
   1.894 +     */
   1.895 +    public void storeToXML(OutputStream os, String comment)
   1.896 +        throws IOException
   1.897 +    {
   1.898 +        if (os == null)
   1.899 +            throw new NullPointerException();
   1.900 +        storeToXML(os, comment, "UTF-8");
   1.901 +    }
   1.902 +
   1.903 +    /**
   1.904 +     * Emits an XML document representing all of the properties contained
   1.905 +     * in this table, using the specified encoding.
   1.906 +     *
   1.907 +     * <p>The XML document will have the following DOCTYPE declaration:
   1.908 +     * <pre>
   1.909 +     * &lt;!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"&gt;
   1.910 +     * </pre>
   1.911 +     *
   1.912 +     *<p>If the specified comment is <code>null</code> then no comment
   1.913 +     * will be stored in the document.
   1.914 +     *
   1.915 +     * <p>The specified stream remains open after this method returns.
   1.916 +     *
   1.917 +     * @param os        the output stream on which to emit the XML document.
   1.918 +     * @param comment   a description of the property list, or <code>null</code>
   1.919 +     *                  if no comment is desired.
   1.920 +     * @param  encoding the name of a supported
   1.921 +     *                  <a href="../lang/package-summary.html#charenc">
   1.922 +     *                  character encoding</a>
   1.923 +     *
   1.924 +     * @throws IOException if writing to the specified output stream
   1.925 +     *         results in an <tt>IOException</tt>.
   1.926 +     * @throws NullPointerException if <code>os</code> is <code>null</code>,
   1.927 +     *         or if <code>encoding</code> is <code>null</code>.
   1.928 +     * @throws ClassCastException  if this <code>Properties</code> object
   1.929 +     *         contains any keys or values that are not
   1.930 +     *         <code>Strings</code>.
   1.931 +     * @see    #loadFromXML(InputStream)
   1.932 +     * @since 1.5
   1.933 +     */
   1.934 +    public void storeToXML(OutputStream os, String comment, String encoding)
   1.935 +        throws IOException
   1.936 +    {
   1.937 +        if (os == null)
   1.938 +            throw new NullPointerException();
   1.939 +        XMLUtils.save(this, os, comment, encoding);
   1.940 +    }
   1.941 +
   1.942 +    /**
   1.943 +     * Searches for the property with the specified key in this property list.
   1.944 +     * If the key is not found in this property list, the default property list,
   1.945 +     * and its defaults, recursively, are then checked. The method returns
   1.946 +     * <code>null</code> if the property is not found.
   1.947 +     *
   1.948 +     * @param   key   the property key.
   1.949 +     * @return  the value in this property list with the specified key value.
   1.950 +     * @see     #setProperty
   1.951 +     * @see     #defaults
   1.952 +     */
   1.953 +    public String getProperty(String key) {
   1.954 +        Object oval = super.get(key);
   1.955 +        String sval = (oval instanceof String) ? (String)oval : null;
   1.956 +        return ((sval == null) && (defaults != null)) ? defaults.getProperty(key) : sval;
   1.957 +    }
   1.958 +
   1.959 +    /**
   1.960 +     * Searches for the property with the specified key in this property list.
   1.961 +     * If the key is not found in this property list, the default property list,
   1.962 +     * and its defaults, recursively, are then checked. The method returns the
   1.963 +     * default value argument if the property is not found.
   1.964 +     *
   1.965 +     * @param   key            the hashtable key.
   1.966 +     * @param   defaultValue   a default value.
   1.967 +     *
   1.968 +     * @return  the value in this property list with the specified key value.
   1.969 +     * @see     #setProperty
   1.970 +     * @see     #defaults
   1.971 +     */
   1.972 +    public String getProperty(String key, String defaultValue) {
   1.973 +        String val = getProperty(key);
   1.974 +        return (val == null) ? defaultValue : val;
   1.975 +    }
   1.976 +
   1.977 +    /**
   1.978 +     * Returns an enumeration of all the keys in this property list,
   1.979 +     * including distinct keys in the default property list if a key
   1.980 +     * of the same name has not already been found from the main
   1.981 +     * properties list.
   1.982 +     *
   1.983 +     * @return  an enumeration of all the keys in this property list, including
   1.984 +     *          the keys in the default property list.
   1.985 +     * @throws  ClassCastException if any key in this property list
   1.986 +     *          is not a string.
   1.987 +     * @see     java.util.Enumeration
   1.988 +     * @see     java.util.Properties#defaults
   1.989 +     * @see     #stringPropertyNames
   1.990 +     */
   1.991 +    public Enumeration<?> propertyNames() {
   1.992 +        Hashtable h = new Hashtable();
   1.993 +        enumerate(h);
   1.994 +        return h.keys();
   1.995 +    }
   1.996 +
   1.997 +    /**
   1.998 +     * Returns a set of keys in this property list where
   1.999 +     * the key and its corresponding value are strings,
  1.1000 +     * including distinct keys in the default property list if a key
  1.1001 +     * of the same name has not already been found from the main
  1.1002 +     * properties list.  Properties whose key or value is not
  1.1003 +     * of type <tt>String</tt> are omitted.
  1.1004 +     * <p>
  1.1005 +     * The returned set is not backed by the <tt>Properties</tt> object.
  1.1006 +     * Changes to this <tt>Properties</tt> are not reflected in the set,
  1.1007 +     * or vice versa.
  1.1008 +     *
  1.1009 +     * @return  a set of keys in this property list where
  1.1010 +     *          the key and its corresponding value are strings,
  1.1011 +     *          including the keys in the default property list.
  1.1012 +     * @see     java.util.Properties#defaults
  1.1013 +     * @since   1.6
  1.1014 +     */
  1.1015 +    public Set<String> stringPropertyNames() {
  1.1016 +        Hashtable<String, String> h = new Hashtable<>();
  1.1017 +        enumerateStringProperties(h);
  1.1018 +        return h.keySet();
  1.1019 +    }
  1.1020 +
  1.1021 +    /**
  1.1022 +     * Prints this property list out to the specified output stream.
  1.1023 +     * This method is useful for debugging.
  1.1024 +     *
  1.1025 +     * @param   out   an output stream.
  1.1026 +     * @throws  ClassCastException if any key in this property list
  1.1027 +     *          is not a string.
  1.1028 +     */
  1.1029 +    public void list(PrintStream out) {
  1.1030 +        out.println("-- listing properties --");
  1.1031 +        Hashtable h = new Hashtable();
  1.1032 +        enumerate(h);
  1.1033 +        for (Enumeration e = h.keys() ; e.hasMoreElements() ;) {
  1.1034 +            String key = (String)e.nextElement();
  1.1035 +            String val = (String)h.get(key);
  1.1036 +            if (val.length() > 40) {
  1.1037 +                val = val.substring(0, 37) + "...";
  1.1038 +            }
  1.1039 +            out.println(key + "=" + val);
  1.1040 +        }
  1.1041 +    }
  1.1042 +
  1.1043 +    /**
  1.1044 +     * Prints this property list out to the specified output stream.
  1.1045 +     * This method is useful for debugging.
  1.1046 +     *
  1.1047 +     * @param   out   an output stream.
  1.1048 +     * @throws  ClassCastException if any key in this property list
  1.1049 +     *          is not a string.
  1.1050 +     * @since   JDK1.1
  1.1051 +     */
  1.1052 +    /*
  1.1053 +     * Rather than use an anonymous inner class to share common code, this
  1.1054 +     * method is duplicated in order to ensure that a non-1.1 compiler can
  1.1055 +     * compile this file.
  1.1056 +     */
  1.1057 +    public void list(PrintWriter out) {
  1.1058 +        out.println("-- listing properties --");
  1.1059 +        Hashtable h = new Hashtable();
  1.1060 +        enumerate(h);
  1.1061 +        for (Enumeration e = h.keys() ; e.hasMoreElements() ;) {
  1.1062 +            String key = (String)e.nextElement();
  1.1063 +            String val = (String)h.get(key);
  1.1064 +            if (val.length() > 40) {
  1.1065 +                val = val.substring(0, 37) + "...";
  1.1066 +            }
  1.1067 +            out.println(key + "=" + val);
  1.1068 +        }
  1.1069 +    }
  1.1070 +
  1.1071 +    /**
  1.1072 +     * Enumerates all key/value pairs in the specified hashtable.
  1.1073 +     * @param h the hashtable
  1.1074 +     * @throws ClassCastException if any of the property keys
  1.1075 +     *         is not of String type.
  1.1076 +     */
  1.1077 +    private synchronized void enumerate(Hashtable h) {
  1.1078 +        if (defaults != null) {
  1.1079 +            defaults.enumerate(h);
  1.1080 +        }
  1.1081 +        for (Enumeration e = keys() ; e.hasMoreElements() ;) {
  1.1082 +            String key = (String)e.nextElement();
  1.1083 +            h.put(key, get(key));
  1.1084 +        }
  1.1085 +    }
  1.1086 +
  1.1087 +    /**
  1.1088 +     * Enumerates all key/value pairs in the specified hashtable
  1.1089 +     * and omits the property if the key or value is not a string.
  1.1090 +     * @param h the hashtable
  1.1091 +     */
  1.1092 +    private synchronized void enumerateStringProperties(Hashtable<String, String> h) {
  1.1093 +        if (defaults != null) {
  1.1094 +            defaults.enumerateStringProperties(h);
  1.1095 +        }
  1.1096 +        for (Enumeration e = keys() ; e.hasMoreElements() ;) {
  1.1097 +            Object k = e.nextElement();
  1.1098 +            Object v = get(k);
  1.1099 +            if (k instanceof String && v instanceof String) {
  1.1100 +                h.put((String) k, (String) v);
  1.1101 +            }
  1.1102 +        }
  1.1103 +    }
  1.1104 +
  1.1105 +    /**
  1.1106 +     * Convert a nibble to a hex character
  1.1107 +     * @param   nibble  the nibble to convert.
  1.1108 +     */
  1.1109 +    private static char toHex(int nibble) {
  1.1110 +        return hexDigit[(nibble & 0xF)];
  1.1111 +    }
  1.1112 +
  1.1113 +    /** A table of hex digits */
  1.1114 +    private static final char[] hexDigit = {
  1.1115 +        '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'
  1.1116 +    };
  1.1117 +}