| Properties.java |
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 * <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
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 * <?xml version="1.0" encoding="UTF-8"?>
72 *
73 * <!-- DTD for properties -->
74 *
75 * <!ELEMENT properties ( comment?, entry* ) >
76 *
77 * <!ATTLIST properties version CDATA #FIXED "1.0">
78 *
79 * <!ELEMENT comment (#PCDATA) >
80 *
81 * <!ELEMENT entry (#PCDATA) >
82 *
83 * <!ATTLIST entry key CDATA #REQUIRED>
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>'\u0020'</code>), tab
174 * (<code>'\t'</code>, <code>'\u0009'</code>), and form feed
175 * (<code>'\f'</code>, <code>'\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>""</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">§3.3</a>
260 * and <a
261 * href="http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#3.10.6">§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 \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 \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>\u</code><i>xxxx</i> for their appropriate unicode
773 * hexadecimal value <i>xxxx</i>.
774 *
775 * <li>Characters less than <code>\u0020</code> and characters greater
776 * than <code>\u007E</code> in property keys or values are written
777 * as <code>\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 * <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
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 * <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
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