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.utils.io;
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
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
037public class SoftHashMap extends AbstractMap {
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 map = new HashMap();
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 hardCache = new LinkedList();
055
056   /** Reference queue for cleared SoftReference objects. */
057
058   private final ReferenceQueue queue = new ReferenceQueue();
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 Object get(Object key) {
073
074      Object result = null;
075
076      // We get the SoftReference represented by that key
077
078      SoftReference soft_ref = (SoftReference) 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 extends SoftReference {
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(Object k, Object key, ReferenceQueue 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   private void clearGCCollected() {
190
191      SoftValue sv;
192
193      while ((sv = (SoftValue) 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   public synchronized Object put(Object key, Object value) {
212
213      clearGCCollected();
214
215      if ( DEBUG)
216         System.out.println("putting " + key + " on cache. size: " + size());
217      return map.put(key, new SoftValue(value, key, queue));
218
219   }
220
221
222
223   public Object remove(Object key) {
224
225      clearGCCollected();
226      if ( DEBUG)
227          System.out.println("removing " + key + " from cache. size: " + size());
228      return map.remove(key);
229
230   }
231
232
233
234   public void clear() {
235
236      synchronized (hardCache){
237         hardCache.clear();
238      }
239
240      clearGCCollected();
241      if ( DEBUG)
242          System.out.println("clearing cache");
243      map.clear();
244
245   }
246
247
248
249   public int size() {
250
251      clearGCCollected();
252
253      return map.size();
254
255   }
256
257
258
259   public Set entrySet() {
260
261      throw new UnsupportedOperationException();
262
263   }
264
265}