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