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