View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.collections.map;
18  
19  import java.io.IOException;
20  import java.io.ObjectInputStream;
21  import java.io.ObjectOutputStream;
22  import java.io.Serializable;
23  import java.util.Map;
24  
25  import org.apache.commons.collections.IterableMap;
26  import org.apache.commons.collections.MapIterator;
27  import org.apache.commons.collections.keyvalue.MultiKey;
28  
29  /**
30   * A <code>Map</code> implementation that uses multiple keys to map the value.
31   * <p>
32   * This class is the most efficient way to uses multiple keys to map to a value.
33   * The best way to use this class is via the additional map-style methods.
34   * These provide <code>get</code>, <code>containsKey</code>, <code>put</code> and
35   * <code>remove</code> for individual keys which operate without extra object creation.
36   * <p>
37   * The additional methods are the main interface of this map.
38   * As such, you will not normally hold this map in a variable of type <code>Map</code>.
39   * <p>
40   * The normal map methods take in and return a {@link MultiKey}.
41   * If you try to use <code>put()</code> with any other object type a
42   * <code>ClassCastException</code> is thrown. If you try to use <code>null</code> as
43   * the key in <code>put()</code> a <code>NullPointerException</code> is thrown.
44   * <p>
45   * This map is implemented as a decorator of a <code>AbstractHashedMap</code> which
46   * enables extra behaviour to be added easily.
47   * <ul>
48   * <li><code>MultiKeyMap.decorate(new LinkedMap())</code> creates an ordered map.
49   * <li><code>MultiKeyMap.decorate(new LRUMap())</code> creates an least recently used map.
50   * <li><code>MultiKeyMap.decorate(new ReferenceMap())</code> creates a garbage collector sensitive map.
51   * </ul>
52   * Note that <code>IdentityMap</code> and <code>ReferenceIdentityMap</code> are unsuitable
53   * for use as the key comparison would work on the whole MultiKey, not the elements within.
54   * <p>
55   * As an example, consider a least recently used cache that uses a String airline code
56   * and a Locale to lookup the airline's name:
57   * <pre>
58   * private MultiKeyMap cache = MultiKeyMap.multiKeyMap(new LRUMap(50));
59   *
60   * public String getAirlineName(String code, String locale) {
61   *   String name = (String) cache.get(code, locale);
62   *   if (name == null) {
63   *     name = getAirlineNameFromDB(code, locale);
64   *     cache.put(code, locale, name);
65   *   }
66   *   return name;
67   * }
68   * </pre>
69   * <p>
70   * <strong>Note that MultiKeyMap is not synchronized and is not thread-safe.</strong>
71   * If you wish to use this map from multiple threads concurrently, you must use
72   * appropriate synchronization. This class may throw exceptions when accessed
73   * by concurrent threads without synchronization.
74   *
75   * @since 3.1
76   * @version $Id: MultiKeyMap.java 1449519 2013-02-24 18:17:27Z tn $
77   */
78  public class MultiKeyMap<K, V> extends AbstractMapDecorator<MultiKey<? extends K>, V>
79          implements IterableMap<MultiKey<? extends K>, V>, Serializable, Cloneable {
80  
81      /** Serialisation version */
82      private static final long serialVersionUID = -1788199231038721040L;
83  
84      //-----------------------------------------------------------------------
85      /**
86       * Decorates the specified map to add the MultiKeyMap API and fast query.
87       * The map must not be null and must be empty.
88       *
89       * @param <K>  the key type
90       * @param <V>  the value type
91       * @param map  the map to decorate, not null
92       * @return a new multi key map
93       * @throws IllegalArgumentException if the map is null or not empty
94       */
95      public static <K, V> MultiKeyMap<K, V> multiKeyMap(final AbstractHashedMap<MultiKey<? extends K>, V> map) {
96          if (map == null) {
97              throw new IllegalArgumentException("Map must not be null");
98          }
99          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      */
198     public V remove(final Object key1, final Object key2) {
199         final int hashCode = hash(key1, key2);
200         final int index = decorated().hashIndex(hashCode, decorated().data.length);
201         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = decorated().data[index];
202         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> previous = null;
203         while (entry != null) {
204             if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2)) {
205                 final V oldValue = entry.getValue();
206                 decorated().removeMapping(entry, index, previous);
207                 return oldValue;
208             }
209             previous = entry;
210             entry = entry.next;
211         }
212         return null;
213     }
214 
215     /**
216      * Gets the hash code for the specified multi-key.
217      * 
218      * @param key1  the first key
219      * @param key2  the second key
220      * @return the hash code
221      */
222     protected int hash(final Object key1, final Object key2) {
223         int h = 0;
224         if (key1 != null) {
225             h ^= key1.hashCode();
226         }
227         if (key2 != null) {
228             h ^= key2.hashCode();
229         }
230         h += ~(h << 9);
231         h ^=  h >>> 14;
232         h +=  h << 4;
233         h ^=  h >>> 10;
234         return h;
235     }
236 
237     /**
238      * Is the key equal to the combined key.
239      * 
240      * @param entry  the entry to compare to
241      * @param key1  the first key
242      * @param key2  the second key
243      * @return true if the key matches
244      */
245     protected boolean isEqualKey(final AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry,
246             final Object key1, final Object key2) {
247         final MultiKey<? extends K> multi = entry.getKey();
248         return
249             multi.size() == 2 &&
250             (key1 == multi.getKey(0) || key1 != null && key1.equals(multi.getKey(0))) &&
251             (key2 == multi.getKey(1) || key1 != null && key2.equals(multi.getKey(1)));
252     }
253 
254     //-----------------------------------------------------------------------
255     /**
256      * Gets the value mapped to the specified multi-key.
257      * 
258      * @param key1  the first key
259      * @param key2  the second key
260      * @param key3  the third key
261      * @return the mapped value, null if no match
262      */
263     public V get(final Object key1, final Object key2, final Object key3) {
264         final int hashCode = hash(key1, key2, key3);
265         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry =
266                 decorated().data[decorated().hashIndex(hashCode, decorated().data.length)];
267         while (entry != null) {
268             if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3)) {
269                 return entry.getValue();
270             }
271             entry = entry.next;
272         }
273         return null;
274     }
275 
276     /**
277      * Checks whether the map contains the specified multi-key.
278      * 
279      * @param key1  the first key
280      * @param key2  the second key
281      * @param key3  the third key
282      * @return true if the map contains the key
283      */
284     public boolean containsKey(final Object key1, final Object key2, final Object key3) {
285         final int hashCode = hash(key1, key2, key3);
286         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry =
287                 decorated().data[decorated().hashIndex(hashCode, decorated().data.length)];
288         while (entry != null) {
289             if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3)) {
290                 return true;
291             }
292             entry = entry.next;
293         }
294         return false;
295     }
296 
297     /**
298      * Stores the value against the specified multi-key.
299      * 
300      * @param key1  the first key
301      * @param key2  the second key
302      * @param key3  the third key
303      * @param value  the value to store
304      * @return the value previously mapped to this combined key, null if none
305      */
306     public V put(final K key1, final K key2, final K key3, final V value) {
307         final int hashCode = hash(key1, key2, key3);
308         final int index = decorated().hashIndex(hashCode, decorated().data.length);
309         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = decorated().data[index];
310         while (entry != null) {
311             if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3)) {
312                 final V oldValue = entry.getValue();
313                 decorated().updateEntry(entry, value);
314                 return oldValue;
315             }
316             entry = entry.next;
317         }
318         decorated().addMapping(index, hashCode, new MultiKey<K>(key1, key2, key3), value);
319         return null;
320     }
321 
322     /**
323      * Removes the specified multi-key from this map.
324      * 
325      * @param key1  the first key
326      * @param key2  the second key
327      * @param key3  the third key
328      * @return the value mapped to the removed key, null if key not in map
329      */
330     public V remove(final Object key1, final Object key2, final Object key3) {
331         final int hashCode = hash(key1, key2, key3);
332         final int index = decorated().hashIndex(hashCode, decorated().data.length);
333         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = decorated().data[index];
334         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> previous = null;
335         while (entry != null) {
336             if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3)) {
337                 final V oldValue = entry.getValue();
338                 decorated().removeMapping(entry, index, previous);
339                 return oldValue;
340             }
341             previous = entry;
342             entry = entry.next;
343         }
344         return null;
345     }
346 
347     /**
348      * Gets the hash code for the specified multi-key.
349      * 
350      * @param key1  the first key
351      * @param key2  the second key
352      * @param key3  the third key
353      * @return the hash code
354      */
355     protected int hash(final Object key1, final Object key2, final Object key3) {
356         int h = 0;
357         if (key1 != null) {
358             h ^= key1.hashCode();
359         }
360         if (key2 != null) {
361             h ^= key2.hashCode();
362         }
363         if (key3 != null) {
364             h ^= key3.hashCode();
365         }
366         h += ~(h << 9);
367         h ^=  h >>> 14;
368         h +=  h << 4;
369         h ^=  h >>> 10;
370         return h;
371     }
372 
373     /**
374      * Is the key equal to the combined key.
375      * 
376      * @param entry  the entry to compare to
377      * @param key1  the first key
378      * @param key2  the second key
379      * @param key3  the third key
380      * @return true if the key matches
381      */
382     protected boolean isEqualKey(final AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry,
383                                  final Object key1, final Object key2, final Object key3) {
384         final MultiKey<? extends K> multi = entry.getKey();
385         return
386             multi.size() == 3 &&
387             (key1 == multi.getKey(0) || key1 != null && key1.equals(multi.getKey(0))) &&
388             (key2 == multi.getKey(1) || key2 != null && key2.equals(multi.getKey(1))) &&
389             (key3 == multi.getKey(2) || key3 != null && key3.equals(multi.getKey(2)));
390     }
391 
392     //-----------------------------------------------------------------------
393     /**
394      * Gets the value mapped to the specified multi-key.
395      * 
396      * @param key1  the first key
397      * @param key2  the second key
398      * @param key3  the third key
399      * @param key4  the fourth key
400      * @return the mapped value, null if no match
401      */
402     public V get(final Object key1, final Object key2, final Object key3, final Object key4) {
403         final int hashCode = hash(key1, key2, key3, key4);
404         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry =
405                 decorated().data[decorated().hashIndex(hashCode, decorated().data.length)];
406         while (entry != null) {
407             if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4)) {
408                 return entry.getValue();
409             }
410             entry = entry.next;
411         }
412         return null;
413     }
414 
415     /**
416      * Checks whether the map contains the specified multi-key.
417      * 
418      * @param key1  the first key
419      * @param key2  the second key
420      * @param key3  the third key
421      * @param key4  the fourth key
422      * @return true if the map contains the key
423      */
424     public boolean containsKey(final Object key1, final Object key2, final Object key3, final Object key4) {
425         final int hashCode = hash(key1, key2, key3, key4);
426         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry =
427                 decorated().data[decorated().hashIndex(hashCode, decorated().data.length)];
428         while (entry != null) {
429             if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4)) {
430                 return true;
431             }
432             entry = entry.next;
433         }
434         return false;
435     }
436 
437     /**
438      * Stores the value against the specified multi-key.
439      * 
440      * @param key1  the first key
441      * @param key2  the second key
442      * @param key3  the third key
443      * @param key4  the fourth key
444      * @param value  the value to store
445      * @return the value previously mapped to this combined key, null if none
446      */
447     public V put(final K key1, final K key2, final K key3, final K key4, final V value) {
448         final int hashCode = hash(key1, key2, key3, key4);
449         final int index = decorated().hashIndex(hashCode, decorated().data.length);
450         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = decorated().data[index];
451         while (entry != null) {
452             if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4)) {
453                 final V oldValue = entry.getValue();
454                 decorated().updateEntry(entry, value);
455                 return oldValue;
456             }
457             entry = entry.next;
458         }
459         decorated().addMapping(index, hashCode, new MultiKey<K>(key1, key2, key3, key4), value);
460         return null;
461     }
462 
463     /**
464      * Removes the specified multi-key from this map.
465      * 
466      * @param key1  the first key
467      * @param key2  the second key
468      * @param key3  the third key
469      * @param key4  the fourth key
470      * @return the value mapped to the removed key, null if key not in map
471      */
472     public V remove(final Object key1, final Object key2, final Object key3, final Object key4) {
473         final int hashCode = hash(key1, key2, key3, key4);
474         final int index = decorated().hashIndex(hashCode, decorated().data.length);
475         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = decorated().data[index];
476         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> previous = null;
477         while (entry != null) {
478             if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4)) {
479                 final V oldValue = entry.getValue();
480                 decorated().removeMapping(entry, index, previous);
481                 return oldValue;
482             }
483             previous = entry;
484             entry = entry.next;
485         }
486         return null;
487     }
488 
489     /**
490      * Gets the hash code for the specified multi-key.
491      * 
492      * @param key1  the first key
493      * @param key2  the second key
494      * @param key3  the third key
495      * @param key4  the fourth key
496      * @return the hash code
497      */
498     protected int hash(final Object key1, final Object key2, final Object key3, final Object key4) {
499         int h = 0;
500         if (key1 != null) {
501             h ^= key1.hashCode();
502         }
503         if (key2 != null) {
504             h ^= key2.hashCode();
505         }
506         if (key3 != null) {
507             h ^= key3.hashCode();
508         }
509         if (key4 != null) {
510             h ^= key4.hashCode();
511         }
512         h += ~(h << 9);
513         h ^=  h >>> 14;
514         h +=  h << 4;
515         h ^=  h >>> 10;
516         return h;
517     }
518 
519     /**
520      * Is the key equal to the combined key.
521      * 
522      * @param entry  the entry to compare to
523      * @param key1  the first key
524      * @param key2  the second key
525      * @param key3  the third key
526      * @param key4  the fourth key
527      * @return true if the key matches
528      */
529     protected boolean isEqualKey(final AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry,
530                                  final Object key1, final Object key2, final Object key3, final Object key4) {
531         final MultiKey<? extends K> multi = entry.getKey();
532         return
533             multi.size() == 4 &&
534             (key1 == multi.getKey(0) || key1 != null && key1.equals(multi.getKey(0))) &&
535             (key2 == multi.getKey(1) || key2 != null && key2.equals(multi.getKey(1))) &&
536             (key3 == multi.getKey(2) || key3 != null && key3.equals(multi.getKey(2))) &&
537             (key4 == multi.getKey(3) || key4 != null && key4.equals(multi.getKey(3)));
538     }
539 
540     //-----------------------------------------------------------------------
541     /**
542      * Gets the value mapped to the specified multi-key.
543      * 
544      * @param key1  the first key
545      * @param key2  the second key
546      * @param key3  the third key
547      * @param key4  the fourth key
548      * @param key5  the fifth key
549      * @return the mapped value, null if no match
550      */
551     public V get(final Object key1, final Object key2, final Object key3, final Object key4, final Object key5) {
552         final int hashCode = hash(key1, key2, key3, key4, key5);
553         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry =
554                 decorated().data[decorated().hashIndex(hashCode, decorated().data.length)];
555         while (entry != null) {
556             if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4, key5)) {
557                 return entry.getValue();
558             }
559             entry = entry.next;
560         }
561         return null;
562     }
563 
564     /**
565      * Checks whether the map contains the specified multi-key.
566      * 
567      * @param key1  the first key
568      * @param key2  the second key
569      * @param key3  the third key
570      * @param key4  the fourth key
571      * @param key5  the fifth key
572      * @return true if the map contains the key
573      */
574     public boolean containsKey(final Object key1, final Object key2, final Object key3,
575                                final Object key4, final Object key5) {
576         final int hashCode = hash(key1, key2, key3, key4, key5);
577         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry =
578                 decorated().data[decorated().hashIndex(hashCode, decorated().data.length)];
579         while (entry != null) {
580             if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4, key5)) {
581                 return true;
582             }
583             entry = entry.next;
584         }
585         return false;
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 key4  the fourth key
595      * @param key5  the fifth key
596      * @param value  the value to store
597      * @return the value previously mapped to this combined key, null if none
598      */
599     public V put(final K key1, final K key2, final K key3, final K key4, final K key5, final V value) {
600         final int hashCode = hash(key1, key2, key3, key4, key5);
601         final int index = decorated().hashIndex(hashCode, decorated().data.length);
602         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = decorated().data[index];
603         while (entry != null) {
604             if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4, key5)) {
605                 final V oldValue = entry.getValue();
606                 decorated().updateEntry(entry, value);
607                 return oldValue;
608             }
609             entry = entry.next;
610         }
611         decorated().addMapping(index, hashCode, new MultiKey<K>(key1, key2, key3, key4, key5), value);
612         return null;
613     }
614 
615     /**
616      * Removes the specified multi-key from this map.
617      * 
618      * @param key1  the first key
619      * @param key2  the second key
620      * @param key3  the third key
621      * @param key4  the fourth key
622      * @param key5  the fifth key
623      * @return the value mapped to the removed key, null if key not in map
624      */
625     public V remove(final Object key1, final Object key2, final Object key3, final Object key4, final Object key5) {
626         final int hashCode = hash(key1, key2, key3, key4, key5);
627         final int index = decorated().hashIndex(hashCode, decorated().data.length);
628         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry = decorated().data[index];
629         AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> previous = null;
630         while (entry != null) {
631             if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3, key4, key5)) {
632                 final V oldValue = entry.getValue();
633                 decorated().removeMapping(entry, index, previous);
634                 return oldValue;
635             }
636             previous = entry;
637             entry = entry.next;
638         }
639         return null;
640     }
641 
642     /**
643      * Gets the hash code for the specified multi-key.
644      * 
645      * @param key1  the first key
646      * @param key2  the second key
647      * @param key3  the third key
648      * @param key4  the fourth key
649      * @param key5  the fifth key
650      * @return the hash code
651      */
652     protected int hash(final Object key1, final Object key2, final Object key3, final Object key4, final Object key5) {
653         int h = 0;
654         if (key1 != null) {
655             h ^= key1.hashCode();
656         }
657         if (key2 != null) {
658             h ^= key2.hashCode();
659         }
660         if (key3 != null) {
661             h ^= key3.hashCode();
662         }
663         if (key4 != null) {
664             h ^= key4.hashCode();
665         }
666         if (key5 != null) {
667             h ^= key5.hashCode();
668         }
669         h += ~(h << 9);
670         h ^=  h >>> 14;
671         h +=  h << 4;
672         h ^=  h >>> 10;
673         return h;
674     }
675 
676     /**
677      * Is the key equal to the combined key.
678      * 
679      * @param entry  the entry to compare to
680      * @param key1  the first key
681      * @param key2  the second key
682      * @param key3  the third key
683      * @param key4  the fourth key
684      * @param key5  the fifth key
685      * @return true if the key matches
686      */
687     protected boolean isEqualKey(final AbstractHashedMap.HashEntry<MultiKey<? extends K>, V> entry,
688             final Object key1, final Object key2, final Object key3, final Object key4, final Object key5) {
689         final MultiKey<? extends K> multi = entry.getKey();
690         return
691             multi.size() == 5 &&
692             (key1 == multi.getKey(0) || key1 != null && key1.equals(multi.getKey(0))) &&
693             (key2 == multi.getKey(1) || key2 != null && key2.equals(multi.getKey(1))) &&
694             (key3 == multi.getKey(2) || key3 != null && key3.equals(multi.getKey(2))) &&
695             (key4 == multi.getKey(3) || key4 != null && key4.equals(multi.getKey(3))) &&
696             (key5 == multi.getKey(4) || key5 != null && key5.equals(multi.getKey(4)));
697     }
698 
699     //-----------------------------------------------------------------------
700     /**
701      * Removes all mappings where the first key is that specified.
702      * <p>
703      * This method removes all the mappings where the <code>MultiKey</code>
704      * has one or more keys, and the first matches that specified.
705      * 
706      * @param key1  the first key
707      * @return true if any elements were removed
708      */
709     public boolean removeAll(final Object key1) {
710         boolean modified = false;
711         final MapIterator<MultiKey<? extends K>, V> it = mapIterator();
712         while (it.hasNext()) {
713             final MultiKey<? extends K> multi = it.next();
714             if (multi.size() >= 1 &&
715                 (key1 == null ? multi.getKey(0) == null : key1.equals(multi.getKey(0)))) {
716                 it.remove();
717                 modified = true;
718             }
719         }
720         return modified;
721     }
722 
723     /**
724      * Removes all mappings where the first two keys are those specified.
725      * <p>
726      * This method removes all the mappings where the <code>MultiKey</code>
727      * has two or more keys, and the first two match those specified.
728      * 
729      * @param key1  the first key
730      * @param key2  the second key
731      * @return true if any elements were removed
732      */
733     public boolean removeAll(final Object key1, final Object key2) {
734         boolean modified = false;
735         final MapIterator<MultiKey<? extends K>, V> it = mapIterator();
736         while (it.hasNext()) {
737             final MultiKey<? extends K> multi = it.next();
738             if (multi.size() >= 2 &&
739                 (key1 == null ? multi.getKey(0) == null : key1.equals(multi.getKey(0))) &&
740                 (key2 == null ? multi.getKey(1) == null : key2.equals(multi.getKey(1)))) {
741                 it.remove();
742                 modified = true;
743             }
744         }
745         return modified;
746     }
747 
748     /**
749      * Removes all mappings where the first three keys are those specified.
750      * <p>
751      * This method removes all the mappings where the <code>MultiKey</code>
752      * has three or more keys, and the first three match those specified.
753      * 
754      * @param key1  the first key
755      * @param key2  the second key
756      * @param key3  the third key
757      * @return true if any elements were removed
758      */
759     public boolean removeAll(final Object key1, final Object key2, final Object key3) {
760         boolean modified = false;
761         final MapIterator<MultiKey<? extends K>, V> it = mapIterator();
762         while (it.hasNext()) {
763             final MultiKey<? extends K> multi = it.next();
764             if (multi.size() >= 3 &&
765                 (key1 == null ? multi.getKey(0) == null : key1.equals(multi.getKey(0))) &&
766                 (key2 == null ? multi.getKey(1) == null : key2.equals(multi.getKey(1))) &&
767                 (key3 == null ? multi.getKey(2) == null : key3.equals(multi.getKey(2)))) {
768                 it.remove();
769                 modified = true;
770             }
771         }
772         return modified;
773     }
774 
775     /**
776      * Removes all mappings where the first four keys are those specified.
777      * <p>
778      * This method removes all the mappings where the <code>MultiKey</code>
779      * has four or more keys, and the first four match those specified.
780      * 
781      * @param key1  the first key
782      * @param key2  the second key
783      * @param key3  the third key
784      * @param key4  the fourth key
785      * @return true if any elements were removed
786      */
787     public boolean removeAll(final Object key1, final Object key2, final Object key3, final Object key4) {
788         boolean modified = false;
789         final MapIterator<MultiKey<? extends K>, V> it = mapIterator();
790         while (it.hasNext()) {
791             final MultiKey<? extends K> multi = it.next();
792             if (multi.size() >= 4 &&
793                 (key1 == null ? multi.getKey(0) == null : key1.equals(multi.getKey(0))) &&
794                 (key2 == null ? multi.getKey(1) == null : key2.equals(multi.getKey(1))) &&
795                 (key3 == null ? multi.getKey(2) == null : key3.equals(multi.getKey(2))) &&
796                 (key4 == null ? multi.getKey(3) == null : key4.equals(multi.getKey(3)))) {
797                 it.remove();
798                 modified = true;
799             }
800         }
801         return modified;
802     }
803 
804     //-----------------------------------------------------------------------
805     /**
806      * Check to ensure that input keys are valid MultiKey objects.
807      * 
808      * @param key  the key to check
809      */
810     protected void checkKey(final MultiKey<?> key) {
811         if (key == null) {
812             throw new NullPointerException("Key must not be null");
813         }
814     }
815 
816     /**
817      * Clones the map without cloning the keys or values.
818      *
819      * @return a shallow clone
820      */
821     @SuppressWarnings("unchecked")
822     @Override
823     public MultiKeyMap<K, V> clone() {
824         try {
825             return (MultiKeyMap<K, V>) super.clone();
826         } catch (CloneNotSupportedException e) {
827             throw new InternalError();
828         }
829     }
830 
831     /**
832      * Puts the key and value into the map, where the key must be a non-null
833      * MultiKey object.
834      * 
835      * @param key  the non-null MultiKey object
836      * @param value  the value to store
837      * @return the previous value for the key
838      * @throws NullPointerException if the key is null
839      * @throws ClassCastException if the key is not a MultiKey
840      */
841     @Override
842     public V put(final MultiKey<? extends K> key, final V value) {
843         checkKey(key);
844         return super.put(key, value);
845     }
846 
847     /**
848      * Copies all of the keys and values from the specified map to this map.
849      * Each key must be non-null and a MultiKey object.
850      * 
851      * @param mapToCopy  to this map
852      * @throws NullPointerException if the mapToCopy or any key within is null
853      * @throws ClassCastException if any key in mapToCopy is not a MultiKey
854      */
855     @Override
856     public void putAll(final Map<? extends MultiKey<? extends K>, ? extends V> mapToCopy) {
857         for (final MultiKey<? extends K> key : mapToCopy.keySet()) {
858             checkKey(key);
859         }
860         super.putAll(mapToCopy);
861     }
862 
863     //-----------------------------------------------------------------------
864     @Override
865     public MapIterator<MultiKey<? extends K>, V> mapIterator() {
866         return decorated().mapIterator();
867     }
868 
869     /**
870      * {@inheritDoc}
871      */
872     @Override
873     protected AbstractHashedMap<MultiKey<? extends K>, V> decorated() {
874         return (AbstractHashedMap<MultiKey<? extends K>, V>) super.decorated();
875     }
876     
877     //-----------------------------------------------------------------------
878     /**
879      * Write the map out using a custom routine.
880      * 
881      * @param out  the output stream
882      * @throws IOException
883      */
884     private void writeObject(final ObjectOutputStream out) throws IOException {
885         out.defaultWriteObject();
886         out.writeObject(map);
887     }
888 
889     /**
890      * Read the map in using a custom routine.
891      * 
892      * @param in  the input stream
893      * @throws IOException
894      * @throws ClassNotFoundException
895      */
896     @SuppressWarnings("unchecked")
897     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
898         in.defaultReadObject();
899         map = (Map<MultiKey<? extends K>, V>) in.readObject();
900     }
901     
902 }