001    /*
002     *  Licensed to the Apache Software Foundation (ASF) under one or more
003     *  contributor license agreements.  See the NOTICE file distributed with
004     *  this work for additional information regarding copyright ownership.
005     *  The ASF licenses this file to You under the Apache License, Version 2.0
006     *  (the "License"); you may not use this file except in compliance with
007     *  the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     *  Unless required by applicable law or agreed to in writing, software
012     *  distributed under the License is distributed on an "AS IS" BASIS,
013     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     *  See the License for the specific language governing permissions and
015     *  limitations under the License.
016     */
017    package org.apache.commons.collections.map;
018    
019    import java.io.IOException;
020    import java.io.ObjectInputStream;
021    import java.io.ObjectOutputStream;
022    import java.lang.ref.Reference;
023    import java.lang.ref.ReferenceQueue;
024    import java.lang.ref.SoftReference;
025    import java.lang.ref.WeakReference;
026    import java.util.ArrayList;
027    import java.util.Collection;
028    import java.util.ConcurrentModificationException;
029    import java.util.Iterator;
030    import java.util.List;
031    import java.util.Map;
032    import java.util.NoSuchElementException;
033    import java.util.Set;
034    
035    import org.apache.commons.collections.MapIterator;
036    import org.apache.commons.collections.keyvalue.DefaultMapEntry;
037    
038    /**
039     * An abstract implementation of a hash-based map that allows the entries to
040     * be removed by the garbage collector.
041     * <p>
042     * This class implements all the features necessary for a subclass reference
043     * hash-based map. Key-value entries are stored in instances of the
044     * <code>ReferenceEntry</code> class which can be overridden and replaced.
045     * The iterators can similarly be replaced, without the need to replace the KeySet,
046     * EntrySet and Values view classes.
047     * <p>
048     * Overridable methods are provided to change the default hashing behaviour, and
049     * to change how entries are added to and removed from the map. Hopefully, all you
050     * need for unusual subclasses is here.
051     * <p>
052     * When you construct an <code>AbstractReferenceMap</code>, you can specify what
053     * kind of references are used to store the map's keys and values.
054     * If non-hard references are used, then the garbage collector can remove
055     * mappings if a key or value becomes unreachable, or if the JVM's memory is
056     * running low. For information on how the different reference types behave,
057     * see {@link Reference}.
058     * <p>
059     * Different types of references can be specified for keys and values.
060     * The keys can be configured to be weak but the values hard,
061     * in which case this class will behave like a
062     * <a href="http://java.sun.com/j2se/1.4/docs/api/java/util/WeakHashMap.html">
063     * <code>WeakHashMap</code></a>. However, you can also specify hard keys and
064     * weak values, or any other combination. The default constructor uses
065     * hard keys and soft values, providing a memory-sensitive cache.
066     * <p>
067     * This {@link Map} implementation does <i>not</i> allow null elements.
068     * Attempting to add a null key or value to the map will raise a
069     * <code>NullPointerException</code>.
070     * <p>
071     * All the available iterators can be reset back to the start by casting to
072     * <code>ResettableIterator</code> and calling <code>reset()</code>.
073     * <p>
074     * This implementation is not synchronized.
075     * You can use {@link java.util.Collections#synchronizedMap} to 
076     * provide synchronized access to a <code>ReferenceMap</code>.
077     *
078     * @see java.lang.ref.Reference
079     * @since Commons Collections 3.1 (extracted from ReferenceMap in 3.0)
080     * @version $Revision: 646777 $ $Date: 2008-04-10 13:33:15 +0100 (Thu, 10 Apr 2008) $
081     * 
082     * @author Paul Jack
083     * @author Stephen Colebourne
084     */
085    public abstract class AbstractReferenceMap extends AbstractHashedMap {
086    
087        /** Constant indicating that hard references should be used */
088        public static final int HARD = 0;
089    
090        /** Constant indicating that soft references should be used */
091        public static final int SOFT = 1;
092    
093        /** Constant indicating that weak references should be used */
094        public static final int WEAK = 2;
095    
096        /**
097         * The reference type for keys.  Must be HARD, SOFT, WEAK.
098         * @serial
099         */
100        protected int keyType;
101    
102        /**
103         * The reference type for values.  Must be HARD, SOFT, WEAK.
104         * @serial
105         */
106        protected int valueType;
107    
108        /**
109         * Should the value be automatically purged when the associated key has been collected?
110         */
111        protected boolean purgeValues;
112    
113        /**
114         * ReferenceQueue used to eliminate stale mappings.
115         * See purge.
116         */
117        private transient ReferenceQueue queue;
118    
119        //-----------------------------------------------------------------------
120        /**
121         * Constructor used during deserialization.
122         */
123        protected AbstractReferenceMap() {
124            super();
125        }
126    
127        /**
128         * Constructs a new empty map with the specified reference types,
129         * load factor and initial capacity.
130         *
131         * @param keyType  the type of reference to use for keys;
132         *   must be {@link #HARD}, {@link #SOFT}, {@link #WEAK}
133         * @param valueType  the type of reference to use for values;
134         *   must be {@link #HARD}, {@link #SOFT}, {@link #WEAK}
135         * @param capacity  the initial capacity for the map
136         * @param loadFactor  the load factor for the map
137         * @param purgeValues  should the value be automatically purged when the 
138         *   key is garbage collected 
139         */
140        protected AbstractReferenceMap(
141                int keyType, int valueType, int capacity, 
142                float loadFactor, boolean purgeValues) {
143            super(capacity, loadFactor);
144            verify("keyType", keyType);
145            verify("valueType", valueType);
146            this.keyType = keyType;
147            this.valueType = valueType;
148            this.purgeValues = purgeValues;
149        }
150    
151        /**
152         * Initialise this subclass during construction, cloning or deserialization.
153         */
154        protected void init() {
155            queue = new ReferenceQueue();
156        }
157    
158        //-----------------------------------------------------------------------
159        /**
160         * Checks the type int is a valid value.
161         * 
162         * @param name  the name for error messages
163         * @param type  the type value to check
164         * @throws IllegalArgumentException if the value if invalid
165         */
166        private static void verify(String name, int type) {
167            if ((type < HARD) || (type > WEAK)) {
168                throw new IllegalArgumentException(name + " must be HARD, SOFT, WEAK.");
169            }
170        }
171    
172        //-----------------------------------------------------------------------
173        /**
174         * Gets the size of the map.
175         * 
176         * @return the size
177         */
178        public int size() {
179            purgeBeforeRead();
180            return super.size();
181        }
182    
183        /**
184         * Checks whether the map is currently empty.
185         * 
186         * @return true if the map is currently size zero
187         */
188        public boolean isEmpty() {
189            purgeBeforeRead();
190            return super.isEmpty();
191        }
192    
193        /**
194         * Checks whether the map contains the specified key.
195         * 
196         * @param key  the key to search for
197         * @return true if the map contains the key
198         */
199        public boolean containsKey(Object key) {
200            purgeBeforeRead();
201            Entry entry = getEntry(key);
202            if (entry == null) {
203                return false;
204            }
205            return (entry.getValue() != null);
206        }
207    
208        /**
209         * Checks whether the map contains the specified value.
210         * 
211         * @param value  the value to search for
212         * @return true if the map contains the value
213         */
214        public boolean containsValue(Object value) {
215            purgeBeforeRead();
216            if (value == null) {
217                return false;
218            }
219            return super.containsValue(value);
220        }
221    
222        /**
223         * Gets the value mapped to the key specified.
224         * 
225         * @param key  the key
226         * @return the mapped value, null if no match
227         */
228        public Object get(Object key) {
229            purgeBeforeRead();
230            Entry entry = getEntry(key);
231            if (entry == null) {
232                return null;
233            }
234            return entry.getValue();
235        }
236    
237    
238        /**
239         * Puts a key-value mapping into this map.
240         * Neither the key nor the value may be null.
241         * 
242         * @param key  the key to add, must not be null
243         * @param value  the value to add, must not be null
244         * @return the value previously mapped to this key, null if none
245         * @throws NullPointerException if either the key or value is null
246         */
247        public Object put(Object key, Object value) {
248            if (key == null) {
249                throw new NullPointerException("null keys not allowed");
250            }
251            if (value == null) {
252                throw new NullPointerException("null values not allowed");
253            }
254    
255            purgeBeforeWrite();
256            return super.put(key, value);
257        }
258        
259        /**
260         * Removes the specified mapping from this map.
261         * 
262         * @param key  the mapping to remove
263         * @return the value mapped to the removed key, null if key not in map
264         */
265        public Object remove(Object key) {
266            if (key == null) {
267                return null;
268            }
269            purgeBeforeWrite();
270            return super.remove(key);
271        }
272    
273        /**
274         * Clears this map.
275         */
276        public void clear() {
277            super.clear();
278            while (queue.poll() != null) {} // drain the queue
279        }
280    
281        //-----------------------------------------------------------------------
282        /**
283         * Gets a MapIterator over the reference map.
284         * The iterator only returns valid key/value pairs.
285         * 
286         * @return a map iterator
287         */
288        public MapIterator mapIterator() {
289            return new ReferenceMapIterator(this);
290        }
291    
292        /**
293         * Returns a set view of this map's entries.
294         * An iterator returned entry is valid until <code>next()</code> is called again.
295         * The <code>setValue()</code> method on the <code>toArray</code> entries has no effect.
296         *
297         * @return a set view of this map's entries
298         */
299        public Set entrySet() {
300            if (entrySet == null) {
301                entrySet = new ReferenceEntrySet(this);
302            }
303            return entrySet;
304        }
305    
306        /**
307         * Returns a set view of this map's keys.
308         *
309         * @return a set view of this map's keys
310         */
311        public Set keySet() {
312            if (keySet == null) {
313                keySet = new ReferenceKeySet(this);
314            }
315            return keySet;
316        }
317    
318        /**
319         * Returns a collection view of this map's values.
320         *
321         * @return a set view of this map's values
322         */
323        public Collection values() {
324            if (values == null) {
325                values = new ReferenceValues(this);
326            }
327            return values;
328        }
329    
330        //-----------------------------------------------------------------------
331        /**
332         * Purges stale mappings from this map before read operations.
333         * <p>
334         * This implementation calls {@link #purge()} to maintain a consistent state.
335         */
336        protected void purgeBeforeRead() {
337            purge();
338        }
339    
340        /**
341         * Purges stale mappings from this map before write operations.
342         * <p>
343         * This implementation calls {@link #purge()} to maintain a consistent state.
344         */
345        protected void purgeBeforeWrite() {
346            purge();
347        }
348    
349        /**
350         * Purges stale mappings from this map.
351         * <p>
352         * Note that this method is not synchronized!  Special
353         * care must be taken if, for instance, you want stale
354         * mappings to be removed on a periodic basis by some
355         * background thread.
356         */
357        protected void purge() {
358            Reference ref = queue.poll();
359            while (ref != null) {
360                purge(ref);
361                ref = queue.poll();
362            }
363        }
364    
365        /**
366         * Purges the specified reference.
367         * 
368         * @param ref  the reference to purge
369         */
370        protected void purge(Reference ref) {
371            // The hashCode of the reference is the hashCode of the
372            // mapping key, even if the reference refers to the 
373            // mapping value...
374            int hash = ref.hashCode();
375            int index = hashIndex(hash, data.length);
376            HashEntry previous = null;
377            HashEntry entry = data[index];
378            while (entry != null) {
379                if (((ReferenceEntry) entry).purge(ref)) {
380                    if (previous == null) {
381                        data[index] = entry.next;
382                    } else {
383                        previous.next = entry.next;
384                    }
385                    this.size--;
386                    return;
387                }
388                previous = entry;
389                entry = entry.next;
390            }
391    
392        }
393    
394        //-----------------------------------------------------------------------
395        /**
396         * Gets the entry mapped to the key specified.
397         * 
398         * @param key  the key
399         * @return the entry, null if no match
400         */
401        protected HashEntry getEntry(Object key) {
402            if (key == null) {
403                return null;
404            } else {
405                return super.getEntry(key);
406            }
407        }
408    
409        /**
410         * Gets the hash code for a MapEntry.
411         * Subclasses can override this, for example to use the identityHashCode.
412         * 
413         * @param key  the key to get a hash code for, may be null
414         * @param value  the value to get a hash code for, may be null
415         * @return the hash code, as per the MapEntry specification
416         */
417        protected int hashEntry(Object key, Object value) {
418            return (key == null ? 0 : key.hashCode()) ^
419                   (value == null ? 0 : value.hashCode()); 
420        }
421        
422        /**
423         * Compares two keys, in internal converted form, to see if they are equal.
424         * <p>
425         * This implementation converts the key from the entry to a real reference
426         * before comparison.
427         * 
428         * @param key1  the first key to compare passed in from outside
429         * @param key2  the second key extracted from the entry via <code>entry.key</code>
430         * @return true if equal
431         */
432        protected boolean isEqualKey(Object key1, Object key2) {
433            key2 = (keyType > HARD ? ((Reference) key2).get() : key2);
434            return (key1 == key2 || key1.equals(key2));
435        }
436        
437        /**
438         * Creates a ReferenceEntry instead of a HashEntry.
439         * 
440         * @param next  the next entry in sequence
441         * @param hashCode  the hash code to use
442         * @param key  the key to store
443         * @param value  the value to store
444         * @return the newly created entry
445         */
446        protected HashEntry createEntry(HashEntry next, int hashCode, Object key, Object value) {
447            return new ReferenceEntry(this, next, hashCode, key, value);
448        }
449    
450        /**
451         * Creates an entry set iterator.
452         * 
453         * @return the entrySet iterator
454         */
455        protected Iterator createEntrySetIterator() {
456            return new ReferenceEntrySetIterator(this);
457        }
458    
459        /**
460         * Creates an key set iterator.
461         * 
462         * @return the keySet iterator
463         */
464        protected Iterator createKeySetIterator() {
465            return new ReferenceKeySetIterator(this);
466        }
467    
468        /**
469         * Creates an values iterator.
470         * 
471         * @return the values iterator
472         */
473        protected Iterator createValuesIterator() {
474            return new ReferenceValuesIterator(this);
475        }
476    
477        //-----------------------------------------------------------------------
478        /**
479         * EntrySet implementation.
480         */
481        static class ReferenceEntrySet extends EntrySet {
482            
483            protected ReferenceEntrySet(AbstractHashedMap parent) {
484                super(parent);
485            }
486    
487            public Object[] toArray() {
488                return toArray(new Object[0]);
489            }
490    
491            public Object[] toArray(Object[] arr) {
492                // special implementation to handle disappearing entries
493                ArrayList list = new ArrayList();
494                Iterator iterator = iterator();
495                while (iterator.hasNext()) {
496                    Entry e = (Entry) iterator.next();
497                    list.add(new DefaultMapEntry(e.getKey(), e.getValue()));
498                }
499                return list.toArray(arr);
500            }
501        }
502    
503        //-----------------------------------------------------------------------
504        /**
505         * KeySet implementation.
506         */
507        static class ReferenceKeySet extends KeySet {
508            
509            protected ReferenceKeySet(AbstractHashedMap parent) {
510                super(parent);
511            }
512    
513            public Object[] toArray() {
514                return toArray(new Object[0]);
515            }
516    
517            public Object[] toArray(Object[] arr) {
518                // special implementation to handle disappearing keys
519                List list = new ArrayList(parent.size());
520                for (Iterator it = iterator(); it.hasNext(); ) {
521                    list.add(it.next());
522                }
523                return list.toArray(arr);
524            }
525        }
526    
527        //-----------------------------------------------------------------------
528        /**
529         * Values implementation.
530         */
531        static class ReferenceValues extends Values {
532            
533            protected ReferenceValues(AbstractHashedMap parent) {
534                super(parent);
535            }
536    
537            public Object[] toArray() {
538                return toArray(new Object[0]);
539            }
540    
541            public Object[] toArray(Object[] arr) {
542                // special implementation to handle disappearing values
543                List list = new ArrayList(parent.size());
544                for (Iterator it = iterator(); it.hasNext(); ) {
545                    list.add(it.next());
546                }
547                return list.toArray(arr);
548            }
549        }
550    
551        //-----------------------------------------------------------------------
552        /**
553         * A MapEntry implementation for the map.
554         * <p>
555         * If getKey() or getValue() returns null, it means
556         * the mapping is stale and should be removed.
557         * 
558         * @since Commons Collections 3.1
559         */
560        protected static class ReferenceEntry extends HashEntry {
561            /** The parent map */
562            protected final AbstractReferenceMap parent;
563    
564            /**
565             * Creates a new entry object for the ReferenceMap.
566             * 
567             * @param parent  the parent map
568             * @param next  the next entry in the hash bucket
569             * @param hashCode  the hash code of the key
570             * @param key  the key
571             * @param value  the value
572             */
573            public ReferenceEntry(AbstractReferenceMap parent, HashEntry next, int hashCode, Object key, Object value) {
574                super(next, hashCode, null, null);
575                this.parent = parent;
576                this.key = toReference(parent.keyType, key, hashCode);
577                this.value = toReference(parent.valueType, value, hashCode); // the key hashCode is passed in deliberately
578            }
579    
580            /**
581             * Gets the key from the entry.
582             * This method dereferences weak and soft keys and thus may return null.
583             * 
584             * @return the key, which may be null if it was garbage collected
585             */
586            public Object getKey() {
587                return (parent.keyType > HARD) ? ((Reference) key).get() : key;
588            }
589    
590            /**
591             * Gets the value from the entry.
592             * This method dereferences weak and soft value and thus may return null.
593             * 
594             * @return the value, which may be null if it was garbage collected
595             */
596            public Object getValue() {
597                return (parent.valueType > HARD) ? ((Reference) value).get() : value;
598            }
599    
600            /**
601             * Sets the value of the entry.
602             * 
603             * @param obj  the object to store
604             * @return the previous value
605             */
606            public Object setValue(Object obj) {
607                Object old = getValue();
608                if (parent.valueType > HARD) {
609                    ((Reference)value).clear();
610                }
611                value = toReference(parent.valueType, obj, hashCode);
612                return old;
613            }
614    
615            /**
616             * Compares this map entry to another.
617             * <p>
618             * This implementation uses <code>isEqualKey</code> and
619             * <code>isEqualValue</code> on the main map for comparison.
620             * 
621             * @param obj  the other map entry to compare to
622             * @return true if equal, false if not
623             */
624            public boolean equals(Object obj) {
625                if (obj == this) {
626                    return true;
627                }
628                if (obj instanceof Map.Entry == false) {
629                    return false;
630                }
631                
632                Map.Entry entry = (Map.Entry)obj;
633                Object entryKey = entry.getKey();  // convert to hard reference
634                Object entryValue = entry.getValue();  // convert to hard reference
635                if ((entryKey == null) || (entryValue == null)) {
636                    return false;
637                }
638                // compare using map methods, aiding identity subclass
639                // note that key is direct access and value is via method
640                return parent.isEqualKey(entryKey, key) &&
641                       parent.isEqualValue(entryValue, getValue());
642            }
643    
644            /**
645             * Gets the hashcode of the entry using temporary hard references.
646             * <p>
647             * This implementation uses <code>hashEntry</code> on the main map.
648             * 
649             * @return the hashcode of the entry
650             */
651            public int hashCode() {
652                return parent.hashEntry(getKey(), getValue());
653            }
654    
655            /**
656             * Constructs a reference of the given type to the given referent.
657             * The reference is registered with the queue for later purging.
658             *
659             * @param type  HARD, SOFT or WEAK
660             * @param referent  the object to refer to
661             * @param hash  the hash code of the <i>key</i> of the mapping;
662             *    this number might be different from referent.hashCode() if
663             *    the referent represents a value and not a key
664             */
665            protected Object toReference(int type, Object referent, int hash) {
666                switch (type) {
667                    case HARD: return referent;
668                    case SOFT: return new SoftRef(hash, referent, parent.queue);
669                    case WEAK: return new WeakRef(hash, referent, parent.queue);
670                    default: throw new Error();
671                }
672            }
673    
674            /**
675             * Purges the specified reference
676             * @param ref  the reference to purge
677             * @return true or false
678             */
679            boolean purge(Reference ref) {
680                boolean r = (parent.keyType > HARD) && (key == ref);
681                r = r || ((parent.valueType > HARD) && (value == ref));
682                if (r) {
683                    if (parent.keyType > HARD) {
684                        ((Reference)key).clear();
685                    }
686                    if (parent.valueType > HARD) {
687                        ((Reference)value).clear();
688                    } else if (parent.purgeValues) {
689                        value = null;
690                    }
691                }
692                return r;
693            }
694    
695            /**
696             * Gets the next entry in the bucket.
697             * 
698             * @return the next entry in the bucket
699             */
700            protected ReferenceEntry next() {
701                return (ReferenceEntry) next;
702            }
703        }
704    
705        //-----------------------------------------------------------------------
706        /**
707         * The EntrySet iterator.
708         */
709        static class ReferenceEntrySetIterator implements Iterator {
710            /** The parent map */
711            final AbstractReferenceMap parent;
712            
713            // These fields keep track of where we are in the table.
714            int index;
715            ReferenceEntry entry;
716            ReferenceEntry previous;
717    
718            // These Object fields provide hard references to the
719            // current and next entry; this assures that if hasNext()
720            // returns true, next() will actually return a valid element.
721            Object nextKey, nextValue;
722            Object currentKey, currentValue;
723    
724            int expectedModCount;
725    
726            public ReferenceEntrySetIterator(AbstractReferenceMap parent) {
727                super();
728                this.parent = parent;
729                index = (parent.size() != 0 ? parent.data.length : 0);
730                // have to do this here!  size() invocation above
731                // may have altered the modCount.
732                expectedModCount = parent.modCount;
733            }
734    
735            public boolean hasNext() {
736                checkMod();
737                while (nextNull()) {
738                    ReferenceEntry e = entry;
739                    int i = index;
740                    while ((e == null) && (i > 0)) {
741                        i--;
742                        e = (ReferenceEntry) parent.data[i];
743                    }
744                    entry = e;
745                    index = i;
746                    if (e == null) {
747                        currentKey = null;
748                        currentValue = null;
749                        return false;
750                    }
751                    nextKey = e.getKey();
752                    nextValue = e.getValue();
753                    if (nextNull()) {
754                        entry = entry.next();
755                    }
756                }
757                return true;
758            }
759    
760            private void checkMod() {
761                if (parent.modCount != expectedModCount) {
762                    throw new ConcurrentModificationException();
763                }
764            }
765    
766            private boolean nextNull() {
767                return (nextKey == null) || (nextValue == null);
768            }
769    
770            protected ReferenceEntry nextEntry() {    
771                checkMod();
772                if (nextNull() && !hasNext()) {
773                    throw new NoSuchElementException();
774                }
775                previous = entry;
776                entry = entry.next();
777                currentKey = nextKey;
778                currentValue = nextValue;
779                nextKey = null;
780                nextValue = null;
781                return previous;
782            }
783    
784            protected ReferenceEntry currentEntry() {
785                checkMod();
786                return previous;
787            }
788            
789            public Object next() {
790                return nextEntry();
791            }
792    
793            public void remove() {
794                checkMod();
795                if (previous == null) {
796                    throw new IllegalStateException();
797                }
798                parent.remove(currentKey);
799                previous = null;
800                currentKey = null;
801                currentValue = null;
802                expectedModCount = parent.modCount;
803            }
804        }
805    
806        /**
807         * The keySet iterator.
808         */
809        static class ReferenceKeySetIterator extends ReferenceEntrySetIterator {
810            
811            ReferenceKeySetIterator(AbstractReferenceMap parent) {
812                super(parent);
813            }
814            
815            public Object next() {
816                return nextEntry().getKey();
817            }
818        }
819    
820        /**
821         * The values iterator.
822         */
823        static class ReferenceValuesIterator extends ReferenceEntrySetIterator {
824            
825            ReferenceValuesIterator(AbstractReferenceMap parent) {
826                super(parent);
827            }
828            
829            public Object next() {
830                return nextEntry().getValue();
831            }
832        }
833    
834        /**
835         * The MapIterator implementation.
836         */
837        static class ReferenceMapIterator extends ReferenceEntrySetIterator implements MapIterator {
838            
839            protected ReferenceMapIterator(AbstractReferenceMap parent) {
840                super(parent);
841            }
842    
843            public Object next() {
844                return nextEntry().getKey();
845            }
846    
847            public Object getKey() {
848                HashEntry current = currentEntry();
849                if (current == null) {
850                    throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID);
851                }
852                return current.getKey();
853            }
854    
855            public Object getValue() {
856                HashEntry current = currentEntry();
857                if (current == null) {
858                    throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID);
859                }
860                return current.getValue();
861            }
862    
863            public Object setValue(Object value) {
864                HashEntry current = currentEntry();
865                if (current == null) {
866                    throw new IllegalStateException(AbstractHashedMap.SETVALUE_INVALID);
867                }
868                return current.setValue(value);
869            }
870        }
871        
872        //-----------------------------------------------------------------------
873        // These two classes store the hashCode of the key of
874        // of the mapping, so that after they're dequeued a quick
875        // lookup of the bucket in the table can occur.
876    
877        /**
878         * A soft reference holder.
879         */
880        static class SoftRef extends SoftReference {
881            /** the hashCode of the key (even if the reference points to a value) */
882            private int hash;
883    
884            public SoftRef(int hash, Object r, ReferenceQueue q) {
885                super(r, q);
886                this.hash = hash;
887            }
888    
889            public int hashCode() {
890                return hash;
891            }
892        }
893    
894        /**
895         * A weak reference holder.
896         */
897        static class WeakRef extends WeakReference {
898            /** the hashCode of the key (even if the reference points to a value) */
899            private int hash;
900    
901            public WeakRef(int hash, Object r, ReferenceQueue q) {
902                super(r, q);
903                this.hash = hash;
904            }
905    
906            public int hashCode() {
907                return hash;
908            }
909        }
910    
911        //-----------------------------------------------------------------------
912        /**
913         * Replaces the superclass method to store the state of this class.
914         * <p>
915         * Serialization is not one of the JDK's nicest topics. Normal serialization will
916         * initialise the superclass before the subclass. Sometimes however, this isn't
917         * what you want, as in this case the <code>put()</code> method on read can be
918         * affected by subclass state.
919         * <p>
920         * The solution adopted here is to serialize the state data of this class in
921         * this protected method. This method must be called by the
922         * <code>writeObject()</code> of the first serializable subclass.
923         * <p>
924         * Subclasses may override if they have a specific field that must be present
925         * on read before this implementation will work. Generally, the read determines
926         * what must be serialized here, if anything.
927         * 
928         * @param out  the output stream
929         */
930        protected void doWriteObject(ObjectOutputStream out) throws IOException {
931            out.writeInt(keyType);
932            out.writeInt(valueType);
933            out.writeBoolean(purgeValues);
934            out.writeFloat(loadFactor);
935            out.writeInt(data.length);
936            for (MapIterator it = mapIterator(); it.hasNext();) {
937                out.writeObject(it.next());
938                out.writeObject(it.getValue());
939            }
940            out.writeObject(null);  // null terminate map
941            // do not call super.doWriteObject() as code there doesn't work for reference map
942        }
943    
944        /**
945         * Replaces the superclassm method to read the state of this class.
946         * <p>
947         * Serialization is not one of the JDK's nicest topics. Normal serialization will
948         * initialise the superclass before the subclass. Sometimes however, this isn't
949         * what you want, as in this case the <code>put()</code> method on read can be
950         * affected by subclass state.
951         * <p>
952         * The solution adopted here is to deserialize the state data of this class in
953         * this protected method. This method must be called by the
954         * <code>readObject()</code> of the first serializable subclass.
955         * <p>
956         * Subclasses may override if the subclass has a specific field that must be present
957         * before <code>put()</code> or <code>calculateThreshold()</code> will work correctly.
958         * 
959         * @param in  the input stream
960         */
961        protected void doReadObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
962            this.keyType = in.readInt();
963            this.valueType = in.readInt();
964            this.purgeValues = in.readBoolean();
965            this.loadFactor = in.readFloat();
966            int capacity = in.readInt();
967            init();
968            data = new HashEntry[capacity];
969            while (true) {
970                Object key = in.readObject();
971                if (key == null) {
972                    break;
973                }
974                Object value = in.readObject();
975                put(key, value);
976            }
977            threshold = calculateThreshold(data.length, loadFactor);
978            // do not call super.doReadObject() as code there doesn't work for reference map
979        }
980    
981    }