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