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