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