View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.collections4.map;
18  
19  import java.io.IOException;
20  import java.io.ObjectInputStream;
21  import java.io.ObjectOutputStream;
22  import java.lang.ref.Reference;
23  import java.lang.ref.ReferenceQueue;
24  import java.lang.ref.SoftReference;
25  import java.lang.ref.WeakReference;
26  import java.util.ArrayList;
27  import java.util.Collection;
28  import java.util.ConcurrentModificationException;
29  import java.util.Iterator;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.NoSuchElementException;
33  import java.util.Objects;
34  import java.util.Set;
35  
36  import org.apache.commons.collections4.MapIterator;
37  import org.apache.commons.collections4.keyvalue.DefaultMapEntry;
38  
39  /**
40   * An abstract implementation of a hash-based map that allows the entries to
41   * be removed by the garbage collector.
42   * <p>
43   * This class implements all the features necessary for a subclass reference
44   * hash-based map. Key-value entries are stored in instances of the
45   * {@code ReferenceEntry} class which can be overridden and replaced.
46   * The iterators can similarly be replaced, without the need to replace the KeySet,
47   * EntrySet and Values view classes.
48   * </p>
49   * <p>
50   * Overridable methods are provided to change the default hashing behavior, and
51   * to change how entries are added to and removed from the map. Hopefully, all you
52   * need for unusual subclasses is here.
53   * </p>
54   * <p>
55   * When you construct an {@code AbstractReferenceMap}, you can specify what
56   * kind of references are used to store the map's keys and values.
57   * If non-hard references are used, then the garbage collector can remove
58   * mappings if a key or value becomes unreachable, or if the JVM's memory is
59   * running low. For information on how the different reference types behave,
60   * see {@link Reference}.
61   * </p>
62   * <p>
63   * Different types of references can be specified for keys and values.
64   * The keys can be configured to be weak but the values hard,
65   * in which case this class will behave like a
66   * <a href="https://docs.oracle.com/javase/8/docs/api/java/util/WeakHashMap.html">
67   * {@code WeakHashMap}</a>. However, you can also specify hard keys and
68   * weak values, or any other combination. The default constructor uses
69   * hard keys and soft values, providing a memory-sensitive cache.
70   * </p>
71   * <p>
72   * This {@link Map} implementation does <i>not</i> allow null elements.
73   * Attempting to add a null key or value to the map will raise a
74   * {@code NullPointerException}.
75   * </p>
76   * <p>
77   * All the available iterators can be reset back to the start by casting to
78   * {@code ResettableIterator} and calling {@code reset()}.
79   * </p>
80   * <p>
81   * This implementation is not synchronized.
82   * You can use {@link java.util.Collections#synchronizedMap} to
83   * provide synchronized access to a {@code ReferenceMap}.
84   * </p>
85   *
86   * @param <K> the type of the keys in this map
87   * @param <V> the type of the values in this map
88   *
89   * @see java.lang.ref.Reference
90   * @since 3.1 (extracted from ReferenceMap in 3.0)
91   */
92  public abstract class AbstractReferenceMap<K, V> extends AbstractHashedMap<K, V> {
93  
94      /**
95       * Base iterator class.
96       */
97      static class ReferenceBaseIterator<K, V> {
98          /** The parent map */
99          final AbstractReferenceMap<K, V> parent;
100 
101         // These fields keep track of where we are in the table.
102         int index;
103         ReferenceEntry<K, V> next;
104         ReferenceEntry<K, V> current;
105 
106         // These Object fields provide hard references to the
107         // current and next entry; this assures that if hasNext()
108         // returns true, next() will actually return a valid element.
109         K currentKey, nextKey;
110         V currentValue, nextValue;
111 
112         int expectedModCount;
113 
114         ReferenceBaseIterator(final AbstractReferenceMap<K, V> parent) {
115             this.parent = parent;
116             index = !parent.isEmpty() ? parent.data.length : 0;
117             // have to do this here!  size() invocation above
118             // may have altered the modCount.
119             expectedModCount = parent.modCount;
120         }
121 
122         private void checkMod() {
123             if (parent.modCount != expectedModCount) {
124                 throw new ConcurrentModificationException();
125             }
126         }
127 
128         protected ReferenceEntry<K, V> currentEntry() {
129             checkMod();
130             return current;
131         }
132 
133         public boolean hasNext() {
134             checkMod();
135             while (nextNull()) {
136                 ReferenceEntry<K, V> e = next;
137                 int i = index;
138                 while (e == null && i > 0) {
139                     i--;
140                     e = (ReferenceEntry<K, V>) parent.data[i];
141                 }
142                 next = e;
143                 index = i;
144                 if (e == null) {
145                     return false;
146                 }
147                 nextKey = e.getKey();
148                 nextValue = e.getValue();
149                 if (nextNull()) {
150                     next = next.next();
151                 }
152             }
153             return true;
154         }
155 
156         protected ReferenceEntry<K, V> nextEntry() {
157             checkMod();
158             if (nextNull() && !hasNext()) {
159                 throw new NoSuchElementException();
160             }
161             current = next;
162             next = next.next();
163             currentKey = nextKey;
164             currentValue = nextValue;
165             nextKey = null;
166             nextValue = null;
167             return current;
168         }
169 
170         private boolean nextNull() {
171             return nextKey == null || nextValue == null;
172         }
173 
174         public void remove() {
175             checkMod();
176             if (current == null) {
177                 throw new IllegalStateException();
178             }
179             parent.remove(currentKey);
180             current = null;
181             currentKey = null;
182             currentValue = null;
183             expectedModCount = parent.modCount;
184         }
185     }
186 
187     /**
188      * A MapEntry implementation for the map.
189      * <p>
190      * If getKey() or getValue() returns null, it means
191      * the mapping is stale and should be removed.
192      * </p>
193      *
194      * @param <K> the type of the keys
195      * @param <V> the type of the values
196      * @since 3.1
197      */
198     protected static class ReferenceEntry<K, V> extends HashEntry<K, V> {
199         /** The parent map */
200         private final AbstractReferenceMap<K, V> parent;
201 
202         /**
203          * Creates a new entry object for the ReferenceMap.
204          *
205          * @param parent  the parent map
206          * @param next  the next entry in the hash bucket
207          * @param hashCode  the hash code of the key
208          * @param key  the key
209          * @param value  the value
210          */
211         public ReferenceEntry(final AbstractReferenceMap<K, V> parent, final HashEntry<K, V> next,
212                               final int hashCode, final K key, final V value) {
213             super(next, hashCode, null, null);
214             this.parent = parent;
215             this.key = toReference(parent.keyType, key, hashCode);
216             this.value = toReference(parent.valueType, value, hashCode); // the key hashCode is passed in deliberately
217         }
218 
219         /**
220          * Compares this map entry to another.
221          * <p>
222          * This implementation uses {@code isEqualKey} and
223          * {@code isEqualValue} on the main map for comparison.
224          *
225          * @param obj  the other map entry to compare to
226          * @return true if equal, false if not
227          */
228         @Override
229         public boolean equals(final Object obj) {
230             if (obj == this) {
231                 return true;
232             }
233             if (!(obj instanceof Map.Entry)) {
234                 return false;
235             }
236 
237             final Map.Entry<?, ?> entry = (Map.Entry<?, ?>) obj;
238             final Object entryKey = entry.getKey();  // convert to hard reference
239             final Object entryValue = entry.getValue();  // convert to hard reference
240             if (entryKey == null || entryValue == null) {
241                 return false;
242             }
243             // compare using map methods, aiding identity subclass
244             // note that key is direct access and value is via method
245             return parent.isEqualKey(entryKey, key) &&
246                    parent.isEqualValue(entryValue, getValue());
247         }
248 
249         /**
250          * Gets the key from the entry.
251          * This method dereferences weak and soft keys and thus may return null.
252          *
253          * @return the key, which may be null if it was garbage collected
254          */
255         @Override
256         @SuppressWarnings("unchecked")
257         public K getKey() {
258             return (K) (parent.keyType == ReferenceStrength.HARD ? key : ((Reference<K>) key).get());
259         }
260 
261         /**
262          * Gets the value from the entry.
263          * This method dereferences weak and soft value and thus may return null.
264          *
265          * @return the value, which may be null if it was garbage collected
266          */
267         @Override
268         @SuppressWarnings("unchecked")
269         public V getValue() {
270             return (V) (parent.valueType == ReferenceStrength.HARD ? value : ((Reference<V>) value).get());
271         }
272 
273         /**
274          * Gets the hash code of the entry using temporary hard references.
275          * <p>
276          * This implementation uses {@code hashEntry} on the main map.
277          *
278          * @return the hash code of the entry
279          */
280         @Override
281         public int hashCode() {
282             return parent.hashEntry(getKey(), getValue());
283         }
284 
285         /**
286          * Gets the next entry in the bucket.
287          *
288          * @return the next entry in the bucket
289          */
290         protected ReferenceEntry<K, V> next() {
291             return (ReferenceEntry<K, V>) next;
292         }
293 
294         /**
295          * This method can be overridden to provide custom logic to purge value
296          */
297         protected void nullValue() {
298             value = null;
299         }
300 
301         /**
302          * This is the callback for custom "after purge" logic
303          */
304         protected void onPurge() {
305             // empty
306         }
307 
308         /**
309          * Purges the specified reference
310          * @param ref  the reference to purge
311          * @return true or false
312          */
313         protected boolean purge(final Reference<?> ref) {
314             boolean r = parent.keyType != ReferenceStrength.HARD && key == ref;
315             r = r || parent.valueType != ReferenceStrength.HARD && value == ref;
316             if (r) {
317                 if (parent.keyType != ReferenceStrength.HARD) {
318                     ((Reference<?>) key).clear();
319                 }
320                 if (parent.valueType != ReferenceStrength.HARD) {
321                     ((Reference<?>) value).clear();
322                 } else if (parent.purgeValues) {
323                     nullValue();
324                 }
325             }
326             return r;
327         }
328 
329         /**
330          * Sets the value of the entry.
331          *
332          * @param obj  the object to store
333          * @return the previous value
334          */
335         @Override
336         @SuppressWarnings("unchecked")
337         public V setValue(final V obj) {
338             final V old = getValue();
339             if (parent.valueType != ReferenceStrength.HARD) {
340                 ((Reference<V>) value).clear();
341             }
342             value = toReference(parent.valueType, obj, hashCode);
343             return old;
344         }
345 
346         /**
347          * Constructs a reference of the given type to the given referent.
348          * The reference is registered with the queue for later purging.
349          *
350          * @param <T> the type of the referenced object
351          * @param type  HARD, SOFT or WEAK
352          * @param referent  the object to refer to
353          * @param hash  the hash code of the <i>key</i> of the mapping;
354          *    this number might be different from referent.hashCode() if
355          *    the referent represents a value and not a key
356          * @return the reference to the object
357          */
358         protected <T> Object toReference(final ReferenceStrength type, final T referent, final int hash) {
359             if (type == ReferenceStrength.HARD) {
360                 return referent;
361             }
362             if (type == ReferenceStrength.SOFT) {
363                 return new SoftRef<>(hash, referent, parent.queue);
364             }
365             if (type == ReferenceStrength.WEAK) {
366                 return new WeakRef<>(hash, referent, parent.queue);
367             }
368             throw new Error();
369         }
370     }
371 
372     /**
373      * EntrySet implementation.
374      */
375     static class ReferenceEntrySet<K, V> extends EntrySet<K, V> {
376 
377         protected ReferenceEntrySet(final AbstractHashedMap<K, V> parent) {
378             super(parent);
379         }
380 
381         @Override
382         public Object[] toArray() {
383             return toArray(new Object[size()]);
384         }
385 
386         @Override
387         public <T> T[] toArray(final T[] arr) {
388             // special implementation to handle disappearing entries
389             final ArrayList<Map.Entry<K, V>> list = new ArrayList<>(size());
390             for (final Map.Entry<K, V> entry : this) {
391                 list.add(new DefaultMapEntry<>(entry));
392             }
393             return list.toArray(arr);
394         }
395     }
396 
397     /**
398      * The EntrySet iterator.
399      */
400     static class ReferenceEntrySetIterator<K, V>
401             extends ReferenceBaseIterator<K, V> implements Iterator<Map.Entry<K, V>> {
402 
403         ReferenceEntrySetIterator(final AbstractReferenceMap<K, V> parent) {
404             super(parent);
405         }
406 
407         @Override
408         public Map.Entry<K, V> next() {
409             return nextEntry();
410         }
411 
412     }
413 
414     /**
415      * KeySet implementation.
416      */
417     static class ReferenceKeySet<K> extends KeySet<K> {
418 
419         protected ReferenceKeySet(final AbstractHashedMap<K, ?> parent) {
420             super(parent);
421         }
422 
423         @Override
424         public Object[] toArray() {
425             return toArray(new Object[size()]);
426         }
427 
428         @Override
429         public <T> T[] toArray(final T[] arr) {
430             // special implementation to handle disappearing keys
431             final List<K> list = new ArrayList<>(size());
432             for (final K key : this) {
433                 list.add(key);
434             }
435             return list.toArray(arr);
436         }
437     }
438 
439     /**
440      * The keySet iterator.
441      */
442     static class ReferenceKeySetIterator<K> extends ReferenceBaseIterator<K, Object> implements Iterator<K> {
443 
444         @SuppressWarnings("unchecked")
445         ReferenceKeySetIterator(final AbstractReferenceMap<K, ?> parent) {
446             super((AbstractReferenceMap<K, Object>) parent);
447         }
448 
449         @Override
450         public K next() {
451             return nextEntry().getKey();
452         }
453     }
454 
455     /**
456      * The MapIterator implementation.
457      */
458     static class ReferenceMapIterator<K, V> extends ReferenceBaseIterator<K, V> implements MapIterator<K, V> {
459 
460         protected ReferenceMapIterator(final AbstractReferenceMap<K, V> parent) {
461             super(parent);
462         }
463 
464         @Override
465         public K getKey() {
466             final HashEntry<K, V> current = currentEntry();
467             if (current == null) {
468                 throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID);
469             }
470             return current.getKey();
471         }
472 
473         @Override
474         public V getValue() {
475             final HashEntry<K, V> current = currentEntry();
476             if (current == null) {
477                 throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID);
478             }
479             return current.getValue();
480         }
481 
482         @Override
483         public K next() {
484             return nextEntry().getKey();
485         }
486 
487         @Override
488         public V setValue(final V value) {
489             final HashEntry<K, V> current = currentEntry();
490             if (current == null) {
491                 throw new IllegalStateException(AbstractHashedMap.SETVALUE_INVALID);
492             }
493             return current.setValue(value);
494         }
495     }
496 
497     /**
498      * Reference type enum.
499      */
500     public enum ReferenceStrength {
501         HARD(0), SOFT(1), WEAK(2);
502 
503         /**
504          * Resolve enum from int.
505          * @param value  the int value
506          * @return ReferenceType
507          * @throws IllegalArgumentException if the specified value is invalid.
508          */
509         public static ReferenceStrength resolve(final int value) {
510             switch (value) {
511             case 0:
512                 return HARD;
513             case 1:
514                 return SOFT;
515             case 2:
516                 return WEAK;
517             default:
518                 throw new IllegalArgumentException();
519             }
520         }
521 
522         /** Value */
523         public final int value;
524 
525         ReferenceStrength(final int value) {
526             this.value = value;
527         }
528 
529     }
530 
531     /**
532      * Values implementation.
533      */
534     static class ReferenceValues<V> extends Values<V> {
535 
536         protected ReferenceValues(final AbstractHashedMap<?, V> parent) {
537             super(parent);
538         }
539 
540         @Override
541         public Object[] toArray() {
542             return toArray(new Object[size()]);
543         }
544 
545         @Override
546         public <T> T[] toArray(final T[] arr) {
547             // special implementation to handle disappearing values
548             final List<V> list = new ArrayList<>(size());
549             for (final V value : this) {
550                 list.add(value);
551             }
552             return list.toArray(arr);
553         }
554     }
555 
556     /**
557      * The values iterator.
558      */
559     static class ReferenceValuesIterator<V> extends ReferenceBaseIterator<Object, V> implements Iterator<V> {
560 
561         @SuppressWarnings("unchecked")
562         ReferenceValuesIterator(final AbstractReferenceMap<?, V> parent) {
563             super((AbstractReferenceMap<Object, V>) parent);
564         }
565 
566         @Override
567         public V next() {
568             return nextEntry().getValue();
569         }
570     }
571 
572     /**
573      * A soft reference holder.
574      */
575     static class SoftRef<T> extends SoftReference<T> {
576         /** The hashCode of the key (even if the reference points to a value) */
577         private final int hash;
578 
579         SoftRef(final int hash, final T r, final ReferenceQueue<? super T> q) {
580             super(r, q);
581             this.hash = hash;
582         }
583 
584         @Override
585         public boolean equals(final Object obj) {
586             if (this == obj) {
587                 return true;
588             }
589             if (obj == null) {
590                 return false;
591             }
592             if (getClass() != obj.getClass()) {
593                 return false;
594             }
595             final SoftRef<?> other = (SoftRef<?>) obj;
596             return hash == other.hash;
597         }
598 
599         @Override
600         public int hashCode() {
601             return hash;
602         }
603     }
604 
605     /**
606      * A weak reference holder.
607      */
608     static class WeakRef<T> extends WeakReference<T> {
609         /** The hashCode of the key (even if the reference points to a value) */
610         private final int hash;
611 
612         WeakRef(final int hash, final T r, final ReferenceQueue<? super T> q) {
613             super(r, q);
614             this.hash = hash;
615         }
616 
617         @Override
618         public boolean equals(final Object obj) {
619             if (this == obj) {
620                 return true;
621             }
622             if (obj == null) {
623                 return false;
624             }
625             if (getClass() != obj.getClass()) {
626                 return false;
627             }
628             final WeakRef<?> other = (WeakRef<?>) obj;
629             return hash == other.hash;
630         }
631 
632         @Override
633         public int hashCode() {
634             return hash;
635         }
636     }
637 
638     /**
639      * The reference type for keys.
640      */
641     private ReferenceStrength keyType;
642 
643     /**
644      * The reference type for values.
645      */
646     private ReferenceStrength valueType;
647 
648     /**
649      * Should the value be automatically purged when the associated key has been collected?
650      */
651     private boolean purgeValues;
652 
653     /**
654      * ReferenceQueue used to eliminate stale mappings.
655      * See purge.
656      */
657     private transient ReferenceQueue<Object> queue;
658 
659     /**
660      * Constructor used during deserialization.
661      */
662     protected AbstractReferenceMap() {
663     }
664 
665     /**
666      * Constructs a new empty map with the specified reference types,
667      * load factor and initial capacity.
668      *
669      * @param keyType  the type of reference to use for keys;
670      *   must be {@link ReferenceStrength#HARD HARD},
671      *   {@link ReferenceStrength#SOFT SOFT},
672      *   {@link ReferenceStrength#WEAK WEAK}
673      * @param valueType  the type of reference to use for values;
674      *   must be {@link ReferenceStrength#HARD},
675      *   {@link ReferenceStrength#SOFT SOFT},
676      *   {@link ReferenceStrength#WEAK WEAK}
677      * @param capacity  the initial capacity for the map
678      * @param loadFactor  the load factor for the map
679      * @param purgeValues  should the value be automatically purged when the
680      *   key is garbage collected
681      */
682     protected AbstractReferenceMap(
683             final ReferenceStrength keyType, final ReferenceStrength valueType, final int capacity,
684             final float loadFactor, final boolean purgeValues) {
685         super(capacity, loadFactor);
686         this.keyType = keyType;
687         this.valueType = valueType;
688         this.purgeValues = purgeValues;
689     }
690 
691     /**
692      * Clears this map.
693      */
694     @Override
695     public void clear() {
696         super.clear();
697         // Drain the queue
698         while (queue.poll() != null) { // NOPMD
699         }
700     }
701 
702     /**
703      * Checks whether the map contains the specified key.
704      *
705      * @param key  the key to search for
706      * @return true if the map contains the key
707      */
708     @Override
709     public boolean containsKey(final Object key) {
710         purgeBeforeRead();
711         final Entry<K, V> entry = getEntry(key);
712         if (entry == null) {
713             return false;
714         }
715         return entry.getValue() != null;
716     }
717 
718     /**
719      * Checks whether the map contains the specified value.
720      *
721      * @param value  the value to search for
722      * @return true if the map contains the value
723      */
724     @Override
725     public boolean containsValue(final Object value) {
726         purgeBeforeRead();
727         if (value == null) {
728             return false;
729         }
730         return super.containsValue(value);
731     }
732 
733     /**
734      * Creates a ReferenceEntry instead of a HashEntry.
735      *
736      * @param next  the next entry in sequence
737      * @param hashCode  the hash code to use
738      * @param key  the key to store
739      * @param value  the value to store
740      * @return the newly created entry
741      */
742     @Override
743     protected ReferenceEntry<K, V> createEntry(final HashEntry<K, V> next, final int hashCode,
744                                                final K key, final V value) {
745         return new ReferenceEntry<>(this, next, hashCode, key, value);
746     }
747 
748     /**
749      * Creates an entry set iterator.
750      *
751      * @return the entrySet iterator
752      */
753     @Override
754     protected Iterator<Map.Entry<K, V>> createEntrySetIterator() {
755         return new ReferenceEntrySetIterator<>(this);
756     }
757 
758     /**
759      * Creates a key set iterator.
760      *
761      * @return the keySet iterator
762      */
763     @Override
764     protected Iterator<K> createKeySetIterator() {
765         return new ReferenceKeySetIterator<>(this);
766     }
767 
768     /**
769      * Creates a values iterator.
770      *
771      * @return the values iterator
772      */
773     @Override
774     protected Iterator<V> createValuesIterator() {
775         return new ReferenceValuesIterator<>(this);
776     }
777 
778     /**
779      * Replaces the superclass method to read the state of this class.
780      * <p>
781      * Serialization is not one of the JDK's nicest topics. Normal serialization will
782      * initialize the superclass before the subclass. Sometimes however, this isn't
783      * what you want, as in this case the {@code put()} method on read can be
784      * affected by subclass state.
785      * <p>
786      * The solution adopted here is to deserialize the state data of this class in
787      * this protected method. This method must be called by the
788      * {@code readObject()} of the first serializable subclass.
789      * <p>
790      * Subclasses may override if the subclass has a specific field that must be present
791      * before {@code put()} or {@code calculateThreshold()} will work correctly.
792      *
793      * @param in  the input stream
794      * @throws IOException if an error occurs while reading from the stream
795      * @throws ClassNotFoundException if an object read from the stream can not be loaded
796      */
797     @Override
798     @SuppressWarnings("unchecked")
799     protected void doReadObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
800         this.keyType = ReferenceStrength.resolve(in.readInt());
801         this.valueType = ReferenceStrength.resolve(in.readInt());
802         this.purgeValues = in.readBoolean();
803         this.loadFactor = in.readFloat();
804         final int capacity = in.readInt();
805         init();
806         data = new HashEntry[capacity];
807 
808         // COLLECTIONS-599: Calculate threshold before populating, otherwise it will be 0
809         // when it hits AbstractHashedMap.checkCapacity() and so will unnecessarily
810         // double up the size of the "data" array during population.
811         //
812         // NB: AbstractHashedMap.doReadObject() DOES calculate the threshold before populating.
813         //
814         threshold = calculateThreshold(data.length, loadFactor);
815 
816         while (true) {
817             final K key = (K) in.readObject();
818             if (key == null) {
819                 break;
820             }
821             final V value = (V) in.readObject();
822             put(key, value);
823         }
824         // do not call super.doReadObject() as code there doesn't work for reference map
825     }
826 
827     /**
828      * Replaces the superclass method to store the state of this class.
829      * <p>
830      * Serialization is not one of the JDK's nicest topics. Normal serialization will
831      * initialize the superclass before the subclass. Sometimes however, this isn't
832      * what you want, as in this case the {@code put()} method on read can be
833      * affected by subclass state.
834      * <p>
835      * The solution adopted here is to serialize the state data of this class in
836      * this protected method. This method must be called by the
837      * {@code writeObject()} of the first serializable subclass.
838      * <p>
839      * Subclasses may override if they have a specific field that must be present
840      * on read before this implementation will work. Generally, the read determines
841      * what must be serialized here, if anything.
842      *
843      * @param out  the output stream
844      * @throws IOException if an error occurs while writing to the stream
845      */
846     @Override
847     protected void doWriteObject(final ObjectOutputStream out) throws IOException {
848         out.writeInt(keyType.value);
849         out.writeInt(valueType.value);
850         out.writeBoolean(purgeValues);
851         out.writeFloat(loadFactor);
852         out.writeInt(data.length);
853         for (final MapIterator<K, V> it = mapIterator(); it.hasNext();) {
854             out.writeObject(it.next());
855             out.writeObject(it.getValue());
856         }
857         out.writeObject(null);  // null terminate map
858         // do not call super.doWriteObject() as code there doesn't work for reference map
859     }
860 
861     /**
862      * Returns a set view of this map's entries.
863      * An iterator returned entry is valid until {@code next()} is called again.
864      * The {@code setValue()} method on the {@code toArray} entries has no effect.
865      *
866      * @return a set view of this map's entries
867      */
868     @Override
869     public Set<Map.Entry<K, V>> entrySet() {
870         if (entrySet == null) {
871             entrySet = new ReferenceEntrySet<>(this);
872         }
873         return entrySet;
874     }
875 
876     /**
877      * Gets the value mapped to the key specified.
878      *
879      * @param key  the key
880      * @return the mapped value, null if no match
881      */
882     @Override
883     public V get(final Object key) {
884         purgeBeforeRead();
885         final Entry<K, V> entry = getEntry(key);
886         if (entry == null) {
887             return null;
888         }
889         return entry.getValue();
890     }
891 
892     /**
893      * Gets the entry mapped to the key specified.
894      *
895      * @param key  the key
896      * @return the entry, null if no match
897      */
898     @Override
899     protected HashEntry<K, V> getEntry(final Object key) {
900         if (key == null) {
901             return null;
902         }
903         return super.getEntry(key);
904     }
905 
906     /**
907      * Gets the hash code for a MapEntry.
908      * Subclasses can override this, for example to use the identityHashCode.
909      *
910      * @param key  the key to get a hash code for, may be null
911      * @param value  the value to get a hash code for, may be null
912      * @return the hash code, as per the MapEntry specification
913      */
914     protected int hashEntry(final Object key, final Object value) {
915         return (key == null ? 0 : key.hashCode()) ^
916                (value == null ? 0 : value.hashCode());
917     }
918 
919     /**
920      * Initialize this subclass during construction, cloning or deserialization.
921      */
922     @Override
923     protected void init() {
924         queue = new ReferenceQueue<>();
925     }
926 
927     /**
928      * Checks whether the map is currently empty.
929      *
930      * @return true if the map is currently size zero
931      */
932     @Override
933     public boolean isEmpty() {
934         purgeBeforeRead();
935         return super.isEmpty();
936     }
937 
938     /**
939      * Compares two keys, in internal converted form, to see if they are equal.
940      * <p>
941      * This implementation converts the key from the entry to a real reference
942      * before comparison.
943      *
944      * @param key1  the first key to compare passed in from outside
945      * @param key2  the second key extracted from the entry via {@code entry.key}
946      * @return true if equal
947      */
948     @Override
949     @SuppressWarnings("unchecked")
950     protected boolean isEqualKey(final Object key1, Object key2) {
951         key2 = keyType == ReferenceStrength.HARD ? key2 : ((Reference<K>) key2).get();
952         return key1 == key2 || key1.equals(key2);
953     }
954 
955     /**
956      * Provided protected read-only access to the key type.
957      * @param type the type to check against.
958      * @return true if keyType has the specified type
959      */
960     protected boolean isKeyType(final ReferenceStrength type) {
961         return this.keyType == type;
962     }
963 
964     /**
965      * Provided protected read-only access to the value type.
966      * @param type the type to check against.
967      * @return true if valueType has the specified type
968      */
969     protected boolean isValueType(final ReferenceStrength type) {
970         return this.valueType == type;
971     }
972 
973     /**
974      * Returns a set view of this map's keys.
975      *
976      * @return a set view of this map's keys
977      */
978     @Override
979     public Set<K> keySet() {
980         if (keySet == null) {
981             keySet = new ReferenceKeySet<>(this);
982         }
983         return keySet;
984     }
985 
986     /**
987      * Gets a MapIterator over the reference map.
988      * The iterator only returns valid key/value pairs.
989      *
990      * @return a map iterator
991      */
992     @Override
993     public MapIterator<K, V> mapIterator() {
994         return new ReferenceMapIterator<>(this);
995     }
996 
997     /**
998      * Purges stale mappings from this map.
999      * <p>
1000      * Note that this method is not synchronized!  Special
1001      * care must be taken if, for instance, you want stale
1002      * mappings to be removed on a periodic basis by some
1003      * background thread.
1004      */
1005     protected void purge() {
1006         Reference<?> ref = queue.poll();
1007         while (ref != null) {
1008             purge(ref);
1009             ref = queue.poll();
1010         }
1011     }
1012 
1013     /**
1014      * Purges the specified reference.
1015      *
1016      * @param ref  the reference to purge
1017      */
1018     protected void purge(final Reference<?> ref) {
1019         // The hashCode of the reference is the hashCode of the
1020         // mapping key, even if the reference refers to the
1021         // mapping value...
1022         final int hash = ref.hashCode();
1023         final int index = hashIndex(hash, data.length);
1024         HashEntry<K, V> previous = null;
1025         HashEntry<K, V> entry = data[index];
1026         while (entry != null) {
1027             final ReferenceEntry<K, V> refEntry = (ReferenceEntry<K, V>) entry;
1028             if (refEntry.purge(ref)) {
1029                 if (previous == null) {
1030                     data[index] = entry.next;
1031                 } else {
1032                     previous.next = entry.next;
1033                 }
1034                 this.size--;
1035                 refEntry.onPurge();
1036                 return;
1037             }
1038             previous = entry;
1039             entry = entry.next;
1040         }
1041 
1042     }
1043 
1044     // These two classes store the hashCode of the key of
1045     // the mapping, so that after they're dequeued a quick
1046     // lookup of the bucket in the table can occur.
1047 
1048     /**
1049      * Purges stale mappings from this map before read operations.
1050      * <p>
1051      * This implementation calls {@link #purge()} to maintain a consistent state.
1052      */
1053     protected void purgeBeforeRead() {
1054         purge();
1055     }
1056 
1057     /**
1058      * Purges stale mappings from this map before write operations.
1059      * <p>
1060      * This implementation calls {@link #purge()} to maintain a consistent state.
1061      */
1062     protected void purgeBeforeWrite() {
1063         purge();
1064     }
1065 
1066     /**
1067      * Puts a key-value mapping into this map.
1068      * Neither the key nor the value may be null.
1069      *
1070      * @param key  the key to add, must not be null
1071      * @param value  the value to add, must not be null
1072      * @return the value previously mapped to this key, null if none
1073      * @throws NullPointerException if either the key or value is null
1074      */
1075     @Override
1076     public V put(final K key, final V value) {
1077         Objects.requireNonNull(key, "key");
1078         Objects.requireNonNull(value, "value");
1079         purgeBeforeWrite();
1080         return super.put(key, value);
1081     }
1082 
1083     /**
1084      * Removes the specified mapping from this map.
1085      *
1086      * @param key  the mapping to remove
1087      * @return the value mapped to the removed key, null if key not in map
1088      */
1089     @Override
1090     public V remove(final Object key) {
1091         if (key == null) {
1092             return null;
1093         }
1094         purgeBeforeWrite();
1095         return super.remove(key);
1096     }
1097 
1098     /**
1099      * Gets the size of the map.
1100      *
1101      * @return the size
1102      */
1103     @Override
1104     public int size() {
1105         purgeBeforeRead();
1106         return super.size();
1107     }
1108 
1109     /**
1110      * Returns a collection view of this map's values.
1111      *
1112      * @return a set view of this map's values
1113      */
1114     @Override
1115     public Collection<V> values() {
1116         if (values == null) {
1117             values = new ReferenceValues<>(this);
1118         }
1119         return values;
1120     }
1121 }