1 /* 2 * Copyright (c) 1995, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.util; 27 28 import java.io.IOException; 29 import java.io.PrintStream; 30 import java.io.PrintWriter; 31 import java.io.InputStream; 32 import java.io.OutputStream; 33 import java.io.Reader; 34 import java.io.Writer; 35 import java.io.OutputStreamWriter; 36 import java.io.BufferedWriter; 37 import java.io.ObjectInputStream; 38 import java.io.ObjectOutputStream; 39 import java.io.StreamCorruptedException; 40 import java.io.UnsupportedEncodingException; 41 import java.nio.charset.Charset; 42 import java.nio.charset.IllegalCharsetNameException; 43 import java.nio.charset.UnsupportedCharsetException; 44 import java.util.concurrent.ConcurrentHashMap; 45 import java.util.function.BiConsumer; 46 import java.util.function.BiFunction; 47 import java.util.function.Function; 48 49 import jdk.internal.misc.SharedSecrets; 50 import jdk.internal.misc.Unsafe; 51 import jdk.internal.util.xml.PropertiesDefaultHandler; 52 53 import sun.nio.cs.UTF_8; 54 import sun.nio.cs.ISO_8859_1; 55 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 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 143 class Properties extends Hashtable<Object,Object> { 144 /** 145 * use serialVersionUID from JDK 1.1.X for interoperability 146 */ 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 3.3 and 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 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 * @exception 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 } else if ((c == ' ' || c == '\t' || c == '\f') && !precedingBackslash) { 433 valueStart = keyLen + 1; 434 break; 435 } 436 if (c == '\\') { 437 precedingBackslash = !precedingBackslash; 438 } else { 439 precedingBackslash = false; 440 } 441 keyLen++; 442 } 443 while (valueStart < limit) { 444 char c = lr.lineBuf[valueStart]; 445 if (c != ' ' && c != '\t' && c != '\f') { 446 if (!hasSep && (c == '=' || c == ':')) { 447 hasSep = true; 448 } else { 449 break; 450 } 451 } 452 valueStart++; 453 } 454 String key = loadConvert(lr.lineBuf, 0, keyLen, outBuffer); 455 String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, outBuffer); 456 put(key, value); 457 } 458 } 459 460 /* Read in a "logical line" from an InputStream/Reader, skip all comment 461 * and blank lines and filter out those leading whitespace characters 462 * (\u0020, \u0009 and \u000c) from the beginning of a "natural line". 463 * Method returns the char length of the "logical line" and stores 464 * the line in "lineBuf". 465 */ 466 private static class LineReader { LineReader(InputStream inStream)467 LineReader(InputStream inStream) { 468 this.inStream = inStream; 469 inByteBuf = new byte[8192]; 470 } 471 LineReader(Reader reader)472 LineReader(Reader reader) { 473 this.reader = reader; 474 inCharBuf = new char[8192]; 475 } 476 477 char[] lineBuf = new char[1024]; 478 private byte[] inByteBuf; 479 private char[] inCharBuf; 480 private int inLimit = 0; 481 private int inOff = 0; 482 private InputStream inStream; 483 private Reader reader; 484 readLine()485 int readLine() throws IOException { 486 // use locals to optimize for interpreted performance 487 int len = 0; 488 int off = inOff; 489 int limit = inLimit; 490 491 boolean skipWhiteSpace = true; 492 boolean appendedLineBegin = false; 493 boolean precedingBackslash = false; 494 boolean fromStream = inStream != null; 495 byte[] byteBuf = inByteBuf; 496 char[] charBuf = inCharBuf; 497 char[] lineBuf = this.lineBuf; 498 char c; 499 500 while (true) { 501 if (off >= limit) { 502 inLimit = limit = fromStream ? inStream.read(byteBuf) 503 : reader.read(charBuf); 504 if (limit <= 0) { 505 if (len == 0) { 506 return -1; 507 } 508 return precedingBackslash ? len - 1 : len; 509 } 510 off = 0; 511 } 512 513 // (char)(byte & 0xFF) is equivalent to calling a ISO8859-1 decoder. 514 c = (fromStream) ? (char)(byteBuf[off++] & 0xFF) : charBuf[off++]; 515 516 if (skipWhiteSpace) { 517 if (c == ' ' || c == '\t' || c == '\f') { 518 continue; 519 } 520 if (!appendedLineBegin && (c == '\r' || c == '\n')) { 521 continue; 522 } 523 skipWhiteSpace = false; 524 appendedLineBegin = false; 525 526 } 527 if (len == 0) { // Still on a new logical line 528 if (c == '#' || c == '!') { 529 // Comment, quickly consume the rest of the line 530 531 // When checking for new line characters a range check, 532 // starting with the higher bound ('\r') means one less 533 // branch in the common case. 534 commentLoop: while (true) { 535 if (fromStream) { 536 byte b; 537 while (off < limit) { 538 b = byteBuf[off++]; 539 if (b <= '\r' && (b == '\r' || b == '\n')) 540 break commentLoop; 541 } 542 if (off == limit) { 543 inLimit = limit = inStream.read(byteBuf); 544 if (limit <= 0) { // EOF 545 return -1; 546 } 547 off = 0; 548 } 549 } else { 550 while (off < limit) { 551 c = charBuf[off++]; 552 if (c <= '\r' && (c == '\r' || c == '\n')) 553 break commentLoop; 554 } 555 if (off == limit) { 556 inLimit = limit = reader.read(charBuf); 557 if (limit <= 0) { // EOF 558 return -1; 559 } 560 off = 0; 561 } 562 } 563 } 564 skipWhiteSpace = true; 565 continue; 566 } 567 } 568 569 if (c != '\n' && c != '\r') { 570 lineBuf[len++] = c; 571 if (len == lineBuf.length) { 572 int maxLen = Integer.MAX_VALUE - 8; // VM allocation limit 573 int newLen = len * 2; 574 if (newLen < 0 || newLen > maxLen) { // check for under/overflow 575 newLen = maxLen; 576 } 577 if (newLen <= len) { // still not good? last-ditch attempt then 578 if (len != Integer.MAX_VALUE) { 579 newLen = len + 1; 580 } else { 581 throw new OutOfMemoryError("Required array length too large"); 582 } 583 } 584 lineBuf = new char[newLen]; 585 System.arraycopy(this.lineBuf, 0, lineBuf, 0, len); 586 this.lineBuf = lineBuf; 587 } 588 // flip the preceding backslash flag 589 precedingBackslash = (c == '\\') ? !precedingBackslash : false; 590 } else { 591 // reached EOL 592 if (len == 0) { 593 skipWhiteSpace = true; 594 continue; 595 } 596 if (off >= limit) { 597 inLimit = limit = fromStream ? inStream.read(byteBuf) 598 : reader.read(charBuf); 599 off = 0; 600 if (limit <= 0) { // EOF 601 return precedingBackslash ? len - 1 : len; 602 } 603 } 604 if (precedingBackslash) { 605 // backslash at EOL is not part of the line 606 len -= 1; 607 // skip leading whitespace characters in the following line 608 skipWhiteSpace = true; 609 appendedLineBegin = true; 610 precedingBackslash = false; 611 // take care not to include any subsequent \n 612 if (c == '\r') { 613 if (fromStream) { 614 if (byteBuf[off] == '\n') { 615 off++; 616 } 617 } else { 618 if (charBuf[off] == '\n') { 619 off++; 620 } 621 } 622 } 623 } else { 624 inOff = off; 625 return len; 626 } 627 } 628 } 629 } 630 } 631 632 /* 633 * Converts encoded \uxxxx to unicode chars 634 * and changes special saved chars to their original forms 635 */ loadConvert(char[] in, int off, int len, StringBuilder out)636 private String loadConvert(char[] in, int off, int len, StringBuilder out) { 637 char aChar; 638 int end = off + len; 639 int start = off; 640 while (off < end) { 641 aChar = in[off++]; 642 if (aChar == '\\') { 643 break; 644 } 645 } 646 if (off == end) { // No backslash 647 return new String(in, start, len); 648 } 649 650 // backslash found at off - 1, reset the shared buffer, rewind offset 651 out.setLength(0); 652 off--; 653 out.append(in, start, off - start); 654 655 while (off < end) { 656 aChar = in[off++]; 657 if (aChar == '\\') { 658 aChar = in[off++]; 659 if(aChar == 'u') { 660 // Read the xxxx 661 int value=0; 662 for (int i=0; i<4; i++) { 663 aChar = in[off++]; 664 switch (aChar) { 665 case '0': case '1': case '2': case '3': case '4': 666 case '5': case '6': case '7': case '8': case '9': 667 value = (value << 4) + aChar - '0'; 668 break; 669 case 'a': case 'b': case 'c': 670 case 'd': case 'e': case 'f': 671 value = (value << 4) + 10 + aChar - 'a'; 672 break; 673 case 'A': case 'B': case 'C': 674 case 'D': case 'E': case 'F': 675 value = (value << 4) + 10 + aChar - 'A'; 676 break; 677 default: 678 throw new IllegalArgumentException( 679 "Malformed \\uxxxx encoding."); 680 } 681 } 682 out.append((char)value); 683 } else { 684 if (aChar == 't') aChar = '\t'; 685 else if (aChar == 'r') aChar = '\r'; 686 else if (aChar == 'n') aChar = '\n'; 687 else if (aChar == 'f') aChar = '\f'; 688 out.append(aChar); 689 } 690 } else { 691 out.append(aChar); 692 } 693 } 694 return out.toString(); 695 } 696 697 /* 698 * Converts unicodes to encoded \uxxxx and escapes 699 * special characters with a preceding slash 700 */ saveConvert(String theString, boolean escapeSpace, boolean escapeUnicode)701 private String saveConvert(String theString, 702 boolean escapeSpace, 703 boolean escapeUnicode) { 704 int len = theString.length(); 705 int bufLen = len * 2; 706 if (bufLen < 0) { 707 bufLen = Integer.MAX_VALUE; 708 } 709 StringBuilder outBuffer = new StringBuilder(bufLen); 710 711 for(int x=0; x<len; x++) { 712 char aChar = theString.charAt(x); 713 // Handle common case first, selecting largest block that 714 // avoids the specials below 715 if ((aChar > 61) && (aChar < 127)) { 716 if (aChar == '\\') { 717 outBuffer.append('\\'); outBuffer.append('\\'); 718 continue; 719 } 720 outBuffer.append(aChar); 721 continue; 722 } 723 switch(aChar) { 724 case ' ': 725 if (x == 0 || escapeSpace) 726 outBuffer.append('\\'); 727 outBuffer.append(' '); 728 break; 729 case '\t':outBuffer.append('\\'); outBuffer.append('t'); 730 break; 731 case '\n':outBuffer.append('\\'); outBuffer.append('n'); 732 break; 733 case '\r':outBuffer.append('\\'); outBuffer.append('r'); 734 break; 735 case '\f':outBuffer.append('\\'); outBuffer.append('f'); 736 break; 737 case '=': // Fall through 738 case ':': // Fall through 739 case '#': // Fall through 740 case '!': 741 outBuffer.append('\\'); outBuffer.append(aChar); 742 break; 743 default: 744 if (((aChar < 0x0020) || (aChar > 0x007e)) & escapeUnicode ) { 745 outBuffer.append('\\'); 746 outBuffer.append('u'); 747 outBuffer.append(toHex((aChar >> 12) & 0xF)); 748 outBuffer.append(toHex((aChar >> 8) & 0xF)); 749 outBuffer.append(toHex((aChar >> 4) & 0xF)); 750 outBuffer.append(toHex( aChar & 0xF)); 751 } else { 752 outBuffer.append(aChar); 753 } 754 } 755 } 756 return outBuffer.toString(); 757 } 758 writeComments(BufferedWriter bw, String comments)759 private static void writeComments(BufferedWriter bw, String comments) 760 throws IOException { 761 bw.write("#"); 762 int len = comments.length(); 763 int current = 0; 764 int last = 0; 765 char[] uu = new char[6]; 766 uu[0] = '\\'; 767 uu[1] = 'u'; 768 while (current < len) { 769 char c = comments.charAt(current); 770 if (c > '\u00ff' || c == '\n' || c == '\r') { 771 if (last != current) 772 bw.write(comments.substring(last, current)); 773 if (c > '\u00ff') { 774 uu[2] = toHex((c >> 12) & 0xf); 775 uu[3] = toHex((c >> 8) & 0xf); 776 uu[4] = toHex((c >> 4) & 0xf); 777 uu[5] = toHex( c & 0xf); 778 bw.write(new String(uu)); 779 } else { 780 bw.newLine(); 781 if (c == '\r' && 782 current != len - 1 && 783 comments.charAt(current + 1) == '\n') { 784 current++; 785 } 786 if (current == len - 1 || 787 (comments.charAt(current + 1) != '#' && 788 comments.charAt(current + 1) != '!')) 789 bw.write("#"); 790 } 791 last = current + 1; 792 } 793 current++; 794 } 795 if (last != current) 796 bw.write(comments.substring(last, current)); 797 bw.newLine(); 798 } 799 800 /** 801 * Calls the {@code store(OutputStream out, String comments)} method 802 * and suppresses IOExceptions that were thrown. 803 * 804 * @deprecated This method does not throw an IOException if an I/O error 805 * occurs while saving the property list. The preferred way to save a 806 * properties list is via the {@code store(OutputStream out, 807 * String comments)} method or the 808 * {@code storeToXML(OutputStream os, String comment)} method. 809 * 810 * @param out an output stream. 811 * @param comments a description of the property list. 812 * @exception ClassCastException if this {@code Properties} object 813 * contains any keys or values that are not 814 * {@code Strings}. 815 */ 816 @Deprecated save(OutputStream out, String comments)817 public void save(OutputStream out, String comments) { 818 try { 819 store(out, comments); 820 } catch (IOException e) { 821 } 822 } 823 824 /** 825 * Writes this property list (key and element pairs) in this 826 * {@code Properties} table to the output character stream in a 827 * format suitable for using the {@link #load(java.io.Reader) load(Reader)} 828 * method. 829 * <p> 830 * Properties from the defaults table of this {@code Properties} 831 * table (if any) are <i>not</i> written out by this method. 832 * <p> 833 * If the comments argument is not null, then an ASCII {@code #} 834 * character, the comments string, and a line separator are first written 835 * to the output stream. Thus, the {@code comments} can serve as an 836 * identifying comment. Any one of a line feed ('\n'), a carriage 837 * return ('\r'), or a carriage return followed immediately by a line feed 838 * in comments is replaced by a line separator generated by the {@code Writer} 839 * and if the next character in comments is not character {@code #} or 840 * character {@code !} then an ASCII {@code #} is written out 841 * after that line separator. 842 * <p> 843 * Next, a comment line is always written, consisting of an ASCII 844 * {@code #} character, the current date and time (as if produced 845 * by the {@code toString} method of {@code Date} for the 846 * current time), and a line separator as generated by the {@code Writer}. 847 * <p> 848 * Then every entry in this {@code Properties} table is 849 * written out, one per line. For each entry the key string is 850 * written, then an ASCII {@code =}, then the associated 851 * element string. For the key, all space characters are 852 * written with a preceding {@code \} character. For the 853 * element, leading space characters, but not embedded or trailing 854 * space characters, are written with a preceding {@code \} 855 * character. The key and element characters {@code #}, 856 * {@code !}, {@code =}, and {@code :} are written 857 * with a preceding backslash to ensure that they are properly loaded. 858 * <p> 859 * After the entries have been written, the output stream is flushed. 860 * The output stream remains open after this method returns. 861 * 862 * @param writer an output character stream writer. 863 * @param comments a description of the property list. 864 * @exception IOException if writing this property list to the specified 865 * output stream throws an {@code IOException}. 866 * @exception ClassCastException if this {@code Properties} object 867 * contains any keys or values that are not {@code Strings}. 868 * @exception NullPointerException if {@code writer} is null. 869 * @since 1.6 870 */ store(Writer writer, String comments)871 public void store(Writer writer, String comments) 872 throws IOException 873 { 874 store0((writer instanceof BufferedWriter)?(BufferedWriter)writer 875 : new BufferedWriter(writer), 876 comments, 877 false); 878 } 879 880 /** 881 * Writes this property list (key and element pairs) in this 882 * {@code Properties} table to the output stream in a format suitable 883 * for loading into a {@code Properties} table using the 884 * {@link #load(InputStream) load(InputStream)} method. 885 * <p> 886 * Properties from the defaults table of this {@code Properties} 887 * table (if any) are <i>not</i> written out by this method. 888 * <p> 889 * This method outputs the comments, properties keys and values in 890 * the same format as specified in 891 * {@link #store(java.io.Writer, java.lang.String) store(Writer)}, 892 * with the following differences: 893 * <ul> 894 * <li>The stream is written using the ISO 8859-1 character encoding. 895 * 896 * <li>Characters not in Latin-1 in the comments are written as 897 * {@code \u005Cu}<i>xxxx</i> for their appropriate unicode 898 * hexadecimal value <i>xxxx</i>. 899 * 900 * <li>Characters less than {@code \u005Cu0020} and characters greater 901 * than {@code \u005Cu007E} in property keys or values are written 902 * as {@code \u005Cu}<i>xxxx</i> for the appropriate hexadecimal 903 * value <i>xxxx</i>. 904 * </ul> 905 * <p> 906 * After the entries have been written, the output stream is flushed. 907 * The output stream remains open after this method returns. 908 * 909 * @param out an output stream. 910 * @param comments a description of the property list. 911 * @exception IOException if writing this property list to the specified 912 * output stream throws an {@code IOException}. 913 * @exception ClassCastException if this {@code Properties} object 914 * contains any keys or values that are not {@code Strings}. 915 * @exception NullPointerException if {@code out} is null. 916 * @since 1.2 917 */ store(OutputStream out, String comments)918 public void store(OutputStream out, String comments) 919 throws IOException 920 { 921 store0(new BufferedWriter(new OutputStreamWriter(out, ISO_8859_1.INSTANCE)), 922 comments, 923 true); 924 } 925 store0(BufferedWriter bw, String comments, boolean escUnicode)926 private void store0(BufferedWriter bw, String comments, boolean escUnicode) 927 throws IOException 928 { 929 if (comments != null) { 930 writeComments(bw, comments); 931 } 932 bw.write("#" + new Date().toString()); 933 bw.newLine(); 934 synchronized (this) { 935 for (Map.Entry<Object, Object> e : entrySet()) { 936 String key = (String)e.getKey(); 937 String val = (String)e.getValue(); 938 key = saveConvert(key, true, escUnicode); 939 /* No need to escape embedded and trailing spaces for value, hence 940 * pass false to flag. 941 */ 942 val = saveConvert(val, false, escUnicode); 943 bw.write(key + "=" + val); 944 bw.newLine(); 945 } 946 } 947 bw.flush(); 948 } 949 950 /** 951 * Loads all of the properties represented by the XML document on the 952 * specified input stream into this properties table. 953 * 954 * <p>The XML document must have the following DOCTYPE declaration: 955 * <pre> 956 * <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> 957 * </pre> 958 * Furthermore, the document must satisfy the properties DTD described 959 * above. 960 * 961 * <p> An implementation is required to read XML documents that use the 962 * "{@code UTF-8}" or "{@code UTF-16}" encoding. An implementation may 963 * support additional encodings. 964 * 965 * <p>The specified stream is closed after this method returns. 966 * 967 * @param in the input stream from which to read the XML document. 968 * @throws IOException if reading from the specified input stream 969 * results in an {@code IOException}. 970 * @throws java.io.UnsupportedEncodingException if the document's encoding 971 * declaration can be read and it specifies an encoding that is not 972 * supported 973 * @throws InvalidPropertiesFormatException Data on input stream does not 974 * constitute a valid XML document with the mandated document type. 975 * @throws NullPointerException if {@code in} is null. 976 * @see #storeToXML(OutputStream, String, String) 977 * @see <a href="http://www.w3.org/TR/REC-xml/#charencoding">Character 978 * Encoding in Entities</a> 979 * @since 1.5 980 */ loadFromXML(InputStream in)981 public synchronized void loadFromXML(InputStream in) 982 throws IOException, InvalidPropertiesFormatException 983 { 984 Objects.requireNonNull(in); 985 PropertiesDefaultHandler handler = new PropertiesDefaultHandler(); 986 handler.load(this, in); 987 in.close(); 988 } 989 990 /** 991 * Emits an XML document representing all of the properties contained 992 * in this table. 993 * 994 * <p> An invocation of this method of the form {@code props.storeToXML(os, 995 * comment)} behaves in exactly the same way as the invocation 996 * {@code props.storeToXML(os, comment, "UTF-8");}. 997 * 998 * @param os the output stream on which to emit the XML document. 999 * @param comment a description of the property list, or {@code null} 1000 * if no comment is desired. 1001 * @throws IOException if writing to the specified output stream 1002 * results in an {@code IOException}. 1003 * @throws NullPointerException if {@code os} is null. 1004 * @throws ClassCastException if this {@code Properties} object 1005 * contains any keys or values that are not 1006 * {@code Strings}. 1007 * @see #loadFromXML(InputStream) 1008 * @since 1.5 1009 */ storeToXML(OutputStream os, String comment)1010 public void storeToXML(OutputStream os, String comment) 1011 throws IOException 1012 { 1013 storeToXML(os, comment, UTF_8.INSTANCE); 1014 } 1015 1016 /** 1017 * Emits an XML document representing all of the properties contained 1018 * in this table, using the specified encoding. 1019 * 1020 * <p>The XML document will have the following DOCTYPE declaration: 1021 * <pre> 1022 * <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> 1023 * </pre> 1024 * 1025 * <p>If the specified comment is {@code null} then no comment 1026 * will be stored in the document. 1027 * 1028 * <p> An implementation is required to support writing of XML documents 1029 * that use the "{@code UTF-8}" or "{@code UTF-16}" encoding. An 1030 * implementation may support additional encodings. 1031 * 1032 * <p>The specified stream remains open after this method returns. 1033 * 1034 * <p>This method behaves the same as 1035 * {@linkplain #storeToXML(OutputStream os, String comment, Charset charset)} 1036 * except that it will {@linkplain java.nio.charset.Charset#forName look up the charset} 1037 * using the given encoding name. 1038 * 1039 * @param os the output stream on which to emit the XML document. 1040 * @param comment a description of the property list, or {@code null} 1041 * if no comment is desired. 1042 * @param encoding the name of a supported 1043 * <a href="../lang/package-summary.html#charenc"> 1044 * character encoding</a> 1045 * 1046 * @throws IOException if writing to the specified output stream 1047 * results in an {@code IOException}. 1048 * @throws java.io.UnsupportedEncodingException if the encoding is not 1049 * supported by the implementation. 1050 * @throws NullPointerException if {@code os} is {@code null}, 1051 * or if {@code encoding} is {@code null}. 1052 * @throws ClassCastException if this {@code Properties} object 1053 * contains any keys or values that are not {@code Strings}. 1054 * @see #loadFromXML(InputStream) 1055 * @see <a href="http://www.w3.org/TR/REC-xml/#charencoding">Character 1056 * Encoding in Entities</a> 1057 * @since 1.5 1058 */ storeToXML(OutputStream os, String comment, String encoding)1059 public void storeToXML(OutputStream os, String comment, String encoding) 1060 throws IOException { 1061 Objects.requireNonNull(os); 1062 Objects.requireNonNull(encoding); 1063 1064 try { 1065 Charset charset = Charset.forName(encoding); 1066 storeToXML(os, comment, charset); 1067 } catch (IllegalCharsetNameException | UnsupportedCharsetException e) { 1068 throw new UnsupportedEncodingException(encoding); 1069 } 1070 } 1071 1072 /** 1073 * Emits an XML document representing all of the properties contained 1074 * in this table, using the specified encoding. 1075 * 1076 * <p>The XML document will have the following DOCTYPE declaration: 1077 * <pre> 1078 * <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> 1079 * </pre> 1080 * 1081 * <p>If the specified comment is {@code null} then no comment 1082 * will be stored in the document. 1083 * 1084 * <p> An implementation is required to support writing of XML documents 1085 * that use the "{@code UTF-8}" or "{@code UTF-16}" encoding. An 1086 * implementation may support additional encodings. 1087 * 1088 * <p> Unmappable characters for the specified charset will be encoded as 1089 * numeric character references. 1090 * 1091 * <p>The specified stream remains open after this method returns. 1092 * 1093 * @param os the output stream on which to emit the XML document. 1094 * @param comment a description of the property list, or {@code null} 1095 * if no comment is desired. 1096 * @param charset the charset 1097 * 1098 * @throws IOException if writing to the specified output stream 1099 * results in an {@code IOException}. 1100 * @throws NullPointerException if {@code os} or {@code charset} is {@code null}. 1101 * @throws ClassCastException if this {@code Properties} object 1102 * contains any keys or values that are not {@code Strings}. 1103 * @see #loadFromXML(InputStream) 1104 * @see <a href="http://www.w3.org/TR/REC-xml/#charencoding">Character 1105 * Encoding in Entities</a> 1106 * @since 10 1107 */ storeToXML(OutputStream os, String comment, Charset charset)1108 public void storeToXML(OutputStream os, String comment, Charset charset) 1109 throws IOException { 1110 Objects.requireNonNull(os, "OutputStream"); 1111 Objects.requireNonNull(charset, "Charset"); 1112 PropertiesDefaultHandler handler = new PropertiesDefaultHandler(); 1113 handler.store(this, os, comment, charset); 1114 } 1115 1116 /** 1117 * Searches for the property with the specified key in this property list. 1118 * If the key is not found in this property list, the default property list, 1119 * and its defaults, recursively, are then checked. The method returns 1120 * {@code null} if the property is not found. 1121 * 1122 * @param key the property key. 1123 * @return the value in this property list with the specified key value. 1124 * @see #setProperty 1125 * @see #defaults 1126 */ getProperty(String key)1127 public String getProperty(String key) { 1128 Object oval = map.get(key); 1129 String sval = (oval instanceof String) ? (String)oval : null; 1130 Properties defaults; 1131 return ((sval == null) && ((defaults = this.defaults) != null)) ? defaults.getProperty(key) : sval; 1132 } 1133 1134 /** 1135 * Searches for the property with the specified key in this property list. 1136 * If the key is not found in this property list, the default property list, 1137 * and its defaults, recursively, are then checked. The method returns the 1138 * default value argument if the property is not found. 1139 * 1140 * @param key the hashtable key. 1141 * @param defaultValue a default value. 1142 * 1143 * @return the value in this property list with the specified key value. 1144 * @see #setProperty 1145 * @see #defaults 1146 */ getProperty(String key, String defaultValue)1147 public String getProperty(String key, String defaultValue) { 1148 String val = getProperty(key); 1149 return (val == null) ? defaultValue : val; 1150 } 1151 1152 /** 1153 * Returns an enumeration of all the keys in this property list, 1154 * including distinct keys in the default property list if a key 1155 * of the same name has not already been found from the main 1156 * properties list. 1157 * 1158 * @return an enumeration of all the keys in this property list, including 1159 * the keys in the default property list. 1160 * @throws ClassCastException if any key in this property list 1161 * is not a string. 1162 * @see java.util.Enumeration 1163 * @see java.util.Properties#defaults 1164 * @see #stringPropertyNames 1165 */ propertyNames()1166 public Enumeration<?> propertyNames() { 1167 Hashtable<String,Object> h = new Hashtable<>(); 1168 enumerate(h); 1169 return h.keys(); 1170 } 1171 1172 /** 1173 * Returns an unmodifiable set of keys from this property list 1174 * where the key and its corresponding value are strings, 1175 * including distinct keys in the default property list if a key 1176 * of the same name has not already been found from the main 1177 * properties list. Properties whose key or value is not 1178 * of type {@code String} are omitted. 1179 * <p> 1180 * The returned set is not backed by this {@code Properties} object. 1181 * Changes to this {@code Properties} object are not reflected in the 1182 * returned set. 1183 * 1184 * @return an unmodifiable set of keys in this property list where 1185 * the key and its corresponding value are strings, 1186 * including the keys in the default property list. 1187 * @see java.util.Properties#defaults 1188 * @since 1.6 1189 */ stringPropertyNames()1190 public Set<String> stringPropertyNames() { 1191 Map<String, String> h = new HashMap<>(); 1192 enumerateStringProperties(h); 1193 return Collections.unmodifiableSet(h.keySet()); 1194 } 1195 1196 /** 1197 * Prints this property list out to the specified output stream. 1198 * This method is useful for debugging. 1199 * 1200 * @param out an output stream. 1201 * @throws ClassCastException if any key in this property list 1202 * is not a string. 1203 */ list(PrintStream out)1204 public void list(PrintStream out) { 1205 out.println("-- listing properties --"); 1206 Map<String, Object> h = new HashMap<>(); 1207 enumerate(h); 1208 for (Map.Entry<String, Object> e : h.entrySet()) { 1209 String key = e.getKey(); 1210 String val = (String)e.getValue(); 1211 if (val.length() > 40) { 1212 val = val.substring(0, 37) + "..."; 1213 } 1214 out.println(key + "=" + val); 1215 } 1216 } 1217 1218 /** 1219 * Prints this property list out to the specified output stream. 1220 * This method is useful for debugging. 1221 * 1222 * @param out an output stream. 1223 * @throws ClassCastException if any key in this property list 1224 * is not a string. 1225 * @since 1.1 1226 */ 1227 /* 1228 * Rather than use an anonymous inner class to share common code, this 1229 * method is duplicated in order to ensure that a non-1.1 compiler can 1230 * compile this file. 1231 */ list(PrintWriter out)1232 public void list(PrintWriter out) { 1233 out.println("-- listing properties --"); 1234 Map<String, Object> h = new HashMap<>(); 1235 enumerate(h); 1236 for (Map.Entry<String, Object> e : h.entrySet()) { 1237 String key = e.getKey(); 1238 String val = (String)e.getValue(); 1239 if (val.length() > 40) { 1240 val = val.substring(0, 37) + "..."; 1241 } 1242 out.println(key + "=" + val); 1243 } 1244 } 1245 1246 /** 1247 * Enumerates all key/value pairs into the specified Map. 1248 * @param h the Map 1249 * @throws ClassCastException if any of the property keys 1250 * is not of String type. 1251 */ enumerate(Map<String, Object> h)1252 private void enumerate(Map<String, Object> h) { 1253 if (defaults != null) { 1254 defaults.enumerate(h); 1255 } 1256 for (Map.Entry<Object, Object> e : entrySet()) { 1257 String key = (String)e.getKey(); 1258 h.put(key, e.getValue()); 1259 } 1260 } 1261 1262 /** 1263 * Enumerates all key/value pairs into the specified Map 1264 * and omits the property if the key or value is not a string. 1265 * @param h the Map 1266 */ enumerateStringProperties(Map<String, String> h)1267 private void enumerateStringProperties(Map<String, String> h) { 1268 if (defaults != null) { 1269 defaults.enumerateStringProperties(h); 1270 } 1271 for (Map.Entry<Object, Object> e : entrySet()) { 1272 Object k = e.getKey(); 1273 Object v = e.getValue(); 1274 if (k instanceof String && v instanceof String) { 1275 h.put((String) k, (String) v); 1276 } 1277 } 1278 } 1279 1280 /** 1281 * Convert a nibble to a hex character 1282 * @param nibble the nibble to convert. 1283 */ toHex(int nibble)1284 private static char toHex(int nibble) { 1285 return hexDigit[(nibble & 0xF)]; 1286 } 1287 1288 /** A table of hex digits */ 1289 private static final char[] hexDigit = { 1290 '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' 1291 }; 1292 1293 // 1294 // Hashtable methods overridden and delegated to a ConcurrentHashMap instance 1295 1296 @Override size()1297 public int size() { 1298 return map.size(); 1299 } 1300 1301 @Override isEmpty()1302 public boolean isEmpty() { 1303 return map.isEmpty(); 1304 } 1305 1306 @Override keys()1307 public Enumeration<Object> keys() { 1308 // CHM.keys() returns Iterator w/ remove() - instead wrap keySet() 1309 return Collections.enumeration(map.keySet()); 1310 } 1311 1312 @Override elements()1313 public Enumeration<Object> elements() { 1314 // CHM.elements() returns Iterator w/ remove() - instead wrap values() 1315 return Collections.enumeration(map.values()); 1316 } 1317 1318 @Override contains(Object value)1319 public boolean contains(Object value) { 1320 return map.contains(value); 1321 } 1322 1323 @Override containsValue(Object value)1324 public boolean containsValue(Object value) { 1325 return map.containsValue(value); 1326 } 1327 1328 @Override containsKey(Object key)1329 public boolean containsKey(Object key) { 1330 return map.containsKey(key); 1331 } 1332 1333 @Override get(Object key)1334 public Object get(Object key) { 1335 return map.get(key); 1336 } 1337 1338 @Override put(Object key, Object value)1339 public synchronized Object put(Object key, Object value) { 1340 return map.put(key, value); 1341 } 1342 1343 @Override remove(Object key)1344 public synchronized Object remove(Object key) { 1345 return map.remove(key); 1346 } 1347 1348 @Override putAll(Map<?, ?> t)1349 public synchronized void putAll(Map<?, ?> t) { 1350 map.putAll(t); 1351 } 1352 1353 @Override clear()1354 public synchronized void clear() { 1355 map.clear(); 1356 } 1357 1358 @Override toString()1359 public synchronized String toString() { 1360 return map.toString(); 1361 } 1362 1363 @Override keySet()1364 public Set<Object> keySet() { 1365 return Collections.synchronizedSet(map.keySet(), this); 1366 } 1367 1368 @Override values()1369 public Collection<Object> values() { 1370 return Collections.synchronizedCollection(map.values(), this); 1371 } 1372 1373 @Override entrySet()1374 public Set<Map.Entry<Object, Object>> entrySet() { 1375 return Collections.synchronizedSet(new EntrySet(map.entrySet()), this); 1376 } 1377 1378 /* 1379 * Properties.entrySet() should not support add/addAll, however 1380 * ConcurrentHashMap.entrySet() provides add/addAll. This class wraps the 1381 * Set returned from CHM, changing add/addAll to throw UOE. 1382 */ 1383 private static class EntrySet implements Set<Map.Entry<Object, Object>> { 1384 private Set<Map.Entry<Object,Object>> entrySet; 1385 EntrySet(Set<Map.Entry<Object, Object>> entrySet)1386 private EntrySet(Set<Map.Entry<Object, Object>> entrySet) { 1387 this.entrySet = entrySet; 1388 } 1389 size()1390 @Override public int size() { return entrySet.size(); } isEmpty()1391 @Override public boolean isEmpty() { return entrySet.isEmpty(); } contains(Object o)1392 @Override public boolean contains(Object o) { return entrySet.contains(o); } toArray()1393 @Override public Object[] toArray() { return entrySet.toArray(); } toArray(T[] a)1394 @Override public <T> T[] toArray(T[] a) { return entrySet.toArray(a); } clear()1395 @Override public void clear() { entrySet.clear(); } remove(Object o)1396 @Override public boolean remove(Object o) { return entrySet.remove(o); } 1397 1398 @Override add(Map.Entry<Object, Object> e)1399 public boolean add(Map.Entry<Object, Object> e) { 1400 throw new UnsupportedOperationException(); 1401 } 1402 1403 @Override addAll(Collection<? extends Map.Entry<Object, Object>> c)1404 public boolean addAll(Collection<? extends Map.Entry<Object, Object>> c) { 1405 throw new UnsupportedOperationException(); 1406 } 1407 1408 @Override containsAll(Collection<?> c)1409 public boolean containsAll(Collection<?> c) { 1410 return entrySet.containsAll(c); 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