1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.jdo.util;
19
20 import java.lang.ref.ReferenceQueue;
21 import java.lang.ref.WeakReference;
22
23 import java.util.AbstractCollection;
24 import java.util.AbstractSet;
25 import java.util.Collection;
26 import java.util.HashMap;
27 import java.util.Iterator;
28 import java.util.Map;
29 import java.util.NoSuchElementException;
30 import java.util.Set;
31
32 /***
33 * A WeakValueHashMap is implemented as a HashMap that maps keys to
34 * WeakValues. Because we don't have access to the innards of the
35 * HashMap, we have to wrap/unwrap value objects with WeakValues on
36 * every operation. Fortunately WeakValues are small, short-lived
37 * objects, so the added allocation overhead is tolerable. This
38 * implementaton directly extends java.util.HashMap.
39 *
40 * @author Markus Fuchs
41 * @see java.util.HashMap
42 * @see java.lang.ref.WeakReference
43 */
44
45 public class WeakValueHashMap extends HashMap {
46
47
48 private ReferenceQueue queue = new ReferenceQueue();
49
50 /***
51 * Returns the number of key-value mappings in this map.<p>
52 * @return the number of key-value mappings in this map.
53 */
54 public int size() {
55
56 return entrySet().size();
57 }
58
59 /***
60 * Returns <tt>true</tt> if this map contains no key-value mappings.<p>
61 * @return <tt>true</tt> if this map contains no key-value mappings.
62 */
63 public boolean isEmpty() {
64 return size() == 0;
65 }
66
67 /***
68 * Returns <tt>true</tt> if this map contains a mapping for the specified
69 * key.<p>
70 * @param key key whose presence in this map is to be tested
71 * @return <tt>true</tt> if this map contains a mapping for the specified
72 * key.
73 */
74 public boolean containsKey(Object key) {
75
76 processQueue();
77 return super.containsKey(key);
78 }
79
80 /***
81 * Returns <tt>true</tt> if this map maps one or more keys to the
82 * specified value.<p>
83 * @param value value whose presence in this map is to be tested
84 * @return <tt>true</tt> if this map maps one or more keys to this value.
85 */
86 public boolean containsValue(Object value) {
87 return super.containsValue(WeakValue.create(value));
88 }
89
90 /***
91 * Gets the value for the given key.<p>
92 * @param key key whose associated value, if any, is to be returned
93 * @return the value to which this map maps the specified key.
94 */
95 public Object get(Object key) {
96
97
98
99
100 return getReferenceObject((WeakReference) super.get(key));
101 }
102
103 /***
104 * Puts a new (key,value) into the map.<p>
105 * @param key key with which the specified value is to be associated.
106 * @param value value to be associated with the specified key.
107 * @return previous value associated with specified key, or null
108 * if there was no mapping for key or the value has been garbage
109 * collected by the garbage collector.
110 */
111 public Object put(Object key, Object value) {
112
113
114
115
116
117
118
119
120
121 processQueue();
122
123 WeakValue oldValue =
124 (WeakValue)super.put(key, WeakValue.create(key, value, queue));
125 return getReferenceObject(oldValue);
126 }
127
128 /***
129 * Removes key and value for the given key.<p>
130 * @param key key whose mapping is to be removed from the map.
131 * @return previous value associated with specified key, or null
132 * if there was no mapping for key or the value has been garbage
133 * collected by the garbage collector.
134 */
135 public Object remove(Object key) {
136 return getReferenceObject((WeakReference) super.remove(key));
137 }
138
139 /***
140 * A convenience method to return the object held by the
141 * weak reference or <code>null</code> if it does not exist.
142 */
143 private final Object getReferenceObject(WeakReference ref) {
144 return (ref == null) ? null : ref.get();
145 }
146
147 /***
148 * Removes all garbage collected values with their keys from the map.
149 * Since we don't know how much the ReferenceQueue.poll() operation
150 * costs, we should not call it every map operation.
151 */
152 private void processQueue() {
153 WeakValue wv = null;
154
155 while ((wv = (WeakValue) this.queue.poll()) != null) {
156
157
158 super.remove(wv.key);
159 }
160 }
161
162
163
164 /***
165 * We need this special class to keep the backward reference from
166 * the value to the key, so that we are able to remove the key if
167 * the value is garbage collected.
168 */
169 private static class WeakValue extends WeakReference {
170 /***
171 * It's the same as the key in the map. We need the key to remove
172 * the value if it is garbage collected.
173 */
174 private Object key;
175
176 private WeakValue(Object value) {
177 super(value);
178 }
179
180 /***
181 * Creates a new weak reference without adding it to a
182 * ReferenceQueue.
183 */
184 private static WeakValue create(Object value) {
185 if (value == null) return null;
186 else return new WeakValue(value);
187 }
188
189 private WeakValue(Object key, Object value, ReferenceQueue queue) {
190 super(value, queue);
191 this.key = key;
192 }
193
194 /***
195 * Creates a new weak reference and adds it to the given queue.
196 */
197 private static WeakValue create(Object key, Object value,
198 ReferenceQueue queue) {
199 if (value == null) return null;
200 else return new WeakValue(key, value, queue);
201 }
202
203 /***
204 * A WeakValue is equal to another WeakValue iff they both refer
205 * to objects that are, in turn, equal according to their own
206 * equals methods.
207 */
208 public boolean equals(Object obj) {
209 if (this == obj)
210 return true;
211
212 if (!(obj instanceof WeakValue))
213 return false;
214
215 Object ref1 = this.get();
216 Object ref2 = ((WeakValue) obj).get();
217
218 if (ref1 == ref2)
219 return true;
220
221 if ((ref1 == null) || (ref2 == null))
222 return false;
223
224 return ref1.equals(ref2);
225 }
226
227 /***
228 *
229 */
230 public int hashCode() {
231 Object ref = this.get();
232
233 return (ref == null) ? 0 : ref.hashCode();
234 }
235 }
236
237 /***
238 * Internal class for entries. This class wraps/unwraps the
239 * values of the Entry objects returned from the underlying map.
240 */
241 private class Entry implements Map.Entry {
242 private Map.Entry ent;
243 private Object value;
244
245
246
247 Entry(Map.Entry ent, Object value) {
248 this.ent = ent;
249 this.value = value;
250 }
251
252 public Object getKey() {
253 return ent.getKey();
254 }
255
256 public Object getValue() {
257 return value;
258 }
259
260 public Object setValue(Object value) {
261
262
263 Object oldValue = this.value;
264 this.value = value;
265 ent.setValue(WeakValue.create(getKey(), value, queue));
266 return oldValue;
267 }
268
269 private boolean valEquals(Object o1, Object o2) {
270 return (o1 == null) ? (o2 == null) : o1.equals(o2);
271 }
272
273 public boolean equals(Object o) {
274 if (!(o instanceof Map.Entry)) return false;
275 Map.Entry e = (Map.Entry) o;
276 return (valEquals(ent.getKey(), e.getKey())
277 && valEquals(value, e.getValue()));
278 }
279
280 public int hashCode() {
281 Object k;
282 return ((((k = ent.getKey()) == null) ? 0 : k.hashCode())
283 ^ ((value == null) ? 0 : value.hashCode()));
284 }
285
286 }
287
288 /***
289 * Internal class for entry sets to unwrap/wrap WeakValues stored
290 * in the map.
291 */
292 private class EntrySet extends AbstractSet {
293
294 public Iterator iterator() {
295
296 processQueue();
297
298 return new Iterator() {
299 Iterator hashIterator = hashEntrySet.iterator();
300 Entry next = null;
301
302 public boolean hasNext() {
303 if (hashIterator.hasNext()) {
304
305
306 Map.Entry ent = (Map.Entry) hashIterator.next();
307 WeakValue wv = (WeakValue) ent.getValue();
308 Object v = (wv == null) ? null : wv.get();
309 next = new Entry(ent, v);
310 return true;
311 }
312 return false;
313 }
314
315 public Object next() {
316 if ((next == null) && !hasNext())
317 throw new NoSuchElementException();
318 Entry e = next;
319 next = null;
320 return e;
321 }
322
323 public void remove() {
324 hashIterator.remove();
325 }
326
327 };
328 }
329
330 public boolean isEmpty() {
331 return !(iterator().hasNext());
332 }
333
334 public int size() {
335 int j = 0;
336 for (Iterator i = iterator(); i.hasNext(); i.next()) j++;
337 return j;
338 }
339
340 public boolean remove(Object o) {
341 if (!(o instanceof Map.Entry)) return false;
342 Map.Entry e = (Map.Entry) o;
343 Object ek = e.getKey();
344 Object ev = e.getValue();
345 Object hv = WeakValueHashMap.this.get(ek);
346 if (hv == null) {
347
348
349 if ((ev == null) && WeakValueHashMap.this.containsKey(ek)) {
350 WeakValueHashMap.this.remove(ek);
351 return true;
352 } else {
353 return false;
354 }
355
356 } else if (hv.equals(ev)) {
357 WeakValueHashMap.this.remove(ek);
358 return true;
359 }
360
361 return false;
362 }
363
364 public int hashCode() {
365 int h = 0;
366 for (Iterator i = hashEntrySet.iterator(); i.hasNext(); ) {
367 Map.Entry ent = (Map.Entry) i.next();
368 Object k;
369 WeakValue wv = (WeakValue) ent.getValue();
370 if (wv == null) continue;
371 h += ((((k = ent.getKey()) == null) ? 0 : k.hashCode())
372 ^ wv.hashCode());
373 }
374 return h;
375 }
376
377 }
378
379
380
381 private Set hashEntrySet = null;
382
383 private Set entrySet = null;
384
385 /***
386 * Returns a <code>Set</code> view of the mappings in this map.<p>
387 * @return a <code>Set</code> view of the mappings in this map.
388 */
389 public Set entrySet() {
390 if (entrySet == null) {
391 hashEntrySet = super.entrySet();
392 entrySet = new EntrySet();
393 }
394 return entrySet;
395 }
396
397
398 private transient Collection values = null;
399
400 /***
401 * Returns a <code>Collection</code> view of the values contained
402 * in this map.<p>
403 * @return a <code>Collection</code> view of the values contained
404 * in this map.
405 */
406 public Collection values() {
407
408
409 if (values == null) {
410 values = new AbstractCollection() {
411 public Iterator iterator() {
412 return new Iterator() {
413 private Iterator i = entrySet().iterator();
414
415 public boolean hasNext() {
416 return i.hasNext();
417 }
418
419 public Object next() {
420 return ((Entry)i.next()).getValue();
421 }
422
423 public void remove() {
424 i.remove();
425 }
426 };
427 }
428
429 public int size() {
430 return WeakValueHashMap.this.size();
431 }
432
433 public boolean contains(Object v) {
434 return WeakValueHashMap.this.containsValue(v);
435 }
436 };
437 }
438 return values;
439 }
440
441 }