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 <em>not</em> 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   * @see java.lang.ref.Reference
89   * @since 3.1 (extracted from ReferenceMap in 3.0)
90   */
91  public abstract class AbstractReferenceMap<K, V> extends AbstractHashedMap<K, V> {
92  
93      /**
94       * Base iterator class.
95       */
96      static class ReferenceBaseIterator<K, V> {
97          /** The parent map */
98          final AbstractReferenceMap<K, V> parent;
99  
100         // These fields keep track of where we are in the table.
101         int index;
102         ReferenceEntry<K, V> next;
103         ReferenceEntry<K, V> current;
104 
105         // These Object fields provide hard references to the
106         // current and next entry; this assures that if hasNext()
107         // returns true, next() will actually return a valid element.
108         K currentKey, nextKey;
109         V currentValue, nextValue;
110 
111         int expectedModCount;
112 
113         ReferenceBaseIterator(final AbstractReferenceMap<K, V> parent) {
114             this.parent = parent;
115             index = !parent.isEmpty() ? parent.data.length : 0;
116             // have to do this here!  size() invocation above
117             // may have altered the modCount.
118             expectedModCount = parent.modCount;
119         }
120 
121         private void checkMod() {
122             if (parent.modCount != expectedModCount) {
123                 throw new ConcurrentModificationException();
124             }
125         }
126 
127         protected ReferenceEntry<K, V> currentEntry() {
128             checkMod();
129             return current;
130         }
131 
132         public boolean hasNext() {
133             checkMod();
134             while (nextNull()) {
135                 ReferenceEntry<K, V> e = next;
136                 int i = index;
137                 while (e == null && i > 0) {
138                     i--;
139                     e = (ReferenceEntry<K, V>) parent.data[i];
140                 }
141                 next = e;
142                 index = i;
143                 if (e == null) {
144                     return false;
145                 }
146                 nextKey = e.getKey();
147                 nextValue = e.getValue();
148                 if (nextNull()) {
149                     next = next.next();
150                 }
151             }
152             return true;
153         }
154 
155         protected ReferenceEntry<K, V> nextEntry() {
156             checkMod();
157             if (nextNull() && !hasNext()) {
158                 throw new NoSuchElementException();
159             }
160             current = next;
161             next = next.next();
162             currentKey = nextKey;
163             currentValue = nextValue;
164             nextKey = null;
165             nextValue = null;
166             return current;
167         }
168 
169         private boolean nextNull() {
170             return nextKey == null || nextValue == null;
171         }
172 
173         public void remove() {
174             checkMod();
175             if (current == null) {
176                 throw new IllegalStateException();
177             }
178             parent.remove(currentKey);
179             current = null;
180             currentKey = null;
181             currentValue = null;
182             expectedModCount = parent.modCount;
183         }
184     }
185 
186     /**
187      * A MapEntry implementation for the map.
188      * <p>
189      * If getKey() or getValue() returns null, it means
190      * the mapping is stale and should be removed.
191      * </p>
192      *
193      * @param <K> the type of the keys
194      * @param <V> the type of the values
195      * @since 3.1
196      */
197     protected static class ReferenceEntry<K, V> extends HashEntry<K, V> {
198         /** The parent map */
199         private final AbstractReferenceMap<K, V> parent;
200 
201         /**
202          * Creates a new entry object for the ReferenceMap.
203          *
204          * @param parent  the parent map
205          * @param next  the next entry in the hash bucket
206          * @param hashCode  the hash code of the key
207          * @param key  the key
208          * @param value  the value
209          */
210         public ReferenceEntry(final AbstractReferenceMap<K, V> parent, final HashEntry<K, V> next,
211                               final int hashCode, final K key, final V value) {
212             super(next, hashCode, null, null);
213             this.parent = parent;
214             this.key = toReference(parent.keyType, key, hashCode);
215             this.value = toReference(parent.valueType, value, hashCode); // the key hashCode is passed in deliberately
216         }
217 
218         /**
219          * Compares this map entry to another.
220          * <p>
221          * This implementation uses {@code isEqualKey} and
222          * {@code isEqualValue} on the main map for comparison.
223          * </p>
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 value  the object to store
333          * @return the previous value
334          */
335         @Override
336         @SuppressWarnings("unchecked")
337         public V setValue(final V value) {
338             final V old = getValue();
339             if (parent.valueType != ReferenceStrength.HARD) {
340                 ((Reference<V>) this.value).clear();
341             }
342             this.value = toReference(parent.valueType, value, 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 <em>key</em> 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             switch (type) {
360             case HARD:
361                 return referent;
362             case SOFT:
363                 return new SoftRef<>(hash, referent, parent.queue);
364             case WEAK:
365                 return new WeakRef<>(hash, referent, parent.queue);
366             default:
367                 break;
368             }
369             throw new Error();
370         }
371     }
372 
373     /**
374      * EntrySet implementation.
375      */
376     static class ReferenceEntrySet<K, V> extends EntrySet<K, V> {
377 
378         protected ReferenceEntrySet(final AbstractHashedMap<K, V> parent) {
379             super(parent);
380         }
381 
382         @Override
383         public Object[] toArray() {
384             return toArray(new Object[size()]);
385         }
386 
387         @Override
388         public <T> T[] toArray(final T[] arr) {
389             // special implementation to handle disappearing entries
390             final ArrayList<Map.Entry<K, V>> list = new ArrayList<>(size());
391             for (final Map.Entry<K, V> entry : this) {
392                 list.add(new DefaultMapEntry<>(entry));
393             }
394             return list.toArray(arr);
395         }
396     }
397 
398     /**
399      * The EntrySet iterator.
400      */
401     static class ReferenceEntrySetIterator<K, V>
402             extends ReferenceBaseIterator<K, V> implements Iterator<Map.Entry<K, V>> {
403 
404         ReferenceEntrySetIterator(final AbstractReferenceMap<K, V> parent) {
405             super(parent);
406         }
407 
408         @Override
409         public Map.Entry<K, V> next() {
410             return nextEntry();
411         }
412 
413     }
414 
415     /**
416      * KeySet implementation.
417      */
418     static class ReferenceKeySet<K> extends KeySet<K> {
419 
420         protected ReferenceKeySet(final AbstractHashedMap<K, ?> parent) {
421             super(parent);
422         }
423 
424         @Override
425         public Object[] toArray() {
426             return toArray(new Object[size()]);
427         }
428 
429         @Override
430         public <T> T[] toArray(final T[] arr) {
431             // special implementation to handle disappearing keys
432             final List<K> list = new ArrayList<>(size());
433             forEach(list::add);
434             return list.toArray(arr);
435         }
436     }
437 
438     /**
439      * The keySet iterator.
440      */
441     static class ReferenceKeySetIterator<K> extends ReferenceBaseIterator<K, Object> implements Iterator<K> {
442 
443         @SuppressWarnings("unchecked")
444         ReferenceKeySetIterator(final AbstractReferenceMap<K, ?> parent) {
445             super((AbstractReferenceMap<K, Object>) parent);
446         }
447 
448         @Override
449         public K next() {
450             return nextEntry().getKey();
451         }
452     }
453 
454     /**
455      * The MapIterator implementation.
456      */
457     static class ReferenceMapIterator<K, V> extends ReferenceBaseIterator<K, V> implements MapIterator<K, V> {
458 
459         protected ReferenceMapIterator(final AbstractReferenceMap<K, V> parent) {
460             super(parent);
461         }
462 
463         @Override
464         public K getKey() {
465             final HashEntry<K, V> current = currentEntry();
466             if (current == null) {
467                 throw new IllegalStateException(GETKEY_INVALID);
468             }
469             return current.getKey();
470         }
471 
472         @Override
473         public V getValue() {
474             final HashEntry<K, V> current = currentEntry();
475             if (current == null) {
476                 throw new IllegalStateException(GETVALUE_INVALID);
477             }
478             return current.getValue();
479         }
480 
481         @Override
482         public K next() {
483             return nextEntry().getKey();
484         }
485 
486         @Override
487         public V setValue(final V value) {
488             final HashEntry<K, V> current = currentEntry();
489             if (current == null) {
490                 throw new IllegalStateException(SETVALUE_INVALID);
491             }
492             return current.setValue(value);
493         }
494     }
495 
496     /**
497      * Enumerates reference types.
498      */
499     public enum ReferenceStrength {
500 
501         /**
502          * Hard reference type.
503          */
504         HARD(0),
505 
506         /**
507          * Soft reference type.
508          */
509         SOFT(1),
510 
511         /**
512          * Weak reference type.
513          */
514         WEAK(2);
515 
516         /**
517          * Resolve enum from int.
518          * @param value  the int value
519          * @return ReferenceType
520          * @throws IllegalArgumentException if the specified value is invalid.
521          */
522         public static ReferenceStrength resolve(final int value) {
523             switch (value) {
524             case 0:
525                 return HARD;
526             case 1:
527                 return SOFT;
528             case 2:
529                 return WEAK;
530             default:
531                 throw new IllegalArgumentException();
532             }
533         }
534 
535         /** Value */
536         public final int value;
537 
538         ReferenceStrength(final int value) {
539             this.value = value;
540         }
541 
542     }
543 
544     /**
545      * Values implementation.
546      */
547     static class ReferenceValues<V> extends Values<V> {
548 
549         protected ReferenceValues(final AbstractHashedMap<?, V> parent) {
550             super(parent);
551         }
552 
553         @Override
554         public Object[] toArray() {
555             return toArray(new Object[size()]);
556         }
557 
558         @Override
559         public <T> T[] toArray(final T[] arr) {
560             // special implementation to handle disappearing values
561             final List<V> list = new ArrayList<>(size());
562             forEach(list::add);
563             return list.toArray(arr);
564         }
565     }
566 
567     /**
568      * The values iterator.
569      */
570     static class ReferenceValuesIterator<V> extends ReferenceBaseIterator<Object, V> implements Iterator<V> {
571 
572         @SuppressWarnings("unchecked")
573         ReferenceValuesIterator(final AbstractReferenceMap<?, V> parent) {
574             super((AbstractReferenceMap<Object, V>) parent);
575         }
576 
577         @Override
578         public V next() {
579             return nextEntry().getValue();
580         }
581     }
582 
583     /**
584      * A soft reference holder.
585      */
586     static class SoftRef<T> extends SoftReference<T> {
587         /** The hashCode of the key (even if the reference points to a value) */
588         private final int hash;
589 
590         SoftRef(final int hash, final T r, final ReferenceQueue<? super T> q) {
591             super(r, q);
592             this.hash = hash;
593         }
594 
595         @Override
596         public boolean equals(final Object obj) {
597             if (this == obj) {
598                 return true;
599             }
600             if (obj == null) {
601                 return false;
602             }
603             if (getClass() != obj.getClass()) {
604                 return false;
605             }
606             final SoftRef<?> other = (SoftRef<?>) obj;
607             return hash == other.hash;
608         }
609 
610         @Override
611         public int hashCode() {
612             return hash;
613         }
614     }
615 
616     /**
617      * A weak reference holder.
618      */
619     static class WeakRef<T> extends WeakReference<T> {
620         /** The hashCode of the key (even if the reference points to a value) */
621         private final int hash;
622 
623         WeakRef(final int hash, final T r, final ReferenceQueue<? super T> q) {
624             super(r, q);
625             this.hash = hash;
626         }
627 
628         @Override
629         public boolean equals(final Object obj) {
630             if (this == obj) {
631                 return true;
632             }
633             if (obj == null) {
634                 return false;
635             }
636             if (getClass() != obj.getClass()) {
637                 return false;
638             }
639             final WeakRef<?> other = (WeakRef<?>) obj;
640             return hash == other.hash;
641         }
642 
643         @Override
644         public int hashCode() {
645             return hash;
646         }
647     }
648 
649     /**
650      * The reference type for keys.
651      */
652     private ReferenceStrength keyType;
653 
654     /**
655      * The reference type for values.
656      */
657     private ReferenceStrength valueType;
658 
659     /**
660      * Should the value be automatically purged when the associated key has been collected?
661      */
662     private boolean purgeValues;
663 
664     /**
665      * ReferenceQueue used to eliminate stale mappings.
666      * See purge.
667      */
668     private transient ReferenceQueue<Object> queue;
669 
670     /**
671      * Constructor used during deserialization.
672      */
673     protected AbstractReferenceMap() {
674     }
675 
676     /**
677      * Constructs a new empty map with the specified reference types,
678      * load factor and initial capacity.
679      *
680      * @param keyType  the type of reference to use for keys;
681      *   must be {@link ReferenceStrength#HARD HARD},
682      *   {@link ReferenceStrength#SOFT SOFT},
683      *   {@link ReferenceStrength#WEAK WEAK}
684      * @param valueType  the type of reference to use for values;
685      *   must be {@link ReferenceStrength#HARD},
686      *   {@link ReferenceStrength#SOFT SOFT},
687      *   {@link ReferenceStrength#WEAK WEAK}
688      * @param capacity  the initial capacity for the map
689      * @param loadFactor  the load factor for the map
690      * @param purgeValues  should the value be automatically purged when the
691      *   key is garbage collected
692      */
693     protected AbstractReferenceMap(
694             final ReferenceStrength keyType, final ReferenceStrength valueType, final int capacity,
695             final float loadFactor, final boolean purgeValues) {
696         super(capacity, loadFactor);
697         this.keyType = keyType;
698         this.valueType = valueType;
699         this.purgeValues = purgeValues;
700     }
701 
702     /**
703      * Clears this map.
704      */
705     @Override
706     public void clear() {
707         super.clear();
708         // Drain the queue
709         while (queue.poll() != null) { // NOPMD
710         }
711     }
712 
713     /**
714      * Checks whether the map contains the specified key.
715      *
716      * @param key  the key to search for
717      * @return true if the map contains the key
718      */
719     @Override
720     public boolean containsKey(final Object key) {
721         purgeBeforeRead();
722         final Entry<K, V> entry = getEntry(key);
723         if (entry == null) {
724             return false;
725         }
726         return entry.getValue() != null;
727     }
728 
729     /**
730      * Checks whether the map contains the specified value.
731      *
732      * @param value  the value to search for
733      * @return true if the map contains the value
734      */
735     @Override
736     public boolean containsValue(final Object value) {
737         purgeBeforeRead();
738         if (value == null) {
739             return false;
740         }
741         return super.containsValue(value);
742     }
743 
744     /**
745      * Creates a ReferenceEntry instead of a HashEntry.
746      *
747      * @param next  the next entry in sequence
748      * @param hashCode  the hash code to use
749      * @param key  the key to store
750      * @param value  the value to store
751      * @return the newly created entry
752      */
753     @Override
754     protected ReferenceEntry<K, V> createEntry(final HashEntry<K, V> next, final int hashCode,
755                                                final K key, final V value) {
756         return new ReferenceEntry<>(this, next, hashCode, key, value);
757     }
758 
759     /**
760      * Creates an entry set iterator.
761      *
762      * @return the entrySet iterator
763      */
764     @Override
765     protected Iterator<Map.Entry<K, V>> createEntrySetIterator() {
766         return new ReferenceEntrySetIterator<>(this);
767     }
768 
769     /**
770      * Creates a key set iterator.
771      *
772      * @return the keySet iterator
773      */
774     @Override
775     protected Iterator<K> createKeySetIterator() {
776         return new ReferenceKeySetIterator<>(this);
777     }
778 
779     /**
780      * Creates a values iterator.
781      *
782      * @return the values iterator
783      */
784     @Override
785     protected Iterator<V> createValuesIterator() {
786         return new ReferenceValuesIterator<>(this);
787     }
788 
789     /**
790      * Replaces the superclass method to read the state of this class.
791      * <p>
792      * Serialization is not one of the JDK's nicest topics. Normal serialization will
793      * initialize the superclass before the subclass. Sometimes however, this isn't
794      * what you want, as in this case the {@code put()} method on read can be
795      * affected by subclass state.
796      * </p>
797      * <p>
798      * The solution adopted here is to deserialize the state data of this class in
799      * this protected method. This method must be called by the
800      * {@code readObject()} of the first serializable subclass.
801      * </p>
802      * <p>
803      * Subclasses may override if the subclass has a specific field that must be present
804      * before {@code put()} or {@code calculateThreshold()} will work correctly.
805      * </p>
806      *
807      * @param in  the input stream
808      * @throws IOException if an error occurs while reading from the stream
809      * @throws ClassNotFoundException if an object read from the stream cannot be loaded
810      */
811     @Override
812     @SuppressWarnings("unchecked")
813     protected void doReadObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
814         keyType = ReferenceStrength.resolve(in.readInt());
815         valueType = ReferenceStrength.resolve(in.readInt());
816         purgeValues = in.readBoolean();
817         loadFactor = in.readFloat();
818         final int capacity = in.readInt();
819         init();
820         data = new HashEntry[capacity];
821 
822         // COLLECTIONS-599: Calculate threshold before populating, otherwise it will be 0
823         // when it hits AbstractHashedMap.checkCapacity() and so will unnecessarily
824         // double up the size of the "data" array during population.
825         //
826         // NB: AbstractHashedMap.doReadObject() DOES calculate the threshold before populating.
827         //
828         threshold = calculateThreshold(data.length, loadFactor);
829 
830         while (true) {
831             final K key = (K) in.readObject();
832             if (key == null) {
833                 break;
834             }
835             final V value = (V) in.readObject();
836             put(key, value);
837         }
838         // do not call super.doReadObject() as code there doesn't work for reference map
839     }
840 
841     /**
842      * Replaces the superclass method to store the state of this class.
843      * <p>
844      * Serialization is not one of the JDK's nicest topics. Normal serialization will
845      * initialize the superclass before the subclass. Sometimes however, this isn't
846      * what you want, as in this case the {@code put()} method on read can be
847      * affected by subclass state.
848      * </p>
849      * <p>
850      * The solution adopted here is to serialize the state data of this class in
851      * this protected method. This method must be called by the
852      * {@code writeObject()} of the first serializable subclass.
853      * </p>
854      * <p>
855      * Subclasses may override if they have a specific field that must be present
856      * on read before this implementation will work. Generally, the read determines
857      * what must be serialized here, if anything.
858      * </p>
859      *
860      * @param out  the output stream
861      * @throws IOException if an error occurs while writing to the stream
862      */
863     @Override
864     protected void doWriteObject(final ObjectOutputStream out) throws IOException {
865         out.writeInt(keyType.value);
866         out.writeInt(valueType.value);
867         out.writeBoolean(purgeValues);
868         out.writeFloat(loadFactor);
869         out.writeInt(data.length);
870         for (final MapIterator<K, V> it = mapIterator(); it.hasNext();) {
871             out.writeObject(it.next());
872             out.writeObject(it.getValue());
873         }
874         out.writeObject(null);  // null terminate map
875         // do not call super.doWriteObject() as code there doesn't work for reference map
876     }
877 
878     /**
879      * Returns a set view of this map's entries.
880      * An iterator returned entry is valid until {@code next()} is called again.
881      * The {@code setValue()} method on the {@code toArray} entries has no effect.
882      *
883      * @return a set view of this map's entries
884      */
885     @Override
886     public Set<Map.Entry<K, V>> entrySet() {
887         if (entrySet == null) {
888             entrySet = new ReferenceEntrySet<>(this);
889         }
890         return entrySet;
891     }
892 
893     /**
894      * Gets the value mapped to the key specified.
895      *
896      * @param key  the key
897      * @return the mapped value, null if no match
898      */
899     @Override
900     public V get(final Object key) {
901         purgeBeforeRead();
902         final Entry<K, V> entry = getEntry(key);
903         if (entry == null) {
904             return null;
905         }
906         return entry.getValue();
907     }
908 
909     /**
910      * Gets the entry mapped to the key specified.
911      *
912      * @param key  the key
913      * @return the entry, null if no match
914      */
915     @Override
916     protected HashEntry<K, V> getEntry(final Object key) {
917         if (key == null) {
918             return null;
919         }
920         return super.getEntry(key);
921     }
922 
923     /**
924      * Gets the hash code for a MapEntry.
925      * Subclasses can override this, for example to use the identityHashCode.
926      *
927      * @param key  the key to get a hash code for, may be null
928      * @param value  the value to get a hash code for, may be null
929      * @return the hash code, as per the MapEntry specification
930      */
931     protected int hashEntry(final Object key, final Object value) {
932         return (key == null ? 0 : key.hashCode()) ^
933                (value == null ? 0 : value.hashCode());
934     }
935 
936     /**
937      * Initialize this subclass during construction, cloning or deserialization.
938      */
939     @Override
940     protected void init() {
941         queue = new ReferenceQueue<>();
942     }
943 
944     /**
945      * Checks whether the map is currently empty.
946      *
947      * @return true if the map is currently size zero
948      */
949     @Override
950     public boolean isEmpty() {
951         purgeBeforeRead();
952         return super.isEmpty();
953     }
954 
955     /**
956      * Compares two keys, in internal converted form, to see if they are equal.
957      * <p>
958      * This implementation converts the key from the entry to a real reference
959      * before comparison.
960      * </p>
961      *
962      * @param key1  the first key to compare passed in from outside
963      * @param key2  the second key extracted from the entry via {@code entry.key}
964      * @return true if equal
965      */
966     @Override
967     @SuppressWarnings("unchecked")
968     protected boolean isEqualKey(final Object key1, Object key2) {
969         key2 = keyType == ReferenceStrength.HARD ? key2 : ((Reference<K>) key2).get();
970         return Objects.equals(key1, key2);
971     }
972 
973     /**
974      * Provided protected read-only access to the key type.
975      *
976      * @param type the type to check against.
977      * @return true if keyType has the specified type
978      */
979     protected boolean isKeyType(final ReferenceStrength type) {
980         return keyType == type;
981     }
982 
983     /**
984      * Provided protected read-only access to the value type.
985      *
986      * @param type the type to check against.
987      * @return true if valueType has the specified type
988      */
989     protected boolean isValueType(final ReferenceStrength type) {
990         return valueType == type;
991     }
992 
993     /**
994      * Returns a set view of this map's keys.
995      *
996      * @return a set view of this map's keys
997      */
998     @Override
999     public Set<K> keySet() {
1000         if (keySet == null) {
1001             keySet = new ReferenceKeySet<>(this);
1002         }
1003         return keySet;
1004     }
1005 
1006     /**
1007      * Gets a MapIterator over the reference map.
1008      * The iterator only returns valid key/value pairs.
1009      *
1010      * @return a map iterator
1011      */
1012     @Override
1013     public MapIterator<K, V> mapIterator() {
1014         return new ReferenceMapIterator<>(this);
1015     }
1016 
1017     /**
1018      * Purges stale mappings from this map.
1019      * <p>
1020      * Note that this method is not synchronized!  Special
1021      * care must be taken if, for instance, you want stale
1022      * mappings to be removed on a periodic basis by some
1023      * background thread.
1024      * </p>
1025      */
1026     protected void purge() {
1027         Reference<?> ref = queue.poll();
1028         while (ref != null) {
1029             purge(ref);
1030             ref = queue.poll();
1031         }
1032     }
1033 
1034     /**
1035      * Purges the specified reference.
1036      *
1037      * @param ref  the reference to purge
1038      */
1039     protected void purge(final Reference<?> ref) {
1040         // The hashCode of the reference is the hashCode of the
1041         // mapping key, even if the reference refers to the
1042         // mapping value...
1043         final int hash = ref.hashCode();
1044         final int index = hashIndex(hash, data.length);
1045         HashEntry<K, V> previous = null;
1046         HashEntry<K, V> entry = data[index];
1047         while (entry != null) {
1048             final ReferenceEntry<K, V> refEntry = (ReferenceEntry<K, V>) entry;
1049             if (refEntry.purge(ref)) {
1050                 if (previous == null) {
1051                     data[index] = entry.next;
1052                 } else {
1053                     previous.next = entry.next;
1054                 }
1055                 size--;
1056                 refEntry.onPurge();
1057                 return;
1058             }
1059             previous = entry;
1060             entry = entry.next;
1061         }
1062 
1063     }
1064 
1065     // These two classes store the hashCode of the key of
1066     // the mapping, so that after they're dequeued a quick
1067     // lookup of the bucket in the table can occur.
1068 
1069     /**
1070      * Purges stale mappings from this map before read operations.
1071      * <p>
1072      * This implementation calls {@link #purge()} to maintain a consistent state.
1073      */
1074     protected void purgeBeforeRead() {
1075         purge();
1076     }
1077 
1078     /**
1079      * Purges stale mappings from this map before write operations.
1080      * <p>
1081      * This implementation calls {@link #purge()} to maintain a consistent state.
1082      * </p>
1083      */
1084     protected void purgeBeforeWrite() {
1085         purge();
1086     }
1087 
1088     /**
1089      * Puts a key-value mapping into this map.
1090      * Neither the key nor the value may be null.
1091      *
1092      * @param key  the key to add, must not be null
1093      * @param value  the value to add, must not be null
1094      * @return the value previously mapped to this key, null if none
1095      * @throws NullPointerException if either the key or value is null
1096      */
1097     @Override
1098     public V put(final K key, final V value) {
1099         Objects.requireNonNull(key, "key");
1100         Objects.requireNonNull(value, "value");
1101         purgeBeforeWrite();
1102         return super.put(key, value);
1103     }
1104 
1105     /**
1106      * Removes the specified mapping from this map.
1107      *
1108      * @param key  the mapping to remove
1109      * @return the value mapped to the removed key, null if key not in map
1110      */
1111     @Override
1112     public V remove(final Object key) {
1113         if (key == null) {
1114             return null;
1115         }
1116         purgeBeforeWrite();
1117         return super.remove(key);
1118     }
1119 
1120     /**
1121      * Gets the size of the map.
1122      *
1123      * @return the size
1124      */
1125     @Override
1126     public int size() {
1127         purgeBeforeRead();
1128         return super.size();
1129     }
1130 
1131     /**
1132      * Returns a collection view of this map's values.
1133      *
1134      * @return a set view of this map's values
1135      */
1136     @Override
1137     public Collection<V> values() {
1138         if (values == null) {
1139             values = new ReferenceValues<>(this);
1140         }
1141         return values;
1142     }
1143 }