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}