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-2023 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<? super K,? super V>; 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<K, V>; 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<K, V>; 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<K, V>; 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<K, V>; 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<K, V>; 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<K, V>; 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<K, V>; 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<K, V>; 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<K, V>; 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<K,V>; 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 /** {@inheritDoc} */
458 @Override
459 public int hashCode()
460 {
461 final int prime = 31;
462 int result = 1;
463 result = prime * result + ((this.wrappedEntry == null) ? 0 : this.wrappedEntry.hashCode());
464 return result;
465 }
466
467 /** {@inheritDoc} */
468 @Override
469 @SuppressWarnings("checkstyle:needbraces")
470 public boolean equals(final Object obj)
471 {
472 if (this == obj)
473 return true;
474 if (obj == null)
475 return false;
476 if (getClass() != obj.getClass())
477 return false;
478 ImmutableEntry<?, ?> other = (ImmutableEntry<?, ?>) obj;
479 if (this.wrappedEntry == null)
480 {
481 if (other.wrappedEntry != null)
482 return false;
483 }
484 else if (!this.wrappedEntry.equals(other.wrappedEntry))
485 return false;
486 return true;
487 }
488
489 /**
490 * Returns a comparator that compares {@link ImmutableMap.ImmutableEntry} in natural order on key.
491 * <p>
492 * The returned comparator is serializable and throws {@link NullPointerException} when comparing an entry with a null
493 * key.
494 * @param <K> the {@link Comparable} type of then map keys
495 * @param <V> the type of the map values
496 * @return a comparator that compares {@link ImmutableMap.ImmutableEntry} in natural order on key.
497 * @see Comparable
498 * @since 1.8
499 */
500 @SuppressWarnings("unchecked")
501 public static <K extends Comparable<? super K>, V> Comparator<ImmutableMap.ImmutableEntry<K, V>> comparingByKey()
502 {
503 return (Comparator<ImmutableMap.ImmutableEntry<K, V>> & Serializable) (c1, c2) -> c1.getKey()
504 .compareTo(c2.getKey());
505 }
506
507 /**
508 * Returns a comparator that compares {@link ImmutableMap.ImmutableEntry} in natural order on value.
509 * <p>
510 * The returned comparator is serializable and throws {@link NullPointerException} when comparing an entry with null
511 * values.
512 * @param <K> the type of the map keys
513 * @param <V> the {@link Comparable} type of the map values
514 * @return a comparator that compares {@link ImmutableMap.ImmutableEntry} in natural order on value.
515 * @see Comparable
516 * @since 1.8
517 */
518 @SuppressWarnings("unchecked")
519 public static <K, V extends Comparable<? super V>> Comparator<ImmutableMap.ImmutableEntry<K, V>> comparingByValue()
520 {
521 return (Comparator<ImmutableMap.ImmutableEntry<K, V>> & Serializable) (c1, c2) -> c1.getValue()
522 .compareTo(c2.getValue());
523 }
524
525 /**
526 * Returns a comparator that compares {@link ImmutableMap.ImmutableEntry} by key using the given {@link Comparator}.
527 * <p>
528 * The returned comparator is serializable if the specified comparator is also serializable.
529 * @param <K> the type of the map keys
530 * @param <V> the type of the map values
531 * @param cmp Comparator<? super K>; the key {@link Comparator}
532 * @return a comparator that compares {@link ImmutableMap.ImmutableEntry} by the key.
533 * @since 1.8
534 */
535 @SuppressWarnings("unchecked")
536 public static <K, V> Comparator<ImmutableMap.ImmutableEntry<K, V>> comparingByKey(final Comparator<? super K> cmp)
537 {
538 Objects.requireNonNull(cmp);
539 return (Comparator<ImmutableMap.ImmutableEntry<K, V>> & Serializable) (c1, c2) -> cmp.compare(c1.getKey(),
540 c2.getKey());
541 }
542
543 /**
544 * Returns a comparator that compares {@link ImmutableMap.ImmutableEntry} by value using the given {@link Comparator}.
545 * <p>
546 * The returned comparator is serializable if the specified comparator is also serializable.
547 * @param <K> the type of the map keys
548 * @param <V> the type of the map values
549 * @param cmp Comparator<? super V>; the value {@link Comparator}
550 * @return a comparator that compares {@link ImmutableMap.ImmutableEntry} by the value.
551 * @since 1.8
552 */
553 @SuppressWarnings("unchecked")
554 public static <K, V> Comparator<ImmutableMap.ImmutableEntry<K, V>> comparingByValue(final Comparator<? super V> cmp)
555 {
556 Objects.requireNonNull(cmp);
557 return (Comparator<ImmutableMap.ImmutableEntry<K, V>> & Serializable) (c1, c2) -> cmp.compare(c1.getValue(),
558 c2.getValue());
559 }
560
561 /** {@inheritDoc} */
562 @Override
563 public String toString()
564 {
565 return "ImmutableEntry [wrappedEntry=" + this.wrappedEntry + "]";
566 }
567
568 }
569
570 /**
571 * Force to redefine toString.
572 * @return String; a description of this immutable map
573 */
574 @Override
575 String toString();
576
577 }