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    }