001/*
002 *                    PDB web development code
003 *
004 * This code may be freely distributed and modified under the
005 * terms of the GNU Lesser General Public Licence.  This should
006 * be distributed with the code.  If you do not have a copy,
007 * see:
008 *
009 *      http://www.gnu.org/copyleft/lesser.html
010 *
011 * Copyright for this code is held jointly by the individual
012 * authors.  These should be listed in @author doc comments.
013 *
014 *
015 * Created on Mar 26, 2009
016 * Created by ap3
017 *
018 */
019
020package org.biojava.nbio.core.util;
021
022import java.lang.ref.ReferenceQueue;
023import java.lang.ref.SoftReference;
024import java.util.AbstractMap;
025import java.util.HashMap;
026import java.util.LinkedList;
027import java.util.Map;
028import java.util.Set;
029
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032
033
034/**
035 * An in memory cache using soft references. (can be garbage collected)
036 * This code is based on: http://java-interview-faqs.blogspot.com/2008/09/building-faster-and-efficient-cache.html
037 * <p>
038 * Note that entrySet() is not implemented and therefore many methods such as keySet(),
039 * containsKey(), values() etc do not work.
040 * <p>
041 * This class is therefore best used as a cache simply to put and get items by a known key
042 * @param <K> the key type
043 * @param <V> the value type
044 */
045public class SoftHashMap<K, V> extends AbstractMap<K, V> {
046
047        private final static Logger logger = LoggerFactory.getLogger(SoftHashMap.class);
048
049        public static final int DEFAULT_LIMIT = 1;
050
051        /** The internal HashMap that stores SoftReference to actual data. */
052        private final Map<K, SoftReference<V>> map = new HashMap<>();
053
054        /** Maximum Number of references you dont want GC to collect. */
055        private final int max_limit;
056
057        /** The FIFO list of hard references, order of last access. */
058        private final LinkedList<V> hardCache = new LinkedList<>();
059
060        /** Reference queue for cleared SoftReference objects. */
061        private final ReferenceQueue<V> queue = new ReferenceQueue<>();
062
063        public SoftHashMap() {
064                this(1000);
065        }
066
067        /**
068         * @param hardSize A maximum number of items to maintain hard references to
069         * that will not be eligible for garbage collection
070         */
071        public SoftHashMap(int hardSize) {
072                max_limit = hardSize;
073        }
074
075        @Override
076        public V get(Object key) {
077
078                V result = null;
079
080                // We get the SoftReference represented by that key
081                SoftReference<V> soft_ref = map.get(key);
082
083                if (soft_ref != null) {
084                        try {
085                                // From the SoftReference we get the value, which can be
086                                // null if it was not in the map, or it was removed in
087                                // the clearGCCollected() method defined below
088
089                                result = soft_ref.get();
090                                if (result == null) {
091                                        // If the value has been garbage collected, remove the
092                                        // entry from the HashMap.
093                                        map.remove(key);
094                                } else {
095                                        // We now add this object to the beginning of the hard
096                                        // reference queue. One reference can occur more than
097                                        // once, because lookups of the FIFO queue are slow, so
098                                        // we don't want to search through it each time to remove
099                                        // duplicates.
100
101                                        synchronized (hardCache){
102                                                hardCache.addFirst(result);
103                                                if (hardCache.size() > max_limit) {
104                                                        // Remove the last entry if list greater than MAX_LIMIT
105                                                        hardCache.removeLast();
106                                                }
107                                        }
108                                }
109                        } catch (Exception e){
110                                logger.error("Exception: ", e);
111                        }
112                }
113                return result;
114        }
115
116        /**
117         * We define our own subclass of SoftReference which contains not only the
118         * value but also the key to make it easier to find the entry in the HashMap
119         * after it's been garbage collected.
120         */
121        private static class SoftValue<K, V> extends SoftReference<V> {
122
123                private final Object key; // always make data member final
124                
125                /**
126                 * Did you know that an outer class can access private data members and
127                 * methods of an inner class? I didn't know that! I thought it was only
128                 * the inner class who could access the outer class's private
129                 * information. An outer class can also access private members of an
130                 * inner class inside its inner class.
131                 */
132                private SoftValue(V k, K key, ReferenceQueue<? super V> q) {
133                        super(k, q);
134                        this.key = key;
135                }
136        }
137
138        /**
139         * Here we go through the ReferenceQueue and remove garbage collected
140         * SoftValue objects from the HashMap by looking them up using the
141         * SoftValue.key data member.
142         */
143        @SuppressWarnings("unchecked") // every Reference in queue is stored as a SoftValue
144        private void clearGCCollected() {
145                SoftValue<K, V> sv;
146                while ((sv = (SoftValue<K, V>) queue.poll()) != null) {
147                        map.remove(sv.key); // we can access private data!
148                }
149        }
150
151        /**
152         * Here we put the key, value pair into the HashMap using a SoftValue
153         * object.
154         */
155        @Override
156        public synchronized V put(K key, V value) {
157                clearGCCollected();
158                logger.debug("Putting {} on cache. size: {}", key, size());
159                map.put(key, new SoftValue<K, V>(value, key, queue));
160                return value;
161        }
162
163        @Override
164        public V remove(Object key) {
165                clearGCCollected();
166                logger.debug("Removing {} from cache. size: {}", key, size());
167                return map.remove(key).get();
168        }
169
170        @Override
171        public void clear() {
172                synchronized (hardCache){
173                        hardCache.clear();
174                }
175
176                clearGCCollected();
177                logger.debug("clearing cache");
178                map.clear();
179        }
180
181        @Override
182        public int size() {
183                clearGCCollected();
184                return map.size();
185        }
186
187        @Override
188        public Set<Map.Entry<K, V>> entrySet() {
189                throw new UnsupportedOperationException();
190        }
191}