001/*
002 * Copyright (c) 1995, 2021, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.  Oracle designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Oracle in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
022 * or visit www.oracle.com if you need additional information or have any
023 * questions.
024 */
025package Torello.Java.ReadOnly;
026
027import java.nio.charset.Charset;
028import java.util.function.BiConsumer;
029import java.util.function.BiFunction;
030
031import Torello.Java.Additional.Ret2;
032import Torello.Java.Additional.Tuple2;
033
034import java.io.*;
035import java.util.*;
036
037/**
038 * Immutable Wrapper for <CODE>java&#46;util&#46;Properties</CODE>, found in the "Java Collections
039 * Framework".
040 * 
041 * <EMBED CLASS=globalDefs DATA-JDK=Properties>
042 * <EMBED CLASS='external-html' DATA-FILE-ID=DATA_CLASS>
043 */
044@Torello.JavaDoc.JDHeaderBackgroundImg(EmbedTagFileID="JDHBI_MAIN")
045public class ReadOnlyProperties extends ReadOnlyHashtable<Object, Object>
046{
047    // ********************************************************************************************
048    // ********************************************************************************************
049    // Protected & Private Fields, Methods, Statics
050    // ********************************************************************************************
051    // ********************************************************************************************
052
053
054    /** <EMBED CLASS='external-html' DATA-FILE-ID=SVUID> */
055    protected static final long serialVersionUID = 1;
056
057    // Minor Optimization where new Properties's that have no contents always re-use this static
058    // instance.
059
060    private static final Properties EMPTY_PROPERTIES = new Properties(0);
061
062    // Singleton & Empty ReadOnlyProperties, Uses the "Supplier Constructor"
063    private static final ReadOnlyProperties EMPTY_READONLY_PROPERTIES =
064        new ReadOnlyProperties(EMPTY_PROPERTIES);
065
066    // The actual Properties used by this instance.
067    private final Properties properties;
068
069    // TRUE     => This was built using the class ROProperties
070    // FALSE    => This was built using the clone() of a standard java.util.Properties constructor
071
072    private final boolean fromBuilderOrProperties;
073
074    // Mimics the C++ Keyword/Concept of "Friend Class".   Is "Friends With" ROPropertiesBuilder
075    static class AccessBadge { private AccessBadge() { } }
076    private static final AccessBadge friendClassBadge = new AccessBadge();
077
078
079    // ********************************************************************************************
080    // ********************************************************************************************
081    // Builder, Acess-Badge Constructor - and Static "Empty" getter (which is used by the builder)
082    // ********************************************************************************************
083    // ********************************************************************************************
084
085
086    public static ReadOnlyProperties emptyROP()
087    { return EMPTY_READONLY_PROPERTIES; }
088
089    // To all the readers out there following along: The "AccessBadge" thing is just a slightly
090    // wimpier substitute for the C++ keyword / concept 'friend' or "Friend Class".  It means this
091    // constructor is (for all intents and purposes) a private-constructor, except for the class
092    // ROPropertiesBuilder
093    //
094    // This is the Constructor used by the Builder.  It has a "Zero-Size" Optimization
095
096    ReadOnlyProperties(ROPropertiesBuilder ropb, ROPropertiesBuilder.AccessBadge badge)
097    {
098        // This is a little bizarre, but necessary.  Properties extends Hashtable
099        super(friendClassBadge);
100
101        Objects.requireNonNull(badge, "Access Badge is null.  Requires Friend-Class Badge");
102
103        this.fromBuilderOrProperties = true;
104        this.properties = ropb;
105    }
106
107
108    // ********************************************************************************************
109    // ********************************************************************************************
110    // Constructors
111    // ********************************************************************************************
112    // ********************************************************************************************
113
114
115    /**
116     * Clones parameter {@code 'properties'} (and saves it) in order to guarantee that
117     * {@code 'this'} instance is Read-Only and furthermore shielded from outside modification.
118     * 
119     * @param properties The {@code Properties} to be cloned and saved into this instance internal
120     * and private {@code 'properties'} field.
121     */
122    @SuppressWarnings("unchecked")
123    public ReadOnlyProperties(Properties properties)
124    {
125        // This is a little bizarre, but necessary.  Properties extends Hashtable
126        super(friendClassBadge);
127        this.fromBuilderOrProperties = false;
128
129        // TO-DO: FIX-ME, CLONE IS NOT ACCEPTABLE IN READ-ONLY !!!  IT IS A BACK-DOOR !!!
130        this.properties = (Properties) properties.clone();
131    }
132
133    /**
134     * If only a small amount of processing needs to be done on the contents of some Java
135     * Map, and using an entire Builder-Class seems disproportionately complex - <I>this
136     * constructor can convert any Java {@code Map} into a {@code ReadOnlyProperties}, using
137     * a simple {@code 'mapTranslator'}</I>.
138     * 
139     * @param <A> The Key-Type of the User-Provided {@code Map}.
140     * @param <B> The Value-Type of the User-Provided {@code Map}.
141     * 
142     * @param map Any Java {@code Map}.
143     * 
144     * @param mapTranslator A function for mapping the iterated elements of Map-Types {@code 'A'}
145     * and {@code 'B'}, into the actual {@code Properties's} Key and Value Type {@code 'String'}.
146     * 
147     * <BR /><BR />If this parameter is passed null, this method will throw a
148     * {@code NullPointerException}.
149     * 
150     * @throws NullPointerException if either parameter {@code 'i'} or parameter 
151     * {@code 'mapTranslator'} is passed null.
152     */
153    public <A, B> ReadOnlyProperties
154        (Map<A, B> map, BiFunction<A, B, Ret2<String, String>> mapTranslator)
155    {
156        super(friendClassBadge);
157
158        Objects.requireNonNull(mapTranslator, "You have passed null to parameter 'mapTranslator'");
159
160        fromBuilderOrProperties = false;
161
162        Properties properties = new Properties(map.size());
163
164        for (Map.Entry<A, B> entry : map.entrySet())
165        {
166            Ret2<String, String> ret2 = mapTranslator.apply(entry.getKey(), entry.getValue());
167            properties.put(ret2.a, ret2.b);
168        }
169
170        // Empty Optimization (throw away, completely, the reference, use static-constant)
171        this.properties = (properties.size() == 0) ? EMPTY_PROPERTIES : properties;
172    }
173
174
175    // ********************************************************************************************
176    // ********************************************************************************************
177    // More Constructors, March 2024
178    // ********************************************************************************************
179    // ********************************************************************************************
180
181
182    /**
183     * Reads a property list (key and element pairs) from the input byte stream. The input stream
184     * is in a simple line-oriented format as specified in {@code load(Reader)} and is assumed to
185     * use the ISO 8859-1 character encoding; that is each byte is one Latin1 character. Characters
186     * not in Latin1, and certain special characters, are represented in keys and elements using
187     * Unicode escapes as defined in section {@code 3.3} of <cite>The Java Language
188     * Specification</cite>.
189     * 
190     * <BR /><BR />The specified stream remains open after this method returns.
191     *
192     * <BR /><BR /><SPAN CLASS=CopiedJDK>This Detail-Comment was copied from class:
193     * {@code java.util.Properties}, <B>JDK 21</B>
194     * 
195     * <BR /><BR /><B>Method:</B> Properties.load(InputStream.inStream)
196     * </SPAN>
197     * 
198     * @param inStream the input stream.
199     * @param sizeIfKnown <EMBED CLASS='external-html' DATA-FILE-ID=PROP_ICAPACITY>
200     * @throws IOException if an error occurred when reading from the input stream.
201     * 
202     * @throws IllegalArgumentException if the input stream contains a malformed Unicode escape
203     * sequence.
204     * 
205     * @throws NullPointerException if {@code inStream} is null.
206     */
207    public ReadOnlyProperties(InputStream inStream, Integer sizeIfKnown) throws IOException
208    {
209        super(friendClassBadge);
210
211        this.fromBuilderOrProperties = false;
212
213        this.properties = (sizeIfKnown != null) ? new Properties(sizeIfKnown) : new Properties();
214
215        this.properties.load(inStream);
216    }
217
218    /**
219     * Reads a property list (key and element pairs) from the input character stream in a simple
220     * line-oriented format.
221     * 
222     * <EMBED CLASS='external-html' DATA-FILE-ID=LOAD_DESC_READER>
223     * 
224     * <BR /><BR /><SPAN CLASS=CopiedJDK>This Detail-Comment was copied from class:
225     * {@code java.util.Properties}, <B>JDK 21</B>
226     * 
227     * <BR /><BR /><B>Method:</B> Properties.load(InputStream.inStream)
228     * </SPAN>
229     * 
230     * @param reader        the input character stream.
231     * @param sizeIfKnown   <EMBED CLASS='external-html' DATA-FILE-ID=PROP_ICAPACITY>
232     * @throws IOException  if an error occurred when reading from the input stream.
233     * 
234     * @throws IllegalArgumentException if a malformed Unicode escape appears in the input.
235     * @throws NullPointerException     if {@code reader} is null.
236     */
237    public ReadOnlyProperties(Reader reader, Integer sizeIfKnown) throws IOException 
238    {
239        super(friendClassBadge);
240
241        this.fromBuilderOrProperties = false;
242
243        this.properties = (sizeIfKnown != null) ? new Properties(sizeIfKnown) : new Properties();
244
245        this.properties.load(reader);
246    }
247
248    /**
249     * Loads all of the properties represented by the XML document on the specified input stream
250     * into this properties table.
251     * 
252     * <EMBED CLASS='external-html' DATA-FILE-ID=LOADXML_DESC_INPUT_STRM>
253     * 
254     * <BR /><BR /><SPAN CLASS=CopiedJDK>This Detail-Comment was copied from class:
255     * {@code java.util.Properties}, <B>JDK 21</B>
256     * 
257     * <BR /><BR /><B>Method:</B> Properties.load(InputStream.inStream)
258     * </SPAN>
259     * 
260     * @param sizeIfKnown   <EMBED CLASS='external-html' DATA-FILE-ID=PROP_ICAPACITY>
261     * @param in            the input stream from which to read the XML document.
262     * 
263     * @throws IOException if reading from the specified input stream results in an
264     * {@code IOException}.
265     * 
266     * @throws java.io.UnsupportedEncodingException if the document's encoding declaration can be
267     * read and it specifies an encoding that is not supported
268     * 
269     * @throws InvalidPropertiesFormatException Data on input stream does not constitute a valid
270     * XML document with the mandated document type.
271     * 
272     * @throws NullPointerException if {@code in} is null.
273     *
274     * @spec https://www.w3.org/TR/xml Extensible Markup Language (XML) 1.0 (Fifth Edition)
275     * @see #storeToXML(OutputStream, String, String)
276     * @see <a href="http://www.w3.org/TR/REC-xml/#charencoding">Character Encoding in Entities</a>
277     */
278    public ReadOnlyProperties(Integer sizeIfKnown, InputStream in)
279        throws IOException, UnsupportedEncodingException, InvalidPropertiesFormatException
280    {
281        super(friendClassBadge);
282
283        this.fromBuilderOrProperties = false;
284
285        this.properties = (sizeIfKnown != null) ? new Properties(sizeIfKnown) : new Properties();
286
287        this.properties.loadFromXML(in);
288    }
289
290
291    // ********************************************************************************************
292    // ********************************************************************************************
293    // One More Constructor, for good luck - March 2024
294    // ********************************************************************************************
295    // ********************************************************************************************
296
297
298    /**
299     * Builds a {@code Properties} instance using the {@code 'refHolder'} &amp;
300     * {@code 'computeNextEntry'} combination.
301     * 
302     * @param refHolder         <EMBED CLASS='external-html' DATA-FILE-ID=REF_HOLDER>
303     * @param computeNextEntry  <EMBED CLASS='external-html' DATA-FILE-ID=COMPUTE_NEXT_RUN>
304     * @param sizeIfKnown       <EMBED CLASS='external-html' DATA-FILE-ID=PROP_ICAPACITY>
305     */
306    public ReadOnlyProperties(
307            Tuple2<Object, Object>  refHolder,
308            Runnable                computeNextEntry,
309            Integer                 sizeIfKnown
310        )
311    {
312        super(friendClassBadge);
313
314        Objects.requireNonNull(refHolder, ROHelpers.NULL_MSG + "'refHolder'");
315        Objects.requireNonNull(computeNextEntry, ROHelpers.NULL_MSG + "'computeNextEntry'");
316
317        fromBuilderOrProperties = false;
318
319        Properties properties = new Properties(((sizeIfKnown == null)  ? 16 : sizeIfKnown));
320
321        do
322        {
323            computeNextEntry.run();
324            if ((refHolder.a == null) && (refHolder.b == null)) break;
325            properties.put(refHolder.a, refHolder.b);
326        }
327        while (true);
328
329        // Empty Optimization (throw away, completely, the reference, use static-constant)
330        this.properties = (properties.size() == 0) ? EMPTY_PROPERTIES : properties;
331    }
332
333
334    // ********************************************************************************************
335    // ********************************************************************************************
336    // Convert to java.util Types
337    // ********************************************************************************************
338    // ********************************************************************************************
339
340
341    /**
342     * Clone's {@code 'this'} instance internal {@code Properties<E>} field, and returns it. 
343     * <EMBED CLASS='external-html' DATA-TYPE=Properties DATA-FILE-ID=CLONE_TO>
344     * 
345     * @return An independent, mutable copy of {@code 'this'} instance' internal
346     * {@code Properties<E>} data-structure.
347     */
348    @SuppressWarnings("unchecked") // The clone() cast
349    public Properties cloneToProperties()
350    {
351        return fromBuilderOrProperties
352            ? new Properties(this.properties)
353            : (Properties) this.properties.clone();
354    }
355
356    /**
357     * Invokes {@code java.util.Collections.unmodifiableMap} on the internal {@code Properties}.
358     * <EMBED CLASS='external-html' DATA-RET_TYPE=Map DATA-FILE-ID=WRAP_TO_IMMUTABLE>
359     * 
360     * @return A {@code Map} which adheres to the JDK interface {@code java.util.Map}, but throws
361     * an {@code UnsupportedOperationException} if a user attempts to invoke a Mutator-Method on
362     * the returned instance.
363     */
364    public Map<Object, Object> wrapToImmutableMap()
365    { return Collections.unmodifiableMap(this.properties); }
366
367
368    // ********************************************************************************************
369    // ********************************************************************************************
370    // Original JDK Methods, java.util.Properties
371    // ********************************************************************************************
372    // ********************************************************************************************
373
374
375    /**
376     * Writes this property list (key and element pairs) from this instance' internal
377     * {@code Properties} table to the output character stream (in a format suitable for using the
378     * {@code Properties.load(Reader)} method.
379     * 
380     * <EMBED CLASS='external-html' DATA-FILE-ID=STORE_DESC_WRITER>
381     * 
382     * @param writer an output character stream writer.
383     * @param comments a description of the property list.
384     * 
385     * @throws IOException if writing this property list to the specified output stream throws an
386     * {@code IOException}.
387     * 
388     * @throws ClassCastException  if this {@code Properties} object contains any keys or values
389     * that are not {@code Strings}.
390     * 
391     * @throws NullPointerException  if {@code writer} is null.
392     */
393    public void store(Writer writer, String comments)
394        throws IOException
395    { this.properties.store(writer, comments); }
396
397    /**
398     * Writes this property list (key and element pairs) from this instance' internal
399     * {@code Properties} table to the output character stream in a format suitable for using the
400     * {@code Properties.load(InputStream)} method.
401     * 
402     * <EMBED CLASS='external-html' DATA-FILE-ID=STORE_DESC_OUTPUTS>
403     * 
404     * @param out an output stream.
405     * @param comments a description of the property list.
406     * 
407     * @throws IOException if writing this property list to the specified output stream throws an
408     * {@code IOException}.
409     * 
410     * @throws ClassCastException if this {@code Properties} object contains any keys or values
411     * that are not {@code Strings}.
412     * 
413     * @throws NullPointerException if {@code out} is null.
414     */
415    public void store(OutputStream out, String comments)
416        throws IOException
417    { this.properties.store(out, comments); }
418
419    /**
420     * Emits an XML document representing all of the properties contained in this table.
421     *
422     * <BR /><BR /> An invocation of this method of the form {@code props.storeToXML(os, comment)}
423     * behaves in exactly the same way as the invocation
424     * {@code props.storeToXML(os, comment, "UTF-8");}.
425     *
426     * @param os the output stream on which to emit the XML document.
427     * @param comment a description of the property list, or {@code null} if no comment is desired.
428     * 
429     * @throws IOException if writing to the specified output stream results in an
430     * {@code IOException}.
431     * 
432     * @throws NullPointerException if {@code os} is null.
433     * 
434     * @throws ClassCastException  if this {@code Properties} object contains any keys or values
435     * that are not {@code Strings}.
436     */
437    public void storeToXML(OutputStream os, String comment)
438        throws IOException
439    { this.properties.storeToXML(os, comment); }
440
441    /**
442     * Emits an XML document representing all of the properties contained in this table, using the
443     * specified encoding.
444     * 
445     * <EMBED CLASS='external-html' DATA-FILE-ID=STOREXML_DESC_OS_S_S>
446     * 
447     * @param os the output stream on which to emit the XML document.
448     * @param comment a description of the property list, or {@code null} if no comment is desired.
449     * 
450     * @param  encoding the name of a supported
451     * <a href="../lang/package-summary.html#charenc">character encoding</a>
452     *
453     * @throws IOException if writing to the specified output stream results in an
454     * {@code IOException}.
455     * 
456     * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the
457     * implementation.
458     * 
459     * @throws NullPointerException if {@code os} is {@code null}, or if {@code encoding} is
460     * {@code null}.
461     * 
462     * @throws ClassCastException  if this {@code Properties} object contains any keys or values
463     * that are not {@code Strings}.
464     *
465     * @spec https://www.w3.org/TR/xml Extensible Markup Language (XML) 1.0 (Fifth Edition)
466     * @see <a href="http://www.w3.org/TR/REC-xml/#charencoding">Character Encoding in Entities</a>
467     */
468    public void storeToXML(OutputStream os, String comment, String encoding) throws IOException
469    { this.properties.storeToXML(os, comment, encoding); }
470
471    /**
472     * Emits an XML document representing all of the properties contained in this table, using
473     * the specified encoding.
474     * 
475     * <EMBED CLASS='external-html' DATA-FILE-ID=STOREXML_DESC_OS_S_CHRS>
476     * 
477     * @param os the output stream on which to emit the XML document.
478     * @param comment a description of the property list, or {@code null} if no comment is desired.
479     * @param charset the charset
480     *
481     * @throws IOException if writing to the specified output stream results in an
482     * {@code IOException}.
483     * 
484     * @throws NullPointerException if {@code os} or {@code charset} is {@code null}.
485     * 
486     * @throws ClassCastException  if this {@code Properties} object contains any keys or values
487     * that are not {@code Strings}.
488     *
489     * @spec https://www.w3.org/TR/xml Extensible Markup Language (XML) 1.0 (Fifth Edition)
490     * @see <a href="http://www.w3.org/TR/REC-xml/#charencoding">Character Encoding in Entities</a>
491     */
492    public void storeToXML(OutputStream os, String comment, Charset charset) throws IOException
493    { this.properties.storeToXML(os, comment, charset); }
494
495    /**
496     * Searches for the property with the specified key in this property list.  If the key is not
497     * found in this property list, the default property list, and its defaults, recursively, are
498     * then checked. The method returns {@code null} if the property is not found.
499     *
500     * @param key the property key.
501     * @return the value in this property list with the specified key value.
502     */
503    public String getProperty(String key)
504    { return this.properties.getProperty(key); }
505
506    /**
507     * Searches for the property with the specified key in this property list.  If the key is not
508     * found in this property list, the default property list, and its defaults, recursively, are
509     * then checked. The method returns the default value argument if the property is not found.
510     *
511     * @param key the hashtable key.
512     * @param defaultValue a default value.
513     *
514     * @return the value in this property list with the specified key value.
515     */
516    public String getProperty(String key, String defaultValue)
517    { return this.properties.getProperty(key, defaultValue); }
518
519    /**
520     * Returns an enumeration of all the keys in this property list, including distinct keys in
521     * the default property list if a key of the same name has not already been found from the main
522     * properties list.
523     *
524     * @return an enumeration of all the keys in this property list, including the keys in the
525     * default property list.
526     * 
527     * @throws ClassCastException if any key in this property list is not a {@code String}.
528     * @see #stringPropertyNames
529     */
530    public Enumeration<?> propertyNames()
531    { return this.properties.propertyNames(); }
532
533    /**
534     * Returns an unmodifiable set of keys from this property list where the key and its
535     * corresponding value are strings, including distinct keys in the default property list if a
536     * key of the same name has not already been found from the main properties list.  Properties
537     * whose key or value is not of type {@code String} are omitted.
538     * 
539     * <BR /><BR />The returned set is not backed by this {@code Properties} object.  Changes to
540     * this {@code Properties} object are not reflected in the returned set.
541     *
542     * @return  an unmodifiable set of keys in this property list where the key and its
543     * corresponding value are strings, including the keys in the default property list.
544     */
545    public ReadOnlySet<String> stringPropertyNames()
546    { return InterfaceBuilder.toReadOnlySet(this.properties.stringPropertyNames()); }
547
548    /**
549     * Prints this property list out to the specified output stream.  This method is useful for
550     * debugging.
551     *
552     * @param out an output stream.
553     * @throws ClassCastException if any key in this property list is not a {@code String}.
554     */
555    public void list(PrintStream out)
556    { this.properties.list(out); }
557
558    /**
559     * Prints this property list out to the specified output stream.  This method is useful for
560     * debugging.
561     *
562     * @param out an output stream.
563     * @throws ClassCastException if any key in this property list is not a {@code String}.
564     */
565    public void list(PrintWriter out)
566    { this.properties.list(out); }
567
568    //
569    // Hashtable methods overridden and delegated to a ConcurrentHashMap instance
570
571    @Override
572    public int size()
573    { return this.properties.size(); }
574
575    @Override
576    public boolean isEmpty()
577    { return this.properties.isEmpty(); }
578
579    @Override
580    public Enumeration<Object> keys()
581    { return this.properties.keys(); }
582
583    @Override
584    public Enumeration<Object> elements()
585    { return this.properties.elements(); }
586
587    @Override
588    public boolean contains(Object value)
589    { return this.properties.contains(value); }
590
591    @Override
592    public boolean containsValue(Object value)
593    { return this.properties.containsValue(value); }
594
595    @Override
596    public boolean containsKey(Object key)
597    { return this.properties.containsKey(key); }
598
599    @Override
600    public Object get(Object key)
601    { return this.properties.get(key); }
602
603    @Override
604    public ReadOnlySet<Object> keySet()
605    {
606        return InterfaceBuilder.toReadOnlySet(
607            fromBuilderOrProperties
608                ? ((ROPropertiesBuilder) this.properties)._keySet(friendClassBadge)
609                : this.properties.keySet()
610        );
611    }
612
613    @Override
614    public ReadOnlyCollection<Object> values()
615    {
616        return InterfaceBuilder.toReadOnlyCollection(
617            fromBuilderOrProperties
618                ? ((ROPropertiesBuilder) this.properties)._values(friendClassBadge)
619                : this.properties.values()
620        );
621    }
622
623    @Override
624    public ReadOnlySet<ReadOnlyMap.Entry<Object, Object>> entrySet()
625    {
626        return ROHelpers.toReadOnlyEntrySet(
627            fromBuilderOrProperties
628                ? ((ROPropertiesBuilder) this.properties)._entrySet(friendClassBadge)
629                : this.properties.entrySet()
630        );
631    }
632
633    @Override
634    public Object getOrDefault(Object key, Object defaultValue)
635    { return this.properties.getOrDefault(key, defaultValue); }
636
637    @Override
638    public synchronized void forEach(BiConsumer<? super Object, ? super Object> action)
639    { this.properties.forEach(action); }
640
641
642    // ********************************************************************************************
643    // ********************************************************************************************
644    // java.lang.Object
645    // ********************************************************************************************
646    // ********************************************************************************************
647
648
649    /**
650     * Returns a {@code String} representation of this {@code Properties}. The {@code String}
651     * representation consists of a list of the collection's elements in the order they are
652     * returned by its iterator, enclosed in square brackets ({@code "[]"}). Adjacent elements are
653     * separated by the characters {@code ", "} (comma and space). Elements are converted to
654     * {@code String's} as by {@code String.valueOf(Object)}.
655     * 
656     * @return a {@code String} representation of this {@code Properties}
657     */
658    @Override
659    public String toString()
660    { return this.properties.toString(); }
661
662    /**
663     * Compares the specified Object with this List for equality, as per the definition in the 
664     * class {@code java.util.Properties}.
665     *
666     * @param  o object to be compared for equality with this {@code Properties}
667     * @return {@code TRUE} if the specified Object is equal to this map
668     */
669    @Override
670    public boolean equals(Object o)
671    {
672        if (this == o) return true;
673        if (! (o instanceof ReadOnlyProperties)) return false;
674        return this.properties.equals(((ReadOnlyProperties) o).properties);
675    }
676
677    @Override
678    public synchronized int hashCode()
679    { return this.properties.hashCode(); }
680}