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.Function;
031import java.util.function.BiFunction;
032
033/**
034 * A Copy of Java's {@code Hashtable} class; used for building a {@link ReadOnlyHashtable}.
035 * Maintains <I>an internal and inaccessible {@code Hashtable<K, V>} instance</I>. 
036 * 
037 * <EMBED CLASS=globalDefs DATA-JDK=Hashtable>
038 * <EMBED CLASS='external-html' DATA-A_AN=a DATA-FILE-ID=BUILDERS>
039 * <EMBED CLASS='external-html' DATA-FILE-ID=SYNCHRONIZED>
040 * 
041 * @param <K> the type of keys maintained by this map
042 * @param <V> the type of mapped values
043 * @see ReadOnlyHashtable
044 */
045@Torello.JavaDoc.JDHeaderBackgroundImg(EmbedTagFileID="JDHBI_BUILDER")
046public final class ROHashtableBuilder<K, V>
047    extends Hashtable<K, V>
048    implements Cloneable, java.io.Serializable
049{
050    // ********************************************************************************************
051    // ********************************************************************************************
052    // Fields & Builder Stuff
053    // ********************************************************************************************
054    // ********************************************************************************************
055
056
057    /** <EMBED CLASS='external-html' DATA-FILE-ID=SVUID> */
058    protected static final long serialVersionUID = 1;
059
060    // Exception messages need this
061    private static final String ROHT = "Hashtable";
062
063    private boolean built = false;
064
065    // Mimics the C++ Concept of "Friend Class".  This badge is utilized by the "build()" method
066    // directly below this inner-class declaration.  It is the only place where this declaration is
067    // actually used anywhere.  This prohibits access to the constructor that is used to anybody
068    // else
069
070    static class AccessBadge { private AccessBadge() { } }
071    private static final AccessBadge friendClassBadge = new AccessBadge();
072
073    /**
074     * Simply transfers {@code 'this'} instance' internal {@code Hashtable} to the 
075     * {@code ReadOnlyHashtable} Wrapper-Class.
076     * 
077     * @return a newly constructed {@code ReadOnlyHashtable} "Wrapper-Class", shielding the
078     * internal {@code 'hashTable'} private-field from any modification.
079     */
080    public synchronized ReadOnlyHashtable<K, V> build()
081    {
082        this.built = true;
083
084        return (size() == 0)
085            ? ReadOnlyHashtable.emptyROHT()
086            : new ReadOnlyHashtable<>(this, friendClassBadge);
087    }
088
089
090    // ********************************************************************************************
091    // ********************************************************************************************
092    // Modified Constructors
093    // ********************************************************************************************
094    // ********************************************************************************************
095
096
097    /**
098     * Constructs a new, empty {@code ROHashtableBuilder} with the specified initial capacity and
099     * the specified load factor.
100     *
101     * @param initialCapacity the initial capacity of the hashtable.
102     * @param loadFactor the load factor of the hashtable.
103     * 
104     * @throws IllegalArgumentException  if the initial capacity is les than zero, or if the load
105     * factor is nonpositive.
106     */
107    public ROHashtableBuilder(int initialCapacity, float loadFactor)
108    { super(initialCapacity, loadFactor); }
109
110    /**
111     * Constructs a new, empty {@code ROHashtableBuilder} with the specified initial capacity and
112     * default load factor (0.75).
113     *
114     * @param initialCapacity   the initial capacity of the hashtable.
115     * @throws IllegalArgumentException if the initial capacity is less than zero.
116     */
117    public ROHashtableBuilder(int initialCapacity)
118    { super(initialCapacity); }
119
120    /**
121     * Constructs a new, empty {@code ROHashtableBuilder} with a default initial capacity (11) and
122     * load factor (0.75).
123     */
124    public ROHashtableBuilder()
125    { this(11, 0.75f); }
126
127    /**
128     * Constructs a new {@code ROHashtableBuilder} with the same mappings as the given Map.  The
129     * builder is created with an initial capacity sufficient to hold the mappings in the given Map
130     * and a default load factor (0.75).
131     *
132     * @param t the map whose mappings are to be placed in this map.
133     * @throws NullPointerException if the specified map is null.
134     */
135    public ROHashtableBuilder(Map<? extends K, ? extends V> t)
136    {
137        // Note that below is the actual Hashtable Constructor Code.  It **DOES NOT** break the
138        // "Read Only Contract" - Specifically, subsequent changes to 't' **WILL NOT** write
139        // through into this internal hashTable instance.  They are copied / cloned "into" this map
140
141        super(t);
142    }
143
144
145    // ********************************************************************************************
146    // ********************************************************************************************
147    // Original JDK Methods
148    // ********************************************************************************************
149    // ********************************************************************************************
150
151
152
153    /**
154     * Maps the specified {@code key} to the specified {@code value} in this hashtable. Neither the
155     * key nor the value can be {@code null}.
156     *
157     * <BR /><BR />The value can be retrieved by calling the {@code get} method with a key that is
158     * equal to the original key.
159     * 
160     * <EMBED DATA-TYPE=Hashtable CLASS='external-html' DATA-FILE-ID=MUTATOR>
161     *
162     * @param key the hashtable key
163     * @param value the value
164     * 
165     * @return the previous value of the specified key in this hashtable, or {@code null} if it did
166     * not have one
167     * 
168     * @throws NullPointerException  if the key or value is {@code null}
169     * @see #get(Object)
170     */
171    public synchronized V put(K key, V value)
172    {
173        if (this.built) throw new AttemptedModificationException(ROHT);
174        return super.put(key, value); 
175    }
176
177    /**
178     * Removes the key (and its corresponding value) from this hashtable. This method does nothing
179     * if the key is not in the hashtable.
180     * 
181     * <EMBED DATA-TYPE=Hashtable CLASS='external-html' DATA-FILE-ID=MUTATOR>
182     *
183     * @param key the key that needs to be removed
184     * 
185     * @return the value to which the key had been mapped in this hashtable, or {@code null} if the
186     * key did not have a mapping
187     * 
188     * @throws NullPointerException if the key is {@code null}
189     */
190    public synchronized V remove(Object key)
191    {
192        if (this.built) throw new AttemptedModificationException(ROHT);
193        return super.remove(key); 
194    }
195
196    /**
197     * Copies all of the mappings from the specified map to this hashtable.  These mappings will
198     * replace any mappings that this hashtable had for any of the keys currently in the specified
199     * map.
200     * 
201     * <EMBED DATA-TYPE=Hashtable CLASS='external-html' DATA-FILE-ID=MUTATOR>
202     *
203     * @param t mappings to be stored in this map
204     * @throws NullPointerException if the specified map is null
205     */
206    public synchronized void putAll(Map<? extends K, ? extends V> t)
207    {
208        if (this.built) throw new AttemptedModificationException(ROHT);
209        super.putAll(t); 
210    }
211
212    /**
213     * Clears this hashtable so that it contains no keys.
214     * <EMBED DATA-TYPE=Hashtable CLASS='external-html' DATA-FILE-ID=MUTATOR>
215     */
216    public synchronized void clear()
217    {
218        if (this.built) throw new AttemptedModificationException(ROHT);
219        super.clear(); 
220    }
221
222
223    // ********************************************************************************************
224    // ********************************************************************************************
225    // Comparison and hashing
226    // ********************************************************************************************
227    // ********************************************************************************************
228
229
230
231    /**
232     * Replaces each entry's value with the result of invoking the given function on that entry
233     * until all entries have been processed or the function throws an exception. Exceptions thrown
234     * by the function are relayed to the caller.
235     * 
236     * The default implementation is equivalent to, for this map:
237     * 
238     * <BR /><DIV CLASS=SNIP>{@code
239     * for (Map.Entry<K, V> entry : map.entrySet())
240     *      entry.setValue(function.apply(entry.getKey(), entry.getValue()));
241     * }</DIV>
242     * 
243     * <EMBED DATA-TYPE=Hashtable CLASS='external-html' DATA-FILE-ID=MUTATOR>
244     * 
245     * @param function the function to apply to each entry
246     * 
247     * @throws ClassCastException if the class of a replacement value prevents it from being stored
248     * in this map
249     * 
250     * @throws NullPointerException if the specified function or the replacement value is null
251     * @throws ConcurrentModificationException - if an entry is found to be removed during iteration
252     */
253    public synchronized void replaceAll(BiFunction<? super K, ? super V, ? extends V> function)
254    {
255        if (this.built) throw new AttemptedModificationException(ROHT);
256        super.replaceAll(function); 
257    }
258
259    /**
260     * If the specified key is not already associated with a value (or is mapped to null)
261     * associates it with the given value and returns null, else returns the current value.
262     * 
263     * <BR /><BR />The default implementation is equivalent to, for this map:
264     * 
265     * <BR /><DIV CLASS=SNIP>{@code
266     * V v = map.get(key);
267     * if (v == null) v = map.put(key, value);
268     * return v;
269     * }</DIV>
270     * 
271     * <EMBED DATA-TYPE=Hashtable CLASS='external-html' DATA-FILE-ID=MUTATOR>
272     *
273     * @param key key with which the specified value is to be associated
274     * @param value value to be associated with the specified key
275     * 
276     * @return the previous value associated with the specified key, or null if there was no
277     * mapping for the key. (A null return can also indicate that the map previously associated
278     * null with the key, if the implementation supports null values.)
279     * 
280     * @throws ClassCastException if the key or value is of an inappropriate type for this map
281     * (optional)
282     * 
283     * @throws NullPointerException if the specified key or value is null
284     */
285    public synchronized V putIfAbsent(K key, V value)
286    {
287        if (this.built) throw new AttemptedModificationException(ROHT);
288        return super.putIfAbsent(key, value); 
289    }
290
291    /**
292     * Replaces the entry for the specified key only if currently mapped to the specified value.
293     * 
294     * <BR /><BR />The default implementation is equivalent to, for this map:
295     * 
296     * <BR /><DIV CLASS=SNIP>{@code
297     * if (map.containsKey(key) && Objects.equals(map.get(key), oldValue))
298     * {
299     *      map.put(key, newValue);
300     *      return true;
301     * }
302     * 
303     * else return false;
304     * }</DIV>
305     * 
306     * <EMBED DATA-TYPE=Hashtable CLASS='external-html' DATA-FILE-ID=MUTATOR>
307     * 
308     * @param key key with which the specified value is associated
309     * @param value value expected to be associated with the specified key
310     * @return {@code TRUE} if the value was replaced
311     * 
312     * @throws ClassCastException if the class of a specified key or value prevents it from being
313     * stored in this map
314     * 
315     * @throws NullPointerException if a specified key or newValue is null, and this map does not
316     * permit null keys or values
317     */
318    public synchronized boolean remove(Object key, Object value)
319    {
320        if (this.built) throw new AttemptedModificationException(ROHT);
321        return super.remove(key, value); 
322    }
323
324    /**
325     * Replaces the entry for the specified key only if currently mapped to the specified value.
326     * 
327     * <BR /><BR />The default implementation is equivalent to, for this map:
328     * 
329     * <BR /><DIV CLASS=SNIP>{@code
330     * if (map.containsKey(key) && Objects.equals(map.get(key), oldValue))
331     * {
332     *      map.put(key, newValue);
333     *      return true;
334     * }
335     * 
336     * else return false;
337     * }</DIV>
338     * 
339     * <BR /><BR />The default implementation does not throw NullPointerException for maps that do
340     * not support null values if oldValue is null unless newValue is also null.
341     * 
342     * <EMBED DATA-TYPE=Hashtable CLASS='external-html' DATA-FILE-ID=MUTATOR>
343     * 
344     * @param key key with which the specified value is associated
345     * @param oldValue value expected to be associated with the specified key
346     * @param newValue value to be associated with the specified key
347     * @return {@code TRUE} if the value was replaced
348     * 
349     * @throws ClassCastException if the class of a specified key or value prevents it from being
350     * stored in this map
351     * 
352     * @throws NullPointerException if a specified key or oldValue / newValue is null
353     */
354    public synchronized boolean replace(K key, V oldValue, V newValue)
355    {
356        if (this.built) throw new AttemptedModificationException(ROHT);
357        return super.replace(key, oldValue, newValue); 
358    }
359
360    /**
361     * Replaces the entry for the specified key only if it is currently mapped to some value.
362     * 
363     * <BR /><BR />The default implementation is equivalent to, for this map:
364     * 
365     * <BR /><DIV CLASS=SNIP>{@code
366     * if (map.containsKey(key))    return map.put(key, value);
367     * else                         return null;
368     * }</DIV>
369     * 
370     * <EMBED DATA-TYPE=Hashtable CLASS='external-html' DATA-FILE-ID=MUTATOR>
371     * 
372     * @param key key with which the specified value is associated
373     * @param value value to be associated with the specified key
374     * 
375     * @return the previous value associated with the specified key, or null if there was no
376     * mapping for the key. (A null return can also indicate that the map previously associated
377     * null with the key, if the implementation supports null values.)
378     * 
379     * @throws ClassCastException if the class of the specified key or value prevents it from being
380     * stored in this map
381     * 
382     * @throws NullPointerException if the specified key or value is null
383     */
384    public synchronized V replace(K key, V value)
385    {
386        if (this.built) throw new AttemptedModificationException(ROHT);
387        return super.replace(key, value); 
388    }
389
390    /**
391     * If the specified key is not already associated with a value (or is mapped to null), attempts
392     * to compute its value using the given mapping function and enters it into this map unless
393     * null.
394     * 
395     * <BR /><BR />If the mapping function returns null, no mapping is recorded. If the mapping
396     * function itself throws an (unchecked) exception, the exception is rethrown, and no mapping
397     * is recorded. The most common usage is to construct a new object serving as an initial mapped
398     * value or memoized result, as in:
399     * 
400     * <BR /><DIV CLASS=SNIP>{@code
401     * map.computeIfAbsent(key, k -> new Value(f(k)));
402     * }</DIV>
403     * 
404     * <BR />Or to implement a multi-value map, {@code Map<K,Collection<V>>}, supporting multiple
405     * values per key:
406     * 
407     * <BR /><DIV CLASS=SNIP>{@code
408     * map.computeIfAbsent(key, k -> new HashSet<V>()).add(v);
409     * }</DIV>
410     * 
411     * <BR /><BR />The mapping function should not modify this map during computation.
412     * 
413     * <BR /><BR />This method will, on a best-effort basis, throw a
414     * {@link java.util.ConcurrentModificationException} if the mapping function modified this map
415     * during computation.
416     * 
417     * <EMBED DATA-TYPE=Hashtable CLASS='external-html' DATA-FILE-ID=MUTATOR>
418     *
419     * @param key key with which the specified value is to be associated
420     * @param mappingFunction the mapping function to compute a value
421     * 
422     * @return the current (existing or computed) value associated with the specified key,
423     * or null if the computed value is null
424     * 
425     * @throws ConcurrentModificationException if it is detected that the
426     * mapping function modified this map
427     */
428    public synchronized V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)
429    {
430        if (this.built) throw new AttemptedModificationException(ROHT);
431        return computeIfAbsent(key, mappingFunction); 
432    }
433
434    /**
435     * If the value for the specified key is present and non-null, attempts to compute a new
436     * mapping given the key and its current mapped value.  If the remapping function returns null,
437     * the mapping is removed.
438     * 
439     * <BR /><BR />If the remapping function itself throws an (unchecked) exception, the exception
440     * is rethrown, and the current mapping is left unchanged.
441     * 
442     * <BR /><BR />The remapping function should not modify this map during computation.
443     * 
444     * <BR /><BR />This method will, on a best-effort basis, throw a
445     * {@link java.util.ConcurrentModificationException} if the remapping function modified this
446     * map during computation.
447     * 
448     * <EMBED DATA-TYPE=Hashtable CLASS='external-html' DATA-FILE-ID=MUTATOR>
449     * 
450     * @param key key with which the specified value is to be associated
451     * @param remappingFunction the remapping function to compute a value
452     * @return the new value associated with the specified key, or null if none
453     * 
454     * @throws ConcurrentModificationException if it is detected that the remapping function
455     * modified this map
456     */
457    public synchronized V computeIfPresent
458       (K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)
459    {
460        if (this.built) throw new AttemptedModificationException(ROHT);
461        return super.computeIfPresent(key, remappingFunction); 
462    }
463
464    /**
465     * Attempts to compute a mapping for the specified key and its current mapped value (or null if
466     * there is no current mapping). For example, to either create or append a String msg to a
467     * value mapping:
468     * 
469     * <BR /><DIV CLASS=SNIP>{@code
470     * map.compute(key, (k, v) -> (v == null) ? msg : v.concat(msg))
471     * }</DIV>
472     * 
473     * <BR /><BR />(Method merge() is often simpler to use for such purposes.)
474     * 
475     * <BR /><BR />If the remapping function returns null, the mapping is removed (or remains
476     * absent if initially absent). If the remapping function itself throws an (unchecked)
477     * exception, the exception is rethrown, and the current mapping is left unchanged.
478     * 
479     * <BR /><BR />The remapping function should not modify this map during computation.
480     * 
481     * <BR /><BR />This method will, on a best-effort basis, throw a
482     * {@code ConcurrentModificationException} if the remapping function modified this map during
483     * computation.
484     * 
485     * <BR /><BR />This method will, on a best-effort basis, throw a
486     * {@link java.util.ConcurrentModificationException} if the remapping function modified this
487     * map during computation.
488     * 
489     * <EMBED DATA-TYPE=Hashtable CLASS='external-html' DATA-FILE-ID=MUTATOR>
490     *
491     * @param key key with which the specified value is to be associated
492     * @param remappingFunction the remapping function to compute a value
493     * @return the new value associated with the specified key, or null if none
494     * 
495     * @throws ConcurrentModificationException if it is detected that the remapping function
496     * modified this map
497     */
498    public synchronized V compute
499        (K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)
500    {
501        if (this.built) throw new AttemptedModificationException(ROHT);
502        return super.compute(key, remappingFunction); 
503    }
504
505    /**
506     * If the specified key is not already associated with a value or is associated with null,
507     * associates it with the given non-null value. Otherwise, replaces the associated value with
508     * the results of the given remapping function, or removes if the result is null. This method
509     * may be of use when combining multiple mapped values for a key. For example, to either create
510     * or append a String msg to a value mapping:
511     * 
512     * <BR /><DIV CLASS=SNIP>{@code
513     * map.merge(key, msg, String::concat)
514     * }</DIV>
515     * 
516     * <BR /><BR />This method will, on a best-effort basis, throw a
517     * {@code java.util.ConcurrentModificationException} if the remapping function modified this
518     * map during computation.
519     * 
520     * <EMBED DATA-TYPE=Hashtable CLASS='external-html' DATA-FILE-ID=MUTATOR>
521     * 
522     * @param key key with which the resulting value is to be associated
523     * 
524     * @param value the non-null value to be merged with the existing value associated with the key
525     * or, if no existing value or a null value is associated with the key, to be associated with
526     * the key
527     * 
528     * @param remappingFunction the remapping function to recompute a value if present
529     * 
530     * @return the new value associated with the specified key, or null if no value is associated
531     * with the key
532     * 
533     * @throws ConcurrentModificationException if it is detected that the remapping function
534     * modified this map
535     */
536    public synchronized V merge
537        (K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction)
538    {
539        if (this.built) throw new AttemptedModificationException(ROHT);
540        return super.merge(key, value, remappingFunction); 
541    }
542
543
544    // ********************************************************************************************
545    // ********************************************************************************************
546    // Methods inherited from class java.util.Hashtable
547    // ********************************************************************************************
548    // ********************************************************************************************
549
550
551    // contains, containsKey, containsValue, elements, entrySet, forEach, get, getOrDefault,
552    // hashCode, isEmpty, keys, keySet, rehash, size, toString, values
553    // 
554    // Introspection Only:  contains, containsKey, containsValue, elements, forEach, get,
555    //                      getOrDefault, hashCode, isEmpty, keys, size, toString
556    //
557    // Potential Mutator: entrySet, keySet, values
558
559    /**
560     * Restricts a back-door into the underlying data-structure.
561     * <EMBED CLASS='external-html'DATA-RET_TYPE=Set DATA-FILE-ID=UNMODIFIABLE>
562     * @return a {@code java.util.Set} that cannot modify this {@code Hashtable-Builder}
563     */
564    public Set<Map.Entry<K, V>> entrySet()
565    { return Collections.unmodifiableSet(super.entrySet()); }
566
567    Set<Map.Entry<K, V>> _entrySet(ReadOnlyHashtable.AccessBadge friendClassBadge)
568    { return super.entrySet(); }
569
570    /**
571     * Restricts a back-door into the underlying data-structure.
572     * <EMBED CLASS='external-html'DATA-RET_TYPE=Set DATA-FILE-ID=UNMODIFIABLE>
573     * @return a {@code java.util.Set} that cannot modify this {@code Hashtable-Builder}
574     */
575    public Set<K> keySet()
576    { return Collections.unmodifiableSet(super.keySet()); }
577
578    Set<K> _keySet(ReadOnlyHashtable.AccessBadge friendClassBadge)
579    { return super.keySet(); }
580
581    /**
582     * Restricts a back-door into the underlying data-structure.
583     * <EMBED CLASS='external-html'DATA-RET_TYPE=Collection DATA-FILE-ID=UNMODIFIABLE>
584     * @return a {@code java.util.Collection} that cannot modify this {@code Hashtable-Builder}
585     */
586    public Collection<V> values()
587    { return Collections.unmodifiableCollection(super.values()); }
588
589    Collection<V> _values(ReadOnlyHashtable.AccessBadge friendClassBadge)
590    { return super.values(); }
591
592
593    // ********************************************************************************************
594    // ********************************************************************************************
595    // java.lang.Object & Cloneable
596    // ********************************************************************************************
597    // ********************************************************************************************
598
599
600    /**
601     * Compares the specified Object with this Builder for equality, as per the definition in the
602     * private and internal field {@code 'hashTable'}.
603     *
604     * @param  o object to be compared for equality with this {@code ROHashtableBuilder} instance
605     * @return true if the specified Object is equal to this Builder
606     */
607    public synchronized boolean equals(Object o)
608    {
609        if (this == o) return true;
610        if (! (o instanceof ROHashtableBuilder)) return false;
611        return super.equals((Hashtable) o);
612    }
613
614    /**
615     * Clones this instance' of {@code ROHashtableBuilder}.
616     * 
617     * <BR /><BR />The clone that's returned has had it's internal {@code 'built'} boolean-flag set
618     * {@code FALSE}
619     * 
620     * @return a clone of this builder
621     */
622    public synchronized ROHashtableBuilder<K, V> clone()
623    { return new ROHashtableBuilder<>(this); }
624
625    private ROHashtableBuilder(ROHashtableBuilder<K, V> rohtb)
626    { super(rohtb); this.built=false; }
627}