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