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&trade; 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  * &lt;!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"&gt;
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  *    &lt;?xml version="1.0" encoding="UTF-8"?&gt;
113  *
114  *    &lt;!-- DTD for properties --&gt;
115  *
116  *    &lt;!ELEMENT properties ( comment?, entry* ) &gt;
117  *
118  *    &lt;!ATTLIST properties version CDATA #FIXED "1.0"&gt;
119  *
120  *    &lt;!ELEMENT comment (#PCDATA) &gt;
121  *
122  *    &lt;!ELEMENT entry (#PCDATA) &gt;
123  *
124  *    &lt;!ATTLIST entry key CDATA #REQUIRED&gt;
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 nullif 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&trade; 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&trade; 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 &#92;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 &#92;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      * &lt;!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"&gt;
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      * &lt;!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"&gt;
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      * &lt;!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"&gt;
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 nullif 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