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