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(); 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[] lineBuf = this.lineBuf; 496 char c; 497 498 while (true) { 499 if (off >= limit) { 500 inLimit = limit = fromStream ? inStream.read(byteBuf) 501 : reader.read(charBuf); 502 if (limit <= 0) { 503 if (len == 0) { 504 return -1; 505 } 506 return precedingBackslash ? len - 1 : len; 507 } 508 off = 0; 509 } 510 511 // (char)(byte & 0xFF) is equivalent to calling a ISO8859-1 decoder. 512 c = (fromStream) ? (char)(byteBuf[off++] & 0xFF) : charBuf[off++]; 513 514 if (skipWhiteSpace) { 515 if (c == ' ' || c == '\t' || c == '\f') { 516 continue; 517 } 518 if (!appendedLineBegin && (c == '\r' || c == '\n')) { 519 continue; 520 } 521 skipWhiteSpace = false; 522 appendedLineBegin = false; 523 524 } 525 if (len == 0) { // Still on a new logical line 526 if (c == '#' || c == '!') { 527 // Comment, quickly consume the rest of the line 528 529 // When checking for new line characters a range check, 530 // starting with the higher bound ('\r') means one less 531 // branch in the common case. 532 commentLoop: while (true) { 533 if (fromStream) { 534 byte b; 535 while (off < limit) { 536 b = byteBuf[off++]; 537 if (b <= '\r' && (b == '\r' || b == '\n')) 538 break commentLoop; 539 } 540 if (off == limit) { 541 inLimit = limit = inStream.read(byteBuf); 542 if (limit <= 0) { // EOF 543 return -1; 544 } 545 off = 0; 546 } 547 } else { 548 while (off < limit) { 549 c = charBuf[off++]; 550 if (c <= '\r' && (c == '\r' || c == '\n')) 551 break commentLoop; 552 } 553 if (off == limit) { 554 inLimit = limit = reader.read(charBuf); 555 if (limit <= 0) { // EOF 556 return -1; 557 } 558 off = 0; 559 } 560 } 561 } 562 skipWhiteSpace = true; 563 continue; 564 } 565 } 566 567 if (c != '\n' && c != '\r') { 568 lineBuf[len++] = c; 569 if (len == lineBuf.length) { 570 lineBuf = new char[ArraysSupport.newLength(len, 1, len)]; 571 System.arraycopy(this.lineBuf, 0, lineBuf, 0, len); 572 this.lineBuf = lineBuf; 573 } 574 // flip the preceding backslash flag 575 precedingBackslash = (c == '\\') ? !precedingBackslash : false; 576 } else { 577 // reached EOL 578 if (len == 0) { 579 skipWhiteSpace = true; 580 continue; 581 } 582 if (off >= limit) { 583 inLimit = limit = fromStream ? inStream.read(byteBuf) 584 : reader.read(charBuf); 585 off = 0; 586 if (limit <= 0) { // EOF 587 return precedingBackslash ? len - 1 : len; 588 } 589 } 590 if (precedingBackslash) { 591 // backslash at EOL is not part of the line 592 len -= 1; 593 // skip leading whitespace characters in the following line 594 skipWhiteSpace = true; 595 appendedLineBegin = true; 596 precedingBackslash = false; 597 // take care not to include any subsequent \n 598 if (c == '\r') { 599 if (fromStream) { 600 if (byteBuf[off] == '\n') { 601 off++; 602 } 603 } else { 604 if (charBuf[off] == '\n') { 605 off++; 606 } 607 } 608 } 609 } else { 610 inOff = off; 611 return len; 612 } 613 } 614 } 615 } 616 } 617 618 /* 619 * Converts encoded \uxxxx to unicode chars 620 * and changes special saved chars to their original forms 621 */ 622 private String loadConvert(char[] in, int off, int len, StringBuilder out) { 623 char aChar; 624 int end = off + len; 625 int start = off; 626 while (off < end) { 627 aChar = in[off++]; 628 if (aChar == '\\') { 629 break; 630 } 631 } 632 if (off == end) { // No backslash 633 return new String(in, start, len); 634 } 635 636 // backslash found at off - 1, reset the shared buffer, rewind offset 637 out.setLength(0); 638 off--; 639 out.append(in, start, off - start); 640 641 while (off < end) { 642 aChar = in[off++]; 643 if (aChar == '\\') { 644 // No need to bounds check since LineReader::readLine excludes 645 // unescaped \s at the end of the line 646 aChar = in[off++]; 647 if(aChar == 'u') { 648 // Read the xxxx 649 if (off > end - 4) 650 throw new IllegalArgumentException( 651 "Malformed \\uxxxx encoding."); 652 int value = 0; 653 for (int i = 0; i < 4; i++) { 654 aChar = in[off++]; 655 switch (aChar) { 656 case '0': case '1': case '2': case '3': case '4': 657 case '5': case '6': case '7': case '8': case '9': 658 value = (value << 4) + aChar - '0'; 659 break; 660 case 'a': case 'b': case 'c': 661 case 'd': case 'e': case 'f': 662 value = (value << 4) + 10 + aChar - 'a'; 663 break; 664 case 'A': case 'B': case 'C': 665 case 'D': case 'E': case 'F': 666 value = (value << 4) + 10 + aChar - 'A'; 667 break; 668 default: 669 throw new IllegalArgumentException( 670 "Malformed \\uxxxx encoding."); 671 } 672 } 673 out.append((char)value); 674 } else { 675 if (aChar == 't') aChar = '\t'; 676 else if (aChar == 'r') aChar = '\r'; 677 else if (aChar == 'n') aChar = '\n'; 678 else if (aChar == 'f') aChar = '\f'; 679 out.append(aChar); 680 } 681 } else { 682 out.append(aChar); 683 } 684 } 685 return out.toString(); 686 } 687 688 /* 689 * Converts unicodes to encoded \uxxxx and escapes 690 * special characters with a preceding slash 691 */ 692 private String saveConvert(String theString, 693 boolean escapeSpace, 694 boolean escapeUnicode) { 695 int len = theString.length(); 696 int bufLen = len * 2; 697 if (bufLen < 0) { 698 bufLen = Integer.MAX_VALUE; 699 } 700 StringBuilder outBuffer = new StringBuilder(bufLen); 701 702 for(int x=0; x<len; x++) { 703 char aChar = theString.charAt(x); 704 // Handle common case first, selecting largest block that 705 // avoids the specials below 706 if ((aChar > 61) && (aChar < 127)) { 707 if (aChar == '\\') { 708 outBuffer.append('\\'); outBuffer.append('\\'); 709 continue; 710 } 711 outBuffer.append(aChar); 712 continue; 713 } 714 switch(aChar) { 715 case ' ': 716 if (x == 0 || escapeSpace) 717 outBuffer.append('\\'); 718 outBuffer.append(' '); 719 break; 720 case '\t':outBuffer.append('\\'); outBuffer.append('t'); 721 break; 722 case '\n':outBuffer.append('\\'); outBuffer.append('n'); 723 break; 724 case '\r':outBuffer.append('\\'); outBuffer.append('r'); 725 break; 726 case '\f':outBuffer.append('\\'); outBuffer.append('f'); 727 break; 728 case '=': // Fall through 729 case ':': // Fall through 730 case '#': // Fall through 731 case '!': 732 outBuffer.append('\\'); outBuffer.append(aChar); 733 break; 734 default: 735 if (((aChar < 0x0020) || (aChar > 0x007e)) & escapeUnicode ) { 736 outBuffer.append('\\'); 737 outBuffer.append('u'); 738 outBuffer.append(toHex((aChar >> 12) & 0xF)); 739 outBuffer.append(toHex((aChar >> 8) & 0xF)); 740 outBuffer.append(toHex((aChar >> 4) & 0xF)); 741 outBuffer.append(toHex( aChar & 0xF)); 742 } else { 743 outBuffer.append(aChar); 744 } 745 } 746 } 747 return outBuffer.toString(); 748 } 749 750 private static void writeComments(BufferedWriter bw, String comments) 751 throws IOException { 752 bw.write("#"); 753 int len = comments.length(); 754 int current = 0; 755 int last = 0; 756 char[] uu = new char[6]; 757 uu[0] = '\\'; 758 uu[1] = 'u'; 759 while (current < len) { 760 char c = comments.charAt(current); 761 if (c > '\u00ff' || c == '\n' || c == '\r') { 762 if (last != current) 763 bw.write(comments.substring(last, current)); 764 if (c > '\u00ff') { 765 uu[2] = toHex((c >> 12) & 0xf); 766 uu[3] = toHex((c >> 8) & 0xf); 767 uu[4] = toHex((c >> 4) & 0xf); 768 uu[5] = toHex( c & 0xf); 769 bw.write(new String(uu)); 770 } else { 771 bw.newLine(); 772 if (c == '\r' && 773 current != len - 1 && 774 comments.charAt(current + 1) == '\n') { 775 current++; 776 } 777 if (current == len - 1 || 778 (comments.charAt(current + 1) != '#' && 779 comments.charAt(current + 1) != '!')) 780 bw.write("#"); 781 } 782 last = current + 1; 783 } 784 current++; 785 } 786 if (last != current) 787 bw.write(comments.substring(last, current)); 788 bw.newLine(); 789 } 790 791 /** 792 * Calls the {@code store(OutputStream out, String comments)} method 793 * and suppresses IOExceptions that were thrown. 794 * 795 * @deprecated This method does not throw an IOException if an I/O error 796 * occurs while saving the property list. The preferred way to save a 797 * properties list is via the {@code store(OutputStream out, 798 * String comments)} method or the 799 * {@code storeToXML(OutputStream os, String comment)} method. 800 * 801 * @param out an output stream. 802 * @param comments a description of the property list. 803 * @exception ClassCastException if this {@code Properties} object 804 * contains any keys or values that are not 805 * {@code Strings}. 806 */ 807 @Deprecated 808 public void save(OutputStream out, String comments) { 809 try { 810 store(out, comments); 811 } catch (IOException e) { 812 } 813 } 814 815 /** 816 * Writes this property list (key and element pairs) in this 817 * {@code Properties} table to the output character stream in a 818 * format suitable for using the {@link #load(java.io.Reader) load(Reader)} 819 * method. 820 * <p> 821 * Properties from the defaults table of this {@code Properties} 822 * table (if any) are <i>not</i> written out by this method. 823 * <p> 824 * If the comments argument is not null, then an ASCII {@code #} 825 * character, the comments string, and a line separator are first written 826 * to the output stream. Thus, the {@code comments} can serve as an 827 * identifying comment. Any one of a line feed ('\n'), a carriage 828 * return ('\r'), or a carriage return followed immediately by a line feed 829 * in comments is replaced by a line separator generated by the {@code Writer} 830 * and if the next character in comments is not character {@code #} or 831 * character {@code !} then an ASCII {@code #} is written out 832 * after that line separator. 833 * <p> 834 * Next, a comment line is always written, consisting of an ASCII 835 * {@code #} character, the current date and time (as if produced 836 * by the {@code toString} method of {@code Date} for the 837 * current time), and a line separator as generated by the {@code Writer}. 838 * <p> 839 * Then every entry in this {@code Properties} table is 840 * written out, one per line. For each entry the key string is 841 * written, then an ASCII {@code =}, then the associated 842 * element string. For the key, all space characters are 843 * written with a preceding {@code \} character. For the 844 * element, leading space characters, but not embedded or trailing 845 * space characters, are written with a preceding {@code \} 846 * character. The key and element characters {@code #}, 847 * {@code !}, {@code =}, and {@code :} are written 848 * with a preceding backslash to ensure that they are properly loaded. 849 * <p> 850 * After the entries have been written, the output stream is flushed. 851 * The output stream remains open after this method returns. 852 * 853 * @param writer an output character stream writer. 854 * @param comments a description of the property list. 855 * @exception IOException if writing this property list to the specified 856 * output stream throws an {@code IOException}. 857 * @exception ClassCastException if this {@code Properties} object 858 * contains any keys or values that are not {@code Strings}. 859 * @exception NullPointerException if {@code writer} is null. 860 * @since 1.6 861 */ 862 public void store(Writer writer, String comments) 863 throws IOException 864 { 865 store0((writer instanceof BufferedWriter)?(BufferedWriter)writer 866 : new BufferedWriter(writer), 867 comments, 868 false); 869 } 870 871 /** 872 * Writes this property list (key and element pairs) in this 873 * {@code Properties} table to the output stream in a format suitable 874 * for loading into a {@code Properties} table using the 875 * {@link #load(InputStream) load(InputStream)} method. 876 * <p> 877 * Properties from the defaults table of this {@code Properties} 878 * table (if any) are <i>not</i> written out by this method. 879 * <p> 880 * This method outputs the comments, properties keys and values in 881 * the same format as specified in 882 * {@link #store(java.io.Writer, java.lang.String) store(Writer)}, 883 * with the following differences: 884 * <ul> 885 * <li>The stream is written using the ISO 8859-1 character encoding. 886 * 887 * <li>Characters not in Latin-1 in the comments are written as 888 * {@code \u005Cu}<i>xxxx</i> for their appropriate unicode 889 * hexadecimal value <i>xxxx</i>. 890 * 891 * <li>Characters less than {@code \u005Cu0020} and characters greater 892 * than {@code \u005Cu007E} in property keys or values are written 893 * as {@code \u005Cu}<i>xxxx</i> for the appropriate hexadecimal 894 * value <i>xxxx</i>. 895 * </ul> 896 * <p> 897 * After the entries have been written, the output stream is flushed. 898 * The output stream remains open after this method returns. 899 * 900 * @param out an output stream. 901 * @param comments a description of the property list. 902 * @exception IOException if writing this property list to the specified 903 * output stream throws an {@code IOException}. 904 * @exception ClassCastException if this {@code Properties} object 905 * contains any keys or values that are not {@code Strings}. 906 * @exception NullPointerException if {@code out} is null. 907 * @since 1.2 908 */ 909 public void store(OutputStream out, String comments) 910 throws IOException 911 { 912 store0(new BufferedWriter(new OutputStreamWriter(out, "8859_1")), 913 comments, 914 true); 915 } 916 917 private void store0(BufferedWriter bw, String comments, boolean escUnicode) 918 throws IOException 919 { 920 if (comments != null) { 921 writeComments(bw, comments); 922 } 923 bw.write("#" + new Date().toString()); 924 bw.newLine(); 925 synchronized (this) { 926 for (Map.Entry<Object, Object> e : entrySet()) { 927 String key = (String)e.getKey(); 928 String val = (String)e.getValue(); 929 key = saveConvert(key, true, escUnicode); 930 /* No need to escape embedded and trailing spaces for value, hence 931 * pass false to flag. 932 */ 933 val = saveConvert(val, false, escUnicode); 934 bw.write(key + "=" + val); 935 bw.newLine(); 936 } 937 } 938 bw.flush(); 939 } 940 941 /** 942 * Loads all of the properties represented by the XML document on the 943 * specified input stream into this properties table. 944 * 945 * <p>The XML document must have the following DOCTYPE declaration: 946 * <pre> 947 * <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> 948 * </pre> 949 * Furthermore, the document must satisfy the properties DTD described 950 * above. 951 * 952 * <p> An implementation is required to read XML documents that use the 953 * "{@code UTF-8}" or "{@code UTF-16}" encoding. An implementation may 954 * support additional encodings. 955 * 956 * <p>The specified stream is closed after this method returns. 957 * 958 * @param in the input stream from which to read the XML document. 959 * @throws IOException if reading from the specified input stream 960 * results in an {@code IOException}. 961 * @throws java.io.UnsupportedEncodingException if the document's encoding 962 * declaration can be read and it specifies an encoding that is not 963 * supported 964 * @throws InvalidPropertiesFormatException Data on input stream does not 965 * constitute a valid XML document with the mandated document type. 966 * @throws NullPointerException if {@code in} is null. 967 * @see #storeToXML(OutputStream, String, String) 968 * @see <a href="http://www.w3.org/TR/REC-xml/#charencoding">Character 969 * Encoding in Entities</a> 970 * @since 1.5 971 */ 972 public synchronized void loadFromXML(InputStream in) 973 throws IOException, InvalidPropertiesFormatException 974 { 975 Objects.requireNonNull(in); 976 PropertiesDefaultHandler handler = new PropertiesDefaultHandler(); 977 handler.load(this, in); 978 in.close(); 979 } 980 981 /** 982 * Emits an XML document representing all of the properties contained 983 * in this table. 984 * 985 * <p> An invocation of this method of the form {@code props.storeToXML(os, 986 * comment)} behaves in exactly the same way as the invocation 987 * {@code props.storeToXML(os, comment, "UTF-8");}. 988 * 989 * @param os the output stream on which to emit the XML document. 990 * @param comment a description of the property list, or {@code null} 991 * if no comment is desired. 992 * @throws IOException if writing to the specified output stream 993 * results in an {@code IOException}. 994 * @throws NullPointerException if {@code os} is null. 995 * @throws ClassCastException if this {@code Properties} object 996 * contains any keys or values that are not 997 * {@code Strings}. 998 * @see #loadFromXML(InputStream) 999 * @since 1.5 1000 */ 1001 public void storeToXML(OutputStream os, String comment) 1002 throws IOException 1003 { 1004 storeToXML(os, comment, "UTF-8"); 1005 } 1006 1007 /** 1008 * Emits an XML document representing all of the properties contained 1009 * in this table, using the specified encoding. 1010 * 1011 * <p>The XML document will have the following DOCTYPE declaration: 1012 * <pre> 1013 * <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> 1014 * </pre> 1015 * 1016 * <p>If the specified comment is {@code null} then no comment 1017 * will be stored in the document. 1018 * 1019 * <p> An implementation is required to support writing of XML documents 1020 * that use the "{@code UTF-8}" or "{@code UTF-16}" encoding. An 1021 * implementation may support additional encodings. 1022 * 1023 * <p>The specified stream remains open after this method returns. 1024 * 1025 * <p>This method behaves the same as 1026 * {@linkplain #storeToXML(OutputStream os, String comment, Charset charset)} 1027 * except that it will {@linkplain java.nio.charset.Charset#forName look up the charset} 1028 * using the given encoding name. 1029 * 1030 * @param os the output stream on which to emit the XML document. 1031 * @param comment a description of the property list, or {@code null} 1032 * if no comment is desired. 1033 * @param encoding the name of a supported 1034 * <a href="../lang/package-summary.html#charenc"> 1035 * character encoding</a> 1036 * 1037 * @throws IOException if writing to the specified output stream 1038 * results in an {@code IOException}. 1039 * @throws java.io.UnsupportedEncodingException if the encoding is not 1040 * supported by the implementation. 1041 * @throws NullPointerException if {@code os} is {@code null}, 1042 * or if {@code encoding} is {@code null}. 1043 * @throws ClassCastException if this {@code Properties} object 1044 * contains any keys or values that are not {@code Strings}. 1045 * @see #loadFromXML(InputStream) 1046 * @see <a href="http://www.w3.org/TR/REC-xml/#charencoding">Character 1047 * Encoding in Entities</a> 1048 * @since 1.5 1049 */ 1050 public void storeToXML(OutputStream os, String comment, String encoding) 1051 throws IOException { 1052 Objects.requireNonNull(os); 1053 Objects.requireNonNull(encoding); 1054 1055 try { 1056 Charset charset = Charset.forName(encoding); 1057 storeToXML(os, comment, charset); 1058 } catch (IllegalCharsetNameException | UnsupportedCharsetException e) { 1059 throw new UnsupportedEncodingException(encoding); 1060 } 1061 } 1062 1063 /** 1064 * Emits an XML document representing all of the properties contained 1065 * in this table, using the specified encoding. 1066 * 1067 * <p>The XML document will have the following DOCTYPE declaration: 1068 * <pre> 1069 * <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> 1070 * </pre> 1071 * 1072 * <p>If the specified comment is {@code null} then no comment 1073 * will be stored in the document. 1074 * 1075 * <p> An implementation is required to support writing of XML documents 1076 * that use the "{@code UTF-8}" or "{@code UTF-16}" encoding. An 1077 * implementation may support additional encodings. 1078 * 1079 * <p> Unmappable characters for the specified charset will be encoded as 1080 * numeric character references. 1081 * 1082 * <p>The specified stream remains open after this method returns. 1083 * 1084 * @param os the output stream on which to emit the XML document. 1085 * @param comment a description of the property list, or {@code null} 1086 * if no comment is desired. 1087 * @param charset the charset 1088 * 1089 * @throws IOException if writing to the specified output stream 1090 * results in an {@code IOException}. 1091 * @throws NullPointerException if {@code os} or {@code charset} is {@code null}. 1092 * @throws ClassCastException if this {@code Properties} object 1093 * contains any keys or values that are not {@code Strings}. 1094 * @see #loadFromXML(InputStream) 1095 * @see <a href="http://www.w3.org/TR/REC-xml/#charencoding">Character 1096 * Encoding in Entities</a> 1097 * @since 10 1098 */ 1099 public void storeToXML(OutputStream os, String comment, Charset charset) 1100 throws IOException { 1101 Objects.requireNonNull(os, "OutputStream"); 1102 Objects.requireNonNull(charset, "Charset"); 1103 PropertiesDefaultHandler handler = new PropertiesDefaultHandler(); 1104 handler.store(this, os, comment, charset); 1105 } 1106 1107 /** 1108 * Searches for the property with the specified key in this property list. 1109 * If the key is not found in this property list, the default property list, 1110 * and its defaults, recursively, are then checked. The method returns 1111 * {@code null} if the property is not found. 1112 * 1113 * @param key the property key. 1114 * @return the value in this property list with the specified key value. 1115 * @see #setProperty 1116 * @see #defaults 1117 */ 1118 public String getProperty(String key) { 1119 Object oval = map.get(key); 1120 String sval = (oval instanceof String) ? (String)oval : null; 1121 Properties defaults; 1122 return ((sval == null) && ((defaults = this.defaults) != null)) ? defaults.getProperty(key) : sval; 1123 } 1124 1125 /** 1126 * Searches for the property with the specified key in this property list. 1127 * If the key is not found in this property list, the default property list, 1128 * and its defaults, recursively, are then checked. The method returns the 1129 * default value argument if the property is not found. 1130 * 1131 * @param key the hashtable key. 1132 * @param defaultValue a default value. 1133 * 1134 * @return the value in this property list with the specified key value. 1135 * @see #setProperty 1136 * @see #defaults 1137 */ 1138 public String getProperty(String key, String defaultValue) { 1139 String val = getProperty(key); 1140 return (val == null) ? defaultValue : val; 1141 } 1142 1143 /** 1144 * Returns an enumeration of all the keys in this property list, 1145 * including distinct keys in the default property list if a key 1146 * of the same name has not already been found from the main 1147 * properties list. 1148 * 1149 * @return an enumeration of all the keys in this property list, including 1150 * the keys in the default property list. 1151 * @throws ClassCastException if any key in this property list 1152 * is not a string. 1153 * @see java.util.Enumeration 1154 * @see java.util.Properties#defaults 1155 * @see #stringPropertyNames 1156 */ 1157 public Enumeration<?> propertyNames() { 1158 Hashtable<String,Object> h = new Hashtable<>(); 1159 enumerate(h); 1160 return h.keys(); 1161 } 1162 1163 /** 1164 * Returns an unmodifiable set of keys from this property list 1165 * where the key and its corresponding value are strings, 1166 * including distinct keys in the default property list if a key 1167 * of the same name has not already been found from the main 1168 * properties list. Properties whose key or value is not 1169 * of type {@code String} are omitted. 1170 * <p> 1171 * The returned set is not backed by this {@code Properties} object. 1172 * Changes to this {@code Properties} object are not reflected in the 1173 * returned set. 1174 * 1175 * @return an unmodifiable set of keys in this property list where 1176 * the key and its corresponding value are strings, 1177 * including the keys in the default property list. 1178 * @see java.util.Properties#defaults 1179 * @since 1.6 1180 */ 1181 public Set<String> stringPropertyNames() { 1182 Map<String, String> h = new HashMap<>(); 1183 enumerateStringProperties(h); 1184 return Collections.unmodifiableSet(h.keySet()); 1185 } 1186 1187 /** 1188 * Prints this property list out to the specified output stream. 1189 * This method is useful for debugging. 1190 * 1191 * @param out an output stream. 1192 * @throws ClassCastException if any key in this property list 1193 * is not a string. 1194 */ 1195 public void list(PrintStream out) { 1196 out.println("-- listing properties --"); 1197 Map<String, Object> h = new HashMap<>(); 1198 enumerate(h); 1199 for (Map.Entry<String, Object> e : h.entrySet()) { 1200 String key = e.getKey(); 1201 String val = (String)e.getValue(); 1202 if (val.length() > 40) { 1203 val = val.substring(0, 37) + "..."; 1204 } 1205 out.println(key + "=" + val); 1206 } 1207 } 1208 1209 /** 1210 * Prints this property list out to the specified output stream. 1211 * This method is useful for debugging. 1212 * 1213 * @param out an output stream. 1214 * @throws ClassCastException if any key in this property list 1215 * is not a string. 1216 * @since 1.1 1217 */ 1218 /* 1219 * Rather than use an anonymous inner class to share common code, this 1220 * method is duplicated in order to ensure that a non-1.1 compiler can 1221 * compile this file. 1222 */ 1223 public void list(PrintWriter out) { 1224 out.println("-- listing properties --"); 1225 Map<String, Object> h = new HashMap<>(); 1226 enumerate(h); 1227 for (Map.Entry<String, Object> e : h.entrySet()) { 1228 String key = e.getKey(); 1229 String val = (String)e.getValue(); 1230 if (val.length() > 40) { 1231 val = val.substring(0, 37) + "..."; 1232 } 1233 out.println(key + "=" + val); 1234 } 1235 } 1236 1237 /** 1238 * Enumerates all key/value pairs into the specified Map. 1239 * @param h the Map 1240 * @throws ClassCastException if any of the property keys 1241 * is not of String type. 1242 */ 1243 private void enumerate(Map<String, Object> h) { 1244 if (defaults != null) { 1245 defaults.enumerate(h); 1246 } 1247 for (Map.Entry<Object, Object> e : entrySet()) { 1248 String key = (String)e.getKey(); 1249 h.put(key, e.getValue()); 1250 } 1251 } 1252 1253 /** 1254 * Enumerates all key/value pairs into the specified Map 1255 * and omits the property if the key or value is not a string. 1256 * @param h the Map 1257 */ 1258 private void enumerateStringProperties(Map<String, String> h) { 1259 if (defaults != null) { 1260 defaults.enumerateStringProperties(h); 1261 } 1262 for (Map.Entry<Object, Object> e : entrySet()) { 1263 Object k = e.getKey(); 1264 Object v = e.getValue(); 1265 if (k instanceof String && v instanceof String) { 1266 h.put((String) k, (String) v); 1267 } 1268 } 1269 } 1270 1271 /** 1272 * Convert a nibble to a hex character 1273 * @param nibble the nibble to convert. 1274 */ 1275 private static char toHex(int nibble) { 1276 return hexDigit[(nibble & 0xF)]; 1277 } 1278 1279 /** A table of hex digits */ 1280 private static final char[] hexDigit = { 1281 '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' 1282 }; 1283 1284 // 1285 // Hashtable methods overridden and delegated to a ConcurrentHashMap instance 1286 1287 @Override 1288 public int size() { 1289 return map.size(); 1290 } 1291 1292 @Override 1293 public boolean isEmpty() { 1294 return map.isEmpty(); 1295 } 1296 1297 @Override 1298 public Enumeration<Object> keys() { 1299 // CHM.keys() returns Iterator w/ remove() - instead wrap keySet() 1300 return Collections.enumeration(map.keySet()); 1301 } 1302 1303 @Override 1304 public Enumeration<Object> elements() { 1305 // CHM.elements() returns Iterator w/ remove() - instead wrap values() 1306 return Collections.enumeration(map.values()); 1307 } 1308 1309 @Override 1310 public boolean contains(Object value) { 1311 return map.contains(value); 1312 } 1313 1314 @Override 1315 public boolean containsValue(Object value) { 1316 return map.containsValue(value); 1317 } 1318 1319 @Override 1320 public boolean containsKey(Object key) { 1321 return map.containsKey(key); 1322 } 1323 1324 @Override 1325 public Object get(Object key) { 1326 return map.get(key); 1327 } 1328 1329 @Override 1330 public synchronized Object put(Object key, Object value) { 1331 return map.put(key, value); 1332 } 1333 1334 @Override 1335 public synchronized Object remove(Object key) { 1336 return map.remove(key); 1337 } 1338 1339 @Override 1340 public synchronized void putAll(Map<?, ?> t) { 1341 map.putAll(t); 1342 } 1343 1344 @Override 1345 public synchronized void clear() { 1346 map.clear(); 1347 } 1348 1349 @Override 1350 public synchronized String toString() { 1351 return map.toString(); 1352 } 1353 1354 @Override 1355 public Set<Object> keySet() { 1356 return Collections.synchronizedSet(map.keySet(), this); 1357 } 1358 1359 @Override 1360 public Collection<Object> values() { 1361 return Collections.synchronizedCollection(map.values(), this); 1362 } 1363 1364 @Override 1365 public Set<Map.Entry<Object, Object>> entrySet() { 1366 return Collections.synchronizedSet(new EntrySet(map.entrySet()), this); 1367 } 1368 1369 /* 1370 * Properties.entrySet() should not support add/addAll, however 1371 * ConcurrentHashMap.entrySet() provides add/addAll. This class wraps the 1372 * Set returned from CHM, changing add/addAll to throw UOE. 1373 */ 1374 private static class EntrySet implements Set<Map.Entry<Object, Object>> { 1375 private Set<Map.Entry<Object,Object>> entrySet; 1376 1377 private EntrySet(Set<Map.Entry<Object, Object>> entrySet) { 1378 this.entrySet = entrySet; 1379 } 1380 1381 @Override public int size() { return entrySet.size(); } 1382 @Override public boolean isEmpty() { return entrySet.isEmpty(); } 1383 @Override public boolean contains(Object o) { return entrySet.contains(o); } 1384 @Override public Object[] toArray() { return entrySet.toArray(); } 1385 @Override public <T> T[] toArray(T[] a) { return entrySet.toArray(a); } 1386 @Override public void clear() { entrySet.clear(); } 1387 @Override public boolean remove(Object o) { return entrySet.remove(o); } 1388 1389 @Override 1390 public boolean add(Map.Entry<Object, Object> e) { 1391 throw new UnsupportedOperationException(); 1392 } 1393 1394 @Override 1395 public boolean addAll(Collection<? extends Map.Entry<Object, Object>> c) { 1396 throw new UnsupportedOperationException(); 1397 } 1398 1399 @Override 1400 public boolean containsAll(Collection<?> c) { 1401 return entrySet.containsAll(c); 1402 } 1403 1404 @Override 1405 public boolean removeAll(Collection<?> c) { 1406 return entrySet.removeAll(c); 1407 } 1408 1409 @Override 1410 public boolean retainAll(Collection<?> c) { 1411 return entrySet.retainAll(c); 1412 } 1413 1414 @Override 1415 public Iterator<Map.Entry<Object, Object>> iterator() { 1416 return entrySet.iterator(); 1417 } 1418 } 1419 1420 @Override 1421 public synchronized boolean equals(Object o) { 1422 return map.equals(o); 1423 } 1424 1425 @Override 1426 public synchronized int hashCode() { 1427 return map.hashCode(); 1428 } 1429 1430 @Override 1431 public Object getOrDefault(Object key, Object defaultValue) { 1432 return map.getOrDefault(key, defaultValue); 1433 } 1434 1435 @Override 1436 public synchronized void forEach(BiConsumer<? super Object, ? super Object> action) { 1437 map.forEach(action); 1438 } 1439 1440 @Override 1441 public synchronized void replaceAll(BiFunction<? super Object, ? super Object, ?> function) { 1442 map.replaceAll(function); 1443 } 1444 1445 @Override 1446 public synchronized Object putIfAbsent(Object key, Object value) { 1447 return map.putIfAbsent(key, value); 1448 } 1449 1450 @Override 1451 public synchronized boolean remove(Object key, Object value) { 1452 return map.remove(key, value); 1453 } 1454 1455 @Override 1456 public synchronized boolean replace(Object key, Object oldValue, Object newValue) { 1457 return map.replace(key, oldValue, newValue); 1458 } 1459 1460 @Override 1461 public synchronized Object replace(Object key, Object value) { 1462 return map.replace(key, value); 1463 } 1464 1465 @Override 1466 public synchronized Object computeIfAbsent(Object key, 1467 Function<? super Object, ?> mappingFunction) { 1468 return map.computeIfAbsent(key, mappingFunction); 1469 } 1470 1471 @Override 1472 public synchronized Object computeIfPresent(Object key, 1473 BiFunction<? super Object, ? super Object, ?> remappingFunction) { 1474 return map.computeIfPresent(key, remappingFunction); 1475 } 1476 1477 @Override 1478 public synchronized Object compute(Object key, 1479 BiFunction<? super Object, ? super Object, ?> remappingFunction) { 1480 return map.compute(key, remappingFunction); 1481 } 1482 1483 @Override 1484 public synchronized Object merge(Object key, Object value, 1485 BiFunction<? super Object, ? super Object, ?> remappingFunction) { 1486 return map.merge(key, value, remappingFunction); 1487 } 1488 1489 // 1490 // Special Hashtable methods 1491 1492 @Override 1493 protected void rehash() { /* no-op */ } 1494 1495 @Override 1496 public synchronized Object clone() { 1497 Properties clone = (Properties) cloneHashtable(); 1498 clone.map = new ConcurrentHashMap<>(map); 1499 return clone; 1500 } 1501 1502 // 1503 // Hashtable serialization overrides 1504 // (these should emit and consume Hashtable-compatible stream) 1505 1506 @Override 1507 void writeHashtable(ObjectOutputStream s) throws IOException { 1508 var map = this.map; 1509 List<Object> entryStack = new ArrayList<>(map.size() * 2); // an estimate 1510 1511 for (Map.Entry<Object, Object> entry : map.entrySet()) { 1512 entryStack.add(entry.getValue()); 1513 entryStack.add(entry.getKey()); 1514 } 1515 1516 // Write out the simulated threshold, loadfactor 1517 float loadFactor = 0.75f; 1518 int count = entryStack.size() / 2; 1519 int length = (int)(count / loadFactor) + (count / 20) + 3; 1520 if (length > count && (length & 1) == 0) { 1521 length--; 1522 } 1523 synchronized (map) { // in case of multiple concurrent serializations 1524 defaultWriteHashtable(s, length, loadFactor); 1525 } 1526 1527 // Write out simulated length and real count of elements 1528 s.writeInt(length); 1529 s.writeInt(count); 1530 1531 // Write out the key/value objects from the stacked entries 1532 for (int i = entryStack.size() - 1; i >= 0; i--) { 1533 s.writeObject(entryStack.get(i)); 1534 } 1535 } 1536 1537 @Override 1538 void readHashtable(ObjectInputStream s) throws IOException, 1539 ClassNotFoundException { 1540 // Read in the threshold and loadfactor 1541 s.defaultReadObject(); 1542 1543 // Read the original length of the array and number of elements 1544 int origlength = s.readInt(); 1545 int elements = s.readInt(); 1546 1547 // Validate # of elements 1548 if (elements < 0) { 1549 throw new StreamCorruptedException("Illegal # of Elements: " + elements); 1550 } 1551 1552 // Constructing the backing map will lazily create an array when the first element is 1553 // added, so check it before construction. Note that CHM's constructor takes a size 1554 // that is the number of elements to be stored -- not the table size -- so it must be 1555 // inflated by the default load factor of 0.75, then inflated to the next power of two. 1556 // (CHM uses the same power-of-two computation as HashMap, and HashMap.tableSizeFor is 1557 // accessible here.) Check Map.Entry[].class since it's the nearest public type to 1558 // what is actually created. 1559 SharedSecrets.getJavaObjectInputStreamAccess() 1560 .checkArray(s, Map.Entry[].class, HashMap.tableSizeFor((int)(elements / 0.75))); 1561 1562 // create CHM of appropriate capacity 1563 var map = new ConcurrentHashMap<>(elements); 1564 1565 // Read all the key/value objects 1566 for (; elements > 0; elements--) { 1567 Object key = s.readObject(); 1568 Object value = s.readObject(); 1569 map.put(key, value); 1570 } 1571 this.map = map; 1572 } 1573 }