001: /*
002: * Copyright 2004 The Apache Software Foundation
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.apache.commons.collections.map;
017:
018: import java.io.Serializable;
019: import java.util.Collection;
020: import java.util.Iterator;
021: import java.util.Map;
022: import java.util.Set;
023:
024: import org.apache.commons.collections.IterableMap;
025: import org.apache.commons.collections.MapIterator;
026: import org.apache.commons.collections.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.decorate(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 Commons Collections 3.1
075: * @version $Revision: 348007 $ $Date: 2005-11-21 22:52:57 +0000 (Mon, 21 Nov 2005) $
076: *
077: * @author Stephen Colebourne
078: */
079: public class MultiKeyMap implements IterableMap, Serializable {
080:
081: /** Serialisation version */
082: private static final long serialVersionUID = -1788199231038721040L;
083:
084: /** The decorated map */
085: protected final AbstractHashedMap map;
086:
087: //-----------------------------------------------------------------------
088: /**
089: * Decorates the specified map to add the MultiKeyMap API and fast query.
090: * The map must not be null and must be empty.
091: *
092: * @param map the map to decorate, not null
093: * @throws IllegalArgumentException if the map is null or not empty
094: */
095: public static MultiKeyMap decorate(AbstractHashedMap 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(map);
103: }
104:
105: //-----------------------------------------------------------------------
106: /**
107: * Constructs a new MultiKeyMap that decorates a <code>HashedMap</code>.
108: */
109: public MultiKeyMap() {
110: super ();
111: map = new HashedMap();
112: }
113:
114: /**
115: * Constructor that decorates the specified map and is called from
116: * {@link #decorate(AbstractHashedMap)}.
117: * The map must not be null and should be empty or only contain valid keys.
118: * This constructor performs no validation.
119: *
120: * @param map the map to decorate
121: */
122: protected MultiKeyMap(AbstractHashedMap map) {
123: super ();
124: this .map = map;
125: }
126:
127: //-----------------------------------------------------------------------
128: /**
129: * Gets the value mapped to the specified multi-key.
130: *
131: * @param key1 the first key
132: * @param key2 the second key
133: * @return the mapped value, null if no match
134: */
135: public Object get(Object key1, Object key2) {
136: int hashCode = hash(key1, key2);
137: AbstractHashedMap.HashEntry entry = map.data[map.hashIndex(
138: hashCode, map.data.length)];
139: while (entry != null) {
140: if (entry.hashCode == hashCode
141: && 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(
159: hashCode, map.data.length)];
160: while (entry != null) {
161: if (entry.hashCode == hashCode
162: && isEqualKey(entry, key1, key2)) {
163: return true;
164: }
165: entry = entry.next;
166: }
167: return false;
168: }
169:
170: /**
171: * Stores the value against the specified multi-key.
172: *
173: * @param key1 the first key
174: * @param key2 the second key
175: * @param value the value to store
176: * @return the value previously mapped to this combined key, null if none
177: */
178: public Object put(Object key1, Object key2, Object value) {
179: int hashCode = hash(key1, key2);
180: int index = map.hashIndex(hashCode, map.data.length);
181: AbstractHashedMap.HashEntry entry = map.data[index];
182: while (entry != null) {
183: if (entry.hashCode == hashCode
184: && isEqualKey(entry, key1, key2)) {
185: Object oldValue = entry.getValue();
186: map.updateEntry(entry, value);
187: return oldValue;
188: }
189: entry = entry.next;
190: }
191:
192: map
193: .addMapping(index, hashCode, new MultiKey(key1, key2),
194: value);
195: return null;
196: }
197:
198: /**
199: * Removes the specified multi-key from this map.
200: *
201: * @param key1 the first key
202: * @param key2 the second key
203: * @return the value mapped to the removed key, null if key not in map
204: */
205: public Object remove(Object key1, Object key2) {
206: int hashCode = hash(key1, key2);
207: int index = map.hashIndex(hashCode, map.data.length);
208: AbstractHashedMap.HashEntry entry = map.data[index];
209: AbstractHashedMap.HashEntry previous = null;
210: while (entry != null) {
211: if (entry.hashCode == hashCode
212: && isEqualKey(entry, key1, key2)) {
213: Object oldValue = entry.getValue();
214: map.removeMapping(entry, index, previous);
215: return oldValue;
216: }
217: previous = entry;
218: entry = entry.next;
219: }
220: return null;
221: }
222:
223: /**
224: * Gets the hash code for the specified multi-key.
225: *
226: * @param key1 the first key
227: * @param key2 the second key
228: * @return the hash code
229: */
230: protected int hash(Object key1, Object key2) {
231: int h = 0;
232: if (key1 != null) {
233: h ^= key1.hashCode();
234: }
235: if (key2 != null) {
236: h ^= key2.hashCode();
237: }
238: h += ~(h << 9);
239: h ^= (h >>> 14);
240: h += (h << 4);
241: h ^= (h >>> 10);
242: return h;
243: }
244:
245: /**
246: * Is the key equal to the combined key.
247: *
248: * @param entry the entry to compare to
249: * @param key1 the first key
250: * @param key2 the second key
251: * @return true if the key matches
252: */
253: protected boolean isEqualKey(AbstractHashedMap.HashEntry entry,
254: Object key1, Object key2) {
255: MultiKey multi = (MultiKey) entry.getKey();
256: return multi.size() == 2
257: && (key1 == null ? multi.getKey(0) == null : key1
258: .equals(multi.getKey(0)))
259: && (key2 == null ? multi.getKey(1) == null : key2
260: .equals(multi.getKey(1)));
261: }
262:
263: //-----------------------------------------------------------------------
264: /**
265: * Gets the value mapped to the specified multi-key.
266: *
267: * @param key1 the first key
268: * @param key2 the second key
269: * @param key3 the third key
270: * @return the mapped value, null if no match
271: */
272: public Object get(Object key1, Object key2, Object key3) {
273: int hashCode = hash(key1, key2, key3);
274: AbstractHashedMap.HashEntry entry = map.data[map.hashIndex(
275: hashCode, map.data.length)];
276: while (entry != null) {
277: if (entry.hashCode == hashCode
278: && isEqualKey(entry, key1, key2, key3)) {
279: return entry.getValue();
280: }
281: entry = entry.next;
282: }
283: return null;
284: }
285:
286: /**
287: * Checks whether the map contains the specified multi-key.
288: *
289: * @param key1 the first key
290: * @param key2 the second key
291: * @param key3 the third key
292: * @return true if the map contains the key
293: */
294: public boolean containsKey(Object key1, Object key2, Object key3) {
295: int hashCode = hash(key1, key2, key3);
296: AbstractHashedMap.HashEntry entry = map.data[map.hashIndex(
297: hashCode, map.data.length)];
298: while (entry != null) {
299: if (entry.hashCode == hashCode
300: && isEqualKey(entry, key1, key2, key3)) {
301: return true;
302: }
303: entry = entry.next;
304: }
305: return false;
306: }
307:
308: /**
309: * Stores the value against the specified multi-key.
310: *
311: * @param key1 the first key
312: * @param key2 the second key
313: * @param key3 the third key
314: * @param value the value to store
315: * @return the value previously mapped to this combined key, null if none
316: */
317: public Object put(Object key1, Object key2, Object key3,
318: Object value) {
319: int hashCode = hash(key1, key2, key3);
320: int index = map.hashIndex(hashCode, map.data.length);
321: AbstractHashedMap.HashEntry entry = map.data[index];
322: while (entry != null) {
323: if (entry.hashCode == hashCode
324: && isEqualKey(entry, key1, key2, key3)) {
325: Object oldValue = entry.getValue();
326: map.updateEntry(entry, value);
327: return oldValue;
328: }
329: entry = entry.next;
330: }
331:
332: map.addMapping(index, hashCode, new MultiKey(key1, key2, key3),
333: value);
334: return null;
335: }
336:
337: /**
338: * Removes the specified multi-key from this map.
339: *
340: * @param key1 the first key
341: * @param key2 the second key
342: * @param key3 the third key
343: * @return the value mapped to the removed key, null if key not in map
344: */
345: public Object remove(Object key1, Object key2, Object key3) {
346: int hashCode = hash(key1, key2, key3);
347: int index = map.hashIndex(hashCode, map.data.length);
348: AbstractHashedMap.HashEntry entry = map.data[index];
349: AbstractHashedMap.HashEntry previous = null;
350: while (entry != null) {
351: if (entry.hashCode == hashCode
352: && isEqualKey(entry, key1, key2, key3)) {
353: Object oldValue = entry.getValue();
354: map.removeMapping(entry, index, previous);
355: return oldValue;
356: }
357: previous = entry;
358: entry = entry.next;
359: }
360: return null;
361: }
362:
363: /**
364: * Gets the hash code for the specified multi-key.
365: *
366: * @param key1 the first key
367: * @param key2 the second key
368: * @param key3 the third key
369: * @return the hash code
370: */
371: protected int hash(Object key1, Object key2, Object key3) {
372: int h = 0;
373: if (key1 != null) {
374: h ^= key1.hashCode();
375: }
376: if (key2 != null) {
377: h ^= key2.hashCode();
378: }
379: if (key3 != null) {
380: h ^= key3.hashCode();
381: }
382: h += ~(h << 9);
383: h ^= (h >>> 14);
384: h += (h << 4);
385: h ^= (h >>> 10);
386: return h;
387: }
388:
389: /**
390: * Is the key equal to the combined key.
391: *
392: * @param entry the entry to compare to
393: * @param key1 the first key
394: * @param key2 the second key
395: * @param key3 the third key
396: * @return true if the key matches
397: */
398: protected boolean isEqualKey(AbstractHashedMap.HashEntry entry,
399: Object key1, Object key2, Object key3) {
400: MultiKey multi = (MultiKey) entry.getKey();
401: return multi.size() == 3
402: && (key1 == null ? multi.getKey(0) == null : key1
403: .equals(multi.getKey(0)))
404: && (key2 == null ? multi.getKey(1) == null : key2
405: .equals(multi.getKey(1)))
406: && (key3 == null ? multi.getKey(2) == null : key3
407: .equals(multi.getKey(2)));
408: }
409:
410: //-----------------------------------------------------------------------
411: /**
412: * Gets the value mapped to the specified multi-key.
413: *
414: * @param key1 the first key
415: * @param key2 the second key
416: * @param key3 the third key
417: * @param key4 the fourth key
418: * @return the mapped value, null if no match
419: */
420: public Object get(Object key1, Object key2, Object key3, Object key4) {
421: int hashCode = hash(key1, key2, key3, key4);
422: AbstractHashedMap.HashEntry entry = map.data[map.hashIndex(
423: hashCode, map.data.length)];
424: while (entry != null) {
425: if (entry.hashCode == hashCode
426: && isEqualKey(entry, key1, key2, key3, key4)) {
427: return entry.getValue();
428: }
429: entry = entry.next;
430: }
431: return null;
432: }
433:
434: /**
435: * Checks whether the map contains 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: * @return true if the map contains the key
442: */
443: public boolean containsKey(Object key1, Object key2, Object key3,
444: Object key4) {
445: int hashCode = hash(key1, key2, key3, key4);
446: AbstractHashedMap.HashEntry entry = map.data[map.hashIndex(
447: hashCode, map.data.length)];
448: while (entry != null) {
449: if (entry.hashCode == hashCode
450: && isEqualKey(entry, key1, key2, key3, key4)) {
451: return true;
452: }
453: entry = entry.next;
454: }
455: return false;
456: }
457:
458: /**
459: * Stores the value against the specified multi-key.
460: *
461: * @param key1 the first key
462: * @param key2 the second key
463: * @param key3 the third key
464: * @param key4 the fourth key
465: * @param value the value to store
466: * @return the value previously mapped to this combined key, null if none
467: */
468: public Object put(Object key1, Object key2, Object key3,
469: Object key4, Object value) {
470: int hashCode = hash(key1, key2, key3, key4);
471: int index = map.hashIndex(hashCode, map.data.length);
472: AbstractHashedMap.HashEntry entry = map.data[index];
473: while (entry != null) {
474: if (entry.hashCode == hashCode
475: && isEqualKey(entry, key1, key2, key3, key4)) {
476: Object oldValue = entry.getValue();
477: map.updateEntry(entry, value);
478: return oldValue;
479: }
480: entry = entry.next;
481: }
482:
483: map.addMapping(index, hashCode, new MultiKey(key1, key2, key3,
484: key4), value);
485: return null;
486: }
487:
488: /**
489: * Removes the specified multi-key from this map.
490: *
491: * @param key1 the first key
492: * @param key2 the second key
493: * @param key3 the third key
494: * @param key4 the fourth key
495: * @return the value mapped to the removed key, null if key not in map
496: */
497: public Object remove(Object key1, Object key2, Object key3,
498: Object key4) {
499: int hashCode = hash(key1, key2, key3, key4);
500: int index = map.hashIndex(hashCode, map.data.length);
501: AbstractHashedMap.HashEntry entry = map.data[index];
502: AbstractHashedMap.HashEntry previous = null;
503: while (entry != null) {
504: if (entry.hashCode == hashCode
505: && isEqualKey(entry, key1, key2, key3, key4)) {
506: Object oldValue = entry.getValue();
507: map.removeMapping(entry, index, previous);
508: return oldValue;
509: }
510: previous = entry;
511: entry = entry.next;
512: }
513: return null;
514: }
515:
516: /**
517: * Gets the hash code for the specified multi-key.
518: *
519: * @param key1 the first key
520: * @param key2 the second key
521: * @param key3 the third key
522: * @param key4 the fourth key
523: * @return the hash code
524: */
525: protected int hash(Object key1, Object key2, Object key3,
526: Object key4) {
527: int h = 0;
528: if (key1 != null) {
529: h ^= key1.hashCode();
530: }
531: if (key2 != null) {
532: h ^= key2.hashCode();
533: }
534: if (key3 != null) {
535: h ^= key3.hashCode();
536: }
537: if (key4 != null) {
538: h ^= key4.hashCode();
539: }
540: h += ~(h << 9);
541: h ^= (h >>> 14);
542: h += (h << 4);
543: h ^= (h >>> 10);
544: return h;
545: }
546:
547: /**
548: * Is the key equal to the combined key.
549: *
550: * @param entry the entry to compare to
551: * @param key1 the first key
552: * @param key2 the second key
553: * @param key3 the third key
554: * @param key4 the fourth key
555: * @return true if the key matches
556: */
557: protected boolean isEqualKey(AbstractHashedMap.HashEntry entry,
558: Object key1, Object key2, Object key3, Object key4) {
559: MultiKey multi = (MultiKey) entry.getKey();
560: return multi.size() == 4
561: && (key1 == null ? multi.getKey(0) == null : key1
562: .equals(multi.getKey(0)))
563: && (key2 == null ? multi.getKey(1) == null : key2
564: .equals(multi.getKey(1)))
565: && (key3 == null ? multi.getKey(2) == null : key3
566: .equals(multi.getKey(2)))
567: && (key4 == null ? multi.getKey(3) == null : key4
568: .equals(multi.getKey(3)));
569: }
570:
571: //-----------------------------------------------------------------------
572: /**
573: * Gets the value mapped to the specified multi-key.
574: *
575: * @param key1 the first key
576: * @param key2 the second key
577: * @param key3 the third key
578: * @param key4 the fourth key
579: * @param key5 the fifth key
580: * @return the mapped value, null if no match
581: */
582: public Object get(Object key1, Object key2, Object key3,
583: Object key4, Object key5) {
584: int hashCode = hash(key1, key2, key3, key4, key5);
585: AbstractHashedMap.HashEntry entry = map.data[map.hashIndex(
586: hashCode, map.data.length)];
587: while (entry != null) {
588: if (entry.hashCode == hashCode
589: && isEqualKey(entry, key1, key2, key3, key4, key5)) {
590: return entry.getValue();
591: }
592: entry = entry.next;
593: }
594: return null;
595: }
596:
597: /**
598: * Checks whether the map contains the specified multi-key.
599: *
600: * @param key1 the first key
601: * @param key2 the second key
602: * @param key3 the third key
603: * @param key4 the fourth key
604: * @param key5 the fifth key
605: * @return true if the map contains the key
606: */
607: public boolean containsKey(Object key1, Object key2, Object key3,
608: Object key4, Object key5) {
609: int hashCode = hash(key1, key2, key3, key4, key5);
610: AbstractHashedMap.HashEntry entry = map.data[map.hashIndex(
611: hashCode, map.data.length)];
612: while (entry != null) {
613: if (entry.hashCode == hashCode
614: && isEqualKey(entry, key1, key2, key3, key4, key5)) {
615: return true;
616: }
617: entry = entry.next;
618: }
619: return false;
620: }
621:
622: /**
623: * Stores the value against the specified multi-key.
624: *
625: * @param key1 the first key
626: * @param key2 the second key
627: * @param key3 the third key
628: * @param key4 the fourth key
629: * @param key5 the fifth key
630: * @param value the value to store
631: * @return the value previously mapped to this combined key, null if none
632: */
633: public Object put(Object key1, Object key2, Object key3,
634: Object key4, Object key5, Object value) {
635: int hashCode = hash(key1, key2, key3, key4, key5);
636: int index = map.hashIndex(hashCode, map.data.length);
637: AbstractHashedMap.HashEntry entry = map.data[index];
638: while (entry != null) {
639: if (entry.hashCode == hashCode
640: && isEqualKey(entry, key1, key2, key3, key4, key5)) {
641: Object oldValue = entry.getValue();
642: map.updateEntry(entry, value);
643: return oldValue;
644: }
645: entry = entry.next;
646: }
647:
648: map.addMapping(index, hashCode, new MultiKey(key1, key2, key3,
649: key4, key5), value);
650: return null;
651: }
652:
653: /**
654: * Removes the specified multi-key from this map.
655: *
656: * @param key1 the first key
657: * @param key2 the second key
658: * @param key3 the third key
659: * @param key4 the fourth key
660: * @param key5 the fifth key
661: * @return the value mapped to the removed key, null if key not in map
662: */
663: public Object remove(Object key1, Object key2, Object key3,
664: Object key4, Object key5) {
665: int hashCode = hash(key1, key2, key3, key4, key5);
666: int index = map.hashIndex(hashCode, map.data.length);
667: AbstractHashedMap.HashEntry entry = map.data[index];
668: AbstractHashedMap.HashEntry previous = null;
669: while (entry != null) {
670: if (entry.hashCode == hashCode
671: && isEqualKey(entry, key1, key2, key3, key4, key5)) {
672: Object oldValue = entry.getValue();
673: map.removeMapping(entry, index, previous);
674: return oldValue;
675: }
676: previous = entry;
677: entry = entry.next;
678: }
679: return null;
680: }
681:
682: /**
683: * Gets the hash code for the specified multi-key.
684: *
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 the hash code
691: */
692: protected int hash(Object key1, Object key2, Object key3,
693: Object key4, Object key5) {
694: int h = 0;
695: if (key1 != null) {
696: h ^= key1.hashCode();
697: }
698: if (key2 != null) {
699: h ^= key2.hashCode();
700: }
701: if (key3 != null) {
702: h ^= key3.hashCode();
703: }
704: if (key4 != null) {
705: h ^= key4.hashCode();
706: }
707: if (key5 != null) {
708: h ^= key5.hashCode();
709: }
710: h += ~(h << 9);
711: h ^= (h >>> 14);
712: h += (h << 4);
713: h ^= (h >>> 10);
714: return h;
715: }
716:
717: /**
718: * Is the key equal to the combined key.
719: *
720: * @param entry the entry to compare to
721: * @param key1 the first key
722: * @param key2 the second key
723: * @param key3 the third key
724: * @param key4 the fourth key
725: * @param key5 the fifth key
726: * @return true if the key matches
727: */
728: protected boolean isEqualKey(AbstractHashedMap.HashEntry entry,
729: Object key1, Object key2, Object key3, Object key4,
730: Object key5) {
731: MultiKey multi = (MultiKey) entry.getKey();
732: return multi.size() == 5
733: && (key1 == null ? multi.getKey(0) == null : key1
734: .equals(multi.getKey(0)))
735: && (key2 == null ? multi.getKey(1) == null : key2
736: .equals(multi.getKey(1)))
737: && (key3 == null ? multi.getKey(2) == null : key3
738: .equals(multi.getKey(2)))
739: && (key4 == null ? multi.getKey(3) == null : key4
740: .equals(multi.getKey(3)))
741: && (key5 == null ? multi.getKey(4) == null : key5
742: .equals(multi.getKey(4)));
743: }
744:
745: //-----------------------------------------------------------------------
746: /**
747: * Removes all mappings where the first key is that specified.
748: * <p>
749: * This method removes all the mappings where the <code>MultiKey</code>
750: * has one or more keys, and the first matches that specified.
751: *
752: * @param key1 the first key
753: * @return true if any elements were removed
754: */
755: public boolean removeAll(Object key1) {
756: boolean modified = false;
757: MapIterator it = mapIterator();
758: while (it.hasNext()) {
759: MultiKey multi = (MultiKey) it.next();
760: if (multi.size() >= 1
761: && (key1 == null ? multi.getKey(0) == null : key1
762: .equals(multi.getKey(0)))) {
763: it.remove();
764: modified = true;
765: }
766: }
767: return modified;
768: }
769:
770: /**
771: * Removes all mappings where the first two keys are those specified.
772: * <p>
773: * This method removes all the mappings where the <code>MultiKey</code>
774: * has two or more keys, and the first two match those specified.
775: *
776: * @param key1 the first key
777: * @param key2 the second key
778: * @return true if any elements were removed
779: */
780: public boolean removeAll(Object key1, Object key2) {
781: boolean modified = false;
782: MapIterator it = mapIterator();
783: while (it.hasNext()) {
784: MultiKey multi = (MultiKey) it.next();
785: if (multi.size() >= 2
786: && (key1 == null ? multi.getKey(0) == null : key1
787: .equals(multi.getKey(0)))
788: && (key2 == null ? multi.getKey(1) == null : key2
789: .equals(multi.getKey(1)))) {
790: it.remove();
791: modified = true;
792: }
793: }
794: return modified;
795: }
796:
797: /**
798: * Removes all mappings where the first three keys are those specified.
799: * <p>
800: * This method removes all the mappings where the <code>MultiKey</code>
801: * has three or more keys, and the first three match those specified.
802: *
803: * @param key1 the first key
804: * @param key2 the second key
805: * @param key3 the third key
806: * @return true if any elements were removed
807: */
808: public boolean removeAll(Object key1, Object key2, Object key3) {
809: boolean modified = false;
810: MapIterator it = mapIterator();
811: while (it.hasNext()) {
812: MultiKey multi = (MultiKey) it.next();
813: if (multi.size() >= 3
814: && (key1 == null ? multi.getKey(0) == null : key1
815: .equals(multi.getKey(0)))
816: && (key2 == null ? multi.getKey(1) == null : key2
817: .equals(multi.getKey(1)))
818: && (key3 == null ? multi.getKey(2) == null : key3
819: .equals(multi.getKey(2)))) {
820: it.remove();
821: modified = true;
822: }
823: }
824: return modified;
825: }
826:
827: /**
828: * Removes all mappings where the first four keys are those specified.
829: * <p>
830: * This method removes all the mappings where the <code>MultiKey</code>
831: * has four or more keys, and the first four match those specified.
832: *
833: * @param key1 the first key
834: * @param key2 the second key
835: * @param key3 the third key
836: * @param key4 the fourth key
837: * @return true if any elements were removed
838: */
839: public boolean removeAll(Object key1, Object key2, Object key3,
840: Object key4) {
841: boolean modified = false;
842: MapIterator it = mapIterator();
843: while (it.hasNext()) {
844: MultiKey multi = (MultiKey) it.next();
845: if (multi.size() >= 4
846: && (key1 == null ? multi.getKey(0) == null : key1
847: .equals(multi.getKey(0)))
848: && (key2 == null ? multi.getKey(1) == null : key2
849: .equals(multi.getKey(1)))
850: && (key3 == null ? multi.getKey(2) == null : key3
851: .equals(multi.getKey(2)))
852: && (key4 == null ? multi.getKey(3) == null : key4
853: .equals(multi.getKey(3)))) {
854: it.remove();
855: modified = true;
856: }
857: }
858: return modified;
859: }
860:
861: //-----------------------------------------------------------------------
862: /**
863: * Check to ensure that input keys are valid MultiKey objects.
864: *
865: * @param key the key to check
866: */
867: protected void checkKey(Object key) {
868: if (key == null) {
869: throw new NullPointerException("Key must not be null");
870: }
871: if (key instanceof MultiKey == false) {
872: throw new ClassCastException("Key must be a MultiKey");
873: }
874: }
875:
876: /**
877: * Clones the map without cloning the keys or values.
878: *
879: * @return a shallow clone
880: */
881: public Object clone() {
882: return new MultiKeyMap((AbstractHashedMap) map.clone());
883: }
884:
885: /**
886: * Puts the key and value into the map, where the key must be a non-null
887: * MultiKey object.
888: *
889: * @param key the non-null MultiKey object
890: * @param value the value to store
891: * @return the previous value for the key
892: * @throws NullPointerException if the key is null
893: * @throws ClassCastException if the key is not a MultiKey
894: */
895: public Object put(Object key, Object value) {
896: checkKey(key);
897: return map.put(key, value);
898: }
899:
900: /**
901: * Copies all of the keys and values from the specified map to this map.
902: * Each key must be non-null and a MultiKey object.
903: *
904: * @param mapToCopy to this map
905: * @throws NullPointerException if the mapToCopy or any key within is null
906: * @throws ClassCastException if any key in mapToCopy is not a MultiKey
907: */
908: public void putAll(Map mapToCopy) {
909: for (Iterator it = mapToCopy.keySet().iterator(); it.hasNext();) {
910: Object key = it.next();
911: checkKey(key);
912: }
913: map.putAll(mapToCopy);
914: }
915:
916: //-----------------------------------------------------------------------
917: public MapIterator mapIterator() {
918: return map.mapIterator();
919: }
920:
921: public int size() {
922: return map.size();
923: }
924:
925: public boolean isEmpty() {
926: return map.isEmpty();
927: }
928:
929: public boolean containsKey(Object key) {
930: return map.containsKey(key);
931: }
932:
933: public boolean containsValue(Object value) {
934: return map.containsValue(value);
935: }
936:
937: public Object get(Object key) {
938: return map.get(key);
939: }
940:
941: public Object remove(Object key) {
942: return map.remove(key);
943: }
944:
945: public void clear() {
946: map.clear();
947: }
948:
949: public Set keySet() {
950: return map.keySet();
951: }
952:
953: public Collection values() {
954: return map.values();
955: }
956:
957: public Set entrySet() {
958: return map.entrySet();
959: }
960:
961: public boolean equals(Object obj) {
962: if (obj == this ) {
963: return true;
964: }
965: return map.equals(obj);
966: }
967:
968: public int hashCode() {
969: return map.hashCode();
970: }
971:
972: public String toString() {
973: return map.toString();
974: }
975:
976: }
|