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