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