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