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}