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