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