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