1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 * Copyright (c) 1995, 2013, Oracle and/or its affiliates. All rights reserved.
4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 *
6 * This code is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License version 2 only, as
8 * published by the Free Software Foundation.  Oracle designates this
9 * particular file as subject to the "Classpath" exception as provided
10 * by Oracle in the LICENSE file that accompanied this code.
11 *
12 * This code is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15 * version 2 for more details (a copy is included in the LICENSE file that
16 * accompanied this code).
17 *
18 * You should have received a copy of the GNU General Public License version
19 * 2 along with this work; if not, write to the Free Software Foundation,
20 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21 *
22 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
23 * or visit www.oracle.com if you need additional information or have any
24 * questions.
25 */
26
27package java.util;
28
29import java.io.IOException;
30import java.io.PrintStream;
31import java.io.PrintWriter;
32import java.io.InputStream;
33import java.io.OutputStream;
34import java.io.Reader;
35import java.io.Writer;
36import java.io.OutputStreamWriter;
37import java.io.BufferedWriter;
38
39// Android-changed: Removed dead native2ascii links
40/**
41 * The {@code Properties} class represents a persistent set of
42 * properties. The {@code Properties} can be saved to a stream
43 * or loaded from a stream. Each key and its corresponding value in
44 * the property list is a string.
45 * <p>
46 * A property list can contain another property list as its
47 * "defaults"; this second property list is searched if
48 * the property key is not found in the original property list.
49 * <p>
50 * Because {@code Properties} inherits from {@code Hashtable}, the
51 * {@code put} and {@code putAll} methods can be applied to a
52 * {@code Properties} object.  Their use is strongly discouraged as they
53 * allow the caller to insert entries whose keys or values are not
54 * {@code Strings}.  The {@code setProperty} method should be used
55 * instead.  If the {@code store} or {@code save} method is called
56 * on a "compromised" {@code Properties} object that contains a
57 * non-{@code String} key or value, the call will fail. Similarly,
58 * the call to the {@code propertyNames} or {@code list} method
59 * will fail if it is called on a "compromised" {@code Properties}
60 * object that contains a non-{@code String} key.
61 *
62 * <p>
63 * The {@link #load(java.io.Reader) load(Reader)} <tt>/</tt>
64 * {@link #store(java.io.Writer, java.lang.String) store(Writer, String)}
65 * methods load and store properties from and to a character based stream
66 * in a simple line-oriented format specified below.
67 *
68 * The {@link #load(java.io.InputStream) load(InputStream)} <tt>/</tt>
69 * {@link #store(java.io.OutputStream, java.lang.String) store(OutputStream, String)}
70 * methods work the same way as the load(Reader)/store(Writer, String) pair, except
71 * the input/output stream is encoded in ISO 8859-1 character encoding.
72 * Characters that cannot be directly represented in this encoding can be written using
73 * Unicode escapes as defined in section 3.3 of
74 * <cite>The Java&trade; Language Specification</cite>;
75 * only a single 'u' character is allowed in an escape
76 * sequence. The native2ascii tool can be used to convert property files to and
77 * from other character encodings.
78 *
79 * <p> The {@link #loadFromXML(InputStream)} and {@link
80 * #storeToXML(OutputStream, String, String)} methods load and store properties
81 * in a simple XML format.  By default the UTF-8 character encoding is used,
82 * however a specific encoding may be specified if required. Implementations
83 * are required to support UTF-8 and UTF-16 and may support other encodings.
84 * An XML properties document has the following DOCTYPE declaration:
85 *
86 * <pre>
87 * &lt;!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"&gt;
88 * </pre>
89 * Note that the system URI (http://java.sun.com/dtd/properties.dtd) is
90 * <i>not</i> accessed when exporting or importing properties; it merely
91 * serves as a string to uniquely identify the DTD, which is:
92 * <pre>
93 *    &lt;?xml version="1.0" encoding="UTF-8"?&gt;
94 *
95 *    &lt;!-- DTD for properties --&gt;
96 *
97 *    &lt;!ELEMENT properties ( comment?, entry* ) &gt;
98 *
99 *    &lt;!ATTLIST properties version CDATA #FIXED "1.0"&gt;
100 *
101 *    &lt;!ELEMENT comment (#PCDATA) &gt;
102 *
103 *    &lt;!ELEMENT entry (#PCDATA) &gt;
104 *
105 *    &lt;!ATTLIST entry key CDATA #REQUIRED&gt;
106 * </pre>
107 *
108 * <p>This class is thread-safe: multiple threads can share a single
109 * <tt>Properties</tt> object without the need for external synchronization.
110 *
111 * @author  Arthur van Hoff
112 * @author  Michael McCloskey
113 * @author  Xueming Shen
114 * @since   JDK1.0
115 */
116public
117class Properties extends Hashtable<Object,Object> {
118    /**
119     * use serialVersionUID from JDK 1.1.X for interoperability
120     */
121     private static final long serialVersionUID = 4112578634029874840L;
122
123    /**
124     * A property list that contains default values for any keys not
125     * found in this property list.
126     *
127     * @serial
128     */
129    protected Properties defaults;
130
131    /**
132     * Creates an empty property list with no default values.
133     */
134    public Properties() {
135        this(null);
136    }
137
138    /**
139     * Creates an empty property list with the specified defaults.
140     *
141     * @param   defaults   the defaults.
142     */
143    public Properties(Properties defaults) {
144        this.defaults = defaults;
145    }
146
147    /**
148     * Calls the <tt>Hashtable</tt> method {@code put}. Provided for
149     * parallelism with the <tt>getProperty</tt> method. Enforces use of
150     * strings for property keys and values. The value returned is the
151     * result of the <tt>Hashtable</tt> call to {@code put}.
152     *
153     * @param key the key to be placed into this property list.
154     * @param value the value corresponding to <tt>key</tt>.
155     * @return     the previous value of the specified key in this property
156     *             list, or {@code null} if it did not have one.
157     * @see #getProperty
158     * @since    1.2
159     */
160    public synchronized Object setProperty(String key, String value) {
161        return put(key, value);
162    }
163
164
165    /**
166     * Reads a property list (key and element pairs) from the input
167     * character stream in a simple line-oriented format.
168     * <p>
169     * Properties are processed in terms of lines. There are two
170     * kinds of line, <i>natural lines</i> and <i>logical lines</i>.
171     * A natural line is defined as a line of
172     * characters that is terminated either by a set of line terminator
173     * characters ({@code \n} or {@code \r} or {@code \r\n})
174     * or by the end of the stream. A natural line may be either a blank line,
175     * a comment line, or hold all or some of a key-element pair. A logical
176     * line holds all the data of a key-element pair, which may be spread
177     * out across several adjacent natural lines by escaping
178     * the line terminator sequence with a backslash character
179     * {@code \}.  Note that a comment line cannot be extended
180     * in this manner; every natural line that is a comment must have
181     * its own comment indicator, as described below. Lines are read from
182     * input until the end of the stream is reached.
183     *
184     * <p>
185     * A natural line that contains only white space characters is
186     * considered blank and is ignored.  A comment line has an ASCII
187     * {@code '#'} or {@code '!'} as its first non-white
188     * space character; comment lines are also ignored and do not
189     * encode key-element information.  In addition to line
190     * terminators, this format considers the characters space
191     * ({@code ' '}, {@code '\u005Cu0020'}), tab
192     * ({@code '\t'}, {@code '\u005Cu0009'}), and form feed
193     * ({@code '\f'}, {@code '\u005Cu000C'}) to be white
194     * space.
195     *
196     * <p>
197     * If a logical line is spread across several natural lines, the
198     * backslash escaping the line terminator sequence, the line
199     * terminator sequence, and any white space at the start of the
200     * following line have no affect on the key or element values.
201     * The remainder of the discussion of key and element parsing
202     * (when loading) will assume all the characters constituting
203     * the key and element appear on a single natural line after
204     * line continuation characters have been removed.  Note that
205     * it is <i>not</i> sufficient to only examine the character
206     * preceding a line terminator sequence to decide if the line
207     * terminator is escaped; there must be an odd number of
208     * contiguous backslashes for the line terminator to be escaped.
209     * Since the input is processed from left to right, a
210     * non-zero even number of 2<i>n</i> contiguous backslashes
211     * before a line terminator (or elsewhere) encodes <i>n</i>
212     * backslashes after escape processing.
213     *
214     * <p>
215     * The key contains all of the characters in the line starting
216     * with the first non-white space character and up to, but not
217     * including, the first unescaped {@code '='},
218     * {@code ':'}, or white space character other than a line
219     * terminator. All of these key termination characters may be
220     * included in the key by escaping them with a preceding backslash
221     * character; for example,<p>
222     *
223     * {@code \:\=}<p>
224     *
225     * would be the two-character key {@code ":="}.  Line
226     * terminator characters can be included using {@code \r} and
227     * {@code \n} escape sequences.  Any white space after the
228     * key is skipped; if the first non-white space character after
229     * the key is {@code '='} or {@code ':'}, then it is
230     * ignored and any white space characters after it are also
231     * skipped.  All remaining characters on the line become part of
232     * the associated element string; if there are no remaining
233     * characters, the element is the empty string
234     * {@code ""}.  Once the raw character sequences
235     * constituting the key and element are identified, escape
236     * processing is performed as described above.
237     *
238     * <p>
239     * As an example, each of the following three lines specifies the key
240     * {@code "Truth"} and the associated element value
241     * {@code "Beauty"}:
242     * <pre>
243     * Truth = Beauty
244     *  Truth:Beauty
245     * Truth                    :Beauty
246     * </pre>
247     * As another example, the following three lines specify a single
248     * property:
249     * <pre>
250     * fruits                           apple, banana, pear, \
251     *                                  cantaloupe, watermelon, \
252     *                                  kiwi, mango
253     * </pre>
254     * The key is {@code "fruits"} and the associated element is:
255     * <pre>"apple, banana, pear, cantaloupe, watermelon, kiwi, mango"</pre>
256     * Note that a space appears before each {@code \} so that a space
257     * will appear after each comma in the final result; the {@code \},
258     * line terminator, and leading white space on the continuation line are
259     * merely discarded and are <i>not</i> replaced by one or more other
260     * characters.
261     * <p>
262     * As a third example, the line:
263     * <pre>cheeses
264     * </pre>
265     * specifies that the key is {@code "cheeses"} and the associated
266     * element is the empty string {@code ""}.
267     * <p>
268     * <a name="unicodeescapes"></a>
269     * Characters in keys and elements can be represented in escape
270     * sequences similar to those used for character and string literals
271     * (see sections 3.3 and 3.10.6 of
272     * <cite>The Java&trade; Language Specification</cite>).
273     *
274     * The differences from the character escape sequences and Unicode
275     * escapes used for characters and strings are:
276     *
277     * <ul>
278     * <li> Octal escapes are not recognized.
279     *
280     * <li> The character sequence {@code \b} does <i>not</i>
281     * represent a backspace character.
282     *
283     * <li> The method does not treat a backslash character,
284     * {@code \}, before a non-valid escape character as an
285     * error; the backslash is silently dropped.  For example, in a
286     * Java string the sequence {@code "\z"} would cause a
287     * compile time error.  In contrast, this method silently drops
288     * the backslash.  Therefore, this method treats the two character
289     * sequence {@code "\b"} as equivalent to the single
290     * character {@code 'b'}.
291     *
292     * <li> Escapes are not necessary for single and double quotes;
293     * however, by the rule above, single and double quote characters
294     * preceded by a backslash still yield single and double quote
295     * characters, respectively.
296     *
297     * <li> Only a single 'u' character is allowed in a Unicode escape
298     * sequence.
299     *
300     * </ul>
301     * <p>
302     * The specified stream remains open after this method returns.
303     *
304     * @param   reader   the input character stream.
305     * @throws  IOException  if an error occurred when reading from the
306     *          input stream.
307     * @throws  IllegalArgumentException if a malformed Unicode escape
308     *          appears in the input.
309     * @since   1.6
310     */
311    public synchronized void load(Reader reader) throws IOException {
312        load0(new LineReader(reader));
313    }
314
315    /**
316     * Reads a property list (key and element pairs) from the input
317     * byte stream. The input stream is in a simple line-oriented
318     * format as specified in
319     * {@link #load(java.io.Reader) load(Reader)} and is assumed to use
320     * the ISO 8859-1 character encoding; that is each byte is one Latin1
321     * character. Characters not in Latin1, and certain special characters,
322     * are represented in keys and elements using Unicode escapes as defined in
323     * section 3.3 of
324     * <cite>The Java&trade; Language Specification</cite>.
325     * <p>
326     * The specified stream remains open after this method returns.
327     *
328     * @param      inStream   the input stream.
329     * @exception  IOException  if an error occurred when reading from the
330     *             input stream.
331     * @throws     IllegalArgumentException if the input stream contains a
332     *             malformed Unicode escape sequence.
333     * @since 1.2
334     */
335    public synchronized void load(InputStream inStream) throws IOException {
336        load0(new LineReader(inStream));
337    }
338
339    private void load0 (LineReader lr) throws IOException {
340        char[] convtBuf = new char[1024];
341        int limit;
342        int keyLen;
343        int valueStart;
344        char c;
345        boolean hasSep;
346        boolean precedingBackslash;
347
348        while ((limit = lr.readLine()) >= 0) {
349            c = 0;
350            keyLen = 0;
351            valueStart = limit;
352            hasSep = false;
353
354            //System.out.println("line=<" + new String(lineBuf, 0, limit) + ">");
355            precedingBackslash = false;
356            while (keyLen < limit) {
357                c = lr.lineBuf[keyLen];
358                //need check if escaped.
359                if ((c == '=' ||  c == ':') && !precedingBackslash) {
360                    valueStart = keyLen + 1;
361                    hasSep = true;
362                    break;
363                }
364                // Android-changed: use of Character.isWhitespace(c) b/25998006
365                else if (Character.isWhitespace(c) && !precedingBackslash) {
366                    valueStart = keyLen + 1;
367                    break;
368                }
369                if (c == '\\') {
370                    precedingBackslash = !precedingBackslash;
371                } else {
372                    precedingBackslash = false;
373                }
374                keyLen++;
375            }
376            while (valueStart < limit) {
377                c = lr.lineBuf[valueStart];
378                // Android-changed: use of Character.isWhitespace(c) b/25998006
379                if (!Character.isWhitespace(c)) {
380                    if (!hasSep && (c == '=' ||  c == ':')) {
381                        hasSep = true;
382                    } else {
383                        break;
384                    }
385                }
386                valueStart++;
387            }
388            String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf);
389            String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, convtBuf);
390            put(key, value);
391        }
392    }
393
394    /* Read in a "logical line" from an InputStream/Reader, skip all comment
395     * and blank lines and filter out those leading whitespace characters
396     * (\u0020, \u0009 and \u000c) from the beginning of a "natural line".
397     * Method returns the char length of the "logical line" and stores
398     * the line in "lineBuf".
399     */
400    class LineReader {
401        public LineReader(InputStream inStream) {
402            this.inStream = inStream;
403            inByteBuf = new byte[8192];
404        }
405
406        public LineReader(Reader reader) {
407            this.reader = reader;
408            inCharBuf = new char[8192];
409        }
410
411        byte[] inByteBuf;
412        char[] inCharBuf;
413        char[] lineBuf = new char[1024];
414        int inLimit = 0;
415        int inOff = 0;
416        InputStream inStream;
417        Reader reader;
418
419        int readLine() throws IOException {
420            int len = 0;
421            char c = 0;
422
423            boolean skipWhiteSpace = true;
424            boolean isCommentLine = false;
425            boolean isNewLine = true;
426            boolean appendedLineBegin = false;
427            boolean precedingBackslash = false;
428            boolean skipLF = false;
429
430            while (true) {
431                if (inOff >= inLimit) {
432                    inLimit = (inStream==null)?reader.read(inCharBuf)
433                                              :inStream.read(inByteBuf);
434                    inOff = 0;
435                    if (inLimit <= 0) {
436                        if (len == 0 || isCommentLine) {
437                            return -1;
438                        }
439                        if (precedingBackslash) {
440                            len--;
441                        }
442                        return len;
443                    }
444                }
445                if (inStream != null) {
446                    //The line below is equivalent to calling a
447                    //ISO8859-1 decoder.
448                    c = (char) (0xff & inByteBuf[inOff++]);
449                } else {
450                    c = inCharBuf[inOff++];
451                }
452                if (skipLF) {
453                    skipLF = false;
454                    if (c == '\n') {
455                        continue;
456                    }
457                }
458                if (skipWhiteSpace) {
459                    // Android-changed: use of Character.isWhitespace(c) b/25998006
460                    if (Character.isWhitespace(c)) {
461                        continue;
462                    }
463                    if (!appendedLineBegin && (c == '\r' || c == '\n')) {
464                        continue;
465                    }
466                    skipWhiteSpace = false;
467                    appendedLineBegin = false;
468                }
469                if (isNewLine) {
470                    isNewLine = false;
471                    if (c == '#' || c == '!') {
472                        isCommentLine = true;
473                        continue;
474                    }
475                }
476
477                if (c != '\n' && c != '\r') {
478                    lineBuf[len++] = c;
479                    if (len == lineBuf.length) {
480                        int newLength = lineBuf.length * 2;
481                        if (newLength < 0) {
482                            newLength = Integer.MAX_VALUE;
483                        }
484                        char[] buf = new char[newLength];
485                        System.arraycopy(lineBuf, 0, buf, 0, lineBuf.length);
486                        lineBuf = buf;
487                    }
488                    //flip the preceding backslash flag
489                    if (c == '\\') {
490                        precedingBackslash = !precedingBackslash;
491                    } else {
492                        precedingBackslash = false;
493                    }
494                }
495                else {
496                    // reached EOL
497                    if (isCommentLine || len == 0) {
498                        isCommentLine = false;
499                        isNewLine = true;
500                        skipWhiteSpace = true;
501                        len = 0;
502                        continue;
503                    }
504                    if (inOff >= inLimit) {
505                        inLimit = (inStream==null)
506                                  ?reader.read(inCharBuf)
507                                  :inStream.read(inByteBuf);
508                        inOff = 0;
509                        if (inLimit <= 0) {
510                            if (precedingBackslash) {
511                                len--;
512                            }
513                            return len;
514                        }
515                    }
516                    if (precedingBackslash) {
517                        len -= 1;
518                        //skip the leading whitespace characters in following line
519                        skipWhiteSpace = true;
520                        appendedLineBegin = true;
521                        precedingBackslash = false;
522                        if (c == '\r') {
523                            skipLF = true;
524                        }
525                    } else {
526                        return len;
527                    }
528                }
529            }
530        }
531    }
532
533    /*
534     * Converts encoded &#92;uxxxx to unicode chars
535     * and changes special saved chars to their original forms
536     */
537    private String loadConvert (char[] in, int off, int len, char[] convtBuf) {
538        if (convtBuf.length < len) {
539            int newLen = len * 2;
540            if (newLen < 0) {
541                newLen = Integer.MAX_VALUE;
542            }
543            convtBuf = new char[newLen];
544        }
545        char aChar;
546        char[] out = convtBuf;
547        int outLen = 0;
548        int end = off + len;
549
550        while (off < end) {
551            aChar = in[off++];
552            if (aChar == '\\') {
553                aChar = in[off++];
554                if(aChar == 'u') {
555                    // Read the xxxx
556                    int value=0;
557                    for (int i=0; i<4; i++) {
558                        aChar = in[off++];
559                        switch (aChar) {
560                          case '0': case '1': case '2': case '3': case '4':
561                          case '5': case '6': case '7': case '8': case '9':
562                             value = (value << 4) + aChar - '0';
563                             break;
564                          case 'a': case 'b': case 'c':
565                          case 'd': case 'e': case 'f':
566                             value = (value << 4) + 10 + aChar - 'a';
567                             break;
568                          case 'A': case 'B': case 'C':
569                          case 'D': case 'E': case 'F':
570                             value = (value << 4) + 10 + aChar - 'A';
571                             break;
572                          default:
573                              throw new IllegalArgumentException(
574                                           "Malformed \\uxxxx encoding.");
575                        }
576                     }
577                    out[outLen++] = (char)value;
578                } else {
579                    if (aChar == 't') aChar = '\t';
580                    else if (aChar == 'r') aChar = '\r';
581                    else if (aChar == 'n') aChar = '\n';
582                    else if (aChar == 'f') aChar = '\f';
583                    out[outLen++] = aChar;
584                }
585            } else {
586                out[outLen++] = aChar;
587            }
588        }
589        return new String (out, 0, outLen);
590    }
591
592    /*
593     * Converts unicodes to encoded &#92;uxxxx and escapes
594     * special characters with a preceding slash
595     */
596    private String saveConvert(String theString,
597                               boolean escapeSpace,
598                               boolean escapeUnicode) {
599        int len = theString.length();
600        int bufLen = len * 2;
601        if (bufLen < 0) {
602            bufLen = Integer.MAX_VALUE;
603        }
604        StringBuffer outBuffer = new StringBuffer(bufLen);
605
606        for(int x=0; x<len; x++) {
607            char aChar = theString.charAt(x);
608            // Handle common case first, selecting largest block that
609            // avoids the specials below
610            if ((aChar > 61) && (aChar < 127)) {
611                if (aChar == '\\') {
612                    outBuffer.append('\\'); outBuffer.append('\\');
613                    continue;
614                }
615                outBuffer.append(aChar);
616                continue;
617            }
618            switch(aChar) {
619                case ' ':
620                    if (x == 0 || escapeSpace)
621                        outBuffer.append('\\');
622                    outBuffer.append(' ');
623                    break;
624                case '\t':outBuffer.append('\\'); outBuffer.append('t');
625                          break;
626                case '\n':outBuffer.append('\\'); outBuffer.append('n');
627                          break;
628                case '\r':outBuffer.append('\\'); outBuffer.append('r');
629                          break;
630                case '\f':outBuffer.append('\\'); outBuffer.append('f');
631                          break;
632                case '=': // Fall through
633                case ':': // Fall through
634                case '#': // Fall through
635                case '!':
636                    outBuffer.append('\\'); outBuffer.append(aChar);
637                    break;
638                default:
639                    if (((aChar < 0x0020) || (aChar > 0x007e)) & escapeUnicode ) {
640                        outBuffer.append('\\');
641                        outBuffer.append('u');
642                        outBuffer.append(toHex((aChar >> 12) & 0xF));
643                        outBuffer.append(toHex((aChar >>  8) & 0xF));
644                        outBuffer.append(toHex((aChar >>  4) & 0xF));
645                        outBuffer.append(toHex( aChar        & 0xF));
646                    } else {
647                        outBuffer.append(aChar);
648                    }
649            }
650        }
651        return outBuffer.toString();
652    }
653
654    private static void writeComments(BufferedWriter bw, String comments)
655        throws IOException {
656        bw.write("#");
657        int len = comments.length();
658        int current = 0;
659        int last = 0;
660        char[] uu = new char[6];
661        uu[0] = '\\';
662        uu[1] = 'u';
663        while (current < len) {
664            char c = comments.charAt(current);
665            if (c > '\u00ff' || c == '\n' || c == '\r') {
666                if (last != current)
667                    bw.write(comments.substring(last, current));
668                if (c > '\u00ff') {
669                    uu[2] = toHex((c >> 12) & 0xf);
670                    uu[3] = toHex((c >>  8) & 0xf);
671                    uu[4] = toHex((c >>  4) & 0xf);
672                    uu[5] = toHex( c        & 0xf);
673                    bw.write(new String(uu));
674                } else {
675                    bw.newLine();
676                    if (c == '\r' &&
677                        current != len - 1 &&
678                        comments.charAt(current + 1) == '\n') {
679                        current++;
680                    }
681                    if (current == len - 1 ||
682                        (comments.charAt(current + 1) != '#' &&
683                        comments.charAt(current + 1) != '!'))
684                        bw.write("#");
685                }
686                last = current + 1;
687            }
688            current++;
689        }
690        if (last != current)
691            bw.write(comments.substring(last, current));
692        bw.newLine();
693    }
694
695    /**
696     * Calls the {@code store(OutputStream out, String comments)} method
697     * and suppresses IOExceptions that were thrown.
698     *
699     * @deprecated This method does not throw an IOException if an I/O error
700     * occurs while saving the property list.  The preferred way to save a
701     * properties list is via the {@code store(OutputStream out,
702     * String comments)} method or the
703     * {@code storeToXML(OutputStream os, String comment)} method.
704     *
705     * @param   out      an output stream.
706     * @param   comments   a description of the property list.
707     * @exception  ClassCastException  if this {@code Properties} object
708     *             contains any keys or values that are not
709     *             {@code Strings}.
710     */
711    @Deprecated
712    public void save(OutputStream out, String comments)  {
713        try {
714            store(out, comments);
715        } catch (IOException e) {
716        }
717    }
718
719    /**
720     * Writes this property list (key and element pairs) in this
721     * {@code Properties} table to the output character stream in a
722     * format suitable for using the {@link #load(java.io.Reader) load(Reader)}
723     * method.
724     * <p>
725     * Properties from the defaults table of this {@code Properties}
726     * table (if any) are <i>not</i> written out by this method.
727     * <p>
728     * If the comments argument is not null, then an ASCII {@code #}
729     * character, the comments string, and a line separator are first written
730     * to the output stream. Thus, the {@code comments} can serve as an
731     * identifying comment. Any one of a line feed ('\n'), a carriage
732     * return ('\r'), or a carriage return followed immediately by a line feed
733     * in comments is replaced by a line separator generated by the {@code Writer}
734     * and if the next character in comments is not character {@code #} or
735     * character {@code !} then an ASCII {@code #} is written out
736     * after that line separator.
737     * <p>
738     * Next, a comment line is always written, consisting of an ASCII
739     * {@code #} character, the current date and time (as if produced
740     * by the {@code toString} method of {@code Date} for the
741     * current time), and a line separator as generated by the {@code Writer}.
742     * <p>
743     * Then every entry in this {@code Properties} table is
744     * written out, one per line. For each entry the key string is
745     * written, then an ASCII {@code =}, then the associated
746     * element string. For the key, all space characters are
747     * written with a preceding {@code \} character.  For the
748     * element, leading space characters, but not embedded or trailing
749     * space characters, are written with a preceding {@code \}
750     * character. The key and element characters {@code #},
751     * {@code !}, {@code =}, and {@code :} are written
752     * with a preceding backslash to ensure that they are properly loaded.
753     * <p>
754     * After the entries have been written, the output stream is flushed.
755     * The output stream remains open after this method returns.
756     * <p>
757     *
758     * @param   writer      an output character stream writer.
759     * @param   comments   a description of the property list.
760     * @exception  IOException if writing this property list to the specified
761     *             output stream throws an <tt>IOException</tt>.
762     * @exception  ClassCastException  if this {@code Properties} object
763     *             contains any keys or values that are not {@code Strings}.
764     * @exception  NullPointerException  if {@code writer} is null.
765     * @since 1.6
766     */
767    public void store(Writer writer, String comments)
768        throws IOException
769    {
770        store0((writer instanceof BufferedWriter)?(BufferedWriter)writer
771                                                 : new BufferedWriter(writer),
772               comments,
773               false);
774    }
775
776    /**
777     * Writes this property list (key and element pairs) in this
778     * {@code Properties} table to the output stream in a format suitable
779     * for loading into a {@code Properties} table using the
780     * {@link #load(InputStream) load(InputStream)} method.
781     * <p>
782     * Properties from the defaults table of this {@code Properties}
783     * table (if any) are <i>not</i> written out by this method.
784     * <p>
785     * This method outputs the comments, properties keys and values in
786     * the same format as specified in
787     * {@link #store(java.io.Writer, java.lang.String) store(Writer)},
788     * with the following differences:
789     * <ul>
790     * <li>The stream is written using the ISO 8859-1 character encoding.
791     *
792     * <li>Characters not in Latin-1 in the comments are written as
793     * {@code \u005Cu}<i>xxxx</i> for their appropriate unicode
794     * hexadecimal value <i>xxxx</i>.
795     *
796     * <li>Characters less than {@code \u005Cu0020} and characters greater
797     * than {@code \u005Cu007E} in property keys or values are written
798     * as {@code \u005Cu}<i>xxxx</i> for the appropriate hexadecimal
799     * value <i>xxxx</i>.
800     * </ul>
801     * <p>
802     * After the entries have been written, the output stream is flushed.
803     * The output stream remains open after this method returns.
804     * <p>
805     * @param   out      an output stream.
806     * @param   comments   a description of the property list.
807     * @exception  IOException if writing this property list to the specified
808     *             output stream throws an <tt>IOException</tt>.
809     * @exception  ClassCastException  if this {@code Properties} object
810     *             contains any keys or values that are not {@code Strings}.
811     * @exception  NullPointerException  if {@code out} is null.
812     * @since 1.2
813     */
814    public void store(OutputStream out, String comments)
815        throws IOException
816    {
817        store0(new BufferedWriter(new OutputStreamWriter(out, "8859_1")),
818               comments,
819               true);
820    }
821
822    private void store0(BufferedWriter bw, String comments, boolean escUnicode)
823        throws IOException
824    {
825        if (comments != null) {
826            writeComments(bw, comments);
827        }
828        bw.write("#" + new Date().toString());
829        bw.newLine();
830        synchronized (this) {
831            for (Enumeration<?> e = keys(); e.hasMoreElements();) {
832                String key = (String)e.nextElement();
833                String val = (String)get(key);
834                key = saveConvert(key, true, escUnicode);
835                /* No need to escape embedded and trailing spaces for value, hence
836                 * pass false to flag.
837                 */
838                val = saveConvert(val, false, escUnicode);
839                bw.write(key + "=" + val);
840                bw.newLine();
841            }
842        }
843        bw.flush();
844    }
845
846    /**
847     * Loads all of the properties represented by the XML document on the
848     * specified input stream into this properties table.
849     *
850     * <p>The XML document must have the following DOCTYPE declaration:
851     * <pre>
852     * &lt;!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"&gt;
853     * </pre>
854     * Furthermore, the document must satisfy the properties DTD described
855     * above.
856     *
857     * <p> An implementation is required to read XML documents that use the
858     * "{@code UTF-8}" or "{@code UTF-16}" encoding. An implementation may
859     * support additional encodings.
860     *
861     * <p>The specified stream is closed after this method returns.
862     *
863     * @param in the input stream from which to read the XML document.
864     * @throws IOException if reading from the specified input stream
865     *         results in an <tt>IOException</tt>.
866     * @throws java.io.UnsupportedEncodingException if the document's encoding
867     *         declaration can be read and it specifies an encoding that is not
868     *         supported
869     * @throws InvalidPropertiesFormatException Data on input stream does not
870     *         constitute a valid XML document with the mandated document type.
871     * @throws NullPointerException if {@code in} is null.
872     * @see    #storeToXML(OutputStream, String, String)
873     * @see    <a href="http://www.w3.org/TR/REC-xml/#charencoding">Character
874     *         Encoding in Entities</a>
875     * @since 1.5
876     */
877    public synchronized void loadFromXML(InputStream in)
878        throws IOException, InvalidPropertiesFormatException
879    {
880        // Android-changed: Keep OpenJDK7u40's XmlUtils.
881        // XmlSupport's system property based XmlPropertiesProvider
882        // selection does not make sense on Android and has too many
883        // dependencies on classes that are not available on Android.
884        //
885        // XmlSupport.load(this, Objects.requireNonNull(in));
886        XMLUtils.load(this, Objects.requireNonNull(in));
887        in.close();
888    }
889
890    /**
891     * Emits an XML document representing all of the properties contained
892     * in this table.
893     *
894     * <p> An invocation of this method of the form <tt>props.storeToXML(os,
895     * comment)</tt> behaves in exactly the same way as the invocation
896     * <tt>props.storeToXML(os, comment, "UTF-8");</tt>.
897     *
898     * @param os the output stream on which to emit the XML document.
899     * @param comment a description of the property list, or {@code null}
900     *        if no comment is desired.
901     * @throws IOException if writing to the specified output stream
902     *         results in an <tt>IOException</tt>.
903     * @throws NullPointerException if {@code os} is null.
904     * @throws ClassCastException  if this {@code Properties} object
905     *         contains any keys or values that are not
906     *         {@code Strings}.
907     * @see    #loadFromXML(InputStream)
908     * @since 1.5
909     */
910    public void storeToXML(OutputStream os, String comment)
911        throws IOException
912    {
913        storeToXML(os, comment, "UTF-8");
914    }
915
916    /**
917     * Emits an XML document representing all of the properties contained
918     * in this table, using the specified encoding.
919     *
920     * <p>The XML document will have the following DOCTYPE declaration:
921     * <pre>
922     * &lt;!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"&gt;
923     * </pre>
924     *
925     * <p>If the specified comment is {@code null} then no comment
926     * will be stored in the document.
927     *
928     * <p> An implementation is required to support writing of XML documents
929     * that use the "{@code UTF-8}" or "{@code UTF-16}" encoding. An
930     * implementation may support additional encodings.
931     *
932     * <p>The specified stream remains open after this method returns.
933     *
934     * @param os        the output stream on which to emit the XML document.
935     * @param comment   a description of the property list, or {@code null}
936     *                  if no comment is desired.
937     * @param  encoding the name of a supported
938     *                  <a href="../lang/package-summary.html#charenc">
939     *                  character encoding</a>
940     *
941     * @throws IOException if writing to the specified output stream
942     *         results in an <tt>IOException</tt>.
943     * @throws java.io.UnsupportedEncodingException if the encoding is not
944     *         supported by the implementation.
945     * @throws NullPointerException if {@code os} is {@code null},
946     *         or if {@code encoding} is {@code null}.
947     * @throws ClassCastException  if this {@code Properties} object
948     *         contains any keys or values that are not
949     *         {@code Strings}.
950     * @see    #loadFromXML(InputStream)
951     * @see    <a href="http://www.w3.org/TR/REC-xml/#charencoding">Character
952     *         Encoding in Entities</a>
953     * @since 1.5
954     */
955    public void storeToXML(OutputStream os, String comment, String encoding)
956        throws IOException
957    {
958        // Android-changed: Keep OpenJDK7u40's XmlUtils.
959        // XmlSupport's system property based XmlPropertiesProvider
960        // selection does not make sense on Android and has too many
961        // dependencies on classes that are not available on Android.
962        //
963        // XmlSupport.save(this, Objects.requireNonNull(os), comment,
964        //                Objects.requireNonNull(encoding));
965        XMLUtils.save(this, Objects.requireNonNull(os), comment,
966                       Objects.requireNonNull(encoding));
967    }
968
969    /**
970     * Searches for the property with the specified key in this property list.
971     * If the key is not found in this property list, the default property list,
972     * and its defaults, recursively, are then checked. The method returns
973     * {@code null} if the property is not found.
974     *
975     * @param   key   the property key.
976     * @return  the value in this property list with the specified key value.
977     * @see     #setProperty
978     * @see     #defaults
979     */
980    public String getProperty(String key) {
981        Object oval = super.get(key);
982        String sval = (oval instanceof String) ? (String)oval : null;
983        return ((sval == null) && (defaults != null)) ? defaults.getProperty(key) : sval;
984    }
985
986    /**
987     * Searches for the property with the specified key in this property list.
988     * If the key is not found in this property list, the default property list,
989     * and its defaults, recursively, are then checked. The method returns the
990     * default value argument if the property is not found.
991     *
992     * @param   key            the hashtable key.
993     * @param   defaultValue   a default value.
994     *
995     * @return  the value in this property list with the specified key value.
996     * @see     #setProperty
997     * @see     #defaults
998     */
999    public String getProperty(String key, String defaultValue) {
1000        String val = getProperty(key);
1001        return (val == null) ? defaultValue : val;
1002    }
1003
1004    /**
1005     * Returns an enumeration of all the keys in this property list,
1006     * including distinct keys in the default property list if a key
1007     * of the same name has not already been found from the main
1008     * properties list.
1009     *
1010     * @return  an enumeration of all the keys in this property list, including
1011     *          the keys in the default property list.
1012     * @throws  ClassCastException if any key in this property list
1013     *          is not a string.
1014     * @see     java.util.Enumeration
1015     * @see     java.util.Properties#defaults
1016     * @see     #stringPropertyNames
1017     */
1018    public Enumeration<?> propertyNames() {
1019        Hashtable<String,Object> h = new Hashtable<>();
1020        enumerate(h);
1021        return h.keys();
1022    }
1023
1024    /**
1025     * Returns a set of keys in this property list where
1026     * the key and its corresponding value are strings,
1027     * including distinct keys in the default property list if a key
1028     * of the same name has not already been found from the main
1029     * properties list.  Properties whose key or value is not
1030     * of type <tt>String</tt> are omitted.
1031     * <p>
1032     * The returned set is not backed by the <tt>Properties</tt> object.
1033     * Changes to this <tt>Properties</tt> are not reflected in the set,
1034     * or vice versa.
1035     *
1036     * @return  a set of keys in this property list where
1037     *          the key and its corresponding value are strings,
1038     *          including the keys in the default property list.
1039     * @see     java.util.Properties#defaults
1040     * @since   1.6
1041     */
1042    public Set<String> stringPropertyNames() {
1043        Hashtable<String, String> h = new Hashtable<>();
1044        enumerateStringProperties(h);
1045        return h.keySet();
1046    }
1047
1048    /**
1049     * Prints this property list out to the specified output stream.
1050     * This method is useful for debugging.
1051     *
1052     * @param   out   an output stream.
1053     * @throws  ClassCastException if any key in this property list
1054     *          is not a string.
1055     */
1056    public void list(PrintStream out) {
1057        out.println("-- listing properties --");
1058        Hashtable<String,Object> h = new Hashtable<>();
1059        enumerate(h);
1060        for (Enumeration<String> e = h.keys() ; e.hasMoreElements() ;) {
1061            String key = e.nextElement();
1062            String val = (String)h.get(key);
1063            if (val.length() > 40) {
1064                val = val.substring(0, 37) + "...";
1065            }
1066            out.println(key + "=" + val);
1067        }
1068    }
1069
1070    /**
1071     * Prints this property list out to the specified output stream.
1072     * This method is useful for debugging.
1073     *
1074     * @param   out   an output stream.
1075     * @throws  ClassCastException if any key in this property list
1076     *          is not a string.
1077     * @since   JDK1.1
1078     */
1079    /*
1080     * Rather than use an anonymous inner class to share common code, this
1081     * method is duplicated in order to ensure that a non-1.1 compiler can
1082     * compile this file.
1083     */
1084    public void list(PrintWriter out) {
1085        out.println("-- listing properties --");
1086        Hashtable<String,Object> h = new Hashtable<>();
1087        enumerate(h);
1088        for (Enumeration<String> e = h.keys() ; e.hasMoreElements() ;) {
1089            String key = e.nextElement();
1090            String val = (String)h.get(key);
1091            if (val.length() > 40) {
1092                val = val.substring(0, 37) + "...";
1093            }
1094            out.println(key + "=" + val);
1095        }
1096    }
1097
1098    /**
1099     * Enumerates all key/value pairs in the specified hashtable.
1100     * @param h the hashtable
1101     * @throws ClassCastException if any of the property keys
1102     *         is not of String type.
1103     */
1104    private synchronized void enumerate(Hashtable<String,Object> h) {
1105        if (defaults != null) {
1106            defaults.enumerate(h);
1107        }
1108        for (Enumeration<?> e = keys() ; e.hasMoreElements() ;) {
1109            String key = (String)e.nextElement();
1110            h.put(key, get(key));
1111        }
1112    }
1113
1114    /**
1115     * Enumerates all key/value pairs in the specified hashtable
1116     * and omits the property if the key or value is not a string.
1117     * @param h the hashtable
1118     */
1119    private synchronized void enumerateStringProperties(Hashtable<String, String> h) {
1120        if (defaults != null) {
1121            defaults.enumerateStringProperties(h);
1122        }
1123        for (Enumeration<?> e = keys() ; e.hasMoreElements() ;) {
1124            Object k = e.nextElement();
1125            Object v = get(k);
1126            if (k instanceof String && v instanceof String) {
1127                h.put((String) k, (String) v);
1128            }
1129        }
1130    }
1131
1132    /**
1133     * Convert a nibble to a hex character
1134     * @param   nibble  the nibble to convert.
1135     */
1136    private static char toHex(int nibble) {
1137        return hexDigit[(nibble & 0xF)];
1138    }
1139
1140    /** A table of hex digits */
1141    private static final char[] hexDigit = {
1142        '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'
1143    };
1144}
1145