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