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