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