1 /* 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 26 package java.util; 27 28 import java.io.IOException; 29 import java.io.PrintStream; 30 import java.io.PrintWriter; 31 import java.io.InputStream; 32 import java.io.OutputStream; 33 import java.io.Reader; 34 import java.io.Writer; 35 import java.io.OutputStreamWriter; 36 import 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 * <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> 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 * <?xml version="1.0" encoding="UTF-8"?> 90 * 91 * <!-- DTD for properties --> 92 * 93 * <!ELEMENT properties ( comment?, entry* ) > 94 * 95 * <!ATTLIST properties version CDATA #FIXED "1.0"> 96 * 97 * <!ELEMENT comment (#PCDATA) > 98 * 99 * <!ELEMENT entry (#PCDATA) > 100 * 101 * <!ATTLIST entry key CDATA #REQUIRED> 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 */ 115 public 116 class 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>'\u0020'</code>), tab 191 * (<code>'\t'</code>, <code>'\u0009'</code>), and form feed 192 * (<code>'\f'</code>, <code>'\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>""</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">§3.3</a> 277 * and <a 278 * href="http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#3.10.6">§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 \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 \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>\u</code><i>xxxx</i> for their appropriate unicode 790 * hexadecimal value <i>xxxx</i>. 791 * 792 * <li>Characters less than <code>\u0020</code> and characters greater 793 * than <code>\u007E</code> in property keys or values are written 794 * as <code>\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 * <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> 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 * <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> 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 }