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<? 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 @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<? super K>; 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<? super V>; 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 }