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