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