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 Torello.Java.Additional.EffectivelyFinal;
028import Torello.Java.Additional.RemoveUnsupportedIterator;
029
030import java.util.*;
031
032import java.util.stream.Collector;
033
034import java.util.function.Function;
035import java.util.function.Supplier;
036import java.util.function.Predicate;
037import java.util.function.Consumer;
038import java.util.function.IntFunction;
039
040/**
041 * Immutable Wrapper for <CODE>java&#46;util&#46;HashSet</CODE>, found in the "Java Collections
042 * Framework".
043 * 
044 * <EMBED CLASS=globalDefs DATA-JDK=HashSet DATA-ENDING=Set>
045 * <EMBED CLASS='external-html' DATA-FILE-ID=MUCHOS_CONSTRUCTORS>
046 * <EMBED CLASS='external-html' DATA-FILE-ID=DATA_CLASS>
047 * 
048 * @param <E> the type of elements maintained by this set
049 */
050@Torello.JavaDoc.JDHeaderBackgroundImg(EmbedTagFileID="JDHBI_MAIN")
051@SuppressWarnings("unchecked")
052public class ReadOnlyHashSet<E>
053    // extends AbstractSet<E>
054    implements ReadOnlySet<E>, Cloneable, java.io.Serializable
055{
056    // ********************************************************************************************
057    // ********************************************************************************************
058    // Protected & Private Fields, Methods, Statics
059    // ********************************************************************************************
060    // ********************************************************************************************
061
062
063    /** <EMBED CLASS='external-html' DATA-FILE-ID=SVUID> */
064    protected static final long serialVersionUID = 1;
065
066    // Minor Optimization where new HashSet's that have no contents always re-use this static
067    // instance.  Since this instance is completely empty, the Raw-Types things is irrelevant.
068
069    @SuppressWarnings("rawtypes")
070    private static final HashSet EMPTY_HASH_SET = new HashSet(0, 0.75f);
071
072    // Singleton & Empty ReadOnlyHashSet, Uses the "Supplier Constructor"
073    @SuppressWarnings("rawtypes")
074    private static final ReadOnlyHashSet EMPTY_READONLY_HASH_SET =
075        new ReadOnlyHashSet(0, 0.75f, () -> null);
076
077    // The actual HashSet used by this instance.
078    private final HashSet<E> hashSet;
079
080    // TRUE     => This was built using the class ROHashSetBuilder
081    // FALSE    => This was built using the clone() of a standard java.util.HashSet constructor
082
083    private final boolean fromBuilderOrHashSet;
084
085    // Mimics the C++ Keyword/Concept of "Friend Class".   Is "Friends With" ROHashSetBuilder
086    static class AccessBadge { private AccessBadge() { } }
087    private static final AccessBadge friendClassBadge = new AccessBadge();
088
089    public static <T> ReadOnlyHashSet<T> emptyROHS()
090    { return (ReadOnlyHashSet<T>) EMPTY_READONLY_HASH_SET; }
091
092
093    // ********************************************************************************************
094    // ********************************************************************************************
095    // Basic Constructors
096    // ********************************************************************************************
097    // ********************************************************************************************
098
099
100    // To all the readers out there following along: The "AccessBadge" thing is just a slightly
101    // wimpier substitute for the C++ keyword / concept 'friend' or "Friend Class".  It means this
102    // constructor is (for all intents and purposes) a private-constructor, except for the class
103    // ROHashSetBuilder
104    //
105    // This is the Constructor used by the Builder.  It has a "Zero-Size" Optimization
106
107    ReadOnlyHashSet(ROHashSetBuilder<E> rohsb, ROHashSetBuilder.AccessBadge badge)
108    {
109        Objects.requireNonNull(badge, "Access Badge is null.  Requires Friend-Class Badge");
110
111        this.fromBuilderOrHashSet = true;
112        this.hashSet = rohsb;
113    }
114
115    /**
116     * Copies parameter {@code 'c'} (and saves it) in order to guarantee that {@code 'this'}
117     * instance is Read-Only, and shielded from outside modification.
118     * 
119     * @param c The {@code Collection} to be copied and saved into this instance internal
120     * and private {@code 'hashSet'} field.
121     */
122    public ReadOnlyHashSet(Collection<E> c)
123    {
124        this.fromBuilderOrHashSet = false;
125        this.hashSet = (c.size() == 0) ? ((HashSet<E>) EMPTY_HASH_SET) : new HashSet<>(c);
126    }
127
128    /**
129     * Use a {@code Supplier<E>} to provide an arbitrary number of elements of type {@code 'E'} 
130     * directly to this constructor.  This constructor will request elements from the
131     * {@code Supplier} provided to parameter {@code 's'} until {@code 's'} returns null.
132     *
133     * <EMBED CLASS=defs DATA-SOURCE=Supplier>
134     * 
135     * @param quantityIfKnown   <EMBED CLASS='external-html' DATA-FILE-ID=HASH_INIT_CAPACITY>
136     * @param loadFactor        <EMBED CLASS='external-html' DATA-FILE-ID=HASH_LOAD_FACTOR>
137     * @param s                 Any Java {@code Supplier<E>}
138     * 
139     * @throws IllegalArgumentException if the specified quantity / capacity is negative
140     */
141    public ReadOnlyHashSet(Integer quantityIfKnown, Float loadFactor, Supplier<E> s)
142    {
143        fromBuilderOrHashSet = false;
144
145        HashSet<E> hashSet = new HashSet<E>(
146            (quantityIfKnown != null)   ? quantityIfKnown   : 16,
147            (loadFactor != null)        ? loadFactor        : 0.75f
148        );
149
150        E e;
151        while ((e = s.get()) != null) hashSet.add(e);
152
153        // Empty Optimization (throw away, completely, the reference, use static-constant)
154        this.hashSet = (hashSet.size() == 0) ? ((HashSet<E>) EMPTY_HASH_SET) : hashSet;
155    }
156
157
158    // ********************************************************************************************
159    // ********************************************************************************************
160    // Iterable & Array Based Constructors - **NO FILTER**
161    // ********************************************************************************************
162    // ********************************************************************************************
163
164
165    /**
166     * If only a small amount of processing needs to be done on the contents of some Java
167     * Data-Type, and using an entire Builder-Class seems disproportionately complex - <I>this
168     * constructor can convert any Java {@code Iterable} into a {@code ReadOnlyHashSet}, using
169     * a simple {@code 'mapper'}</I>.
170     * 
171     * <EMBED CLASS=defs DATA-SOURCE=Iterable>
172     *  
173     * @param <T>           <EMBED CLASS='external-html' DATA-FILE-ID=ITERABLE_TYPE_PARAM>
174     * @param i             Any Java {@code Iterable<T>}
175     * @param mapper        <EMBED CLASS='external-html' DATA-FILE-ID=ITERABLE_MAPPER>
176     * @param sizeIfKnown   <EMBED CLASS='external-html' DATA-FILE-ID=HASH_INIT_CAPACITY>
177     * @param loadFactor    <EMBED CLASS='external-html' DATA-FILE-ID=HASH_LOAD_FACTOR>
178     * 
179     * @throws NullPointerException if either {@code 'i'} or {@code 'mapper'} are passed null.
180     * 
181     * @throws IllegalArgumentException if the initial capacity ({@code 'sizeIfKnown'}) is less
182     * than zero, or if the load factor is nonpositive
183     */
184    public <T> ReadOnlyHashSet(
185            Iterable<T>                         i,
186            Function<? super T, ? extends E>    mapper,
187            Integer                             sizeIfKnown,
188            Float                               loadFactor
189        )
190    {
191        Objects.requireNonNull(mapper, ROHelpers.NULL_MSG + "'mapper'");
192
193        fromBuilderOrHashSet = false;
194
195        HashSet<E> hashSet = new HashSet<E>(
196            (sizeIfKnown != null)   ? sizeIfKnown   : 16,
197            (loadFactor != null)    ? loadFactor    : 0.75f
198        );
199
200        for (T t : i) hashSet.add(mapper.apply(t));
201
202        // Empty Optimization (throw away, completely, the reference, use static-constant)
203        this.hashSet = (hashSet.size() == 0) ? ((HashSet<E>) EMPTY_HASH_SET) : hashSet;
204    }
205
206    /**
207     * If a Standard Java {@code Iterable} can be directly mapped into a {@code HashSet}
208     * (and, ergo, an entire Builder-Instance would be a bit excessive) - <I>this constructor will
209     * seamlessly convert any Java {@code Iterable<E>} directly into a {@code ReadOnlyHashSet<E>}
210     * with just this single invocation</I>.
211     *
212     * <EMBED CLASS=defs DATA-SOURCE=Iterable>
213     * 
214     * @param i             Any Java {@code Iterator<E>}
215     * @param sizeIfKnown   <EMBED CLASS='external-html' DATA-FILE-ID=HASH_INIT_CAPACITY>
216     * @param loadFactor    <EMBED CLASS='external-html' DATA-FILE-ID=HASH_LOAD_FACTOR>
217     * 
218     * @throws NullPointerException if {@code 'i'} is passed null.
219     * 
220     * @throws IllegalArgumentException if the initial capacity ({@code 'sizeIfKnown'}) is less
221     * than zero, or if the load factor is nonpositive
222     */
223    public ReadOnlyHashSet(Iterable<E> i, Integer sizeIfKnown, Float loadFactor)
224    {
225        fromBuilderOrHashSet = false;
226
227        HashSet<E> hashSet = new HashSet<E>(
228            (sizeIfKnown != null)   ? sizeIfKnown   : 16,
229            (loadFactor != null)    ? loadFactor    : 0.75f
230        );
231
232        for (E element : i) hashSet.add(element);
233
234        // Empty Optimization (throw away, completely, the reference, use static-constant)
235        this.hashSet = (hashSet.size() == 0) ? ((HashSet<E>) EMPTY_HASH_SET) : hashSet;
236    }
237
238    /**
239     * Builds {@code ReadOnlyHashSet<E>} instance having Generic-Type {@code 'E'}, and contents
240     * {@code 'elements'}.
241     * 
242     * @param elementsType  <EMBED CLASS='external-html' DATA-FILE-ID=ELEMENTS_TYPE>
243     * @param loadFactor    <EMBED CLASS='external-html' DATA-FILE-ID=HASH_LOAD_FACTOR>
244     * @param elements      <EMBED CLASS='external-html' DATA-FILE-ID=ELEMENTS_VAR_ARGS>
245     * 
246     * @throws ClassCastException   <EMBED CLASS='external-html' DATA-FILE-ID=CCEX>
247     * @throws NullPointerException If {@code 'elementsType'} is passed null.
248     */
249    public ReadOnlyHashSet(Class<E> elementsType, Float loadFactor, Object... elements)
250    {
251        Objects.requireNonNull(elementsType, ROHelpers.NULL_MSG + "'elementsType'");
252
253        fromBuilderOrHashSet = false;
254
255        if (elements.length == 0)
256            this.hashSet = (HashSet<E>) EMPTY_HASH_SET;
257
258        else
259        {
260            this.hashSet = (loadFactor != null)
261                ? new HashSet<>(elements.length, loadFactor)
262                : new HashSet<>(elements.length, 0.75f);
263
264            for (Object element : elements) this.hashSet.add(elementsType.cast(element));
265        }
266    }
267
268    /**
269     * Builds {@code ReadOnlyHashSet<E>} instance having Generic-Type {@code 'E'}, and contents
270     * {@code 'elements'}.
271     * 
272     * @param mapper        <EMBED CLASS='external-html' DATA-FILE-ID=OBJ_E_MAPPER>
273     * @param loadFactor    <EMBED CLASS='external-html' DATA-FILE-ID=HASH_LOAD_FACTOR>
274     * @param elements      <EMBED CLASS='external-html' DATA-FILE-ID=ELEMENTS_VAR_ARGS>
275     * 
276     * @throws ClassCastException   <EMBED CLASS='external-html' DATA-FILE-ID=CCEX>
277     * @throws NullPointerException If {@code 'mapper'} is passed null.
278     */
279    public ReadOnlyHashSet
280        (Function<Object, ? extends E> mapper, Float loadFactor, Object... elements)
281    {
282        Objects.requireNonNull(mapper, ROHelpers.NULL_MSG + "'mapper'");
283
284        fromBuilderOrHashSet = false;
285
286        if (elements.length == 0)
287            this.hashSet = (HashSet<E>) EMPTY_HASH_SET;
288
289        else
290        {
291            this.hashSet = (loadFactor != null)
292                ? new HashSet<>(elements.length, loadFactor)
293                : new HashSet<>(elements.length, 0.75f);
294
295            for (Object element : elements) this.hashSet.add(mapper.apply(element));
296        }
297    }
298
299
300    // ********************************************************************************************
301    // ********************************************************************************************
302    // Iterable & Array Based Constructors - **INCLUDES ELEMENT FILTER**
303    // ********************************************************************************************
304    // ********************************************************************************************
305
306
307    /**
308     * If only a small amount of processing needs to be done on the contents of some Java
309     * Data-Type, and using an entire Builder-Class seems disproportionately complex - <I>this
310     * constructor can convert any Java {@code Iterable} into a {@code ReadOnlyHashSet}, using
311     * a simple {@code 'mapper'}</I>.
312     * 
313     * <EMBED CLASS=defs DATA-SOURCE=Iterable>
314     * 
315     * @param <T>           <EMBED CLASS='external-html' DATA-FILE-ID=ITERABLE_TYPE_PARAM>
316     * @param i             Any Java {@code Iterable<T>}
317     * @param mapper        <EMBED CLASS='external-html' DATA-FILE-ID=ITERABLE_MAPPER>
318     * @param filter        <EMBED CLASS='external-html' DATA-FTP=T DATA-FILE-ID=PREDICATE_FILTER>
319     * @param sizeIfKnown   <EMBED CLASS='external-html' DATA-FILE-ID=HASH_INIT_CAPACITY>
320     * @param loadFactor    <EMBED CLASS='external-html' DATA-FILE-ID=HASH_LOAD_FACTOR>
321     * 
322     * @throws NullPointerException if either {@code 'mapper'} or {@code 'filter'} are passed null
323     * 
324     * @throws IllegalArgumentException if the initial capacity ({@code 'sizeIfKnown'}) is less
325     * than zero, or if the load factor is nonpositive
326     */
327    public <T> ReadOnlyHashSet(
328            Iterable<T> i, Function<T, E>   mapper,
329            Predicate<? super T>            filter,
330            Integer                         sizeIfKnown,
331            Float                           loadFactor
332        )
333    {
334        Objects.requireNonNull(mapper, ROHelpers.NULL_MSG + "'mapper'");
335        Objects.requireNonNull(filter, ROHelpers.NULL_MSG + "'filter'");
336
337        fromBuilderOrHashSet = false;
338
339        HashSet<E> hashSet = new HashSet<E>(
340            (sizeIfKnown != null)   ? sizeIfKnown   : 16,
341            (loadFactor != null)    ? loadFactor    : 0.75f
342        );
343
344        for (T t : i) if (filter.test(t)) hashSet.add(mapper.apply(t));
345
346        // Empty Optimization (throw away, completely, the reference, use static-constant)
347        this.hashSet = (hashSet.size() == 0) ? ((HashSet<E>) EMPTY_HASH_SET) : hashSet;
348    }
349
350    /**
351     * If a Standard Java {@code Iterable} can be directly mapped into a {@code HashSet}
352     * (and, ergo, an entire Builder-Instance would be a bit excessive) - <I>this constructor will
353     * seamlessly convert any Java {@code Iterable<E>} directly into a {@code ReadOnlyHashSet<E>}
354     * with just this single invocation</I>.
355     *
356     * <EMBED CLASS=defs DATA-SOURCE=Iterable>
357     * 
358     * @param i             Any Java {@code Iterator<E>}
359     * @param filter        <EMBED CLASS='external-html' DATA-FTP=E DATA-FILE-ID=PREDICATE_FILTER>
360     * @param sizeIfKnown   <EMBED CLASS='external-html' DATA-FILE-ID=HASH_INIT_CAPACITY>
361     * @param loadFactor    <EMBED CLASS='external-html' DATA-FILE-ID=HASH_LOAD_FACTOR>
362     * 
363     * @throws NullPointerException if either {@code 'i'} or {@code 'filter'} are passed null
364     * 
365     * @throws IllegalArgumentException if the initial capacity ({@code 'sizeIfKnown'}) is less
366     * than zero, or if the load factor is nonpositive
367     */
368    public ReadOnlyHashSet
369        (Iterable<E> i, Predicate<? super E> filter, Integer sizeIfKnown, Float loadFactor)
370    {
371        Objects.requireNonNull(filter, ROHelpers.NULL_MSG + "'filter'");
372
373        fromBuilderOrHashSet = false;
374
375        HashSet<E> hashSet = new HashSet<E>(
376            (sizeIfKnown != null)   ? sizeIfKnown   : 16,
377            (loadFactor != null)    ? loadFactor    : 0.75f
378        );
379
380        for (E element : i) if (filter.test(element)) hashSet.add(element);
381
382        // Empty Optimization (throw away, completely, the reference, use static-constant)
383        this.hashSet = (hashSet.size() == 0) ? ((HashSet<E>) EMPTY_HASH_SET) : hashSet;
384    }
385
386    /**
387     * Builds {@code ReadOnlyHashSet<E>} instance having Generic-Type {@code 'E'}, and contents
388     * {@code 'elements'}.
389     * 
390     * @param elementsType  <EMBED CLASS='external-html' DATA-FILE-ID=ELEMENTS_TYPE>
391     * @param filter        <EMBED CLASS='external-html' DATA-FTP=E DATA-FILE-ID=PREDICATE_FILTER>
392     * @param loadFactor    <EMBED CLASS='external-html' DATA-FILE-ID=HASH_LOAD_FACTOR>
393     * @param elements      <EMBED CLASS='external-html' DATA-FILE-ID=ELEMENTS_VAR_ARGS>
394     * 
395     * @throws ClassCastException <EMBED CLASS='external-html' DATA-FILE-ID=CCEX>
396     * 
397     * @throws NullPointerException If either {@code 'elementsType'} or {@code 'filter'} is passed
398     * null
399     */
400    public ReadOnlyHashSet(
401            Class<E>                elementsType,
402            Predicate<? super E>    filter,
403            Float                   loadFactor,
404            Object...               elements
405        )
406    {
407        Objects.requireNonNull(filter, ROHelpers.NULL_MSG + "'filter'");
408        Objects.requireNonNull(elementsType, ROHelpers.NULL_MSG + "'elementsType'");
409
410        fromBuilderOrHashSet = false;
411
412        HashSet<E> hashSet = (loadFactor != null)
413            ? new HashSet<>(elements.length, loadFactor)
414            : new HashSet<>(elements.length, 0.75f);
415
416        E e;
417        for (Object element : elements)
418            if (filter.test(e = elementsType.cast(element)))
419                hashSet.add(e);
420
421        // Empty Optimization (throw away, completely, the reference, use static-constant)
422        this.hashSet = (hashSet.size() == 0) ? ((HashSet<E>) EMPTY_HASH_SET) : hashSet;
423    }
424
425    /**
426     * Builds {@code ReadOnlyHashSet<E>} instance having Generic-Type {@code 'E'}, and contents
427     * {@code 'elements'}.
428     * 
429     * @param mapper <EMBED CLASS='external-html' DATA-FILE-ID=OBJ_E_MAPPER>
430     * @param filter <EMBED CLASS='external-html' DATA-FTP=Object DATA-FILE-ID=PREDICATE_FILTER>
431     * 
432     * @param loadFactor <EMBED CLASS='external-html' DATA-FILE-ID=HASH_LOAD_FACTOR>
433     * @param elements   <EMBED CLASS='external-html' DATA-FILE-ID=ELEMENTS_VAR_ARGS>
434     * 
435     * @throws ClassCastException <EMBED CLASS='external-html' DATA-FILE-ID=CCEX>
436     * @throws NullPointerException If either {@code 'mapper'} or {@code 'filter'} is passed null
437     */
438    public ReadOnlyHashSet(
439            Function<Object, ? extends E>   mapper,
440            Predicate<Object>               filter,
441            Float                           loadFactor,
442            Object...                       elements
443        )
444    {
445        Objects.requireNonNull(filter, ROHelpers.NULL_MSG + "'filter'");
446        Objects.requireNonNull(mapper, ROHelpers.NULL_MSG + "'mapper'");
447
448        fromBuilderOrHashSet = false;
449
450        HashSet<E> hashSet = (loadFactor != null)
451            ? new HashSet<>(elements.length, loadFactor)
452            : new HashSet<>(elements.length, 0.75f);
453
454        for (Object element : elements)
455            if (filter.test(element))
456                hashSet.add(mapper.apply(element));
457
458        // Empty Optimization (throw away, completely, the reference, use static-constant)
459        this.hashSet = (hashSet.size() == 0) ? ((HashSet<E>) EMPTY_HASH_SET) : hashSet;
460    }
461
462
463    // ********************************************************************************************
464    // ********************************************************************************************
465    // @SafeVarargs / Variable-Arity / VarArgs: with Parameterized / Generic Type's
466    // ********************************************************************************************
467    // ********************************************************************************************
468
469
470    /**
471     * Builds {@code ReadOnlyHashSet<E>} instance having Generic-Type {@code 'E'}, and contents
472     * {@code 'elements'}.
473     * 
474     * @param loadFactor <EMBED CLASS='external-html' DATA-FILE-ID=HASH_LOAD_FACTOR>
475     * @param elements   <EMBED CLASS='external-html' DATA-FILE-ID=ELEMENTS_VAR_ARGS>
476     */
477    @SafeVarargs
478    public ReadOnlyHashSet(Float loadFactor, E... elements)
479    {
480        fromBuilderOrHashSet = false;
481
482        if (elements.length == 0)
483            this.hashSet = (HashSet<E>) EMPTY_HASH_SET;
484
485        else
486        {
487            this.hashSet = (loadFactor != null)
488                ? new HashSet<>(elements.length, loadFactor)
489                : new HashSet<>(elements.length, 0.75f);
490
491            for (E e : elements) this.hashSet.add(e);
492        }
493    }
494
495    /**
496     * Builds {@code ReadOnlyHashSet<E>} instance having Generic-Type {@code 'E'}, and contents
497     * {@code 'elements'}.
498     * 
499     * @param <T>           <EMBED CLASS='external-html' DATA-FILE-ID=VARARGS_TYPE_PARAM>
500     * @param loadFactor    <EMBED CLASS='external-html' DATA-FILE-ID=HASH_LOAD_FACTOR>
501     * @param mapper        <EMBED CLASS='external-html' DATA-FILE-ID=T_ARR_E_MAPPER>
502     * @param elements      <EMBED CLASS='external-html' DATA-FILE-ID=ELEMENTS_VAR_ARGS>
503     * 
504     * @throws NullPointerException If {@code 'mapper'} is passed null.
505     */
506    @SafeVarargs
507    public <T> ReadOnlyHashSet(
508            Float                               loadFactor,
509            Function<? super T, ? extends E>    mapper,
510            T...                                elements
511        )
512    {
513        Objects.requireNonNull(mapper, ROHelpers.NULL_MSG + "'mapper'");
514
515        fromBuilderOrHashSet = false;
516
517        if (elements.length == 0)
518            this.hashSet = (HashSet<E>) EMPTY_HASH_SET;
519
520        else
521        {
522            this.hashSet = (loadFactor != null)
523                ? new HashSet<>(elements.length, loadFactor)
524                : new HashSet<>(elements.length, 0.75f);
525
526            for (T t : elements) this.hashSet.add(mapper.apply(t));
527        }
528    }
529
530    /**
531     * Builds {@code ReadOnlyHashSet<E>} instance having Generic-Type {@code 'E'}, and contents
532     * {@code 'elements'}.
533     * 
534     * @param filter        <EMBED CLASS='external-html' DATA-FTP=E DATA-FILE-ID=PREDICATE_FILTER>
535     * @param loadFactor    <EMBED CLASS='external-html' DATA-FILE-ID=HASH_LOAD_FACTOR>
536     * @param elements      <EMBED CLASS='external-html' DATA-FILE-ID=ELEMENTS_VAR_ARGS>
537     * 
538     * @throws NullPointerException If {@code 'filter'} is passed null
539     */
540    @SafeVarargs
541    public ReadOnlyHashSet(
542            Predicate<? super E>    filter,
543            Float                   loadFactor,
544            E...                    elements
545        )
546    {
547        Objects.requireNonNull(filter, ROHelpers.NULL_MSG + "'filter'");
548
549        fromBuilderOrHashSet = false;
550
551        HashSet<E> hashSet = (loadFactor != null)
552            ? new HashSet<>(elements.length, loadFactor)
553            : new HashSet<>(elements.length, 0.75f);
554
555        for (E e : elements) if (filter.test(e)) hashSet.add(e);
556
557        // Empty Optimization (throw away, completely, the reference, use static-constant)
558        this.hashSet = (hashSet.size() == 0) ? ((HashSet<E>) EMPTY_HASH_SET) : hashSet;
559    }
560
561    /**
562     * Builds {@code ReadOnlyHashSet<E>} instance having Generic-Type {@code 'E'}, and contents
563     * {@code 'elements'}.
564     * 
565     * @param <T>           <EMBED CLASS='external-html' DATA-FILE-ID=VARARGS_TYPE_PARAM>
566     * @param filter        <EMBED CLASS='external-html' DATA-FTP=T DATA-FILE-ID=PREDICATE_FILTER>
567     * @param mapper        <EMBED CLASS='external-html' DATA-FILE-ID=T_ARR_E_MAPPER>
568     * @param loadFactor    <EMBED CLASS='external-html' DATA-FILE-ID=HASH_LOAD_FACTOR>
569     * @param elements      <EMBED CLASS='external-html' DATA-FILE-ID=ELEMENTS_VAR_ARGS>
570     * 
571     * @throws NullPointerException If either {@code 'mapper'} or {@code 'filter'} is passed null
572     */
573    @SafeVarargs
574    public <T> ReadOnlyHashSet(
575            Predicate<? super T>                filter,
576            Function<? super T, ? extends E>    mapper,
577            Float                               loadFactor,
578            T...                                elements
579        )
580    {
581        Objects.requireNonNull(filter, ROHelpers.NULL_MSG + "'filter'");
582        Objects.requireNonNull(mapper, ROHelpers.NULL_MSG + "'mapper'");
583
584        fromBuilderOrHashSet = false;
585
586        HashSet<E> hashSet = (loadFactor != null)
587            ? new HashSet<>(elements.length, loadFactor)
588            : new HashSet<>(elements.length, 0.75f);
589
590        for (T t : elements) if (filter.test(t)) hashSet.add(mapper.apply(t));
591
592        // Empty Optimization (throw away, completely, the reference, use static-constant)
593        this.hashSet = (hashSet.size() == 0) ? ((HashSet<E>) EMPTY_HASH_SET) : hashSet;
594    }
595
596
597    // ********************************************************************************************
598    // ********************************************************************************************
599    // PRIMITIVE-ARRAY INPUT
600    // ********************************************************************************************
601    // ********************************************************************************************
602
603
604    /**
605     * Converts a Java Primitive-Array to a {@code ReadOnlyHashSet<E>}, where {@code 'E'} is the
606     * Java Boxed-Type which corresponds to the Primitive-Array's Type.
607     *
608     * <EMBED CLASS='external-html' DATA-FILE-ID=PRIM_ARR_CTOR_DESC>
609     * @param primitiveArray        <EMBED CLASS='external-html' DATA-FILE-ID=PRIMITIVE_ARRAY>
610     * @throws ClassCastException   <EMBED CLASS='external-html' DATA-FILE-ID=PRIM_ARR_CCEX>
611     * @throws NullPointerException If {@code 'primitiveArray'} is passed null;
612     */
613    public ReadOnlyHashSet(
614            Object  primitiveArray,
615            Float   loadFactor
616        )
617    {
618        fromBuilderOrHashSet = false;
619
620        HashSet<E> hs = ROHelpers.buildROListOrSet(
621            primitiveArray,
622            (int arrayLen) -> new HashSet<E>(arrayLen, (loadFactor != null) ? loadFactor : 0.75f),
623            null
624        );
625
626        // Empty Optimization (throw away, completely, the reference, use static-constant)
627        this.hashSet = (hs.size() == 0) ? ((HashSet<E>) EMPTY_HASH_SET) : hs;
628    }
629
630    /**
631     * Converts a Java Primitive-Array to a {@code ReadOnlyHashSet<E>}, where {@code 'E'} is the
632     * Java Boxed-Type which corresponds to the Primitive-Array's Type - <I>but also accepts a 
633     * {@code 'filter'} that can remove any array-entries that need to be removed</I>.
634     *
635     * <EMBED CLASS='external-html' DATA-FILE-ID=PRIM_ARR_CTOR_DESC>
636     * @param primitiveArray        <EMBED CLASS='external-html' DATA-FILE-ID=PRIMITIVE_ARRAY>
637     * @param filter                <EMBED CLASS='external-html' DATA-FILE-ID=PRED_FILT_PRIM>
638     * @throws ClassCastException   <EMBED CLASS='external-html' DATA-FILE-ID=PRIM_ARR_CCEX>
639     * @throws NullPointerException If {@code 'primitiveArray'} is passed null;
640     */
641    public ReadOnlyHashSet(
642            Object          primitiveArray,
643            Float           loadFactor,
644            Predicate<?>    filter
645        )
646    {
647        Objects.requireNonNull(filter, ROHelpers.NULL_MSG + "'filter'");
648
649        fromBuilderOrHashSet = false;
650
651        HashSet<E> hs = ROHelpers.buildROListOrSet(
652            primitiveArray,
653            (int arrayLen) -> new HashSet<E>(arrayLen, (loadFactor != null) ? loadFactor : 0.75f),
654            filter
655        );
656
657        // Empty Optimization (throw away, completely, the reference, use static-constant)
658        this.hashSet = (hs.size() == 0) ? ((HashSet<E>) EMPTY_HASH_SET) : hs;
659    }
660
661
662    // ********************************************************************************************
663    // ********************************************************************************************
664    // java.util.stream.Stream HELPER
665    // ********************************************************************************************
666    // ********************************************************************************************
667
668
669    /**
670     * For use with a the Java Stream method {@code 'collect(Collector c)'}.
671     * 
672     * <EMBED CLASS='external-html' DATA-ABBREV=hs DATA-FILE-ID=STREAM_COLLECTOR>
673     * 
674     * @param <T> This is the Generic-Type of the Input-Stream.  It will also be the Generic-Type
675     * of the {@code ReadOnlyHashSet} that's returned from the stream's {@code collect} method.
676     * 
677     * @param characteristics Optional Characteristics List.  See Java Stream-API Documentation on
678     * {@code Collector.Characteristics} inner-class for more details.
679     * 
680     * @return This returns a collector that may be piped into a stream's {@code 'collect'} method,
681     * as in the example, above.
682     */
683    public static <T> java.util.stream.Collector
684        <T, ROHashSetBuilder<T>, ReadOnlyHashSet<T>>
685        streamCollector(Collector.Characteristics... characteristics)
686    {
687        return Collector.of(
688            ROHashSetBuilder<T>::new,    // The "Supplier"    (builds a new ROHashSetBuilder)
689            ROHashSetBuilder<T>::add,    // The "Accumulator" (adds elements to the builder)
690
691            // Oracle Making Life Difficult - It should be the line below, but, alas, it is not!
692            // ROHashSetBuilder<T>::addAll, // The "Combiner"    (combines multiple ROHashSetBuilders)
693            //
694            // In Stream.collect(), the 3rd parameter - the "combiner" - is a "BiConsumer<R, R>"
695            // NOTE: A "BiConsumer" is a FunctionalInterface that does not return anything - it is
696            // (obviously) a "void" return method!
697            //
698            // **BUT**
699            //
700            // In Collector.of, the 3rd parameter - the "combiner" - is a "BinaryOperation<R>"
701    
702            (ROHashSetBuilder<T> rohsb1, ROHashSetBuilder<T> rohsb2) ->
703            {
704                rohsb1.addAll(rohsb2);
705                return rohsb1;
706            },
707
708            ROHashSetBuilder<T>::build,  // The "Finisher"    (Converts Builder to ReadOnlyHashSet)
709            characteristics
710        );
711    }
712
713
714    // ********************************************************************************************
715    // ********************************************************************************************
716    // Convert to java.util Types
717    // ********************************************************************************************
718    // ********************************************************************************************
719
720
721    /**
722     * Clone's {@code 'this'} instance internal {@code HashSet<E>} field, and returns it. 
723     * <EMBED CLASS='external-html' DATA-TYPE=HashSet DATA-FILE-ID=CLONE_TO>
724     * 
725     * @return An independent, mutable copy of {@code 'this'} instance' internal {@code HashSet<E>}
726     * data-structure.
727     */
728    @SuppressWarnings("unchecked") // The clone() cast
729    public HashSet<E> cloneToHashSet()
730    { 
731        return fromBuilderOrHashSet
732            ? new HashSet<E>(this.hashSet)
733            : (HashSet<E>) this.hashSet.clone();
734    }
735
736    /**
737     * Invokes {@code java.util.Collections.unmodifiableSet} on the internal {@code HashSet}.
738     * <EMBED CLASS='external-html' DATA-RET_TYPE=Set DATA-FILE-ID=WRAP_TO_IMMUTABLE>
739     * 
740     * @return A {@code Set} which adheres to the JDK interface {@code java.util.Set}, but throws
741     * an {@code UnsupportedOperationException} if a user attempts to invoke a Mutator-Method on
742     * the returned instance.
743     */
744    public Set<E> wrapToImmutableSet()
745    { return Collections.unmodifiableSet(this.hashSet); }
746
747
748    // ********************************************************************************************
749    // ********************************************************************************************
750    // Original JDK Methods, java.util.HashSet
751    // ********************************************************************************************
752    // ********************************************************************************************
753
754
755    /**
756     * Returns an iterator over the elements in this set.  The elements are returned in no
757     * particular order.
758     *
759     * @return an {@code Iterator} over the elements in this set
760     * @see ConcurrentModificationException
761     */
762    public RemoveUnsupportedIterator<E> iterator()
763    {
764        return fromBuilderOrHashSet
765            ? ((ROHashSetBuilder<E>) this.hashSet).iterator()
766            : new RemoveUnsupportedIterator<>(this.hashSet.iterator());
767    }
768
769    /**
770     * Returns the number of elements in this set (its cardinality).
771     * @return the number of elements in this set (its cardinality)
772     */
773    public int size()
774    { return this.hashSet.size(); }
775
776    /**
777     * Returns {@code TRUE} if this set contains no elements.
778     * @return {@code TRUE} if this set contains no elements
779     */
780    public boolean isEmpty()
781    { return this.hashSet.isEmpty(); }
782
783    /**
784     * Returns {@code TRUE} if this set contains the specified element. More formally, returns
785     * {@code TRUE} if and only if this set contains an element {@code e} such that
786     * {@code Objects.equals(o, e)}.
787     *
788     * @param o element whose presence in this set is to be tested
789     * @return {@code TRUE} if this set contains the specified element
790     */
791    public boolean contains(Object o)
792    { return this.hashSet.contains(o); }
793
794    /**
795     * Creates a <em><a href="Spliterator.html#binding">late-binding</a></em> and
796     * <em>fail-fast</em> {@code Spliterator} over the elements in this set.
797     *
798     * <BR /><BR />The {@code Spliterator} reports {@code Spliterator.SIZED} and
799     * {@code Spliterator.DISTINCT}.  Overriding implementations should document the reporting of
800     * additional characteristic values.
801     *
802     * @return a {@code Spliterator} over the elements in this set
803     */
804    public Spliterator<E> spliterator()
805    { return this.hashSet.spliterator(); }
806
807
808    // ********************************************************************************************
809    // ********************************************************************************************
810    // Misc Stuff that is defined in a super-class - for java.util, but not for Java.ReadOnly
811    // ********************************************************************************************
812    // ********************************************************************************************
813
814
815    public boolean containsAll(Collection<?> c)
816    { return this.hashSet.containsAll(c); }
817
818    @Override
819    public Object[] toArray()
820    { return this.hashSet.toArray(); }
821
822    @Override
823    public <T> T[] toArray(T[] a)
824    { return this.hashSet.toArray(a);  }
825
826
827    // ********************************************************************************************
828    // ********************************************************************************************
829    // java.lang.Object
830    // ********************************************************************************************
831    // ********************************************************************************************
832
833
834    /**
835     * Returns a {@code String} representation of this {@code HashSet}. The {@code String}
836     * representation consists of a list of the collection's elements in the order they are
837     * returned by its iterator, enclosed in square brackets ({@code "[]"}). Adjacent elements are
838     * separated by the characters {@code ", "} (comma and space). Elements are converted to
839     * {@code String's} as by {@code String.valueOf(Object)}.
840     * 
841     * @return a {@code String} representation of this {@code HashSet}
842     */
843    public String toString()
844    { return this.hashSet.toString(); }
845
846    /**
847     * Compares the specified Object with this Set for equality, as per the definition in the 
848     * class {@code java.util.HashSet}.
849     *
850     * @param  o object to be compared for equality with this {@code ReadOnlyHashSet}.
851     * @return {@code TRUE} if the specified Object is equal to this set
852     */
853    public boolean equals(Object o)
854    { return ROHelpers.roSetEq(this, o); }
855
856    /**
857     * Returns the hash code value for this Set as per the definition in the class
858     * {@code java.util.HashSet}.
859     */
860    public int hashCode() 
861    { return this.hashSet.hashCode(); }
862}