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