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