View Javadoc
1   package org.djutils.immutablecollections;
2   
3   import java.io.Serializable;
4   import java.util.Comparator;
5   import java.util.ConcurrentModificationException;
6   import java.util.LinkedHashMap;
7   import java.util.Map;
8   import java.util.Map.Entry;
9   import java.util.Objects;
10  import java.util.Set;
11  import java.util.function.BiConsumer;
12  
13  /**
14   * A Map interface without the methods that can change it. The constructor of the ImmutableMap needs to be given an initial Map.
15   * <p>
16   * Copyright (c) 2016-2024 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
17   * for project information <a href="https://djutils.org" target="_blank"> https://djutils.org</a>. The DJUTILS project is
18   * distributed under a three-clause BSD-style license, which can be found at
19   * <a href="https://djutils.org/docs/license.html" target="_blank"> https://djutils.org/docs/license.html</a>.
20   * </p>
21   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
22   * @author <a href="https://www.tudelft.nl/staff/p.knoppers/">Peter Knoppers</a>
23   * @param <K> the key type of content of this Map
24   * @param <V> the value type of content of this Map
25   */
26  public interface ImmutableMap<K, V> extends Serializable
27  {
28      /**
29       * Returns the number of elements in this immutable collection. If this immutable collection contains more than
30       * <code>Integer.MAX_VALUE</code> elements, returns <code>Integer.MAX_VALUE</code>.
31       * @return the number of elements in this immutable collection
32       */
33      int size();
34  
35      /**
36       * Returns <code>true</code> if this immutable collection contains no elements.
37       * @return <code>true</code> if this immutable collection contains no elements
38       */
39      boolean isEmpty();
40  
41      /**
42       * Returns <code>true</code> if this map contains a mapping for the specified key. More formally, returns <code>true</code>
43       * if and only if this map contains a mapping for a key <code>k</code> such that
44       * <code>(key==null ? k==null : key.equals(k))</code>. (There can be at most one such mapping.)
45       * @param key Object; key whose presence in this map is to be tested
46       * @return <code>true</code> if this map contains a mapping for the specified key
47       * @throws ClassCastException if the key is of an inappropriate type for this map
48       * @throws NullPointerException if the specified key is null and this map does not permit null keys
49       */
50      boolean containsKey(Object key);
51  
52      /**
53       * Returns <code>true</code> if this map maps one or more keys to the specified value. More formally, returns
54       * <code>true</code> if and only if this map contains at least one mapping to a value <code>v</code> such that
55       * <code>(value==null ? v==null : value.equals(v))</code>. This operation will probably require time linear in the map size
56       * for most implementations of the <code>Map</code> interface.
57       * @param value Object; value whose presence in this map is to be tested
58       * @return <code>true</code> if this map maps one or more keys to the specified value
59       * @throws ClassCastException if the value is of an inappropriate type for this map
60       * @throws NullPointerException if the specified value is null and this map does not permit null values
61       */
62      boolean containsValue(Object value);
63  
64      /**
65       * Returns the value to which the specified key is mapped, or {@code null} if this map contains no mapping for the key.
66       * <p>
67       * More formally, if this map contains a mapping from a key {@code k} to a value {@code v} such that
68       * {@code (key==null ? k==null : key.equals(k))}, then this method returns {@code v}; otherwise it returns {@code null}.
69       * (There can be at most one such mapping.)
70       * <p>
71       * If this map permits null values, then a return value of {@code null} does not <i>necessarily</i> indicate that the map
72       * contains no mapping for the key; it's also possible that the map explicitly maps the key to {@code null}. The
73       * {@link #containsKey containsKey} operation may be used to distinguish these two cases.
74       * @param key Object; the key whose associated value is to be returned
75       * @return the value to which the specified key is mapped, or {@code null} if this map contains no mapping for the key
76       * @throws ClassCastException if the key is of an inappropriate type for this map
77       * @throws NullPointerException if the specified key is null and this map does not permit null keys
78       */
79      V get(Object key);
80  
81      /**
82       * Returns a {@link Set} view of the keys contained in this map.
83       * @return an immutable set of the keys contained in this map
84       */
85      ImmutableSet<K> keySet();
86  
87      /**
88       * Returns a {@link Set} view of the entries contained in this map.
89       * @return an immutable set of the entries contained in this map
90       */
91      ImmutableSet<ImmutableEntry<K, V>> entrySet();
92  
93      /**
94       * Returns a {@link ImmutableCollection} view of the values contained in this map.
95       * @return an immutable collection view of the values contained in this map
96       */
97      ImmutableCollection<V> values();
98  
99      /**
100      * Returns the value to which the specified key is mapped, or {@code defaultValue} if this map contains no mapping for the
101      * key. The default implementation makes no guarantees about synchronization or atomicity properties of this method. Any
102      * implementation providing atomicity guarantees must override this method and document its concurrency properties.
103      * @param key Object; the key whose associated value is to be returned
104      * @param defaultValue V; the default mapping of the key
105      * @return the value to which the specified key is mapped, or {@code defaultValue} if this map contains no mapping for the
106      *         key
107      * @throws ClassCastException if the key is of an inappropriate type for this map
108      * @throws NullPointerException if the specified key is null and this map does not permit null keys
109      */
110     default V getOrDefault(final Object key, final V defaultValue)
111     {
112         V v = get(key);
113         return ((v != null) || containsKey(key)) ? v : defaultValue;
114     }
115 
116     /**
117      * Performs the given action for each entry in this map until all entries have been processed or the action throws an
118      * exception. Unless otherwise specified by the implementing class, actions are performed in the order of entry set
119      * iteration (if an iteration order is specified.) Exceptions thrown by the action are relayed to the caller. The default
120      * implementation makes no guarantees about synchronization or atomicity properties of this method. Any implementation
121      * providing atomicity guarantees must override this method and document its concurrency properties.
122      * @param action BiConsumer&lt;? super K,? super V&gt;; The action to be performed for each entry
123      * @throws NullPointerException if the specified action is null
124      * @throws ConcurrentModificationException if an entry is found to be removed during iteration
125      */
126     default void forEach(final BiConsumer<? super K, ? super V> action)
127     {
128         Objects.requireNonNull(action);
129         for (ImmutableEntry<K, V> entry : entrySet())
130         {
131             K k;
132             V v;
133             try
134             {
135                 k = entry.getKey();
136                 v = entry.getValue();
137             }
138             catch (IllegalStateException ise)
139             {
140                 // this usually means the entry is no longer in the map.
141                 throw new ConcurrentModificationException(ise);
142             }
143             action.accept(k, v);
144         }
145     }
146 
147     /**
148      * Returns a modifiable copy of this immutable map.
149      * @return a modifiable copy of this immutable map.
150      */
151     Map<K, V> toMap();
152 
153     /**
154      * Force to redefine equals for the implementations of immutable collection classes.
155      * @param obj Object; the object to compare this collection with
156      * @return whether the objects are equal
157      */
158     @Override
159     boolean equals(Object obj);
160 
161     /**
162      * Force to redefine hashCode for the implementations of immutable collection classes.
163      * @return the calculated hashCode
164      */
165     @Override
166     int hashCode();
167 
168     /**
169      * Return whether the internal storage is a wrapped pointer to the original map. If true, this means that anyone holding a
170      * pointer to this data structure can still change it. The users of the ImmutableMap itself can, however, not make any
171      * changes.
172      * @return boolean; whether the internal storage is a wrapped pointer to the original map
173      */
174     boolean isWrap();
175 
176     /**
177      * Return whether the internal storage is a (shallow) copy of the original map. If true, this means that anyone holding a
178      * pointer to the original of the data structure can not change it anymore. Nor can the users of the ImmutableMap itself
179      * make any changes.
180      * @return boolean; whether the internal storage is a safe copy of the original map
181      */
182     default boolean isCopy()
183     {
184         return !isWrap();
185     }
186 
187     /**
188      * Return an empty ImmutableMap, backed by a LinkedHashMap.
189      * @param <K> the key type
190      * @param <V> the value type
191      * @return ImmutableMap&lt;K, V&gt;; an empty ImmutableMap
192      */
193     static <K, V> ImmutableMap<K, V> of()
194     {
195         return new ImmutableLinkedHashMap<>(new LinkedHashMap<K, V>(), Immutable.WRAP);
196     }
197 
198     /**
199      * Return an ImmutableMap with 1 entry, backed by a LinkedHashMap.
200      * @param <K> the key type
201      * @param <V> the value type
202      * @param k1 K; key 1
203      * @param v1 V; value 1
204      * @return ImmutableMap&lt;K, V&gt;; an ImmutableMap with 1 entry, backed by a LinkedHashMap
205      */
206     static <K, V> ImmutableMap<K, V> of(final K k1, final V v1)
207     {
208         LinkedHashMap<K, V> map = new LinkedHashMap<>();
209         map.put(k1, v1);
210         return new ImmutableLinkedHashMap<>(map, Immutable.WRAP);
211     }
212 
213     /**
214      * Return an ImmutableMap with 2 entries, backed by a LinkedHashMap.
215      * @param <K> the key type
216      * @param <V> the value type
217      * @param k1 K; key 1
218      * @param v1 V; value 1
219      * @param k2 K; key 2
220      * @param v2 V; value 2
221      * @return ImmutableMap&lt;K, V&gt;; an ImmutableMap with 2 entries, backed by a LinkedHashMap
222      */
223     static <K, V> ImmutableMap<K, V> of(final K k1, final V v1, final K k2, final V v2)
224     {
225         LinkedHashMap<K, V> map = new LinkedHashMap<>();
226         map.put(k1, v1);
227         map.put(k2, v2);
228         return new ImmutableLinkedHashMap<>(map, Immutable.WRAP);
229     }
230 
231     /**
232      * Return an ImmutableMap with 3 entries, backed by a LinkedHashMap.
233      * @param <K> the key type
234      * @param <V> the value type
235      * @param k1 K; key 1
236      * @param v1 V; value 1
237      * @param k2 K; key 2
238      * @param v2 V; value 2
239      * @param k3 K; key 3
240      * @param v3 V; value 3
241      * @return ImmutableMap&lt;K, V&gt;; an ImmutableMap with 3 entries, backed by a LinkedHashMap
242      */
243     static <K, V> ImmutableMap<K, V> of(final K k1, final V v1, final K k2, final V v2, final K k3, final V v3)
244     {
245         LinkedHashMap<K, V> map = new LinkedHashMap<>();
246         map.put(k1, v1);
247         map.put(k2, v2);
248         map.put(k3, v3);
249         return new ImmutableLinkedHashMap<>(map, Immutable.WRAP);
250     }
251 
252     /**
253      * Return an ImmutableMap with 4 entries, backed by a LinkedHashMap.
254      * @param <K> the key type
255      * @param <V> the value type
256      * @param k1 K; key 1
257      * @param v1 V; value 1
258      * @param k2 K; key 2
259      * @param v2 V; value 2
260      * @param k3 K; key 3
261      * @param v3 V; value 3
262      * @param k4 K; key 4
263      * @param v4 V; value 4
264      * @return ImmutableMap&lt;K, V&gt;; an ImmutableMap with 4 entries, backed by a LinkedHashMap
265      */
266     @SuppressWarnings("checkstyle:parameternumber")
267     static <K, V> ImmutableMap<K, V> of(final K k1, final V v1, final K k2, final V v2, final K k3, final V v3, final K k4,
268             final V v4)
269     {
270         LinkedHashMap<K, V> map = new LinkedHashMap<>();
271         map.put(k1, v1);
272         map.put(k2, v2);
273         map.put(k3, v3);
274         map.put(k4, v4);
275         return new ImmutableLinkedHashMap<>(map, Immutable.WRAP);
276     }
277 
278     /**
279      * Return an ImmutableMap with 5 entries, backed by a LinkedHashMap.
280      * @param <K> the key type
281      * @param <V> the value type
282      * @param k1 K; key 1
283      * @param v1 V; value 1
284      * @param k2 K; key 2
285      * @param v2 V; value 2
286      * @param k3 K; key 3
287      * @param v3 V; value 3
288      * @param k4 K; key 4
289      * @param v4 V; value 4
290      * @param k5 K; key 5
291      * @param v5 V; value 5
292      * @return ImmutableMap&lt;K, V&gt;; an ImmutableMap with 5 entries, backed by a LinkedHashMap
293      */
294     @SuppressWarnings("checkstyle:parameternumber")
295     static <K, V> ImmutableMap<K, V> of(final K k1, final V v1, final K k2, final V v2, final K k3, final V v3, final K k4,
296             final V v4, final K k5, final V v5)
297     {
298         LinkedHashMap<K, V> map = new LinkedHashMap<>();
299         map.put(k1, v1);
300         map.put(k2, v2);
301         map.put(k3, v3);
302         map.put(k4, v4);
303         map.put(k5, v5);
304         return new ImmutableLinkedHashMap<>(map, Immutable.WRAP);
305     }
306 
307     /**
308      * Return an ImmutableMap with 6 entries, backed by a LinkedHashMap.
309      * @param <K> the key type
310      * @param <V> the value type
311      * @param k1 K; key 1
312      * @param v1 V; value 1
313      * @param k2 K; key 2
314      * @param v2 V; value 2
315      * @param k3 K; key 3
316      * @param v3 V; value 3
317      * @param k4 K; key 4
318      * @param v4 V; value 4
319      * @param k5 K; key 5
320      * @param v5 V; value 5
321      * @param k6 K; key 6
322      * @param v6 V; value 6
323      * @return ImmutableMap&lt;K, V&gt;; an ImmutableMap with 6 entries, backed by a LinkedHashMap
324      */
325     @SuppressWarnings("checkstyle:parameternumber")
326     static <K, V> ImmutableMap<K, V> of(final K k1, final V v1, final K k2, final V v2, final K k3, final V v3, final K k4,
327             final V v4, final K k5, final V v5, final K k6, final V v6)
328     {
329         LinkedHashMap<K, V> map = new LinkedHashMap<>();
330         map.put(k1, v1);
331         map.put(k2, v2);
332         map.put(k3, v3);
333         map.put(k4, v4);
334         map.put(k5, v5);
335         map.put(k6, v6);
336         return new ImmutableLinkedHashMap<>(map, Immutable.WRAP);
337     }
338 
339     /**
340      * Return an ImmutableMap with 7 entries, backed by a LinkedHashMap.
341      * @param <K> the key type
342      * @param <V> the value type
343      * @param k1 K; key 1
344      * @param v1 V; value 1
345      * @param k2 K; key 2
346      * @param v2 V; value 2
347      * @param k3 K; key 3
348      * @param v3 V; value 3
349      * @param k4 K; key 4
350      * @param v4 V; value 4
351      * @param k5 K; key 5
352      * @param v5 V; value 5
353      * @param k6 K; key 6
354      * @param v6 V; value 6
355      * @param k7 K; key 7
356      * @param v7 V; value 7
357      * @return ImmutableMap&lt;K, V&gt;; an ImmutableMap with 7 entries, backed by a LinkedHashMap
358      */
359     @SuppressWarnings("checkstyle:parameternumber")
360     static <K, V> ImmutableMap<K, V> of(final K k1, final V v1, final K k2, final V v2, final K k3, final V v3, final K k4,
361             final V v4, final K k5, final V v5, final K k6, final V v6, final K k7, final V v7)
362     {
363         LinkedHashMap<K, V> map = new LinkedHashMap<>();
364         map.put(k1, v1);
365         map.put(k2, v2);
366         map.put(k3, v3);
367         map.put(k4, v4);
368         map.put(k5, v5);
369         map.put(k6, v6);
370         map.put(k7, v7);
371         return new ImmutableLinkedHashMap<>(map, Immutable.WRAP);
372     }
373 
374     /**
375      * Return an ImmutableMap with 8 entries, backed by a LinkedHashMap.
376      * @param <K> the key type
377      * @param <V> the value type
378      * @param k1 K; key 1
379      * @param v1 V; value 1
380      * @param k2 K; key 2
381      * @param v2 V; value 2
382      * @param k3 K; key 3
383      * @param v3 V; value 3
384      * @param k4 K; key 4
385      * @param v4 V; value 4
386      * @param k5 K; key 5
387      * @param v5 V; value 5
388      * @param k6 K; key 6
389      * @param v6 V; value 6
390      * @param k7 K; key 7
391      * @param v7 V; value 7
392      * @param k8 K; key 8
393      * @param v8 V; value 8
394      * @return ImmutableMap&lt;K, V&gt;; an ImmutableMap with 8 entries, backed by a LinkedHashMap
395      */
396     @SuppressWarnings("checkstyle:parameternumber")
397     static <K, V> ImmutableMap<K, V> of(final K k1, final V v1, final K k2, final V v2, final K k3, final V v3, final K k4,
398             final V v4, final K k5, final V v5, final K k6, final V v6, final K k7, final V v7, final K k8, final V v8)
399     {
400         LinkedHashMap<K, V> map = new LinkedHashMap<>();
401         map.put(k1, v1);
402         map.put(k2, v2);
403         map.put(k3, v3);
404         map.put(k4, v4);
405         map.put(k5, v5);
406         map.put(k6, v6);
407         map.put(k7, v7);
408         map.put(k8, v8);
409         return new ImmutableLinkedHashMap<>(map, Immutable.WRAP);
410     }
411 
412     /**
413      * A map entry (key-value pair). The <code>Map.entrySet</code> method returns a collection-view of the map, whose elements
414      * are of this class. The <i>only</i> way to obtain a reference to a map entry is from the iterator of this collection-view.
415      * These <code>ImmutableMap.ImmutableEntry</code> objects are valid <i>only</i> for the duration of the iteration; more
416      * formally, the behavior of a map entry is undefined if the backing map has been modified after the entry was returned by
417      * the iterator, except through the <code>setValue</code> operation on the map entry.
418      * @param <K> key
419      * @param <V> value
420      */
421     class ImmutableEntry<K, V>
422     {
423         /** the wrapped entry. */
424         private final Entry<K, V> wrappedEntry;
425 
426         /**
427          * @param wrappedEntry Entry&lt;K,V&gt;; the wrapped entry
428          */
429         public ImmutableEntry(final Entry<K, V> wrappedEntry)
430         {
431             this.wrappedEntry = wrappedEntry;
432         }
433 
434         /**
435          * Returns the key corresponding to this entry.
436          * @return the key corresponding to this entry
437          * @throws IllegalStateException implementations may, but are not required to, throw this exception if the entry has
438          *             been removed from the backing map.
439          */
440         public K getKey()
441         {
442             return this.wrappedEntry.getKey();
443         }
444 
445         /**
446          * Returns the value corresponding to this entry. If the mapping has been removed from the backing map (by the
447          * iterator's <code>remove</code> operation), the results of this call are undefined.
448          * @return the value corresponding to this entry
449          * @throws IllegalStateException implementations may, but are not required to, throw this exception if the entry has
450          *             been removed from the backing map.
451          */
452         public V getValue()
453         {
454             return this.wrappedEntry.getValue();
455         }
456 
457         @Override
458         public int hashCode()
459         {
460             final int prime = 31;
461             int result = 1;
462             result = prime * result + ((this.wrappedEntry == null) ? 0 : this.wrappedEntry.hashCode());
463             return result;
464         }
465 
466         @Override
467         @SuppressWarnings("checkstyle:needbraces")
468         public boolean equals(final Object obj)
469         {
470             if (this == obj)
471                 return true;
472             if (obj == null)
473                 return false;
474             if (getClass() != obj.getClass())
475                 return false;
476             ImmutableEntry<?, ?> other = (ImmutableEntry<?, ?>) obj;
477             if (this.wrappedEntry == null)
478             {
479                 if (other.wrappedEntry != null)
480                     return false;
481             }
482             else if (!this.wrappedEntry.equals(other.wrappedEntry))
483                 return false;
484             return true;
485         }
486 
487         /**
488          * Returns a comparator that compares {@link ImmutableMap.ImmutableEntry} in natural order on key.
489          * <p>
490          * The returned comparator is serializable and throws {@link NullPointerException} when comparing an entry with a null
491          * key.
492          * @param <K> the {@link Comparable} type of then map keys
493          * @param <V> the type of the map values
494          * @return a comparator that compares {@link ImmutableMap.ImmutableEntry} in natural order on key.
495          * @see Comparable
496          * @since 1.8
497          */
498         @SuppressWarnings("unchecked")
499         public static <K extends Comparable<? super K>, V> Comparator<ImmutableMap.ImmutableEntry<K, V>> comparingByKey()
500         {
501             return (Comparator<ImmutableMap.ImmutableEntry<K, V>> & Serializable) (c1, c2) -> c1.getKey()
502                     .compareTo(c2.getKey());
503         }
504 
505         /**
506          * Returns a comparator that compares {@link ImmutableMap.ImmutableEntry} in natural order on value.
507          * <p>
508          * The returned comparator is serializable and throws {@link NullPointerException} when comparing an entry with null
509          * values.
510          * @param <K> the type of the map keys
511          * @param <V> the {@link Comparable} type of the map values
512          * @return a comparator that compares {@link ImmutableMap.ImmutableEntry} in natural order on value.
513          * @see Comparable
514          * @since 1.8
515          */
516         @SuppressWarnings("unchecked")
517         public static <K, V extends Comparable<? super V>> Comparator<ImmutableMap.ImmutableEntry<K, V>> comparingByValue()
518         {
519             return (Comparator<ImmutableMap.ImmutableEntry<K, V>> & Serializable) (c1, c2) -> c1.getValue()
520                     .compareTo(c2.getValue());
521         }
522 
523         /**
524          * Returns a comparator that compares {@link ImmutableMap.ImmutableEntry} by key using the given {@link Comparator}.
525          * <p>
526          * The returned comparator is serializable if the specified comparator is also serializable.
527          * @param <K> the type of the map keys
528          * @param <V> the type of the map values
529          * @param cmp Comparator&lt;? super K&gt;; the key {@link Comparator}
530          * @return a comparator that compares {@link ImmutableMap.ImmutableEntry} by the key.
531          * @since 1.8
532          */
533         @SuppressWarnings("unchecked")
534         public static <K, V> Comparator<ImmutableMap.ImmutableEntry<K, V>> comparingByKey(final Comparator<? super K> cmp)
535         {
536             Objects.requireNonNull(cmp);
537             return (Comparator<ImmutableMap.ImmutableEntry<K, V>> & Serializable) (c1, c2) -> cmp.compare(c1.getKey(),
538                     c2.getKey());
539         }
540 
541         /**
542          * Returns a comparator that compares {@link ImmutableMap.ImmutableEntry} by value using the given {@link Comparator}.
543          * <p>
544          * The returned comparator is serializable if the specified comparator is also serializable.
545          * @param <K> the type of the map keys
546          * @param <V> the type of the map values
547          * @param cmp Comparator&lt;? super V&gt;; the value {@link Comparator}
548          * @return a comparator that compares {@link ImmutableMap.ImmutableEntry} by the value.
549          * @since 1.8
550          */
551         @SuppressWarnings("unchecked")
552         public static <K, V> Comparator<ImmutableMap.ImmutableEntry<K, V>> comparingByValue(final Comparator<? super V> cmp)
553         {
554             Objects.requireNonNull(cmp);
555             return (Comparator<ImmutableMap.ImmutableEntry<K, V>> & Serializable) (c1, c2) -> cmp.compare(c1.getValue(),
556                     c2.getValue());
557         }
558 
559         @Override
560         public String toString()
561         {
562             return "ImmutableEntry [wrappedEntry=" + this.wrappedEntry + "]";
563         }
564 
565     }
566 
567     /**
568      * Force to redefine toString.
569      * @return String; a description of this immutable map
570      */
571     @Override
572     String toString();
573 
574 }