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}