001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.collections4.map; 018 019import java.io.IOException; 020import java.io.ObjectInputStream; 021import java.io.ObjectOutputStream; 022import java.io.Serializable; 023import java.util.Map; 024import java.util.Objects; 025 026import org.apache.commons.collections4.MapIterator; 027import org.apache.commons.collections4.keyvalue.MultiKey; 028 029/** 030 * A {@code Map} implementation that uses multiple keys to map the value. 031 * <p> 032 * This class is the most efficient way to uses multiple keys to map to a value. 033 * The best way to use this class is via the additional map-style methods. 034 * These provide {@code get}, {@code containsKey}, {@code put} and 035 * {@code remove} for individual keys which operate without extra object creation. 036 * </p> 037 * <p> 038 * The additional methods are the main interface of this map. 039 * As such, you will not normally hold this map in a variable of type {@code Map}. 040 * </p> 041 * <p> 042 * The normal map methods take in and return a {@link MultiKey}. 043 * If you try to use {@code put()} with any other object type a 044 * {@code ClassCastException} is thrown. If you try to use {@code null} as 045 * the key in {@code put()} a {@code NullPointerException} is thrown. 046 * </p> 047 * <p> 048 * This map is implemented as a decorator of a {@code AbstractHashedMap} which 049 * enables extra behavior to be added easily. 050 * </p> 051 * <ul> 052 * <li>{@code MultiKeyMap.decorate(new LinkedMap())} creates an ordered map. 053 * <li>{@code MultiKeyMap.decorate(new LRUMap())} creates an least recently used map. 054 * <li>{@code MultiKeyMap.decorate(new ReferenceMap())} creates a garbage collector sensitive map. 055 * </ul> 056 * <p> 057 * Note that {@code IdentityMap} and {@code ReferenceIdentityMap} are unsuitable 058 * for use as the key comparison would work on the whole MultiKey, not the elements within. 059 * </p> 060 * <p> 061 * As an example, consider a least recently used cache that uses a String airline code 062 * and a Locale to lookup the airline's name: 063 * </p> 064 * <pre> 065 * private MultiKeyMap cache = MultiKeyMap.multiKeyMap(new LRUMap(50)); 066 * 067 * public String getAirlineName(String code, String locale) { 068 * String name = (String) cache.get(code, locale); 069 * if (name == null) { 070 * name = getAirlineNameFromDB(code, locale); 071 * cache.put(code, locale, name); 072 * } 073 * return name; 074 * } 075 * </pre> 076 * <p> 077 * <strong>Note that MultiKeyMap is not synchronized and is not thread-safe.</strong> 078 * If you wish to use this map from multiple threads concurrently, you must use 079 * appropriate synchronization. This class may throw exceptions when accessed 080 * by concurrent threads without synchronization. 081 * </p> 082 * 083 * @param <K> the type of the keys in this map 084 * @param <V> the type of the values in this map 085 * @since 3.1 086 */ 087public class MultiKeyMap<K, V> extends AbstractMapDecorator<MultiKey<? extends K>, V> 088 implements Serializable, Cloneable { 089 090 /** Serialisation version */ 091 private static final long serialVersionUID = -1788199231038721040L; 092 093 /** 094 * Decorates the specified map to add the MultiKeyMap API and fast query. 095 * The map must not be null and must be empty. 096 * 097 * @param <K> the key type 098 * @param <V> the value type 099 * @param map the map to decorate, not null 100 * @return a new multi key map 101 * @throws NullPointerException if map is null 102 * @throws IllegalArgumentException if the map is not empty 103 * @since 4.0 104 */ 105 public static <K, V> MultiKeyMap<K, V> multiKeyMap(final AbstractHashedMap<MultiKey<? extends K>, V> map) { 106 Objects.requireNonNull(map, "map"); 107 if (map.isEmpty()) { 108 return new MultiKeyMap<>(map); 109 } 110 throw new IllegalArgumentException("Map must be empty"); 111 } 112 113 /** 114 * Constructs a new MultiKeyMap that decorates a {@code HashedMap}. 115 */ 116 public MultiKeyMap() { 117 this(new HashedMap<>()); 118 } 119 120 /** 121 * Constructor that decorates the specified map and is called from 122 * {@link #multiKeyMap(AbstractHashedMap)}. 123 * The map must not be null and should be empty or only contain valid keys. 124 * This constructor performs no validation. 125 * 126 * @param map the map to decorate 127 */ 128 protected MultiKeyMap(final AbstractHashedMap<MultiKey<? extends K>, V> map) { 129 super(map); 130 this.map = map; 131 } 132 133 /** 134 * Check to ensure that input keys are valid MultiKey objects. 135 * 136 * @param key the key to check 137 */ 138 protected void checkKey(final MultiKey<?> key) { 139 Objects.requireNonNull(key, "key"); 140 } 141 142 /** 143 * Clones the map without cloning the keys or values. 144 * 145 * @return a shallow clone 146 */ 147 @SuppressWarnings("unchecked") 148 @Override 149 public MultiKeyMap<K, V> clone() { 150 try { 151 return (MultiKeyMap<K, V>) super.clone(); 152 } catch (final CloneNotSupportedException e) { 153 throw new UnsupportedOperationException(e); 154 } 155 } 156 157 /** 158 * Checks whether the map contains the specified multi-key. 159 * 160 * @param key1 the first key 161 * @param key2 the second key 162 * @return true if the map contains the key 163 */ 164 public boolean containsKey(final Object key1, final Object key2) { 165 final int hashCode = hash(key1, key2); 166 AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = 167 decorated().data[decorated().hashIndex(hashCode, decorated().data.length)]; 168 while (entry != null) { 169 if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2)) { 170 return true; 171 } 172 entry = entry.next; 173 } 174 return false; 175 } 176 177 /** 178 * Checks whether the map contains the specified multi-key. 179 * 180 * @param key1 the first key 181 * @param key2 the second key 182 * @param key3 the third key 183 * @return true if the map contains the key 184 */ 185 public boolean containsKey(final Object key1, final Object key2, final Object key3) { 186 final int hashCode = hash(key1, key2, key3); 187 AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = 188 decorated().data[decorated().hashIndex(hashCode, decorated().data.length)]; 189 while (entry != null) { 190 if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3)) { 191 return true; 192 } 193 entry = entry.next; 194 } 195 return false; 196 } 197 198 /** 199 * Checks whether the map contains the specified multi-key. 200 * 201 * @param key1 the first key 202 * @param key2 the second key 203 * @param key3 the third key 204 * @param key4 the fourth key 205 * @return true if the map contains the key 206 */ 207 public boolean containsKey(final Object key1, final Object key2, final Object key3, final Object key4) { 208 final int hashCode = hash(key1, key2, key3, key4); 209 AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = 210 decorated().data[decorated().hashIndex(hashCode, decorated().data.length)]; 211 while (entry != null) { 212 if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4)) { 213 return true; 214 } 215 entry = entry.next; 216 } 217 return false; 218 } 219 220 /** 221 * Checks whether the map contains the specified multi-key. 222 * 223 * @param key1 the first key 224 * @param key2 the second key 225 * @param key3 the third key 226 * @param key4 the fourth key 227 * @param key5 the fifth key 228 * @return true if the map contains the key 229 */ 230 public boolean containsKey(final Object key1, final Object key2, final Object key3, 231 final Object key4, final Object key5) { 232 final int hashCode = hash(key1, key2, key3, key4, key5); 233 AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = 234 decorated().data[decorated().hashIndex(hashCode, decorated().data.length)]; 235 while (entry != null) { 236 if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4, key5)) { 237 return true; 238 } 239 entry = entry.next; 240 } 241 return false; 242 } 243 244 /** 245 * {@inheritDoc} 246 */ 247 @Override 248 protected AbstractHashedMap<MultiKey<? extends K>, V> decorated() { 249 return (AbstractHashedMap<MultiKey<? extends K>, V>) super.decorated(); 250 } 251 252 /** 253 * Gets the value mapped to the specified multi-key. 254 * 255 * @param key1 the first key 256 * @param key2 the second key 257 * @return the mapped value, null if no match 258 */ 259 public V get(final Object key1, final Object key2) { 260 final int hashCode = hash(key1, key2); 261 AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = 262 decorated().data[decorated().hashIndex(hashCode, decorated().data.length)]; 263 while (entry != null) { 264 if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2)) { 265 return entry.getValue(); 266 } 267 entry = entry.next; 268 } 269 return null; 270 } 271 272 /** 273 * Gets the value mapped to the specified multi-key. 274 * 275 * @param key1 the first key 276 * @param key2 the second key 277 * @param key3 the third key 278 * @return the mapped value, null if no match 279 */ 280 public V get(final Object key1, final Object key2, final Object key3) { 281 final int hashCode = hash(key1, key2, key3); 282 AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = 283 decorated().data[decorated().hashIndex(hashCode, decorated().data.length)]; 284 while (entry != null) { 285 if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3)) { 286 return entry.getValue(); 287 } 288 entry = entry.next; 289 } 290 return null; 291 } 292 293 /** 294 * Gets the value mapped to the specified multi-key. 295 * 296 * @param key1 the first key 297 * @param key2 the second key 298 * @param key3 the third key 299 * @param key4 the fourth key 300 * @return the mapped value, null if no match 301 */ 302 public V get(final Object key1, final Object key2, final Object key3, final Object key4) { 303 final int hashCode = hash(key1, key2, key3, key4); 304 AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = 305 decorated().data[decorated().hashIndex(hashCode, decorated().data.length)]; 306 while (entry != null) { 307 if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4)) { 308 return entry.getValue(); 309 } 310 entry = entry.next; 311 } 312 return null; 313 } 314 315 /** 316 * Gets the value mapped to the specified multi-key. 317 * 318 * @param key1 the first key 319 * @param key2 the second key 320 * @param key3 the third key 321 * @param key4 the fourth key 322 * @param key5 the fifth key 323 * @return the mapped value, null if no match 324 */ 325 public V get(final Object key1, final Object key2, final Object key3, final Object key4, final Object key5) { 326 final int hashCode = hash(key1, key2, key3, key4, key5); 327 AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = 328 decorated().data[decorated().hashIndex(hashCode, decorated().data.length)]; 329 while (entry != null) { 330 if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4, key5)) { 331 return entry.getValue(); 332 } 333 entry = entry.next; 334 } 335 return null; 336 } 337 338 /** 339 * Gets the hash code for the specified multi-key. 340 * 341 * @param key1 the first key 342 * @param key2 the second key 343 * @return the hash code 344 */ 345 protected int hash(final Object key1, final Object key2) { 346 int h = 0; 347 if (key1 != null) { 348 h ^= key1.hashCode(); 349 } 350 if (key2 != null) { 351 h ^= key2.hashCode(); 352 } 353 h += ~(h << 9); 354 h ^= h >>> 14; 355 h += h << 4; 356 h ^= h >>> 10; 357 return h; 358 } 359 360 /** 361 * Gets the hash code for the specified multi-key. 362 * 363 * @param key1 the first key 364 * @param key2 the second key 365 * @param key3 the third key 366 * @return the hash code 367 */ 368 protected int hash(final Object key1, final Object key2, final Object key3) { 369 int h = 0; 370 if (key1 != null) { 371 h ^= key1.hashCode(); 372 } 373 if (key2 != null) { 374 h ^= key2.hashCode(); 375 } 376 if (key3 != null) { 377 h ^= key3.hashCode(); 378 } 379 h += ~(h << 9); 380 h ^= h >>> 14; 381 h += h << 4; 382 h ^= h >>> 10; 383 return h; 384 } 385 386 /** 387 * Gets the hash code for the specified multi-key. 388 * 389 * @param key1 the first key 390 * @param key2 the second key 391 * @param key3 the third key 392 * @param key4 the fourth key 393 * @return the hash code 394 */ 395 protected int hash(final Object key1, final Object key2, final Object key3, final Object key4) { 396 int h = 0; 397 if (key1 != null) { 398 h ^= key1.hashCode(); 399 } 400 if (key2 != null) { 401 h ^= key2.hashCode(); 402 } 403 if (key3 != null) { 404 h ^= key3.hashCode(); 405 } 406 if (key4 != null) { 407 h ^= key4.hashCode(); 408 } 409 h += ~(h << 9); 410 h ^= h >>> 14; 411 h += h << 4; 412 h ^= h >>> 10; 413 return h; 414 } 415 416 /** 417 * Gets the hash code for the specified multi-key. 418 * 419 * @param key1 the first key 420 * @param key2 the second key 421 * @param key3 the third key 422 * @param key4 the fourth key 423 * @param key5 the fifth key 424 * @return the hash code 425 */ 426 protected int hash(final Object key1, final Object key2, final Object key3, final Object key4, final Object key5) { 427 int h = 0; 428 if (key1 != null) { 429 h ^= key1.hashCode(); 430 } 431 if (key2 != null) { 432 h ^= key2.hashCode(); 433 } 434 if (key3 != null) { 435 h ^= key3.hashCode(); 436 } 437 if (key4 != null) { 438 h ^= key4.hashCode(); 439 } 440 if (key5 != null) { 441 h ^= key5.hashCode(); 442 } 443 h += ~(h << 9); 444 h ^= h >>> 14; 445 h += h << 4; 446 h ^= h >>> 10; 447 return h; 448 } 449 450 /** 451 * Is the key equal to the combined key. 452 * 453 * @param entry the entry to compare to 454 * @param key1 the first key 455 * @param key2 the second key 456 * @return true if the key matches 457 */ 458 protected boolean isEqualKey(final AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry, 459 final Object key1, final Object key2) { 460 final MultiKey<? extends K> multi = entry.getKey(); 461 return 462 multi.size() == 2 && 463 (key1 == multi.getKey(0) || key1 != null && key1.equals(multi.getKey(0))) && 464 (key2 == multi.getKey(1) || key2 != null && key2.equals(multi.getKey(1))); 465 } 466 467 /** 468 * Is the key equal to the combined key. 469 * 470 * @param entry the entry to compare to 471 * @param key1 the first key 472 * @param key2 the second key 473 * @param key3 the third key 474 * @return true if the key matches 475 */ 476 protected boolean isEqualKey(final AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry, 477 final Object key1, final Object key2, final Object key3) { 478 final MultiKey<? extends K> multi = entry.getKey(); 479 return 480 multi.size() == 3 && 481 (key1 == multi.getKey(0) || key1 != null && key1.equals(multi.getKey(0))) && 482 (key2 == multi.getKey(1) || key2 != null && key2.equals(multi.getKey(1))) && 483 (key3 == multi.getKey(2) || key3 != null && key3.equals(multi.getKey(2))); 484 } 485 486 /** 487 * Is the key equal to the combined key. 488 * 489 * @param entry the entry to compare to 490 * @param key1 the first key 491 * @param key2 the second key 492 * @param key3 the third key 493 * @param key4 the fourth key 494 * @return true if the key matches 495 */ 496 protected boolean isEqualKey(final AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry, 497 final Object key1, final Object key2, final Object key3, final Object key4) { 498 final MultiKey<? extends K> multi = entry.getKey(); 499 return 500 multi.size() == 4 && 501 (key1 == multi.getKey(0) || key1 != null && key1.equals(multi.getKey(0))) && 502 (key2 == multi.getKey(1) || key2 != null && key2.equals(multi.getKey(1))) && 503 (key3 == multi.getKey(2) || key3 != null && key3.equals(multi.getKey(2))) && 504 (key4 == multi.getKey(3) || key4 != null && key4.equals(multi.getKey(3))); 505 } 506 507 /** 508 * Is the key equal to the combined key. 509 * 510 * @param entry the entry to compare to 511 * @param key1 the first key 512 * @param key2 the second key 513 * @param key3 the third key 514 * @param key4 the fourth key 515 * @param key5 the fifth key 516 * @return true if the key matches 517 */ 518 protected boolean isEqualKey(final AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry, 519 final Object key1, final Object key2, final Object key3, final Object key4, final Object key5) { 520 final MultiKey<? extends K> multi = entry.getKey(); 521 return 522 multi.size() == 5 && 523 (key1 == multi.getKey(0) || key1 != null && key1.equals(multi.getKey(0))) && 524 (key2 == multi.getKey(1) || key2 != null && key2.equals(multi.getKey(1))) && 525 (key3 == multi.getKey(2) || key3 != null && key3.equals(multi.getKey(2))) && 526 (key4 == multi.getKey(3) || key4 != null && key4.equals(multi.getKey(3))) && 527 (key5 == multi.getKey(4) || key5 != null && key5.equals(multi.getKey(4))); 528 } 529 530 @Override 531 public MapIterator<MultiKey<? extends K>, V> mapIterator() { 532 return decorated().mapIterator(); 533 } 534 535 /** 536 * Stores the value against the specified multi-key. 537 * 538 * @param key1 the first key 539 * @param key2 the second key 540 * @param key3 the third key 541 * @param key4 the fourth key 542 * @param key5 the fifth key 543 * @param value the value to store 544 * @return the value previously mapped to this combined key, null if none 545 */ 546 public V put(final K key1, final K key2, final K key3, final K key4, final K key5, final V value) { 547 final int hashCode = hash(key1, key2, key3, key4, key5); 548 final int index = decorated().hashIndex(hashCode, decorated().data.length); 549 AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = decorated().data[index]; 550 while (entry != null) { 551 if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4, key5)) { 552 final V oldValue = entry.getValue(); 553 decorated().updateEntry(entry, value); 554 return oldValue; 555 } 556 entry = entry.next; 557 } 558 decorated().addMapping(index, hashCode, new MultiKey<>(key1, key2, key3, key4, key5), value); 559 return null; 560 } 561 562 /** 563 * Stores the value against the specified multi-key. 564 * 565 * @param key1 the first key 566 * @param key2 the second key 567 * @param key3 the third key 568 * @param key4 the fourth key 569 * @param value the value to store 570 * @return the value previously mapped to this combined key, null if none 571 */ 572 public V put(final K key1, final K key2, final K key3, final K key4, final V value) { 573 final int hashCode = hash(key1, key2, key3, key4); 574 final int index = decorated().hashIndex(hashCode, decorated().data.length); 575 AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = decorated().data[index]; 576 while (entry != null) { 577 if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4)) { 578 final V oldValue = entry.getValue(); 579 decorated().updateEntry(entry, value); 580 return oldValue; 581 } 582 entry = entry.next; 583 } 584 decorated().addMapping(index, hashCode, new MultiKey<>(key1, key2, key3, key4), value); 585 return null; 586 } 587 588 /** 589 * Stores the value against the specified multi-key. 590 * 591 * @param key1 the first key 592 * @param key2 the second key 593 * @param key3 the third key 594 * @param value the value to store 595 * @return the value previously mapped to this combined key, null if none 596 */ 597 public V put(final K key1, final K key2, final K key3, final V value) { 598 final int hashCode = hash(key1, key2, key3); 599 final int index = decorated().hashIndex(hashCode, decorated().data.length); 600 AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = decorated().data[index]; 601 while (entry != null) { 602 if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3)) { 603 final V oldValue = entry.getValue(); 604 decorated().updateEntry(entry, value); 605 return oldValue; 606 } 607 entry = entry.next; 608 } 609 decorated().addMapping(index, hashCode, new MultiKey<>(key1, key2, key3), value); 610 return null; 611 } 612 613 /** 614 * Stores the value against the specified multi-key. 615 * 616 * @param key1 the first key 617 * @param key2 the second key 618 * @param value the value to store 619 * @return the value previously mapped to this combined key, null if none 620 */ 621 public V put(final K key1, final K key2, final V value) { 622 final int hashCode = hash(key1, key2); 623 final int index = decorated().hashIndex(hashCode, decorated().data.length); 624 AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = decorated().data[index]; 625 while (entry != null) { 626 if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2)) { 627 final V oldValue = entry.getValue(); 628 decorated().updateEntry(entry, value); 629 return oldValue; 630 } 631 entry = entry.next; 632 } 633 decorated().addMapping(index, hashCode, new MultiKey<>(key1, key2), value); 634 return null; 635 } 636 637 /** 638 * Puts the key and value into the map, where the key must be a non-null 639 * MultiKey object. 640 * 641 * @param key the non-null MultiKey object 642 * @param value the value to store 643 * @return the previous value for the key 644 * @throws NullPointerException if the key is null 645 * @throws ClassCastException if the key is not a MultiKey 646 */ 647 @Override 648 public V put(final MultiKey<? extends K> key, final V value) { 649 checkKey(key); 650 return super.put(key, value); 651 } 652 653 /** 654 * Copies all of the keys and values from the specified map to this map. 655 * Each key must be non-null and a MultiKey object. 656 * 657 * @param mapToCopy to this map 658 * @throws NullPointerException if the mapToCopy or any key within is null 659 * @throws ClassCastException if any key in mapToCopy is not a MultiKey 660 */ 661 @Override 662 public void putAll(final Map<? extends MultiKey<? extends K>, ? extends V> mapToCopy) { 663 for (final MultiKey<? extends K> key : mapToCopy.keySet()) { 664 checkKey(key); 665 } 666 super.putAll(mapToCopy); 667 } 668 669 /** 670 * Read the map in using a custom routine. 671 * 672 * @param in the input stream 673 * @throws IOException if an error occurs while reading from the stream 674 * @throws ClassNotFoundException if an object read from the stream can not be loaded 675 */ 676 @SuppressWarnings("unchecked") 677 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { 678 in.defaultReadObject(); 679 map = (Map<MultiKey<? extends K>, V>) in.readObject(); 680 } 681 682 /** 683 * Removes all mappings where the first key is that specified. 684 * <p> 685 * This method removes all the mappings where the {@code MultiKey} 686 * has one or more keys, and the first matches that specified. 687 * 688 * @param key1 the first key 689 * @return true if any elements were removed 690 */ 691 public boolean removeAll(final Object key1) { 692 boolean modified = false; 693 final MapIterator<MultiKey<? extends K>, V> it = mapIterator(); 694 while (it.hasNext()) { 695 final MultiKey<? extends K> multi = it.next(); 696 if (multi.size() >= 1 && 697 (key1 == null ? multi.getKey(0) == null : key1.equals(multi.getKey(0)))) { 698 it.remove(); 699 modified = true; 700 } 701 } 702 return modified; 703 } 704 705 /** 706 * Removes all mappings where the first two keys are those specified. 707 * <p> 708 * This method removes all the mappings where the {@code MultiKey} 709 * has two or more keys, and the first two match those specified. 710 * 711 * @param key1 the first key 712 * @param key2 the second key 713 * @return true if any elements were removed 714 */ 715 public boolean removeAll(final Object key1, final Object key2) { 716 boolean modified = false; 717 final MapIterator<MultiKey<? extends K>, V> it = mapIterator(); 718 while (it.hasNext()) { 719 final MultiKey<? extends K> multi = it.next(); 720 if (multi.size() >= 2 && 721 (key1 == null ? multi.getKey(0) == null : key1.equals(multi.getKey(0))) && 722 (key2 == null ? multi.getKey(1) == null : key2.equals(multi.getKey(1)))) { 723 it.remove(); 724 modified = true; 725 } 726 } 727 return modified; 728 } 729 730 /** 731 * Removes all mappings where the first three keys are those specified. 732 * <p> 733 * This method removes all the mappings where the {@code MultiKey} 734 * has three or more keys, and the first three match those specified. 735 * 736 * @param key1 the first key 737 * @param key2 the second key 738 * @param key3 the third key 739 * @return true if any elements were removed 740 */ 741 public boolean removeAll(final Object key1, final Object key2, final Object key3) { 742 boolean modified = false; 743 final MapIterator<MultiKey<? extends K>, V> it = mapIterator(); 744 while (it.hasNext()) { 745 final MultiKey<? extends K> multi = it.next(); 746 if (multi.size() >= 3 && 747 (key1 == null ? multi.getKey(0) == null : key1.equals(multi.getKey(0))) && 748 (key2 == null ? multi.getKey(1) == null : key2.equals(multi.getKey(1))) && 749 (key3 == null ? multi.getKey(2) == null : key3.equals(multi.getKey(2)))) { 750 it.remove(); 751 modified = true; 752 } 753 } 754 return modified; 755 } 756 757 /** 758 * Removes all mappings where the first four keys are those specified. 759 * <p> 760 * This method removes all the mappings where the {@code MultiKey} 761 * has four or more keys, and the first four match those specified. 762 * 763 * @param key1 the first key 764 * @param key2 the second key 765 * @param key3 the third key 766 * @param key4 the fourth key 767 * @return true if any elements were removed 768 */ 769 public boolean removeAll(final Object key1, final Object key2, final Object key3, final Object key4) { 770 boolean modified = false; 771 final MapIterator<MultiKey<? extends K>, V> it = mapIterator(); 772 while (it.hasNext()) { 773 final MultiKey<? extends K> multi = it.next(); 774 if (multi.size() >= 4 && 775 (key1 == null ? multi.getKey(0) == null : key1.equals(multi.getKey(0))) && 776 (key2 == null ? multi.getKey(1) == null : key2.equals(multi.getKey(1))) && 777 (key3 == null ? multi.getKey(2) == null : key3.equals(multi.getKey(2))) && 778 (key4 == null ? multi.getKey(3) == null : key4.equals(multi.getKey(3)))) { 779 it.remove(); 780 modified = true; 781 } 782 } 783 return modified; 784 } 785 786 /** 787 * Removes the specified multi-key from this map. 788 * 789 * @param key1 the first key 790 * @param key2 the second key 791 * @return the value mapped to the removed key, null if key not in map 792 * @since 4.0 (previous name: remove(Object, Object)) 793 */ 794 public V removeMultiKey(final Object key1, final Object key2) { 795 final int hashCode = hash(key1, key2); 796 final int index = decorated().hashIndex(hashCode, decorated().data.length); 797 AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = decorated().data[index]; 798 AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> previous = null; 799 while (entry != null) { 800 if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2)) { 801 final V oldValue = entry.getValue(); 802 decorated().removeMapping(entry, index, previous); 803 return oldValue; 804 } 805 previous = entry; 806 entry = entry.next; 807 } 808 return null; 809 } 810 811 /** 812 * Removes the specified multi-key from this map. 813 * 814 * @param key1 the first key 815 * @param key2 the second key 816 * @param key3 the third key 817 * @return the value mapped to the removed key, null if key not in map 818 * @since 4.0 (previous name: remove(Object, Object, Object)) 819 */ 820 public V removeMultiKey(final Object key1, final Object key2, final Object key3) { 821 final int hashCode = hash(key1, key2, key3); 822 final int index = decorated().hashIndex(hashCode, decorated().data.length); 823 AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = decorated().data[index]; 824 AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> previous = null; 825 while (entry != null) { 826 if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3)) { 827 final V oldValue = entry.getValue(); 828 decorated().removeMapping(entry, index, previous); 829 return oldValue; 830 } 831 previous = entry; 832 entry = entry.next; 833 } 834 return null; 835 } 836 837 /** 838 * Removes the specified multi-key from this map. 839 * 840 * @param key1 the first key 841 * @param key2 the second key 842 * @param key3 the third key 843 * @param key4 the fourth key 844 * @return the value mapped to the removed key, null if key not in map 845 * @since 4.0 (previous name: remove(Object, Object, Object, Object)) 846 */ 847 public V removeMultiKey(final Object key1, final Object key2, final Object key3, final Object key4) { 848 final int hashCode = hash(key1, key2, key3, key4); 849 final int index = decorated().hashIndex(hashCode, decorated().data.length); 850 AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = decorated().data[index]; 851 AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> previous = null; 852 while (entry != null) { 853 if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4)) { 854 final V oldValue = entry.getValue(); 855 decorated().removeMapping(entry, index, previous); 856 return oldValue; 857 } 858 previous = entry; 859 entry = entry.next; 860 } 861 return null; 862 } 863 864 /** 865 * Removes the specified multi-key from this map. 866 * 867 * @param key1 the first key 868 * @param key2 the second key 869 * @param key3 the third key 870 * @param key4 the fourth key 871 * @param key5 the fifth key 872 * @return the value mapped to the removed key, null if key not in map 873 * @since 4.0 (previous name: remove(Object, Object, Object, Object, Object)) 874 */ 875 public V removeMultiKey(final Object key1, final Object key2, final Object key3, 876 final Object key4, final Object key5) { 877 final int hashCode = hash(key1, key2, key3, key4, key5); 878 final int index = decorated().hashIndex(hashCode, decorated().data.length); 879 AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = decorated().data[index]; 880 AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> previous = null; 881 while (entry != null) { 882 if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4, key5)) { 883 final V oldValue = entry.getValue(); 884 decorated().removeMapping(entry, index, previous); 885 return oldValue; 886 } 887 previous = entry; 888 entry = entry.next; 889 } 890 return null; 891 } 892 893 /** 894 * Write the map out using a custom routine. 895 * 896 * @param out the output stream 897 * @throws IOException if an error occurs while writing to the stream 898 */ 899 private void writeObject(final ObjectOutputStream out) throws IOException { 900 out.defaultWriteObject(); 901 out.writeObject(map); 902 } 903 904}