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