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