001/* 002 * BioJava 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 * For more information on the BioJava project and its aims, 015 * or to join the biojava-l mailing list, visit the home page 016 * at: 017 * 018 * http://www.biojava.org/ 019 * 020 */ 021 022package org.biojava.utils.cache; 023 024import java.lang.ref.PhantomReference; 025import java.lang.ref.Reference; 026import java.lang.ref.ReferenceQueue; 027import java.util.AbstractMap; 028import java.util.AbstractSet; 029import java.util.HashMap; 030import java.util.HashSet; 031import java.util.Iterator; 032import java.util.Map; 033import java.util.NoSuchElementException; 034import java.util.Set; 035 036/** 037 * Map implementation which keeps weak references to values. 038 * Entries are removed from the map when their value is 039 * no longer reachable using normal (hard) references. This is 040 * useful for maintaining canonical copies of objects without forcing 041 * these objects to remain in memory forever. 042 * 043 * <p> 044 * Note that this is distinct from the standard library class, 045 * <code>WeakHashMap</code> which has weak <em>keys</em>. 046 * </p> 047 * 048 * @author Thomas Down 049 * @since 1.3 050 */ 051 052public class WeakValueHashMap extends AbstractMap { 053 private final Map keyToRefMap; 054 private final ReferenceQueue queue; 055 private final Set iteratorRefs; 056 private final ReferenceQueue iteratorRefQueue; 057 058 public WeakValueHashMap() { 059 keyToRefMap = new HashMap(); 060 queue = new ReferenceQueue(); 061 iteratorRefs = new HashSet(); 062 iteratorRefQueue = new ReferenceQueue(); 063 } 064 065 private void diddleReferenceQueue() { 066 // Avoid making behind-the-scenes modifications while iterators exist. 067 068 if (iteratorRefs.size() > 0) { 069 Reference ref; 070 while ((ref = iteratorRefQueue.poll()) != null) { 071 iteratorRefs.remove(ref); 072 } 073 if (iteratorRefs.size() > 0) { 074 return; 075 } 076 } 077 078 KeyedWeakReference ref; 079 while ((ref = (KeyedWeakReference) queue.poll()) != null) { 080 keyToRefMap.remove(ref.getKey()); 081 } 082 } 083 084 public Object put(Object key, Object value) { 085 diddleReferenceQueue(); 086 Reference oldRef = (Reference) keyToRefMap.put(key, new KeyedWeakReference(key, value, queue)); 087 if (oldRef != null) { 088 Object oldRefVal = oldRef.get(); 089 oldRef.clear(); 090 return oldRefVal; 091 } else { 092 return null; 093 } 094 } 095 096 public Object get(Object key) { 097 Reference ref = (Reference) keyToRefMap.get(key); 098 if (ref != null) { 099 return ref.get(); 100 } else { 101 return null; 102 } 103 } 104 105 public boolean containsKey(Object o) { 106 diddleReferenceQueue(); 107 return keyToRefMap.containsKey(o); 108 } 109 110 public Set entrySet() { 111 diddleReferenceQueue(); 112 return new WVEntrySet(); 113 } 114 115 private class WVEntrySet extends AbstractSet { 116 private Set keyRefEntrySet; 117 118 public WVEntrySet() { 119 super(); 120 keyRefEntrySet = keyToRefMap.entrySet(); 121 } 122 123 public int size() { 124 return keyRefEntrySet.size(); 125 } 126 127 public Iterator iterator() { 128 Iterator i = new WVEntryIterator(keyRefEntrySet.iterator()); 129 iteratorRefs.add(new PhantomReference(i, iteratorRefQueue)); 130 return i; 131 } 132 } 133 134 private class WVEntryIterator implements Iterator { 135 private Object cache; 136 private Iterator keyRefIterator; 137 138 public WVEntryIterator(Iterator keyRefIterator) { 139 this.keyRefIterator = keyRefIterator; 140 } 141 142 public boolean hasNext() { 143 if (cache == null) { 144 primeCache(); 145 } 146 return cache != null; 147 } 148 149 public Object next() { 150 if (cache == null) { 151 primeCache(); 152 } 153 if (cache == null) { 154 throw new NoSuchElementException(); 155 } else { 156 Object o = cache; 157 cache = null; 158 return o; 159 } 160 } 161 162 public void remove() { 163 if (cache != null) { 164 throw new IllegalStateException("next() not called"); 165 } else { 166 keyRefIterator.remove(); 167 } 168 } 169 170 private void primeCache() { 171 while (keyRefIterator.hasNext()) { 172 Map.Entry krme = (Map.Entry) keyRefIterator.next(); 173 Object ref = ((Reference) krme.getValue()).get(); 174 if (ref != null) { 175 cache = new WVMapEntry(krme.getKey(), ref); 176 return; 177 } 178 } 179 } 180 } 181 182 private static class WVMapEntry implements Map.Entry { 183 private Object key; 184 private Object value; 185 186 private WVMapEntry(Object key, Object value) { 187 this.key = key; 188 this.value = value; 189 } 190 191 public Object getKey() { 192 return key; 193 } 194 195 public Object getValue() { 196 return value; 197 } 198 199 public Object setValue(Object v) { 200 throw new UnsupportedOperationException(); 201 } 202 203 public boolean equals(Object o) { 204 if (! (o instanceof Map.Entry)) { 205 return false; 206 } 207 208 Map.Entry mo = (Map.Entry) o; 209 return ((key == null ? mo.getKey() == null : key.equals(mo.getKey())) && 210 (value == null ? mo.getValue() == null : value.equals(mo.getValue()))); 211 } 212 213 public int hashCode() { 214 return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode()); 215 } 216 } 217}