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