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