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 org.slf4j.Logger;
023import org.slf4j.LoggerFactory;
024
025import java.lang.ref.ReferenceQueue;
026import java.lang.ref.SoftReference;
027import java.util.*;
028
029
030/** A in memory cache using soft references. (can be garbage collected)
031 *
032 * This code is based on: http://java-interview-faqs.blogspot.com/2008/09/building-faster-and-efficient-cache.html
033 * */
034
035
036public class SoftHashMap<K, V> extends AbstractMap<K, V> {
037
038        private final static Logger logger = LoggerFactory.getLogger(SoftHashMap.class);
039
040        public static final int DEFAULT_LIMIT = 1;
041
042        /** The internal HashMap that stores SoftReference to actual data. */
043
044        private final Map<K, SoftReference<V>> map = new HashMap<K, SoftReference<V>>();
045
046        /** Maximum Number of references you dont want GC to collect. */
047
048        private final int max_limit;
049
050        /** The FIFO list of hard references, order of last access. */
051
052        private final LinkedList<V> hardCache = new LinkedList<V>();
053
054        /** Reference queue for cleared SoftReference objects. */
055
056        private final ReferenceQueue<V> queue = new ReferenceQueue<V>();
057
058        public SoftHashMap() {
059
060                this(1000);
061
062        }
063
064        public SoftHashMap(int hardSize) {
065
066                max_limit = hardSize;
067
068        }
069
070        @Override
071public V get(Object key) {
072
073                V result = null;
074
075                // We get the SoftReference represented by that key
076
077                SoftReference<V> soft_ref = map.get(key);
078
079                if (soft_ref != null) {
080
081                        try {
082
083                                // From the SoftReference we get the value, which can be
084
085                                // null if it was not in the map, or it was removed in
086
087                                // the clearGCCollected() method defined below
088
089                                result = soft_ref.get();
090
091                                if (result == null) {
092
093                                        // If the value has been garbage collected, remove the
094
095                                        // entry from the HashMap.
096
097                                        map.remove(key);
098
099                                } else {
100
101                                        // We now add this object to the beginning of the hard
102
103                                        // reference queue. One reference can occur more than
104
105                                        // once, because lookups of the FIFO queue are slow, so
106
107                                        // we don't want to search through it each time to remove
108
109                                        // duplicates.
110
111                                        synchronized (hardCache){
112                                                hardCache.addFirst(result);
113
114                                                if (hardCache.size() > max_limit) {
115
116                                                        // Remove the last entry if list greater than MAX_LIMIT
117
118                                                        hardCache.removeLast();
119
120                                                }
121                                        }
122
123                                }
124                        } catch (Exception e){
125                         logger.error("Exception: ", e);
126                        }
127
128                }
129
130                return result;
131
132        }
133
134
135
136        /**
137
138         * We define our own subclass of SoftReference which contains not only the
139
140         * value but also the key to make it easier to find the entry in the HashMap
141
142         * after it's been garbage collected.
143
144         */
145
146        private static class SoftValue<K, V> extends SoftReference<V> {
147
148                private final Object key; // always make data member final
149
150
151
152                /**
153
154                 * Did you know that an outer class can access private data members and
155
156                 * methods of an inner class? I didn't know that! I thought it was only
157
158                 * the inner class who could access the outer class's private
159
160                 * information. An outer class can also access private members of an
161
162                 * inner class inside its inner class.
163
164                 */
165
166                private SoftValue(V k, K key, ReferenceQueue<? super V> q) {
167
168                        super(k, q);
169
170                        this.key = key;
171
172                }
173
174        }
175
176
177
178        /**
179
180         * Here we go through the ReferenceQueue and remove garbage collected
181
182         * SoftValue objects from the HashMap by looking them up using the
183
184         * SoftValue.key data member.
185
186         */
187
188        @SuppressWarnings("unchecked") // every Reference in queue is stored as a SoftValue
189        private void clearGCCollected() {
190
191                SoftValue<K, V> sv;
192
193                while ((sv = (SoftValue<K, V>) queue.poll()) != null) {
194
195                        map.remove(sv.key); // we can access private data!
196
197                }
198
199        }
200
201
202
203        /**
204
205         * Here we put the key, value pair into the HashMap using a SoftValue
206
207         * object.
208
209         */
210
211        @Override
212public synchronized V put(K key, V value) {
213
214                clearGCCollected();
215
216                logger.debug("Putting {} on cache. size: {}", key, size());
217
218                map.put(key, new SoftValue<K, V>(value, key, queue));
219
220                return value;
221
222        }
223
224
225
226        @Override
227        public V remove(Object key) {
228                clearGCCollected();
229                logger.debug("Removing {} from cache. size: {}", key, size());
230                return map.remove(key).get();
231        }
232
233
234
235        @Override
236public void clear() {
237
238                synchronized (hardCache){
239                        hardCache.clear();
240                }
241
242                clearGCCollected();
243                logger.debug("clearing cache");
244                map.clear();
245
246        }
247
248
249
250        @Override
251public int size() {
252
253                clearGCCollected();
254
255                return map.size();
256
257        }
258
259
260
261        @Override
262public Set<Map.Entry<K, V>> entrySet() {
263
264                throw new UnsupportedOperationException();
265
266        }
267
268}