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