1 /* 2 * Copyright (c) 1995, 2015, Oracle and/or its affiliates. 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * 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 import java.io.ObjectInputStream; 38 import java.io.ObjectOutputStream; 39 import java.util.concurrent.ConcurrentHashMap; 40 import java.util.function.BiConsumer; 41 import java.util.function.BiFunction; 42 import java.util.function.Function; 43 44 import jdk.internal.util.xml.PropertiesDefaultHandler; 45 46 /** 47 * The {@code Properties} class represents a persistent set of 48 * properties. The {@code Properties} can be saved to a stream 49 * or loaded from a stream. Each key and its corresponding value in 50 * the property list is a string. 51 * <p> 52 * A property list can contain another property list as its 53 * "defaults"; this second property list is searched if 54 * the property key is not found in the original property list. 55 * <p> 56 * Because {@code Properties} inherits from {@code Hashtable}, the 57 * {@code put} and {@code putAll} methods can be applied to a 58 * {@code Properties} object. Their use is strongly discouraged as they 59 * allow the caller to insert entries whose keys or values are not 60 * {@code Strings}. The {@code setProperty} method should be used 61 * instead. If the {@code store} or {@code save} method is called 62 * on a "compromised" {@code Properties} object that contains a 63 * non-{@code String} key or value, the call will fail. Similarly, 64 * the call to the {@code propertyNames} or {@code list} method 65 * will fail if it is called on a "compromised" {@code Properties} 66 * object that contains a non-{@code String} key. 67 * 68 * <p> 69 * The {@link #load(java.io.Reader) load(Reader)} <tt>/</tt> 70 * {@link #store(java.io.Writer, java.lang.String) store(Writer, String)} 71 * methods load and store properties from and to a character based stream 72 * in a simple line-oriented format specified below. 73 * 74 * The {@link #load(java.io.InputStream) load(InputStream)} <tt>/</tt> 75 * {@link #store(java.io.OutputStream, java.lang.String) store(OutputStream, String)} 76 * methods work the same way as the load(Reader)/store(Writer, String) pair, except 77 * the input/output stream is encoded in ISO 8859-1 character encoding. 78 * Characters that cannot be directly represented in this encoding can be written using 79 * Unicode escapes as defined in section 3.3 of 80 * <cite>The Java™ Language Specification</cite>; 81 * only a single 'u' character is allowed in an escape 82 * sequence. The native2ascii tool can be used to convert property files to and 83 * from other character encodings. 84 * 85 * <p> The {@link #loadFromXML(InputStream)} and {@link 86 * #storeToXML(OutputStream, String, String)} methods load and store properties 87 * in a simple XML format. By default the UTF-8 character encoding is used, 88 * however a specific encoding may be specified if required. Implementations 89 * are required to support UTF-8 and UTF-16 and may support other encodings. 90 * An XML properties document has the following DOCTYPE declaration: 91 * 92 * <pre> 93 * <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> 94 * </pre> 95 * Note that the system URI (http://java.sun.com/dtd/properties.dtd) is 96 * <i>not</i> accessed when exporting or importing properties; it merely 97 * serves as a string to uniquely identify the DTD, which is: 98 * <pre> 99 * <?xml version="1.0" encoding="UTF-8"?> 100 * 101 * <!-- DTD for properties --> 102 * 103 * <!ELEMENT properties ( comment?, entry* ) > 104 * 105 * <!ATTLIST properties version CDATA #FIXED "1.0"> 106 * 107 * <!ELEMENT comment (#PCDATA) > 108 * 109 * <!ELEMENT entry (#PCDATA) > 110 * 111 * <!ATTLIST entry key CDATA #REQUIRED> 112 * </pre> 113 * 114 * <p>This class is thread-safe: multiple threads can share a single 115 * <tt>Properties</tt> object without the need for external synchronization. 116 * 117 * @see <a href="../../../technotes/tools/solaris/native2ascii.html">native2ascii tool for Solaris</a> 118 * @see <a href="../../../technotes/tools/windows/native2ascii.html">native2ascii tool for Windows</a> 119 * 120 * @author Arthur van Hoff 121 * @author Michael McCloskey 122 * @author Xueming Shen 123 * @since 1.0 124 */ 125 public 126 class Properties extends Hashtable<Object,Object> { 127 /** 128 * use serialVersionUID from JDK 1.1.X for interoperability 129 */ 130 private static final long serialVersionUID = 4112578634029874840L; 131 132 /** 133 * A property list that contains default values for any keys not 134 * found in this property list. 135 * 136 * @serial 137 */ 138 protected Properties defaults; 139 140 /** 141 * Creates an empty property list with no default values. 142 */ 143 public Properties() { 144 this(null); 145 } 146 147 /** 148 * Creates an empty property list with the specified defaults. 149 * 150 * @param defaults the defaults. 151 */ 152 public Properties(Properties defaults) { 153 // use package-private constructor to 154 // initialize unused fields with dummy values 155 super((Void) null); 156 this.defaults = defaults; 157 } 158 159 /** 160 * Calls the <tt>Hashtable</tt> method {@code put}. Provided for 161 * parallelism with the <tt>getProperty</tt> method. Enforces use of 162 * strings for property keys and values. The value returned is the 163 * result of the <tt>Hashtable</tt> call to {@code put}. 164 * 165 * @param key the key to be placed into this property list. 166 * @param value the value corresponding to <tt>key</tt>. 167 * @return the previous value of the specified key in this property 168 * list, or {@code null} if it did not have one. 169 * @see #getProperty 170 * @since 1.2 171 */ 172 public Object setProperty(String key, String value) { 173 return put(key, value); 174 } 175 176 177 /** 178 * Reads a property list (key and element pairs) from the input 179 * character stream in a simple line-oriented format. 180 * <p> 181 * Properties are processed in terms of lines. There are two 182 * kinds of line, <i>natural lines</i> and <i>logical lines</i>. 183 * A natural line is defined as a line of 184 * characters that is terminated either by a set of line terminator 185 * characters ({@code \n} or {@code \r} or {@code \r\n}) 186 * or by the end of the stream. A natural line may be either a blank line, 187 * a comment line, or hold all or some of a key-element pair. A logical 188 * line holds all the data of a key-element pair, which may be spread 189 * out across several adjacent natural lines by escaping 190 * the line terminator sequence with a backslash character 191 * {@code \}. Note that a comment line cannot be extended 192 * in this manner; every natural line that is a comment must have 193 * its own comment indicator, as described below. Lines are read from 194 * input until the end of the stream is reached. 195 * 196 * <p> 197 * A natural line that contains only white space characters is 198 * considered blank and is ignored. A comment line has an ASCII 199 * {@code '#'} or {@code '!'} as its first non-white 200 * space character; comment lines are also ignored and do not 201 * encode key-element information. In addition to line 202 * terminators, this format considers the characters space 203 * ({@code ' '}, {@code '\u005Cu0020'}), tab 204 * ({@code '\t'}, {@code '\u005Cu0009'}), and form feed 205 * ({@code '\f'}, {@code '\u005Cu000C'}) to be white 206 * space. 207 * 208 * <p> 209 * If a logical line is spread across several natural lines, the 210 * backslash escaping the line terminator sequence, the line 211 * terminator sequence, and any white space at the start of the 212 * following line have no affect on the key or element values. 213 * The remainder of the discussion of key and element parsing 214 * (when loading) will assume all the characters constituting 215 * the key and element appear on a single natural line after 216 * line continuation characters have been removed. Note that 217 * it is <i>not</i> sufficient to only examine the character 218 * preceding a line terminator sequence to decide if the line 219 * terminator is escaped; there must be an odd number of 220 * contiguous backslashes for the line terminator to be escaped. 221 * Since the input is processed from left to right, a 222 * non-zero even number of 2<i>n</i> contiguous backslashes 223 * before a line terminator (or elsewhere) encodes <i>n</i> 224 * backslashes after escape processing. 225 * 226 * <p> 227 * The key contains all of the characters in the line starting 228 * with the first non-white space character and up to, but not 229 * including, the first unescaped {@code '='}, 230 * {@code ':'}, or white space character other than a line 231 * terminator. All of these key termination characters may be 232 * included in the key by escaping them with a preceding backslash 233 * character; for example,<p> 234 * 235 * {@code \:\=}<p> 236 * 237 * would be the two-character key {@code ":="}. Line 238 * terminator characters can be included using {@code \r} and 239 * {@code \n} escape sequences. Any white space after the 240 * key is skipped; if the first non-white space character after 241 * the key is {@code '='} or {@code ':'}, then it is 242 * ignored and any white space characters after it are also 243 * skipped. All remaining characters on the line become part of 244 * the associated element string; if there are no remaining 245 * characters, the element is the empty string 246 * {@code ""}. Once the raw character sequences 247 * constituting the key and element are identified, escape 248 * processing is performed as described above. 249 * 250 * <p> 251 * As an example, each of the following three lines specifies the key 252 * {@code "Truth"} and the associated element value 253 * {@code "Beauty"}: 254 * <pre> 255 * Truth = Beauty 256 * Truth:Beauty 257 * Truth :Beauty 258 * </pre> 259 * As another example, the following three lines specify a single 260 * property: 261 * <pre> 262 * fruits apple, banana, pear, \ 263 * cantaloupe, watermelon, \ 264 * kiwi, mango 265 * </pre> 266 * The key is {@code "fruits"} and the associated element is: 267 * <pre>"apple, banana, pear, cantaloupe, watermelon, kiwi, mango"</pre> 268 * Note that a space appears before each {@code \} so that a space 269 * will appear after each comma in the final result; the {@code \}, 270 * line terminator, and leading white space on the continuation line are 271 * merely discarded and are <i>not</i> replaced by one or more other 272 * characters. 273 * <p> 274 * As a third example, the line: 275 * <pre>cheeses 276 * </pre> 277 * specifies that the key is {@code "cheeses"} and the associated 278 * element is the empty string {@code ""}. 279 * <p> 280 * <a name="unicodeescapes"></a> 281 * Characters in keys and elements can be represented in escape 282 * sequences similar to those used for character and string literals 283 * (see sections 3.3 and 3.10.6 of 284 * <cite>The Java™ Language Specification</cite>). 285 * 286 * The differences from the character escape sequences and Unicode 287 * escapes used for characters and strings are: 288 * 289 * <ul> 290 * <li> Octal escapes are not recognized. 291 * 292 * <li> The character sequence {@code \b} does <i>not</i> 293 * represent a backspace character. 294 * 295 * <li> The method does not treat a backslash character, 296 * {@code \}, before a non-valid escape character as an 297 * error; the backslash is silently dropped. For example, in a 298 * Java string the sequence {@code "\z"} would cause a 299 * compile time error. In contrast, this method silently drops 300 * the backslash. Therefore, this method treats the two character 301 * sequence {@code "\b"} as equivalent to the single 302 * character {@code 'b'}. 303 * 304 * <li> Escapes are not necessary for single and double quotes; 305 * however, by the rule above, single and double quote characters 306 * preceded by a backslash still yield single and double quote 307 * characters, respectively. 308 * 309 * <li> Only a single 'u' character is allowed in a Unicode escape 310 * sequence. 311 * 312 * </ul> 313 * <p> 314 * The specified stream remains open after this method returns. 315 * 316 * @param reader the input character stream. 317 * @throws IOException if an error occurred when reading from the 318 * input stream. 319 * @throws IllegalArgumentException if a malformed Unicode escape 320 * appears in the input. 321 * @throws NullPointerException if {@code reader} is null. 322 * @since 1.6 323 */ 324 public void load(Reader reader) throws IOException { 325 Objects.requireNonNull(reader, "reader parameter is null"); 326 load0(new LineReader(reader)); 327 } 328 329 /** 330 * Reads a property list (key and element pairs) from the input 331 * byte stream. The input stream is in a simple line-oriented 332 * format as specified in 333 * {@link #load(java.io.Reader) load(Reader)} and is assumed to use 334 * the ISO 8859-1 character encoding; that is each byte is one Latin1 335 * character. Characters not in Latin1, and certain special characters, 336 * are represented in keys and elements using Unicode escapes as defined in 337 * section 3.3 of 338 * <cite>The Java™ Language Specification</cite>. 339 * <p> 340 * The specified stream remains open after this method returns. 341 * 342 * @param inStream the input stream. 343 * @exception IOException if an error occurred when reading from the 344 * input stream. 345 * @throws IllegalArgumentException if the input stream contains a 346 * malformed Unicode escape sequence. 347 * @throws NullPointerException if {@code inStream} is null. 348 * @since 1.2 349 */ 350 public void load(InputStream inStream) throws IOException { 351 Objects.requireNonNull(inStream, "inStream parameter is null"); 352 load0(new LineReader(inStream)); 353 } 354 355 private void load0 (LineReader lr) throws IOException { 356 char[] convtBuf = new char[1024]; 357 int limit; 358 int keyLen; 359 int valueStart; 360 char c; 361 boolean hasSep; 362 boolean precedingBackslash; 363 364 while ((limit = lr.readLine()) >= 0) { 365 c = 0; 366 keyLen = 0; 367 valueStart = limit; 368 hasSep = false; 369 370 //System.out.println("line=<" + new String(lineBuf, 0, limit) + ">"); 371 precedingBackslash = false; 372 while (keyLen < limit) { 373 c = lr.lineBuf[keyLen]; 374 //need check if escaped. 375 if ((c == '=' || c == ':') && !precedingBackslash) { 376 valueStart = keyLen + 1; 377 hasSep = true; 378 break; 379 } else if ((c == ' ' || c == '\t' || c == '\f') && !precedingBackslash) { 380 valueStart = keyLen + 1; 381 break; 382 } 383 if (c == '\\') { 384 precedingBackslash = !precedingBackslash; 385 } else { 386 precedingBackslash = false; 387 } 388 keyLen++; 389 } 390 while (valueStart < limit) { 391 c = lr.lineBuf[valueStart]; 392 if (c != ' ' && c != '\t' && c != '\f') { 393 if (!hasSep && (c == '=' || c == ':')) { 394 hasSep = true; 395 } else { 396 break; 397 } 398 } 399 valueStart++; 400 } 401 String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf); 402 String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, convtBuf); 403 put(key, value); 404 } 405 } 406 407 /* Read in a "logical line" from an InputStream/Reader, skip all comment 408 * and blank lines and filter out those leading whitespace characters 409 * (\u0020, \u0009 and \u000c) from the beginning of a "natural line". 410 * Method returns the char length of the "logical line" and stores 411 * the line in "lineBuf". 412 */ 413 class LineReader { 414 public LineReader(InputStream inStream) { 415 this.inStream = inStream; 416 inByteBuf = new byte[8192]; 417 } 418 419 public LineReader(Reader reader) { 420 this.reader = reader; 421 inCharBuf = new char[8192]; 422 } 423 424 byte[] inByteBuf; 425 char[] inCharBuf; 426 char[] lineBuf = new char[1024]; 427 int inLimit = 0; 428 int inOff = 0; 429 InputStream inStream; 430 Reader reader; 431 432 int readLine() throws IOException { 433 int len = 0; 434 char c = 0; 435 436 boolean skipWhiteSpace = true; 437 boolean isCommentLine = false; 438 boolean isNewLine = true; 439 boolean appendedLineBegin = false; 440 boolean precedingBackslash = false; 441 boolean skipLF = false; 442 443 while (true) { 444 if (inOff >= inLimit) { 445 inLimit = (inStream==null)?reader.read(inCharBuf) 446 :inStream.read(inByteBuf); 447 inOff = 0; 448 if (inLimit <= 0) { 449 if (len == 0 || isCommentLine) { 450 return -1; 451 } 452 if (precedingBackslash) { 453 len--; 454 } 455 return len; 456 } 457 } 458 if (inStream != null) { 459 //The line below is equivalent to calling a 460 //ISO8859-1 decoder. 461 c = (char) (0xff & inByteBuf[inOff++]); 462 } else { 463 c = inCharBuf[inOff++]; 464 } 465 if (skipLF) { 466 skipLF = false; 467 if (c == '\n') { 468 continue; 469 } 470 } 471 if (skipWhiteSpace) { 472 if (c == ' ' || c == '\t' || c == '\f') { 473 continue; 474 } 475 if (!appendedLineBegin && (c == '\r' || c == '\n')) { 476 continue; 477 } 478 skipWhiteSpace = false; 479 appendedLineBegin = false; 480 } 481 if (isNewLine) { 482 isNewLine = false; 483 if (c == '#' || c == '!') { 484 isCommentLine = true; 485 continue; 486 } 487 } 488 489 if (c != '\n' && c != '\r') { 490 lineBuf[len++] = c; 491 if (len == lineBuf.length) { 492 int newLength = lineBuf.length * 2; 493 if (newLength < 0) { 494 newLength = Integer.MAX_VALUE; 495 } 496 char[] buf = new char[newLength]; 497 System.arraycopy(lineBuf, 0, buf, 0, lineBuf.length); 498 lineBuf = buf; 499 } 500 //flip the preceding backslash flag 501 if (c == '\\') { 502 precedingBackslash = !precedingBackslash; 503 } else { 504 precedingBackslash = false; 505 } 506 } 507 else { 508 // reached EOL 509 if (isCommentLine || len == 0) { 510 isCommentLine = false; 511 isNewLine = true; 512 skipWhiteSpace = true; 513 len = 0; 514 continue; 515 } 516 if (inOff >= inLimit) { 517 inLimit = (inStream==null) 518 ?reader.read(inCharBuf) 519 :inStream.read(inByteBuf); 520 inOff = 0; 521 if (inLimit <= 0) { 522 if (precedingBackslash) { 523 len--; 524 } 525 return len; 526 } 527 } 528 if (precedingBackslash) { 529 len -= 1; 530 //skip the leading whitespace characters in following line 531 skipWhiteSpace = true; 532 appendedLineBegin = true; 533 precedingBackslash = false; 534 if (c == '\r') { 535 skipLF = true; 536 } 537 } else { 538 return len; 539 } 540 } 541 } 542 } 543 } 544 545 /* 546 * Converts encoded \uxxxx to unicode chars 547 * and changes special saved chars to their original forms 548 */ 549 private String loadConvert (char[] in, int off, int len, char[] convtBuf) { 550 if (convtBuf.length < len) { 551 int newLen = len * 2; 552 if (newLen < 0) { 553 newLen = Integer.MAX_VALUE; 554 } 555 convtBuf = new char[newLen]; 556 } 557 char aChar; 558 char[] out = convtBuf; 559 int outLen = 0; 560 int end = off + len; 561 562 while (off < end) { 563 aChar = in[off++]; 564 if (aChar == '\\') { 565 aChar = in[off++]; 566 if(aChar == 'u') { 567 // Read the xxxx 568 int value=0; 569 for (int i=0; i<4; i++) { 570 aChar = in[off++]; 571 switch (aChar) { 572 case '0': case '1': case '2': case '3': case '4': 573 case '5': case '6': case '7': case '8': case '9': 574 value = (value << 4) + aChar - '0'; 575 break; 576 case 'a': case 'b': case 'c': 577 case 'd': case 'e': case 'f': 578 value = (value << 4) + 10 + aChar - 'a'; 579 break; 580 case 'A': case 'B': case 'C': 581 case 'D': case 'E': case 'F': 582 value = (value << 4) + 10 + aChar - 'A'; 583 break; 584 default: 585 throw new IllegalArgumentException( 586 "Malformed \\uxxxx encoding."); 587 } 588 } 589 out[outLen++] = (char)value; 590 } else { 591 if (aChar == 't') aChar = '\t'; 592 else if (aChar == 'r') aChar = '\r'; 593 else if (aChar == 'n') aChar = '\n'; 594 else if (aChar == 'f') aChar = '\f'; 595 out[outLen++] = aChar; 596 } 597 } else { 598 out[outLen++] = aChar; 599 } 600 } 601 return new String (out, 0, outLen); 602 } 603 604 /* 605 * Converts unicodes to encoded \uxxxx and escapes 606 * special characters with a preceding slash 607 */ 608 private String saveConvert(String theString, 609 boolean escapeSpace, 610 boolean escapeUnicode) { 611 int len = theString.length(); 612 int bufLen = len * 2; 613 if (bufLen < 0) { 614 bufLen = Integer.MAX_VALUE; 615 } 616 StringBuilder outBuffer = new StringBuilder(bufLen); 617 618 for(int x=0; x<len; x++) { 619 char aChar = theString.charAt(x); 620 // Handle common case first, selecting largest block that 621 // avoids the specials below 622 if ((aChar > 61) && (aChar < 127)) { 623 if (aChar == '\\') { 624 outBuffer.append('\\'); outBuffer.append('\\'); 625 continue; 626 } 627 outBuffer.append(aChar); 628 continue; 629 } 630 switch(aChar) { 631 case ' ': 632 if (x == 0 || escapeSpace) 633 outBuffer.append('\\'); 634 outBuffer.append(' '); 635 break; 636 case '\t':outBuffer.append('\\'); outBuffer.append('t'); 637 break; 638 case '\n':outBuffer.append('\\'); outBuffer.append('n'); 639 break; 640 case '\r':outBuffer.append('\\'); outBuffer.append('r'); 641 break; 642 case '\f':outBuffer.append('\\'); outBuffer.append('f'); 643 break; 644 case '=': // Fall through 645 case ':': // Fall through 646 case '#': // Fall through 647 case '!': 648 outBuffer.append('\\'); outBuffer.append(aChar); 649 break; 650 default: 651 if (((aChar < 0x0020) || (aChar > 0x007e)) & escapeUnicode ) { 652 outBuffer.append('\\'); 653 outBuffer.append('u'); 654 outBuffer.append(toHex((aChar >> 12) & 0xF)); 655 outBuffer.append(toHex((aChar >> 8) & 0xF)); 656 outBuffer.append(toHex((aChar >> 4) & 0xF)); 657 outBuffer.append(toHex( aChar & 0xF)); 658 } else { 659 outBuffer.append(aChar); 660 } 661 } 662 } 663 return outBuffer.toString(); 664 } 665 666 private static void writeComments(BufferedWriter bw, String comments) 667 throws IOException { 668 bw.write("#"); 669 int len = comments.length(); 670 int current = 0; 671 int last = 0; 672 char[] uu = new char[6]; 673 uu[0] = '\\'; 674 uu[1] = 'u'; 675 while (current < len) { 676 char c = comments.charAt(current); 677 if (c > '\u00ff' || c == '\n' || c == '\r') { 678 if (last != current) 679 bw.write(comments.substring(last, current)); 680 if (c > '\u00ff') { 681 uu[2] = toHex((c >> 12) & 0xf); 682 uu[3] = toHex((c >> 8) & 0xf); 683 uu[4] = toHex((c >> 4) & 0xf); 684 uu[5] = toHex( c & 0xf); 685 bw.write(new String(uu)); 686 } else { 687 bw.newLine(); 688 if (c == '\r' && 689 current != len - 1 && 690 comments.charAt(current + 1) == '\n') { 691 current++; 692 } 693 if (current == len - 1 || 694 (comments.charAt(current + 1) != '#' && 695 comments.charAt(current + 1) != '!')) 696 bw.write("#"); 697 } 698 last = current + 1; 699 } 700 current++; 701 } 702 if (last != current) 703 bw.write(comments.substring(last, current)); 704 bw.newLine(); 705 } 706 707 /** 708 * Calls the {@code store(OutputStream out, String comments)} method 709 * and suppresses IOExceptions that were thrown. 710 * 711 * @deprecated This method does not throw an IOException if an I/O error 712 * occurs while saving the property list. The preferred way to save a 713 * properties list is via the {@code store(OutputStream out, 714 * String comments)} method or the 715 * {@code storeToXML(OutputStream os, String comment)} method. 716 * 717 * @param out an output stream. 718 * @param comments a description of the property list. 719 * @exception ClassCastException if this {@code Properties} object 720 * contains any keys or values that are not 721 * {@code Strings}. 722 */ 723 @Deprecated 724 public void save(OutputStream out, String comments) { 725 try { 726 store(out, comments); 727 } catch (IOException e) { 728 } 729 } 730 731 /** 732 * Writes this property list (key and element pairs) in this 733 * {@code Properties} table to the output character stream in a 734 * format suitable for using the {@link #load(java.io.Reader) load(Reader)} 735 * method. 736 * <p> 737 * Properties from the defaults table of this {@code Properties} 738 * table (if any) are <i>not</i> written out by this method. 739 * <p> 740 * If the comments argument is not null, then an ASCII {@code #} 741 * character, the comments string, and a line separator are first written 742 * to the output stream. Thus, the {@code comments} can serve as an 743 * identifying comment. Any one of a line feed ('\n'), a carriage 744 * return ('\r'), or a carriage return followed immediately by a line feed 745 * in comments is replaced by a line separator generated by the {@code Writer} 746 * and if the next character in comments is not character {@code #} or 747 * character {@code !} then an ASCII {@code #} is written out 748 * after that line separator. 749 * <p> 750 * Next, a comment line is always written, consisting of an ASCII 751 * {@code #} character, the current date and time (as if produced 752 * by the {@code toString} method of {@code Date} for the 753 * current time), and a line separator as generated by the {@code Writer}. 754 * <p> 755 * Then every entry in this {@code Properties} table is 756 * written out, one per line. For each entry the key string is 757 * written, then an ASCII {@code =}, then the associated 758 * element string. For the key, all space characters are 759 * written with a preceding {@code \} character. For the 760 * element, leading space characters, but not embedded or trailing 761 * space characters, are written with a preceding {@code \} 762 * character. The key and element characters {@code #}, 763 * {@code !}, {@code =}, and {@code :} are written 764 * with a preceding backslash to ensure that they are properly loaded. 765 * <p> 766 * After the entries have been written, the output stream is flushed. 767 * The output stream remains open after this method returns. 768 * 769 * @param writer an output character stream writer. 770 * @param comments a description of the property list. 771 * @exception IOException if writing this property list to the specified 772 * output stream throws an <tt>IOException</tt>. 773 * @exception ClassCastException if this {@code Properties} object 774 * contains any keys or values that are not {@code Strings}. 775 * @exception NullPointerException if {@code writer} is null. 776 * @since 1.6 777 */ 778 public void store(Writer writer, String comments) 779 throws IOException 780 { 781 store0((writer instanceof BufferedWriter)?(BufferedWriter)writer 782 : new BufferedWriter(writer), 783 comments, 784 false); 785 } 786 787 /** 788 * Writes this property list (key and element pairs) in this 789 * {@code Properties} table to the output stream in a format suitable 790 * for loading into a {@code Properties} table using the 791 * {@link #load(InputStream) load(InputStream)} method. 792 * <p> 793 * Properties from the defaults table of this {@code Properties} 794 * table (if any) are <i>not</i> written out by this method. 795 * <p> 796 * This method outputs the comments, properties keys and values in 797 * the same format as specified in 798 * {@link #store(java.io.Writer, java.lang.String) store(Writer)}, 799 * with the following differences: 800 * <ul> 801 * <li>The stream is written using the ISO 8859-1 character encoding. 802 * 803 * <li>Characters not in Latin-1 in the comments are written as 804 * {@code \u005Cu}<i>xxxx</i> for their appropriate unicode 805 * hexadecimal value <i>xxxx</i>. 806 * 807 * <li>Characters less than {@code \u005Cu0020} and characters greater 808 * than {@code \u005Cu007E} in property keys or values are written 809 * as {@code \u005Cu}<i>xxxx</i> for the appropriate hexadecimal 810 * value <i>xxxx</i>. 811 * </ul> 812 * <p> 813 * After the entries have been written, the output stream is flushed. 814 * The output stream remains open after this method returns. 815 * 816 * @param out an output stream. 817 * @param comments a description of the property list. 818 * @exception IOException if writing this property list to the specified 819 * output stream throws an <tt>IOException</tt>. 820 * @exception ClassCastException if this {@code Properties} object 821 * contains any keys or values that are not {@code Strings}. 822 * @exception NullPointerException if {@code out} is null. 823 * @since 1.2 824 */ 825 public void store(OutputStream out, String comments) 826 throws IOException 827 { 828 store0(new BufferedWriter(new OutputStreamWriter(out, "8859_1")), 829 comments, 830 true); 831 } 832 833 private void store0(BufferedWriter bw, String comments, boolean escUnicode) 834 throws IOException 835 { 836 if (comments != null) { 837 writeComments(bw, comments); 838 } 839 bw.write("#" + new Date().toString()); 840 bw.newLine(); 841 for (Map.Entry<Object, Object> e : entrySet()) { 842 String key = (String)e.getKey(); 843 String val = (String)e.getValue(); 844 key = saveConvert(key, true, escUnicode); 845 /* No need to escape embedded and trailing spaces for value, hence 846 * pass false to flag. 847 */ 848 val = saveConvert(val, false, escUnicode); 849 bw.write(key + "=" + val); 850 bw.newLine(); 851 } 852 bw.flush(); 853 } 854 855 /** 856 * Loads all of the properties represented by the XML document on the 857 * specified input stream into this properties table. 858 * 859 * <p>The XML document must have the following DOCTYPE declaration: 860 * <pre> 861 * <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> 862 * </pre> 863 * Furthermore, the document must satisfy the properties DTD described 864 * above. 865 * 866 * <p> An implementation is required to read XML documents that use the 867 * "{@code UTF-8}" or "{@code UTF-16}" encoding. An implementation may 868 * support additional encodings. 869 * 870 * <p>The specified stream is closed after this method returns. 871 * 872 * @param in the input stream from which to read the XML document. 873 * @throws IOException if reading from the specified input stream 874 * results in an <tt>IOException</tt>. 875 * @throws java.io.UnsupportedEncodingException if the document's encoding 876 * declaration can be read and it specifies an encoding that is not 877 * supported 878 * @throws InvalidPropertiesFormatException Data on input stream does not 879 * constitute a valid XML document with the mandated document type. 880 * @throws NullPointerException if {@code in} is null. 881 * @see #storeToXML(OutputStream, String, String) 882 * @see <a href="http://www.w3.org/TR/REC-xml/#charencoding">Character 883 * Encoding in Entities</a> 884 * @since 1.5 885 */ 886 public void loadFromXML(InputStream in) 887 throws IOException, InvalidPropertiesFormatException 888 { 889 Objects.requireNonNull(in); 890 PropertiesDefaultHandler handler = new PropertiesDefaultHandler(); 891 handler.load(this, in); 892 in.close(); 893 } 894 895 /** 896 * Emits an XML document representing all of the properties contained 897 * in this table. 898 * 899 * <p> An invocation of this method of the form <tt>props.storeToXML(os, 900 * comment)</tt> behaves in exactly the same way as the invocation 901 * <tt>props.storeToXML(os, comment, "UTF-8");</tt>. 902 * 903 * @param os the output stream on which to emit the XML document. 904 * @param comment a description of the property list, or {@code null} 905 * if no comment is desired. 906 * @throws IOException if writing to the specified output stream 907 * results in an <tt>IOException</tt>. 908 * @throws NullPointerException if {@code os} is null. 909 * @throws ClassCastException if this {@code Properties} object 910 * contains any keys or values that are not 911 * {@code Strings}. 912 * @see #loadFromXML(InputStream) 913 * @since 1.5 914 */ 915 public void storeToXML(OutputStream os, String comment) 916 throws IOException 917 { 918 storeToXML(os, comment, "UTF-8"); 919 } 920 921 /** 922 * Emits an XML document representing all of the properties contained 923 * in this table, using the specified encoding. 924 * 925 * <p>The XML document will have the following DOCTYPE declaration: 926 * <pre> 927 * <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> 928 * </pre> 929 * 930 * <p>If the specified comment is {@code null} then no comment 931 * will be stored in the document. 932 * 933 * <p> An implementation is required to support writing of XML documents 934 * that use the "{@code UTF-8}" or "{@code UTF-16}" encoding. An 935 * implementation may support additional encodings. 936 * 937 * <p>The specified stream remains open after this method returns. 938 * 939 * @param os the output stream on which to emit the XML document. 940 * @param comment a description of the property list, or {@code null} 941 * if no comment is desired. 942 * @param encoding the name of a supported 943 * <a href="../lang/package-summary.html#charenc"> 944 * character encoding</a> 945 * 946 * @throws IOException if writing to the specified output stream 947 * results in an <tt>IOException</tt>. 948 * @throws java.io.UnsupportedEncodingException if the encoding is not 949 * supported by the implementation. 950 * @throws NullPointerException if {@code os} is {@code null}, 951 * or if {@code encoding} is {@code null}. 952 * @throws ClassCastException if this {@code Properties} object 953 * contains any keys or values that are not 954 * {@code Strings}. 955 * @see #loadFromXML(InputStream) 956 * @see <a href="http://www.w3.org/TR/REC-xml/#charencoding">Character 957 * Encoding in Entities</a> 958 * @since 1.5 959 */ 960 public void storeToXML(OutputStream os, String comment, String encoding) 961 throws IOException 962 { 963 Objects.requireNonNull(os); 964 Objects.requireNonNull(encoding); 965 PropertiesDefaultHandler handler = new PropertiesDefaultHandler(); 966 handler.store(this, os, comment, encoding); 967 } 968 969 /** 970 * Searches for the property with the specified key in this property list. 971 * If the key is not found in this property list, the default property list, 972 * and its defaults, recursively, are then checked. The method returns 973 * {@code null} if the property is not found. 974 * 975 * @param key the property key. 976 * @return the value in this property list with the specified key value. 977 * @see #setProperty 978 * @see #defaults 979 */ 980 public String getProperty(String key) { 981 Object oval = map.get(key); 982 String sval = (oval instanceof String) ? (String)oval : null; 983 return ((sval == null) && (defaults != null)) ? defaults.getProperty(key) : sval; 984 } 985 986 /** 987 * Searches for the property with the specified key in this property list. 988 * If the key is not found in this property list, the default property list, 989 * and its defaults, recursively, are then checked. The method returns the 990 * default value argument if the property is not found. 991 * 992 * @param key the hashtable key. 993 * @param defaultValue a default value. 994 * 995 * @return the value in this property list with the specified key value. 996 * @see #setProperty 997 * @see #defaults 998 */ 999 public String getProperty(String key, String defaultValue) { 1000 String val = getProperty(key); 1001 return (val == null) ? defaultValue : val; 1002 } 1003 1004 /** 1005 * Returns an enumeration of all the keys in this property list, 1006 * including distinct keys in the default property list if a key 1007 * of the same name has not already been found from the main 1008 * properties list. 1009 * 1010 * @return an enumeration of all the keys in this property list, including 1011 * the keys in the default property list. 1012 * @throws ClassCastException if any key in this property list 1013 * is not a string. 1014 * @see java.util.Enumeration 1015 * @see java.util.Properties#defaults 1016 * @see #stringPropertyNames 1017 */ 1018 public Enumeration<?> propertyNames() { 1019 Hashtable<String, Object> h = new Hashtable<>(); 1020 enumerate(h); 1021 return h.keys(); 1022 } 1023 1024 /** 1025 * Returns a set of keys in this property list where 1026 * the key and its corresponding value are strings, 1027 * including distinct keys in the default property list if a key 1028 * of the same name has not already been found from the main 1029 * properties list. Properties whose key or value is not 1030 * of type <tt>String</tt> are omitted. 1031 * <p> 1032 * The returned set is not backed by the <tt>Properties</tt> object. 1033 * Changes to this <tt>Properties</tt> are not reflected in the set, 1034 * or vice versa. 1035 * 1036 * @return a set of keys in this property list where 1037 * the key and its corresponding value are strings, 1038 * including the keys in the default property list. 1039 * @see java.util.Properties#defaults 1040 * @since 1.6 1041 */ 1042 public Set<String> stringPropertyNames() { 1043 Map<String, String> h = new HashMap<>(); 1044 enumerateStringProperties(h); 1045 return h.keySet(); 1046 } 1047 1048 /** 1049 * Prints this property list out to the specified output stream. 1050 * This method is useful for debugging. 1051 * 1052 * @param out an output stream. 1053 * @throws ClassCastException if any key in this property list 1054 * is not a string. 1055 */ 1056 public void list(PrintStream out) { 1057 out.println("-- listing properties --"); 1058 Map<String, Object> h = new HashMap<>(); 1059 enumerate(h); 1060 for (Map.Entry<String, Object> e : h.entrySet()) { 1061 String key = e.getKey(); 1062 String val = (String)e.getValue(); 1063 if (val.length() > 40) { 1064 val = val.substring(0, 37) + "..."; 1065 } 1066 out.println(key + "=" + val); 1067 } 1068 } 1069 1070 /** 1071 * Prints this property list out to the specified output stream. 1072 * This method is useful for debugging. 1073 * 1074 * @param out an output stream. 1075 * @throws ClassCastException if any key in this property list 1076 * is not a string. 1077 * @since 1.1 1078 */ 1079 /* 1080 * Rather than use an anonymous inner class to share common code, this 1081 * method is duplicated in order to ensure that a non-1.1 compiler can 1082 * compile this file. 1083 */ 1084 public void list(PrintWriter out) { 1085 out.println("-- listing properties --"); 1086 Map<String, Object> h = new HashMap<>(); 1087 enumerate(h); 1088 for (Map.Entry<String, Object> e : h.entrySet()) { 1089 String key = e.getKey(); 1090 String val = (String)e.getValue(); 1091 if (val.length() > 40) { 1092 val = val.substring(0, 37) + "..."; 1093 } 1094 out.println(key + "=" + val); 1095 } 1096 } 1097 1098 /** 1099 * Enumerates all key/value pairs in the specified Map. 1100 * @param h the Map 1101 * @throws ClassCastException if any of the property keys 1102 * is not of String type. 1103 */ 1104 private void enumerate(Map<String, Object> h) { 1105 if (defaults != null) { 1106 defaults.enumerate(h); 1107 } 1108 for (Map.Entry<Object, Object> e : entrySet()) { 1109 String key = (String)e.getKey(); 1110 h.put(key, e.getValue()); 1111 } 1112 } 1113 1114 /** 1115 * Enumerates all key/value pairs in the specified Map 1116 * and omits the property if the key or value is not a string. 1117 * @param h the Map 1118 */ 1119 private void enumerateStringProperties(Map<String, String> h) { 1120 if (defaults != null) { 1121 defaults.enumerateStringProperties(h); 1122 } 1123 for (Map.Entry<Object, Object> e : entrySet()) { 1124 Object k = e.getKey(); 1125 Object v = e.getValue(); 1126 if (k instanceof String && v instanceof String) { 1127 h.put((String) k, (String) v); 1128 } 1129 } 1130 } 1131 1132 /** 1133 * Convert a nibble to a hex character 1134 * @param nibble the nibble to convert. 1135 */ 1136 private static char toHex(int nibble) { 1137 return hexDigit[(nibble & 0xF)]; 1138 } 1139 1140 /** A table of hex digits */ 1141 private static final char[] hexDigit = { 1142 '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' 1143 }; 1144 1145 // 1146 // Hashtable methods overridden and delegated to a ConcurrentHashMap instance 1147 1148 private transient ConcurrentHashMap<Object, Object> map = 1149 new ConcurrentHashMap<>(8); 1150 1151 @Override 1152 public int size() { 1153 return map.size(); 1154 } 1155 1156 @Override 1157 public boolean isEmpty() { 1158 return map.isEmpty(); 1159 } 1160 1161 @Override 1162 public Enumeration<Object> keys() { 1163 return map.keys(); 1164 } 1165 1166 @Override 1167 public Enumeration<Object> elements() { 1168 return map.elements(); 1169 } 1170 1171 @Override 1172 public boolean contains(Object value) { 1173 return map.contains(value); 1174 } 1175 1176 @Override 1177 public boolean containsValue(Object value) { 1178 return map.containsValue(value); 1179 } 1180 1181 @Override 1182 public boolean containsKey(Object key) { 1183 return map.containsKey(key); 1184 } 1185 1186 @Override 1187 public Object get(Object key) { 1188 return map.get(key); 1189 } 1190 1191 @Override 1192 public Object put(Object key, Object value) { 1193 return map.put(key, value); 1194 } 1195 1196 @Override 1197 public Object remove(Object key) { 1198 return map.remove(key); 1199 } 1200 1201 @Override 1202 public void putAll(Map<?, ?> t) { 1203 map.putAll(t); 1204 } 1205 1206 @Override 1207 public void clear() { 1208 map.clear(); 1209 } 1210 1211 @Override 1212 public String toString() { 1213 return map.toString(); 1214 } 1215 1216 @Override 1217 public Set<Object> keySet() { 1218 return map.keySet(); 1219 } 1220 1221 @Override 1222 public Set<Map.Entry<Object, Object>> entrySet() { 1223 return map.entrySet(); 1224 } 1225 1226 @Override 1227 public Collection<Object> values() { 1228 return map.values(); 1229 } 1230 1231 @Override 1232 public boolean equals(Object o) { 1233 return map.equals(o); 1234 } 1235 1236 @Override 1237 public int hashCode() { 1238 return map.hashCode(); 1239 } 1240 1241 @Override 1242 public Object getOrDefault(Object key, Object defaultValue) { 1243 return map.getOrDefault(key, defaultValue); 1244 } 1245 1246 @Override 1247 public void forEach(BiConsumer<? super Object, ? super Object> action) { 1248 map.forEach(action); 1249 } 1250 1251 @Override 1252 public void replaceAll(BiFunction<? super Object, ? super Object, ?> function) { 1253 map.replaceAll(function); 1254 } 1255 1256 @Override 1257 public Object putIfAbsent(Object key, Object value) { 1258 return map.putIfAbsent(key, value); 1259 } 1260 1261 @Override 1262 public boolean remove(Object key, Object value) { 1263 return map.remove(key, value); 1264 } 1265 1266 @Override 1267 public boolean replace(Object key, Object oldValue, Object newValue) { 1268 return map.replace(key, oldValue, newValue); 1269 } 1270 1271 @Override 1272 public Object replace(Object key, Object value) { 1273 return map.replace(key, value); 1274 } 1275 1276 @Override 1277 public Object computeIfAbsent(Object key, 1278 Function<? super Object, ?> mappingFunction) { 1279 return map.computeIfAbsent(key, mappingFunction); 1280 } 1281 1282 @Override 1283 public Object computeIfPresent(Object key, 1284 BiFunction<? super Object, ? super Object, ?> remappingFunction) { 1285 return map.computeIfPresent(key, remappingFunction); 1286 } 1287 1288 @Override 1289 public Object compute(Object key, 1290 BiFunction<? super Object, ? super Object, ?> remappingFunction) { 1291 return map.compute(key, remappingFunction); 1292 } 1293 1294 @Override 1295 public Object merge(Object key, Object value, 1296 BiFunction<? super Object, ? super Object, ?> remappingFunction) { 1297 return map.merge(key, value, remappingFunction); 1298 } 1299 1300 // 1301 // Special Hashtable methods 1302 1303 @Override 1304 protected void rehash() { 1305 // no-op 1306 } 1307 1308 @Override 1309 public Object clone() { 1310 Properties clone = (Properties) cloneHashtable(); 1311 clone.map = new ConcurrentHashMap<>(map); 1312 return clone; 1313 } 1314 1315 // 1316 // Hashtable serialization overrides 1317 // (these should emit and consume Hashtable-compatible stream) 1318 1319 @Override 1320 void writeHashtable(ObjectOutputStream s) throws IOException { 1321 List<Object> entryStack = new ArrayList<>(map.size() * 2); // an estimate 1322 1323 for (Map.Entry<Object, Object> entry : map.entrySet()) { 1324 entryStack.add(entry.getValue()); 1325 entryStack.add(entry.getKey()); 1326 } 1327 1328 // Write out the simulated threshold, loadfactor 1329 float loadFactor = 0.75f; 1330 int count = entryStack.size() / 2; 1331 int length = (int)(count / loadFactor) + (count / 20) + 3; 1332 if (length > count && (length & 1) == 0) { 1333 length--; 1334 } 1335 synchronized (map) { // in case of multiple concurrent serializations 1336 defaultWriteHashtable(s, length, loadFactor); 1337 } 1338 1339 // Write out simulated length and real count of elements 1340 s.writeInt(length); 1341 s.writeInt(count); 1342 1343 // Write out the key/value objects from the stacked entries 1344 for (int i = entryStack.size() - 1; i >= 0; i--) { 1345 s.writeObject(entryStack.get(i)); 1346 } 1347 } 1348 1349 @Override 1350 void readHashtable(ObjectInputStream s) throws IOException, 1351 ClassNotFoundException { 1352 // Read in the threshold and loadfactor 1353 s.defaultReadObject(); 1354 1355 // Read the original length of the array and number of elements 1356 int origlength = s.readInt(); 1357 int elements = s.readInt(); 1358 1359 // create CHM of appropriate capacity 1360 map = new ConcurrentHashMap<>(elements); 1361 1362 // Read all the key/value objects 1363 for (; elements > 0; elements--) { 1364 Object key = s.readObject(); 1365 Object value = s.readObject(); 1366 map.put(key, value); 1367 } 1368 } 1369 }