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