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