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}