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