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