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