001/*
002 * Copyright (c) 1994, 2023, 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.util.*;
028
029import java.util.function.BiConsumer;
030import java.util.function.Consumer;
031import java.util.function.Predicate;
032import java.util.function.Function;
033
034import Torello.Java.Additional.Tuple2;
035import Torello.JavaDoc.LinkJavaSource;
036
037/**
038 * Immutable Wrapper for <CODE>java&#46;util&#46;Hashtable</CODE>, found in the "Java Collections
039 * Framework".
040 * 
041 * <EMBED CLASS=globalDefs DATA-JDK=Hashtable DATA-ENDING=table>
042 * <EMBED CLASS='external-html' DATA-FILE-ID=DATA_CLASS>
043 * <EMBED CLASS='external-html' DATA-FILE-ID=RO_SYNCHRONIZED>
044 * 
045 * @param <K> the type of keys maintained by this map
046 * @param <V> the type of mapped values
047 */
048@Torello.JavaDoc.JDHeaderBackgroundImg(EmbedTagFileID="JDHBI_MAIN")
049@SuppressWarnings("unchecked")
050public class ReadOnlyHashtable<K, V>
051    implements ReadOnlyDictionary<K, V>, ReadOnlyMap<K, V>, Cloneable, java.io.Serializable
052{
053    // ********************************************************************************************
054    // ********************************************************************************************
055    // Protected & Private Fields, Methods, Statics
056    // ********************************************************************************************
057    // ********************************************************************************************
058
059
060    /** <EMBED CLASS='external-html' DATA-FILE-ID=SVUID> */
061    protected static final long serialVersionUID = 1;
062
063    // Minor Optimization where new Hashtable's that have no contents always re-use this static
064    // instance.  Since this instance is completely empty, the Raw-Types things is irrelevant.
065
066    @SuppressWarnings("rawtypes")
067    private static final Hashtable EMPTY_HASH_TABLE = new Hashtable(0, 0.75f);
068
069    // Singleton & Empty ReadOnlyHashtable, Uses the "Supplier Constructor"
070    @SuppressWarnings("rawtypes")
071    private static final ReadOnlyHashtable EMPTY_READONLY_HASH_TABLE =
072        new ReadOnlyHashtable(EMPTY_HASH_TABLE);
073
074    // The actual Hashtable used by this instance.
075    private final Hashtable<K, V> hashTable;
076
077    // TRUE     => This was built using the class ROHashtableBuilder
078    // FALSE    => This was built using the clone() of a standard java.util.Hashtable constructor
079
080    private final boolean fromBuilderOrHashtable;
081
082    // Mimics the C++ Keyword/Concept of "Friend Class".   Is "Friends With" ROHashtableBuilder
083    static class AccessBadge { private AccessBadge() { } }
084    private static final AccessBadge friendClassBadge = new AccessBadge();
085
086
087    // ********************************************************************************************
088    // ********************************************************************************************
089    // Builder, Acess-Badge Constructor - and Static "Empty" getter (which is used by the builder)
090    // ********************************************************************************************
091    // ********************************************************************************************
092
093
094    /**
095     * Returns an empty, <B STYLE='color: red;'>singleton</B>, instance of
096     * {@code ReadOnlyHashtable}.
097     * 
098     * @param <X> Returned {@link ReadOnlyMap}'s Key-Type.
099     * @param <Y> Returned {@link ReadOnlyMap}'s Value-Type.  
100     * 
101     * @return An empty map.  Since this map is both empty &amp; read-only, a raw-type singleton 
102     * will suffice for all operations offered by this clas.
103     * 
104     * <EMBED CLASS='external-html' DATA-FILE-ID=EMPTY_SYNCHRONIZED>
105     */
106    public static <X, Y> ReadOnlyHashtable<X, Y> emptyROHT()
107    { return (ReadOnlyHashtable<X, Y>) EMPTY_READONLY_HASH_TABLE; }
108
109    // Used by the **Builder**
110    // To all the readers out there following along: The "AccessBadge" thing is just a slightly
111    // wimpier substitute for the C++ keyword / concept 'friend' or "Friend Class".  It means this
112    // constructor is (for all intents and purposes) a private-constructor, except for the class
113    // ROHashtableBuilder
114    //
115    // This is the Constructor used by the Builder.  It has a "Zero-Size" Optimization
116
117    ReadOnlyHashtable(ROHashtableBuilder<K, V> rohtb, ROHashtableBuilder.AccessBadge badge)
118    {
119        Objects.requireNonNull(badge, "Access Badge is null.  Requires Friend-Class Badge");
120
121        this.fromBuilderOrHashtable = true;
122        this.hashTable = rohtb;
123    }
124
125    // SPECIAL CASE: ReadOnlyProperties inherits ReadOnlyHashtable
126    // NOTE:         ROPropertiesBuilder **DOES NOT** inherit ROHashtableBuilder
127    //
128    // The parent class of ReadOnlyProperties (a.k.a. this class) is completely unused, and the
129    // private fields are not important.  For inheritance purposes, though, this is the parent
130    // class, and as such, it has to be initialized with something!
131
132    ReadOnlyHashtable(ReadOnlyProperties.AccessBadge badge)
133    {
134        this.fromBuilderOrHashtable = true; // This is unused
135        this.hashTable = null;              // This is unused
136    }
137
138
139    // ********************************************************************************************
140    // ********************************************************************************************
141    // Modified-Original Constructors
142    // ********************************************************************************************
143    // ********************************************************************************************
144
145
146    /**
147     * Copies the contents of parameter {@code 'map'}, and saves saves it, thereby guaranteeing
148     * {@code 'this'} instance is Read-Only and fully-shielded from outside modification.
149     * 
150     * @param map The {@code map} to be copied into {@code 'this'} instance internal and private
151     * {@code 'hashTable'} field.
152     */
153    public ReadOnlyHashtable(Map<K, V> map)
154    {
155        this.fromBuilderOrHashtable = false;
156
157        this.hashTable = (map.size() == 0)
158            ? ((Hashtable<K, V>) EMPTY_HASH_TABLE)
159            : new Hashtable<>(map);
160    }
161
162    /**
163     * If only a small amount of processing needs to be done on the contents of some Java
164     * Map, and using an entire Builder-Class seems disproportionately complex - <I>this
165     * constructor can convert any Java {@code Map} into a {@code ReadOnlyHashtable}, using
166     * a simple {@code 'mapTranslator'}</I>.
167     * 
168     * @param <X> The Key-Type of the User-Provided {@code Map}.
169     * @param <Y> The Value-Type of the User-Provided {@code Map}.
170     * 
171     * @param refHolder This must a non-null instance of {@link Tuple2}.  The provided
172     * {@code Consumer} is just that, a {@code 'Consumer'} rather than a {@code 'Function'}, since
173     * the results of each translation must be assigned to the values inside this tuple in order
174     * for them to be inserted into this {@code ReadOnlyHashtable}.
175     * 
176     * @param map Any Java {@code Map}.
177     * 
178     * @param mapTranslator A consumer for mapping the iterated elements of Map-Types {@code 'X'}
179     * and {@code 'Y'}, into the actual {@code Hashtable's} Key-Type {@code 'K'}, and Value-Type
180     * {@code 'V'}.  The results of this translation must be placed into the fields inside
181     * {@code 'refHolder'}.
182     * 
183     * <BR /><BR />If this parameter is passed null, this method will throw a
184     * {@code NullPointerException}.
185     * 
186     * @param filter An optional filter that can be used to prevent &amp; prohibit any chosen
187     * elements from input {@code 'map'} from being inserted into {@code 'this'}
188     * {@code ReadOnlyHashtable}.
189     * 
190     * <BR /><BR />This parameter may be passed null, and if it is, it will be silently ignored, 
191     * and all entries present inside {@code 'map'} will be processed and inserted into
192     * {@code 'this'}
193     * 
194     * @param loadFactor <EMBED CLASS='external-html' DATA-FILE-ID=HASH_LOAD_FACTOR>
195     * 
196     * @throws NullPointerException if either parameter {@code 'i'} or parameter 
197     * {@code 'mapTranslator'} is passed null.
198     */
199    public <X, Y> ReadOnlyHashtable(
200            Tuple2<K, V>                refHolder,
201            Map<X, Y>                   map,
202            Consumer<Map.Entry<X, Y>>   mapTranslator,
203            Predicate<Map.Entry<X, Y>>  filter,
204            Float                       loadFactor
205        )
206    {
207        Objects.requireNonNull(refHolder, ROHelpers.NULL_MSG + "'refHolder'");
208        Objects.requireNonNull(mapTranslator, ROHelpers.NULL_MSG + "'mapTranslator'");
209
210        fromBuilderOrHashtable = false;
211
212        Hashtable<K, V> hashTable = (loadFactor != null)
213            ? new Hashtable<>(map.size(), loadFactor)
214            : new Hashtable<>(map.size());
215
216        if (filter == null)
217
218            for (Map.Entry<X, Y> entry : map.entrySet())
219            {
220                mapTranslator.accept(entry);
221                hashTable.put(refHolder.a, refHolder.b);
222            }
223
224        else for (Map.Entry<X, Y> entry : map.entrySet())
225        {
226            if (! filter.test(entry)) continue;
227            mapTranslator.accept(entry);
228            hashTable.put(refHolder.a, refHolder.b);
229        }
230
231        // Empty Optimization (throw away, completely, the reference, use static-constant)
232        this.hashTable = (hashTable.size() == 0)
233            ? ((Hashtable<K, V>) EMPTY_HASH_TABLE)
234            : hashTable;
235    }
236
237
238    // ********************************************************************************************
239    // ********************************************************************************************
240    // New Stuff, January 2024
241    // ********************************************************************************************
242    // ********************************************************************************************
243
244
245    /**
246     * Constructs an instance of {@code ReadOnlyHashtable} that contains the keys present in
247     * parameter {@code 'keys'}, and values generated by {@code 'valueMapper'} - using each of the
248     * {@code 'keys'} as input.
249     * 
250     * <EMBED CLASS='external-html' DATA-FILE-ID=LOOK_AT_IT>
251     * 
252     * @param keys          Any Java {@code Iterable} instance.
253     * @param valueMapper   A user provided function to compute a map value, based on a map key.
254     * @param filter        <EMBED CLASS='external-html' DATA-FILE-ID=MAP_ITERABLE_FILTER>
255     * @param loadFactor    <EMBED CLASS='external-html' DATA-FILE-ID=HASH_LOAD_FACTOR>
256     * @param sizeIfKnown   <EMBED CLASS='external-html' DATA-FILE-ID=MAP_TABLE_ICAPACITY>
257     *  
258     * @throws NullPointerException if either {@code 'keys'} or {@code 'valueMapper'} are passed
259     * null.
260     */
261    public ReadOnlyHashtable(
262            Iterable<? extends K>               keys,
263            Function<? super K, ? extends V>    valueMapper,
264            Predicate<? super K>                filter,
265            Float                               loadFactor,
266            Integer                             sizeIfKnown
267        )
268    {
269        Objects.requireNonNull(keys, ROHelpers.NULL_MSG + "'keys'");
270        Objects.requireNonNull(valueMapper, ROHelpers.NULL_MSG + "'valueMapper'");
271
272        fromBuilderOrHashtable = false;
273
274        Hashtable<K, V> hashTable = new Hashtable<>(
275            ((sizeIfKnown == null)  ? 16    : sizeIfKnown),
276            ((loadFactor == null)   ? 0.75f : loadFactor)
277        );
278
279        if (filter == null)
280            for (K key : keys)
281                hashTable.put(key, valueMapper.apply(key));
282
283        else
284            for (K key : keys)
285                if (filter.test(key))
286                    hashTable.put(key, valueMapper.apply(key));
287
288        // Empty Optimization (throw away, completely, the reference, use static-constant)
289        this.hashTable = (hashTable.size() == 0)
290            ? ((Hashtable<K, V>) EMPTY_HASH_TABLE)
291            : hashTable;
292    }
293
294    /**
295     * Constructs an instance of {@code ReadOnlyHashtable} that has been populated by the Key-Value
296     * Pairs left in {@code 'refHolder'} by each invocation of the {@code Runnable} parameter
297     * {@code 'computeNextEntry'}.  Key-Value Pairs are inserted until an invocation of the 
298     * {@code Runnable} leaves null in {@code refHolder.a} and {@code refHolder.b}
299     * 
300     * <EMBED CLASS='external-html' DATA-FILE-ID=LOOK_AT_IT>
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 loadFactor        <EMBED CLASS='external-html' DATA-FILE-ID=HASH_LOAD_FACTOR>
305     * @param sizeIfKnown       <EMBED CLASS='external-html' DATA-FILE-ID=MAP_TABLE_ICAPACITY>
306     *  
307     * @throws NullPointerException if either {@code 'refHolder'} or {@code 'computeNextEntry'} are
308     * passed null.
309     */
310    public ReadOnlyHashtable(
311            Tuple2<K, V>    refHolder,
312            Runnable        computeNextEntry,
313            Float           loadFactor,
314            Integer         sizeIfKnown
315        )
316    {
317        Objects.requireNonNull(refHolder, ROHelpers.NULL_MSG + "'refHolder'");
318        Objects.requireNonNull(computeNextEntry, ROHelpers.NULL_MSG + "'computeNextEntry'");
319
320        fromBuilderOrHashtable = false;
321
322        Hashtable<K, V> hashTable = new Hashtable<>(
323            ((sizeIfKnown == null)  ? 16    : sizeIfKnown),
324            ((loadFactor == null)   ? 0.75f : loadFactor)
325        );
326
327        do
328        {
329            computeNextEntry.run();
330            if ((refHolder.a == null) && (refHolder.b == null)) break;
331            hashTable.put(refHolder.a, refHolder.b);
332        }
333        while (true);
334
335        // Empty Optimization (throw away, completely, the reference, use static-constant)
336        this.hashTable = (hashTable.size() == 0)
337            ? ((Hashtable<K, V>) EMPTY_HASH_TABLE)
338            : hashTable;
339    }
340
341    /**
342     * Populates an instance of {@code ReadOnlyHashtable} by iterating the input {@code 'source'}
343     * iterable, and passing each value returned by that {@code Iterator} to the
344     * {@code 'computeNextEntry'} Java {@code Consumer}.
345     * 
346     * <BR /><BR />It is the programmer's responsibility to properly place each Key-Value Pair 
347     * that is intending to be inserted into the final {@code Map} instance into the
348     * {@code 'refHolder'} instance.  After each invocation of {@code 'computeNextEntry'}, this 
349     * constructor's logic will retrieve the values within {@code 'refHolder.a'} and
350     * {@code 'refHolder.b'} and insert them into this instance internal {@code Hashtable}.
351     * 
352     * <EMBED CLASS='external-html' DATA-FILE-ID=LOOK_AT_IT>
353     * 
354     * @param <X>               The type of the elements inside {@code 'source'}
355     * @param source            Any Java {@code Iterable} instance.
356     * @param refHolder         <EMBED CLASS='external-html' DATA-FILE-ID=REF_HOLDER>
357     * @param computeNextEntry  <EMBED CLASS='external-html' DATA-FILE-ID=COMPUTE_NEXT_CONS>
358     * @param filter            May be used to filter out some of the elements of {@code 'source'}
359     * @param loadFactor        <EMBED CLASS='external-html' DATA-FILE-ID=HASH_LOAD_FACTOR>
360     * @param sizeIfKnown       <EMBED CLASS='external-html' DATA-FILE-ID=MAP_TABLE_ICAPACITY>
361     * 
362     * @throws NullPointerException if either {@code 'refHolder'}, {@code 'computeNextEntry'} or
363     * {@code 'source'} are passed null.
364     */
365    public <X> ReadOnlyHashtable(
366            Iterable<X>             source,
367            Tuple2<K, V>            refHolder,
368            Consumer<? super X>     computeNextEntry,
369            Predicate<? super X>    filter,
370            Float                   loadFactor,
371            Integer                 sizeIfKnown
372        )
373    {
374        Objects.requireNonNull(refHolder, ROHelpers.NULL_MSG + "'refHolder'");
375        Objects.requireNonNull(computeNextEntry, ROHelpers.NULL_MSG + "'computeNextEntry'");
376
377        fromBuilderOrHashtable = false;
378
379        Hashtable<K, V> hashTable = new Hashtable<>(
380            ((sizeIfKnown == null)  ? 16    : sizeIfKnown),
381            ((loadFactor == null)   ? 0.75f : loadFactor)
382        );
383
384        X x; // temp var
385        Iterator<X> iter = source.iterator();
386
387        if (filter == null)
388
389            while (iter.hasNext())
390            {
391                computeNextEntry.accept(iter.next());
392                hashTable.put(refHolder.a, refHolder.b);
393            }
394
395        else
396
397            while (iter.hasNext())
398                if (filter.test(x = iter.next()))
399                {
400                    computeNextEntry.accept(x);
401                    hashTable.put(refHolder.a, refHolder.b);
402                }
403
404        // Empty Optimization (throw away, completely, the reference, use static-constant)
405        this.hashTable = (hashTable.size() == 0)
406            ? ((Hashtable<K, V>) EMPTY_HASH_TABLE)
407            : hashTable;
408    }
409
410
411    // ********************************************************************************************
412    // ********************************************************************************************
413    // Array Constructors, March 2024
414    // ********************************************************************************************
415    // ********************************************************************************************
416
417
418    /**
419     * Retrieves elements from the VarArgs Generic-Array parameter {@code 'elements'}, and
420     * subsequently invokes the {@code 'computeNextEntry'} processor to populate this
421     * {@code ReadOnlyHashtable}.
422     * 
423     * @param <X>                  The type of array parameter {@code 'elements'}
424     * @param refHolder            <EMBED CLASS='external-html' DATA-FILE-ID=REF_HOLDER>
425     * @param computeNextEntry     <EMBED CLASS='external-html' DATA-FILE-ID=ARR_COMPUTE_NEXT_CONS>
426     * @param filter               <EMBED CLASS='external-html' DATA-FILE-ID=MAP_ARRAY_FILTER>
427     * @param loadFactor           <EMBED CLASS='external-html' DATA-FILE-ID=HASH_LOAD_FACTOR>
428     * @param elements             Any Generic VarArgs-Array
429     * @throws ClassCastException  <EMBED CLASS='external-html' DATA-FILE-ID=PRIM_ARR_CCEX>
430     * 
431     * @throws NullPointerException if either {@code 'refHolder'} or {@code 'computeNextEntry'}
432     * are passed null
433     */
434    public <X> ReadOnlyHashtable(
435            Tuple2<K, V>            refHolder,
436            Consumer<? super X>     computeNextEntry,
437            Predicate<? super X>    filter,
438            Float                   loadFactor,
439            X...                    elements
440        )
441    {
442        Objects.requireNonNull(refHolder, ROHelpers.NULL_MSG + "'refHolder'");
443        Objects.requireNonNull(computeNextEntry, ROHelpers.NULL_MSG + "'computeNextEntry'");
444
445        this.fromBuilderOrHashtable = false;
446
447        Hashtable<K, V> hashTable =
448            new Hashtable<>(elements.length, ((loadFactor == null) ? 0.75f : loadFactor));
449
450        if (filter == null) for (X e : elements)
451        {
452            computeNextEntry.accept(e);
453            hashTable.put(refHolder.a, refHolder.b);
454        }
455
456        else for (X x : elements) if (filter.test(x))
457        {
458            computeNextEntry.accept(x);
459            hashTable.put(refHolder.a, refHolder.b);
460        }
461
462        // Empty Optimization (throw away, completely, the reference, use static-constant)
463        this.hashTable = (hashTable.size() == 0)
464            ? ((Hashtable<K, V>) EMPTY_HASH_TABLE)
465            : hashTable;
466    }
467
468    /**
469     * Retrieves elements from the Java Primitive-Array parameter {@code 'primitiveArray'}, and
470     * subsequently invokes the {@code 'computeNextEntry'} processor to populate this
471     * {@code ReadOnlyHashtable}.
472     * 
473     * @param filter               <EMBED CLASS='external-html' DATA-FILE-ID=PRED_FILT_PRIM>
474     * @param computeNextEntry     <EMBED CLASS='external-html' DATA-FILE-ID=ARR_COMPUTE_NEXT_CONS>
475     * @param refHolder            <EMBED CLASS='external-html' DATA-FILE-ID=REF_HOLDER>
476     * @param loadFactor           <EMBED CLASS='external-html' DATA-FILE-ID=HASH_LOAD_FACTOR>
477     * @param primitiveArray       Any Java Primitive-Array
478     * @throws ClassCastException  <EMBED CLASS='external-html' DATA-FILE-ID=PRIM_ARR_CCEX>
479     * 
480     * @throws NullPointerException if either {@code 'refHolder'} or {@code 'computeNextEntry'}
481     * are passed null
482     */
483    @LinkJavaSource(handle="ROHelperPrimitiveArrays", name="buildROMap")
484    public <X> ReadOnlyHashtable(
485            Predicate<?>    filter,
486            Consumer<?>     computeNextEntry,
487            Tuple2<K, V>    refHolder,
488            Float           loadFactor,
489            Object          primitiveArray
490        )
491    {
492        Objects.requireNonNull(refHolder, ROHelpers.NULL_MSG + "'refHolder'");
493        Objects.requireNonNull(computeNextEntry, ROHelpers.NULL_MSG + "'computeNextEntry'");
494
495        this.fromBuilderOrHashtable = false;
496
497        Hashtable<K, V> hashTable = ROHelperPrimitiveArrays.buildROMap(
498            primitiveArray,
499            (int arrayLen) -> new Hashtable<>(arrayLen, ((loadFactor == null) ? 0.75f : loadFactor)),
500            filter,
501            refHolder,
502            computeNextEntry
503        );
504
505        // Empty Optimization (throw away, completely, the reference, use static-constant)
506        this.hashTable = (hashTable.size() == 0)
507            ? ((Hashtable<K, V>) EMPTY_HASH_TABLE)
508            : hashTable;
509    }
510
511
512    // ********************************************************************************************
513    // ********************************************************************************************
514    // Convert to java.util Types
515    // ********************************************************************************************
516    // ********************************************************************************************
517
518
519    /**
520     * Clone's {@code 'this'} instance internal {@code Hashtable<K, V>} field, and returns it. 
521     * <EMBED CLASS='external-html' DATA-TYPE=Hashtable DATA-FILE-ID=CLONE_TO>
522     * 
523     * @return An independent, mutable copy of {@code 'this'} instance internal
524     * {@code Hashtable<K, V>} data-structure.
525     */
526    public Hashtable<K, V> cloneToHashtableOLD()
527    {
528        if (! fromBuilderOrHashtable) return (Hashtable<K, V>) this.hashTable.clone();
529
530        Hashtable<K, V> ret = new Hashtable<K, V>();
531
532        for (Map.Entry<K, V> e :
533            ((ROHashtableBuilder<K, V>) this.hashTable)._entrySet(friendClassBadge))
534
535            ret.put(e.getKey(), e.getValue());
536
537        return ret;
538    }
539
540
541    // ********************************************************************************************
542    // ********************************************************************************************
543    // Original JDK Methods, java.util.Hashtable
544    // ********************************************************************************************
545    // ********************************************************************************************
546
547
548    /**
549     * Returns the number of keys in this {@code Hashtable}.
550     * @return  the number of keys in this {@code Hashtable}.
551     */
552    public int size()
553    { return this.hashTable.size(); }
554
555    /**
556     * Tests if this {@code Hashtable} maps no keys to values.
557     * 
558     * @return  {@code TRUE} if this {@code Hashtable} maps no keys to values; {@code FALSE}
559     * otherwise.
560     */
561    public boolean isEmpty()
562    { return this.hashTable.isEmpty(); }
563
564    /**
565     * Returns an enumeration of the keys in this {@code Hashtable}.  Use the Enumeration methods
566     * on the returned object to fetch the keys sequentially.
567     * 
568     * @return  an enumeration of the keys in this {@code Hashtable}.
569     * 
570     * @see #elements()
571     * @see #keySet()
572     * @see ReadOnlyMap
573     */
574    public Enumeration<K> keys()
575    { return this.hashTable.keys(); }
576
577    /**
578     * Returns an enumeration of the values in this {@code Hashtable}.  Use the Enumeration methods
579     * on the returned object to fetch the elements sequentially.
580     * 
581     * @return  an enumeration of the values in this {@code Hashtable}.
582     * 
583     * @see #keys()
584     * @see #values()
585     * @see ReadOnlyMap
586     */
587    public Enumeration<V> elements()
588    { return this.hashTable.elements(); }
589
590    /**
591     * Tests if some key maps into the specified value in this {@code Hashtable}.  This operation
592     * is more expensive than the {@link #containsKey containsKey} method.
593     *
594     * <BR /><BR />Note that this method is identical in functionality to
595     * {@link #containsValue containsValue}, (which is part of the {@link ReadOnlyMap} interface.
596     *
597     * @param value a value to search for
598     * 
599     * @return {@code TRUE} if and only if some key maps to the {@code value} argument in this
600     * {@code Hashtable} as determined by the {@code equals} method; {@code false} otherwise.
601     * 
602     * @throws NullPointerException if the value is {@code null}
603     */
604    public boolean contains(Object value)
605    { return this.hashTable.contains(value); }
606
607    /**
608     * Returns {@code TRUE} if this {@code Hashtable} maps one or more keys to this value.
609     *
610     * <BR /><BR />Note that this method is identical in functionality to {@link #contains}.
611     *
612     * @param value value whose presence in this {@code Hashtable} is to be tested
613     * @return {@code TRUE} if this map maps one or more keys to the specified value
614     * @throws NullPointerException  if the value is {@code null}
615     */
616    public boolean containsValue(Object value)
617    { return this.hashTable.contains(value); }
618
619    /**
620     * Tests if the specified object is a key in this {@code Hashtable}.
621     *
622     * @param key possible key
623     * 
624     * @return {@code TRUE} if and only if the specified object is a key in this {@code Hashtable},
625     * as determined by the {@code equals} method; {@code false} otherwise.
626     * 
627     * @throws NullPointerException  if the key is {@code null}
628     * 
629     * @see #contains(Object)
630     */
631    public boolean containsKey(Object key)
632    { return this.hashTable.containsKey(key); }
633
634    /**
635     * Returns the value to which the specified key is mapped, or {@code null} if this map contains
636     * no mapping for the key.
637     *
638     * <BR /><BR />More formally, if this map contains a mapping from a key {@code k} to a value
639     * {@code v} such that {@code (key.equals(k))}, then this method returns {@code v}; otherwise
640     * it returns {@code null}.  (There can be at most one such mapping.)
641     *
642     * @param key the key whose associated value is to be returned
643     * 
644     * @return the value to which the specified key is mapped, or {@code null} if this map contains
645     * no mapping for the key
646     * 
647     * @throws NullPointerException if the specified key is null
648     */
649    public V get(Object key)
650    { return this.hashTable.get(key); }
651
652
653    // ********************************************************************************************
654    // ********************************************************************************************
655    // Views
656    // ********************************************************************************************
657    // ********************************************************************************************
658
659
660    /**
661     * Returns a {@link ReadOnlySet} view of the keys contained in this map.  The set is backed by
662     * the map, so changes to the map are reflected in the set, and vice-versa.
663     */
664    @LinkJavaSource(handle="JavaHTMLReadOnlySet")
665    public ReadOnlySet<K> keySet()
666    {
667        return new JavaHTMLReadOnlySet<>(
668            fromBuilderOrHashtable
669                ? ((ROHashtableBuilder<K, V>) this.hashTable)._keySet(friendClassBadge)
670                : this.hashTable.keySet()
671        );
672    }
673
674    /** Returns a {@link ReadOnlySet} view of the mappings contained in this map. */
675    @LinkJavaSource(handle="ROHelperEntrySet")
676    public ReadOnlySet<ReadOnlyMap.Entry<K, V>> entrySet()
677    {
678        return ROHelperEntrySet.toReadOnlyEntrySet(
679            fromBuilderOrHashtable
680                ? ((ROHashtableBuilder<K, V>) this.hashTable)._entrySet(friendClassBadge)
681                : this.hashTable.entrySet()
682        );
683    }
684
685    /** Returns a {@link ReadOnlyCollection} view of the values contained in this map. */
686    @LinkJavaSource(handle="JavaHTMLReadOnlyCollection")
687    public ReadOnlyCollection<V> values()
688    {
689        return new JavaHTMLReadOnlyCollection<>(
690            fromBuilderOrHashtable
691                ? ((ROHashtableBuilder<K, V>) this.hashTable)._values(friendClassBadge)
692                : this.hashTable.values()
693        );
694    }
695
696
697    // ********************************************************************************************
698    // ********************************************************************************************
699    // Comparison and hashing
700    // ********************************************************************************************
701    // ********************************************************************************************
702
703
704    public V getOrDefault(Object key, V defaultValue)
705    { return this.hashTable.getOrDefault(key, defaultValue); }
706
707    public void forEach(BiConsumer<? super K, ? super V> action)
708    { this.hashTable.forEach(action); }
709
710
711    // ********************************************************************************************
712    // ********************************************************************************************
713    // Convert to java.util Types
714    // ********************************************************************************************
715    // ********************************************************************************************
716
717
718    /**
719     * Clone's {@code 'this'} instance internal {@code Hashtable<K, V>} field, and returns it. 
720     * <EMBED CLASS='external-html' DATA-TYPE=Hashtable DATA-FILE-ID=CLONE_TO>
721     * 
722     * @return An independent, mutable copy of {@code 'this'} instance' internal
723     * {@code Hashtable<K, V>} data-structure.
724     */
725    public Hashtable<K, V> cloneToHashtable()
726    {
727        return fromBuilderOrHashtable
728            ? new Hashtable<K, V>(this.hashTable)
729            : (Hashtable<K, V>) this.hashTable.clone();
730    }
731
732    /**
733     * <BR>Same: Identical to the method {@link #cloneToHashtable()}.
734     * <BR>Returns: Has Return-Type {@code Map<K, V>}, instead.
735     * <BR>Implements: Parent-Class {@link ReadOnlyMap#cloneToMap}
736     */
737    @Torello.JavaDoc.IntoHTMLTable(title="Converts this ReadOnlyHashtable to a java.util.Hashtable")
738    public Map<K, V> cloneToMap() { return cloneToHashtable(); }
739
740    // Documented in the Implemented-Interface ReadOnlyMap
741    public Map<K, V> wrapToImmutableMap()
742    { return Collections.unmodifiableMap(this.hashTable); }
743
744
745    // ********************************************************************************************
746    // ********************************************************************************************
747    // java.lang.Object
748    // ********************************************************************************************
749    // ********************************************************************************************
750
751
752    /**
753     * Returns a string representation of this {@code Hashtable} object in the form of a set of
754     * entries, enclosed in braces and separated by the ASCII characters {@code " , "} (comma and
755     * space). Each entry is rendered as the key, an equals sign {@code '='}, and the associated
756     * element, where the {@code toString} method is used to convert the key and element to
757     * {@code String's}.
758     * 
759     * @return a {@code String} representation of this {@code Hashtable}
760     */
761    @LinkJavaSource(handle="ROHelperEntrySet")
762    public String toString()
763    {
764        return ROHelperEntrySet.toString(
765            this.hashTable, // if the map contains itself, it is needed for printing purposes
766            fromBuilderOrHashtable
767                ? ((ROHashtableBuilder<K, V>) this.hashTable)._entrySet(friendClassBadge)
768                : this.hashTable.entrySet()
769        );
770    }
771
772    /**
773     * Compares the specified Object with this Map for equality, as per the definition in the 
774     * class {@code java.util.Hashtable}.
775     *
776     * @param  o object to be compared for equality with this {@code ReadOnlyHashtable}.
777     * @return {@code TRUE} if the specified Object is equal to this Map
778     */
779    @LinkJavaSource(handle="ROHelperEquals", name="roMapEq")
780    public boolean equals(Object o)
781    { return ROHelperEquals.roMapEq(this, o); }
782
783    /**
784     * Returns the hash code value for this Map as per the definition in the class
785     * {@code java.util.Hashtable}.
786     */
787    public int hashCode() 
788    { return this.hashTable.hashCode(); }
789}