001: /*
002: * @(#)IdentityWeakHashMap.java 1.20 06/10/10
003: *
004: * Copyright 1990-2006 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: *
026: */
027:
028: package sun.awt;
029:
030: import java.lang.ref.WeakReference;
031: import java.lang.ref.ReferenceQueue;
032: import java.util.AbstractCollection;
033: import java.util.AbstractMap;
034: import java.util.AbstractSet;
035: import java.util.ArrayList;
036: import java.util.Collection;
037: import java.util.ConcurrentModificationException;
038: import java.util.Iterator;
039: import java.util.Map;
040: import java.util.NoSuchElementException;
041: import java.util.Set;
042:
043: class IdentityWeakHashMap extends AbstractMap implements Map {
044:
045: /**
046: * The default initial capacity -- MUST be a power of two.
047: */
048: private static final int DEFAULT_INITIAL_CAPACITY = 16;
049:
050: /**
051: * The maximum capacity, used if a higher value is implicitly specified
052: * by either of the constructors with arguments.
053: * MUST be a power of two <= 1<<30.
054: */
055: private static final int MAXIMUM_CAPACITY = 1 << 30;
056:
057: /**
058: * The load fast used when none specified in constructor.
059: */
060: private static final float DEFAULT_LOAD_FACTOR = 0.75f;
061:
062: /**
063: * The table, resized as necessary. Length MUST Always be a power of two.
064: */
065: private Entry[] table;
066:
067: /**
068: * The number of key-value mappings contained in this weak hash map.
069: */
070: private int size;
071:
072: /**
073: * The next size value at which to resize (capacity * load factor).
074: */
075: private int threshold;
076:
077: /**
078: * The load factor for the hash table.
079: */
080: private final float loadFactor;
081:
082: /**
083: * Reference queue for cleared WeakEntries
084: */
085: private final ReferenceQueue queue = new ReferenceQueue();
086:
087: /**
088: * The number of times this HashMap has been structurally modified
089: * Structural modifications are those that change the number of mappings in
090: * the HashMap or otherwise modify its internal structure (e.g.,
091: * rehash). This field is used to make iterators on Collection-views of
092: * the HashMap fail-fast. (See ConcurrentModificationException).
093: */
094: private volatile int modCount;
095:
096: /**
097: * Constructs a new, empty <tt>IdentityWeakHashMap</tt> with the given initial
098: * capacity and the given load factor.
099: *
100: * @param initialCapacity The initial capacity of the <tt>IdentityWeakHashMap</tt>
101: * @param loadFactor The load factor of the <tt>IdentityWeakHashMap</tt>
102: * @throws IllegalArgumentException If the initial capacity is negative,
103: * or if the load factor is nonpositive.
104: */
105: public IdentityWeakHashMap(int initialCapacity, float loadFactor) {
106: if (initialCapacity < 0)
107: throw new IllegalArgumentException(
108: "Illegal Initial Capacity: " + initialCapacity);
109: if (initialCapacity > MAXIMUM_CAPACITY)
110: initialCapacity = MAXIMUM_CAPACITY;
111:
112: if (loadFactor <= 0 || Float.isNaN(loadFactor))
113: throw new IllegalArgumentException("Illegal Load factor: "
114: + loadFactor);
115: int capacity = 1;
116: while (capacity < initialCapacity)
117: capacity <<= 1;
118: table = new Entry[capacity];
119: this .loadFactor = loadFactor;
120: threshold = (int) (capacity * loadFactor);
121: }
122:
123: /**
124: * Constructs a new, empty <tt>IdentityWeakHashMap</tt> with the given initial
125: * capacity and the default load factor, which is <tt>0.75</tt>.
126: *
127: * @param initialCapacity The initial capacity of the <tt>IdentityWeakHashMap</tt>
128: * @throws IllegalArgumentException If the initial capacity is negative.
129: */
130: public IdentityWeakHashMap(int initialCapacity) {
131: this (initialCapacity, DEFAULT_LOAD_FACTOR);
132: }
133:
134: /**
135: * Constructs a new, empty <tt>IdentityWeakHashMap</tt> with the default initial
136: * capacity (16) and the default load factor (0.75).
137: */
138: public IdentityWeakHashMap() {
139: this .loadFactor = DEFAULT_LOAD_FACTOR;
140: threshold = (int) (DEFAULT_INITIAL_CAPACITY);
141: table = new Entry[DEFAULT_INITIAL_CAPACITY];
142: }
143:
144: /**
145: * Constructs a new <tt>IdentityWeakHashMap</tt> with the same mappings as the
146: * specified <tt>Map</tt>. The <tt>IdentityWeakHashMap</tt> is created with
147: * default load factor, which is <tt>0.75</tt> and an initial capacity
148: * sufficient to hold the mappings in the specified <tt>Map</tt>.
149: *
150: * @param t the map whose mappings are to be placed in this map.
151: * @throws NullPointerException if the specified map is null.
152: * @since 1.3
153: */
154: public IdentityWeakHashMap(Map t) {
155: this (Math.max((int) (t.size() / DEFAULT_LOAD_FACTOR) + 1, 16),
156: DEFAULT_LOAD_FACTOR);
157: putAll(t);
158: }
159:
160: // internal utilities
161:
162: /**
163: * Value representing null keys inside tables.
164: */
165: private static final Object NULL_KEY = new Object();
166:
167: /**
168: * Use NULL_KEY for key if it is null.
169: */
170: private static Object maskNull(Object key) {
171: return (key == null ? NULL_KEY : key);
172: }
173:
174: /**
175: * Return internal representation of null key back to caller as null
176: */
177: private static Object unmaskNull(Object key) {
178: return (key == NULL_KEY ? null : key);
179: }
180:
181: /**
182: * Check for equality of non-null reference x and possibly-null y. By
183: * default uses Object.equals.
184: */
185: static boolean eq(Object x, Object y) {
186: return x == y;
187: }
188:
189: /**
190: * Return index for hash code h.
191: */
192: static int indexFor(int h, int length) {
193: return h & (length - 1);
194: }
195:
196: /**
197: * Expunge stale entries from the table.
198: */
199: private void expungeStaleEntries() {
200: Object r;
201: while ((r = queue.poll()) != null) {
202: Entry e = (Entry) r;
203: int h = e.hash;
204: int i = indexFor(h, table.length);
205:
206: Entry prev = table[i];
207: Entry p = prev;
208: while (p != null) {
209: Entry next = p.next;
210: if (p == e) {
211: if (prev == e)
212: table[i] = next;
213: else
214: prev.next = next;
215: e.next = null; // Help GC
216: e.value = null; // " "
217: size--;
218: break;
219: }
220: prev = p;
221: p = next;
222: }
223: }
224: }
225:
226: /**
227: * Return the table after first expunging stale entries
228: */
229: private Entry[] getTable() {
230: expungeStaleEntries();
231: return table;
232: }
233:
234: /**
235: * Returns the number of key-value mappings in this map.
236: * This result is a snapshot, and may not reflect unprocessed
237: * entries that will be removed before next attempted access
238: * because they are no longer referenced.
239: */
240: public int size() {
241: if (size == 0)
242: return 0;
243: expungeStaleEntries();
244: return size;
245: }
246:
247: /**
248: * Returns <tt>true</tt> if this map contains no key-value mappings.
249: * This result is a snapshot, and may not reflect unprocessed
250: * entries that will be removed before next attempted access
251: * because they are no longer referenced.
252: */
253: public boolean isEmpty() {
254: return size() == 0;
255: }
256:
257: /**
258: * Returns the value to which the specified key is mapped in this weak
259: * hash map, or <tt>null</tt> if the map contains no mapping for
260: * this key. A return value of <tt>null</tt> does not <i>necessarily</i>
261: * indicate that the map contains no mapping for the key; it is also
262: * possible that the map explicitly maps the key to <tt>null</tt>. The
263: * <tt>containsKey</tt> method may be used to distinguish these two
264: * cases.
265: *
266: * @param key the key whose associated value is to be returned.
267: * @return the value to which this map maps the specified key, or
268: * <tt>null</tt> if the map contains no mapping for this key.
269: * @see #put(Object, Object)
270: */
271: public Object get(Object key) {
272: Object k = maskNull(key);
273: int h = hash(k);
274: Entry[] tab = getTable();
275: int index = indexFor(h, tab.length);
276: Entry e = tab[index];
277: while (e != null) {
278: if (e.hash == h && eq(k, e.get()))
279: return e.value;
280: e = e.next;
281: }
282: return null;
283: }
284:
285: /**
286: * Returns <tt>true</tt> if this map contains a mapping for the
287: * specified key.
288: *
289: * @param key The key whose presence in this map is to be tested
290: * @return <tt>true</tt> if there is a mapping for <tt>key</tt>;
291: * <tt>false</tt> otherwise
292: */
293: public boolean containsKey(Object key) {
294: return getEntry(key) != null;
295: }
296:
297: /**
298: * Returns the entry associated with the specified key in the HashMap.
299: * Returns null if the HashMap contains no mapping for this key.
300: */
301: Entry getEntry(Object key) {
302: Object k = maskNull(key);
303: int h = hash(k);
304: Entry[] tab = getTable();
305: int index = indexFor(h, tab.length);
306: Entry e = tab[index];
307: while (e != null && !(e.hash == h && eq(k, e.get())))
308: e = e.next;
309: return e;
310: }
311:
312: /**
313: * Associates the specified value with the specified key in this map.
314: * If the map previously contained a mapping for this key, the old
315: * value is replaced.
316: *
317: * @param key key with which the specified value is to be associated.
318: * @param value value to be associated with the specified key.
319: * @return previous value associated with specified key, or <tt>null</tt>
320: * if there was no mapping for key. A <tt>null</tt> return can
321: * also indicate that the HashMap previously associated
322: * <tt>null</tt> with the specified key.
323: */
324: public Object put(Object key, Object value) {
325: Object k = maskNull(key);
326: int h = hash(k);
327: Entry[] tab = getTable();
328: int i = indexFor(h, tab.length);
329:
330: for (Entry e = tab[i]; e != null; e = e.next) {
331: if (h == e.hash && eq(k, e.get())) {
332: Object oldValue = e.value;
333: if (value != oldValue)
334: e.value = value;
335: return oldValue;
336: }
337: }
338:
339: modCount++;
340: tab[i] = new Entry(k, value, queue, h, tab[i]);
341: if (++size >= threshold)
342: resize(tab.length * 2);
343: return null;
344: }
345:
346: /**
347: * Rehashes the contents of this map into a new array with a
348: * larger capacity. This method is called automatically when the
349: * number of keys in this map reaches its threshold.
350: *
351: * If current capacity is MAXIMUM_CAPACITY, this method does not
352: * resize the map, but but sets threshold to Integer.MAX_VALUE.
353: * This has the effect of preventing future calls.
354: *
355: * @param newCapacity the new capacity, MUST be a power of two;
356: * must be greater than current capacity unless current
357: * capacity is MAXIMUM_CAPACITY (in which case value
358: * is irrelevant).
359: */
360: void resize(int newCapacity) {
361: Entry[] oldTable = getTable();
362: int oldCapacity = oldTable.length;
363: if (oldCapacity == MAXIMUM_CAPACITY) {
364: threshold = Integer.MAX_VALUE;
365: return;
366: }
367:
368: Entry[] newTable = new Entry[newCapacity];
369: transfer(oldTable, newTable);
370: table = newTable;
371:
372: /*
373: * If ignoring null elements and processing ref queue caused massive
374: * shrinkage, then restore old table. This should be rare, but avoids
375: * unbounded expansion of garbage-filled tables.
376: */
377: if (size >= threshold / 2) {
378: threshold = (int) (newCapacity * loadFactor);
379: } else {
380: expungeStaleEntries();
381: transfer(newTable, oldTable);
382: table = oldTable;
383: }
384: }
385:
386: /** Transfer all entries from src to dest tables */
387: private void transfer(Entry[] src, Entry[] dest) {
388: for (int j = 0; j < src.length; ++j) {
389: Entry e = src[j];
390: src[j] = null;
391: while (e != null) {
392: Entry next = e.next;
393: Object key = e.get();
394: if (key == null) {
395: e.next = null; // Help GC
396: e.value = null; // " "
397: size--;
398: } else {
399: int i = indexFor(e.hash, dest.length);
400: e.next = dest[i];
401: dest[i] = e;
402: }
403: e = next;
404: }
405: }
406: }
407:
408: /**
409: * Copies all of the mappings from the specified map to this map These
410: * mappings will replace any mappings that this map had for any of the
411: * keys currently in the specified map.<p>
412: *
413: * @param m mappings to be stored in this map.
414: * @throws NullPointerException if the specified map is null.
415: */
416: public void putAll(Map m) {
417: int numKeysToBeAdded = m.size();
418: if (numKeysToBeAdded == 0)
419: return;
420:
421: /*
422: * Expand the map if the map if the number of mappings to be added
423: * is greater than or equal to threshold. This is conservative; the
424: * obvious condition is (m.size() + size) >= threshold, but this
425: * condition could result in a map with twice the appropriate capacity,
426: * if the keys to be added overlap with the keys already in this map.
427: * By using the conservative calculation, we subject ourself
428: * to at most one extra resize.
429: */
430: if (numKeysToBeAdded > threshold) {
431: int targetCapacity = (int) (numKeysToBeAdded / loadFactor + 1);
432: if (targetCapacity > MAXIMUM_CAPACITY)
433: targetCapacity = MAXIMUM_CAPACITY;
434: int newCapacity = table.length;
435: while (newCapacity < targetCapacity)
436: newCapacity <<= 1;
437: if (newCapacity > table.length)
438: resize(newCapacity);
439: }
440:
441: for (Iterator i = m.entrySet().iterator(); i.hasNext();) {
442: Map.Entry e = (Map.Entry) i.next();
443: put(e.getKey(), e.getValue());
444: }
445: }
446:
447: /**
448: * Removes the mapping for this key from this map if present.
449: *
450: * @param key key whose mapping is to be removed from the map.
451: * @return previous value associated with specified key, or <tt>null</tt>
452: * if there was no mapping for key. A <tt>null</tt> return can
453: * also indicate that the map previously associated <tt>null</tt>
454: * with the specified key.
455: */
456: public Object remove(Object key) {
457: Object k = maskNull(key);
458: int h = hash(k);
459: Entry[] tab = getTable();
460: int i = indexFor(h, tab.length);
461: Entry prev = tab[i];
462: Entry e = prev;
463:
464: while (e != null) {
465: Entry next = e.next;
466: if (h == e.hash && eq(k, e.get())) {
467: modCount++;
468: size--;
469: if (prev == e)
470: tab[i] = next;
471: else
472: prev.next = next;
473: return e.value;
474: }
475: prev = e;
476: e = next;
477: }
478:
479: return null;
480: }
481:
482: /** Special version of remove needed by Entry set */
483: Entry removeMapping(Object o) {
484: if (!(o instanceof Map.Entry))
485: return null;
486: Entry[] tab = getTable();
487: Map.Entry entry = (Map.Entry) o;
488: Object k = maskNull(entry.getKey());
489: int h = hash(k);
490: int i = indexFor(h, tab.length);
491: Entry prev = tab[i];
492: Entry e = prev;
493:
494: while (e != null) {
495: Entry next = e.next;
496: if (h == e.hash && e.equals(entry)) {
497: modCount++;
498: size--;
499: if (prev == e)
500: tab[i] = next;
501: else
502: prev.next = next;
503: return e;
504: }
505: prev = e;
506: e = next;
507: }
508:
509: return null;
510: }
511:
512: /**
513: * Removes all mappings from this map.
514: */
515: public void clear() {
516: // clear out ref queue. We don't need to expunge entries
517: // since table is getting cleared.
518: while (queue.poll() != null)
519: ;
520:
521: modCount++;
522: Entry tab[] = table;
523: for (int i = 0; i < tab.length; ++i)
524: tab[i] = null;
525: size = 0;
526:
527: // Allocation of array may have caused GC, which may have caused
528: // additional entries to go stale. Removing these entries from the
529: // reference queue will make them eligible for reclamation.
530: while (queue.poll() != null)
531: ;
532: }
533:
534: /**
535: * Returns <tt>true</tt> if this map maps one or more keys to the
536: * specified value.
537: *
538: * @param value value whose presence in this map is to be tested.
539: * @return <tt>true</tt> if this map maps one or more keys to the
540: * specified value.
541: */
542: public boolean containsValue(Object value) {
543: if (value == null)
544: return containsNullValue();
545:
546: Entry tab[] = getTable();
547: for (int i = tab.length; i-- > 0;)
548: for (Entry e = tab[i]; e != null; e = e.next)
549: if (value == e.value)
550: return true;
551: return false;
552: }
553:
554: /**
555: * Special-case code for containsValue with null argument
556: */
557: private boolean containsNullValue() {
558: Entry tab[] = getTable();
559: for (int i = tab.length; i-- > 0;)
560: for (Entry e = tab[i]; e != null; e = e.next)
561: if (e.value == null)
562: return true;
563: return false;
564: }
565:
566: /**
567: * Returns a hash value for the specified object. In addition to
568: * the object's own hashCode, this method applies a "supplemental
569: * hash function," which defends against poor quality hash functions.
570: * This is critical because HashMap uses power-of two length
571: * hash tables.<p>
572: *
573: * The shift distances in this function were chosen as the result
574: * of an automated search over the entire four-dimensional search space.
575: */
576: static int hash(Object x) {
577: int h = x.hashCode();
578:
579: h += ~(h << 9);
580: h ^= (h >>> 14);
581: h += (h << 4);
582: h ^= (h >>> 10);
583: return h;
584: }
585:
586: /**
587: * The entries in this hash table extend WeakReference, using its main ref
588: * field as the key.
589: */
590: private static class Entry extends WeakReference implements
591: Map.Entry {
592: private Object value;
593: private final int hash;
594: private Entry next;
595:
596: /**
597: * Create new entry.
598: */
599: Entry(Object key, Object value, ReferenceQueue queue, int hash,
600: Entry next) {
601: super (key, queue);
602: this .value = value;
603: this .hash = hash;
604: this .next = next;
605: }
606:
607: public Object getKey() {
608: return unmaskNull(get());
609: }
610:
611: public Object getValue() {
612: return value;
613: }
614:
615: public Object setValue(Object newValue) {
616: Object oldValue = value;
617: value = newValue;
618: return oldValue;
619: }
620:
621: public boolean equals(Object o) {
622: if (!(o instanceof Map.Entry))
623: return false;
624: Map.Entry e = (Map.Entry) o;
625: Object k1 = getKey();
626: Object k2 = e.getKey();
627: if (k1 == k2) {
628: Object v1 = getValue();
629: Object v2 = e.getValue();
630: if (v1 == v2)
631: return true;
632: }
633: return false;
634: }
635:
636: public int hashCode() {
637: Object k = getKey();
638: Object v = getValue();
639: return ((k == null ? 0 : k.hashCode()) ^ (v == null ? 0 : v
640: .hashCode()));
641: }
642:
643: public String toString() {
644: return getKey() + "=" + getValue();
645: }
646: }
647:
648: private abstract class HashIterator implements Iterator {
649: int index;
650: Entry entry = null;
651: Entry lastReturned = null;
652: int expectedModCount = modCount;
653:
654: /**
655: * Strong reference needed to avoid disappearance of key
656: * between hasNext and next
657: */
658: Object nextKey = null;
659:
660: /**
661: * Strong reference needed to avoid disappearance of key
662: * between nextEntry() and any use of the entry
663: */
664: Object currentKey = null;
665:
666: HashIterator() {
667: index = (size() != 0 ? table.length : 0);
668: }
669:
670: public boolean hasNext() {
671: Entry[] t = table;
672:
673: while (nextKey == null) {
674: Entry e = entry;
675: int i = index;
676: while (e == null && i > 0)
677: e = t[--i];
678: entry = e;
679: index = i;
680: if (e == null) {
681: currentKey = null;
682: return false;
683: }
684: nextKey = e.get(); // hold on to key in strong ref
685: if (nextKey == null)
686: entry = entry.next;
687: }
688: return true;
689: }
690:
691: /** The common parts of next() across different types of iterators */
692: protected Entry nextEntry() {
693: if (modCount != expectedModCount)
694: throw new ConcurrentModificationException();
695: if (nextKey == null && !hasNext())
696: throw new NoSuchElementException();
697:
698: lastReturned = entry;
699: entry = entry.next;
700: currentKey = nextKey;
701: nextKey = null;
702: return lastReturned;
703: }
704:
705: public void remove() {
706: if (lastReturned == null)
707: throw new IllegalStateException();
708: if (modCount != expectedModCount)
709: throw new ConcurrentModificationException();
710:
711: IdentityWeakHashMap.this .remove(currentKey);
712: expectedModCount = modCount;
713: lastReturned = null;
714: currentKey = null;
715: }
716:
717: }
718:
719: private class ValueIterator extends HashIterator {
720: public Object next() {
721: return nextEntry().value;
722: }
723: }
724:
725: private class KeyIterator extends HashIterator {
726: public Object next() {
727: return nextEntry().getKey();
728: }
729: }
730:
731: private class EntryIterator extends HashIterator {
732: public Object next() {
733: return nextEntry();
734: }
735: }
736:
737: // Views
738:
739: private transient Set entrySet = null;
740:
741: /**
742: * Each of these fields are initialized to contain an instance of the
743: * appropriate view the first time this view is requested. The views are
744: * stateless, so there's no reason to create more than one of each.
745: */
746: transient volatile Set ks = null;
747: transient volatile Collection vs = null;
748:
749: /**
750: * Returns a set view of the keys contained in this map. The set is
751: * backed by the map, so changes to the map are reflected in the set, and
752: * vice-versa. The set supports element removal, which removes the
753: * corresponding mapping from this map, via the <tt>Iterator.remove</tt>,
754: * <tt>Set.remove</tt>, <tt>removeAll</tt>, <tt>retainAll</tt>, and
755: * <tt>clear</tt> operations. It does not support the <tt>add</tt> or
756: * <tt>addAll</tt> operations.
757: *
758: * @return a set view of the keys contained in this map.
759: */
760: public Set keySet() {
761: return (ks != null ? ks : (ks = new KeySet()));
762: }
763:
764: private class KeySet extends AbstractSet {
765: public Iterator iterator() {
766: return new KeyIterator();
767: }
768:
769: public int size() {
770: return IdentityWeakHashMap.this .size();
771: }
772:
773: public boolean contains(Object o) {
774: return containsKey(o);
775: }
776:
777: public boolean remove(Object o) {
778: if (containsKey(o)) {
779: IdentityWeakHashMap.this .remove(o);
780: return true;
781: } else
782: return false;
783: }
784:
785: public void clear() {
786: IdentityWeakHashMap.this .clear();
787: }
788:
789: public Object[] toArray() {
790: Collection c = new ArrayList(size());
791: for (Iterator i = iterator(); i.hasNext();)
792: c.add(i.next());
793: return c.toArray();
794: }
795:
796: public Object[] toArray(Object a[]) {
797: Collection c = new ArrayList(size());
798: for (Iterator i = iterator(); i.hasNext();)
799: c.add(i.next());
800: return c.toArray(a);
801: }
802: }
803:
804: /**
805: * Returns a collection view of the values contained in this map. The
806: * collection is backed by the map, so changes to the map are reflected in
807: * the collection, and vice-versa. The collection supports element
808: * removal, which removes the corresponding mapping from this map, via the
809: * <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,
810: * <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> operations.
811: * It does not support the <tt>add</tt> or <tt>addAll</tt> operations.
812: *
813: * @return a collection view of the values contained in this map.
814: */
815: public Collection values() {
816: return (vs != null ? vs : (vs = new Values()));
817: }
818:
819: private class Values extends AbstractCollection {
820: public Iterator iterator() {
821: return new ValueIterator();
822: }
823:
824: public int size() {
825: return IdentityWeakHashMap.this .size();
826: }
827:
828: public boolean contains(Object o) {
829: return containsValue(o);
830: }
831:
832: public void clear() {
833: IdentityWeakHashMap.this .clear();
834: }
835:
836: public Object[] toArray() {
837: Collection c = new ArrayList(size());
838: for (Iterator i = iterator(); i.hasNext();)
839: c.add(i.next());
840: return c.toArray();
841: }
842:
843: public Object[] toArray(Object a[]) {
844: Collection c = new ArrayList(size());
845: for (Iterator i = iterator(); i.hasNext();)
846: c.add(i.next());
847: return c.toArray(a);
848: }
849: }
850:
851: /**
852: * Returns a collection view of the mappings contained in this map. Each
853: * element in the returned collection is a <tt>Map.Entry</tt>. The
854: * collection is backed by the map, so changes to the map are reflected in
855: * the collection, and vice-versa. The collection supports element
856: * removal, which removes the corresponding mapping from the map, via the
857: * <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,
858: * <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> operations.
859: * It does not support the <tt>add</tt> or <tt>addAll</tt> operations.
860: *
861: * @return a collection view of the mappings contained in this map.
862: * @see Map.Entry
863: */
864: public Set entrySet() {
865: Set es = entrySet;
866: return (es != null ? es : (entrySet = new EntrySet()));
867: }
868:
869: private class EntrySet extends AbstractSet {
870: public Iterator iterator() {
871: return new EntryIterator();
872: }
873:
874: public boolean contains(Object o) {
875: if (!(o instanceof Map.Entry))
876: return false;
877: Map.Entry e = (Map.Entry) o;
878: Object k = e.getKey();
879: Entry candidate = getEntry(e.getKey());
880: return candidate != null && candidate.equals(e);
881: }
882:
883: public boolean remove(Object o) {
884: return removeMapping(o) != null;
885: }
886:
887: public int size() {
888: return IdentityWeakHashMap.this .size();
889: }
890:
891: public void clear() {
892: IdentityWeakHashMap.this .clear();
893: }
894:
895: public Object[] toArray() {
896: Collection c = new ArrayList(size());
897: for (Iterator i = iterator(); i.hasNext();)
898: c.add(new SimpleEntry((Map.Entry) i.next()));
899: return c.toArray();
900: }
901:
902: public Object[] toArray(Object a[]) {
903: Collection c = new ArrayList(size());
904: for (Iterator i = iterator(); i.hasNext();)
905: c.add(new SimpleEntry((Map.Entry) i.next()));
906: return c.toArray(a);
907: }
908: }
909:
910: /**
911: * Taken from AbstractMap.
912: */
913: static class SimpleEntry {
914: Object key;
915: Object value;
916:
917: public SimpleEntry(Object key, Object value) {
918: this .key = key;
919: this .value = value;
920: }
921:
922: public SimpleEntry(Map.Entry e) {
923: this .key = e.getKey();
924: this .value = e.getValue();
925: }
926:
927: public Object getKey() {
928: return key;
929: }
930:
931: public Object getValue() {
932: return value;
933: }
934:
935: public Object setValue(Object value) {
936: Object oldValue = this .value;
937: this .value = value;
938: return oldValue;
939: }
940:
941: public boolean equals(Object o) {
942: if (!(o instanceof Map.Entry))
943: return false;
944: Map.Entry e = (Map.Entry) o;
945: return eq(key, e.getKey()) && eq(value, e.getValue());
946: }
947:
948: public int hashCode() {
949: Object v;
950: return ((key == null) ? 0 : key.hashCode())
951: ^ ((value == null) ? 0 : value.hashCode());
952: }
953:
954: public String toString() {
955: return key + "=" + value;
956: }
957:
958: private static boolean eq(Object o1, Object o2) {
959: return (o1 == null ? o2 == null : o1.equals(o2));
960: }
961: }
962:
963: }
|