• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
27 package java.util;
28 
29 import java.io.IOException;
30 import java.io.PrintStream;
31 import java.io.PrintWriter;
32 import java.io.InputStream;
33 import java.io.OutputStream;
34 import java.io.Reader;
35 import java.io.Writer;
36 import java.io.OutputStreamWriter;
37 import 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}openjdk-redirect.html?v=8&path=/technotes/tools/solaris/native2ascii.html">native2ascii tool for Solaris</a>
110  * @see <a href="{@docRoot}openjdk-redirect.html?v=8&path=/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  */
117 public
118 class 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      */
Properties()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      */
Properties(Properties defaults)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      */
setProperty(String key, String value)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      */
load(Reader reader)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      */
load(InputStream inStream)341     public synchronized void load(InputStream inStream) throws IOException {
342         load0(new LineReader(inStream));
343     }
344 
load0(LineReader lr)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 {
LineReader(InputStream inStream)404         public LineReader(InputStream inStream) {
405             this.inStream = inStream;
406             inByteBuf = new byte[8192];
407         }
408 
LineReader(Reader reader)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 
readLine()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      */
loadConvert(char[] in, int off, int len, char[] convtBuf)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      */
saveConvert(String theString, boolean escapeSpace, boolean escapeUnicode)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 
writeComments(BufferedWriter bw, String comments)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
save(OutputStream out, String comments)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      */
store(Writer writer, String comments)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      */
store(OutputStream out, String comments)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 
store0(BufferedWriter bw, String comments, boolean escUnicode)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      */
loadFromXML(InputStream in)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      */
storeToXML(OutputStream os, String comment)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      */
storeToXML(OutputStream os, String comment, String encoding)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      */
getProperty(String key)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      */
getProperty(String key, String defaultValue)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      */
propertyNames()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      */
stringPropertyNames()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      */
list(PrintStream out)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      */
list(PrintWriter out)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      */
enumerate(Hashtable h)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      */
enumerateStringProperties(Hashtable<String, String> h)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      */
toHex(int nibble)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