Libraryless. Click here for Pure Java version (3792L/22K).
1 | /* |
2 | * @(#)WeakHashMap.java 1.5 98/09/30 |
3 | * |
4 | * Copyright 1998 by Sun Microsystems, Inc., |
5 | * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A. |
6 | * All rights reserved. |
7 | * |
8 | * This software is the confidential and proprietary information |
9 | * of Sun Microsystems, Inc. ("Confidential Information"). You |
10 | * shall not disclose such Confidential Information and shall use |
11 | * it only in accordance with the terms of the license agreement |
12 | * you entered into with Sun. |
13 | */ |
14 | |
15 | // From https://github.com/mernst/plume-lib/blob/df0bfafc3c16848d88f4ea0ef3c8bf3367ae085e/java/src/plume/WeakHasherMap.java |
16 | |
17 | static final class WeakHasherMap<K,V> extends AbstractMap<K,V> implements Map<K,V> { |
18 | |
19 | private Hasher hasher = null; |
20 | /*@Pure*/ |
21 | private boolean keyEquals(Object k1, Object k2) { |
22 | return (hasher==null ? k1.equals(k2) |
23 | : hasher.equals(k1, k2)); |
24 | } |
25 | /*@Pure*/ |
26 | private int keyHashCode(Object k1) { |
27 | return (hasher==null ? k1.hashCode() |
28 | : hasher.hashCode(k1)); |
29 | } |
30 | |
31 | // The WeakKey class can't be static because it depends on the hasher. |
32 | // That in turn means that its methods can't be static. |
33 | // However, I need to be able to call the methods such as create() that |
34 | // were static in the original version of this code. |
35 | // This finesses that. |
36 | |
37 | private /*@Nullable*/ WeakKey WeakKeyCreate(K k) { |
38 | if (k == null) return null; |
39 | else return new WeakKey(k); |
40 | } |
41 | private /*@Nullable*/ WeakKey WeakKeyCreate(K k, ReferenceQueue<? super K> q) { |
42 | if (k == null) return null; |
43 | else return new WeakKey(k, q); |
44 | } |
45 | |
46 | // Cannot be a static class: uses keyHashCode() and keyEquals() |
47 | private final class WeakKey extends WeakReference<K> { |
48 | private int hash; /* Hashcode of key, stored here since the key |
49 | may be tossed by the GC */ |
50 | |
51 | private WeakKey(K k) { |
52 | super(k); |
53 | hash = keyHashCode(k); |
54 | } |
55 | |
56 | private /*@Nullable*/ WeakKey create(K k) { |
57 | if (k == null) return null; |
58 | else return new WeakKey(k); |
59 | } |
60 | |
61 | private WeakKey(K k, ReferenceQueue<? super K> q) { |
62 | super(k, q); |
63 | hash = keyHashCode(k); |
64 | } |
65 | |
66 | private /*@Nullable*/ WeakKey create(K k, ReferenceQueue<? super K> q) { |
67 | if (k == null) return null; |
68 | else return new WeakKey(k, q); |
69 | } |
70 | |
71 | /* A WeakKey is equal to another WeakKey iff they both refer to objects |
72 | that are, in turn, equal according to their own equals methods */ |
73 | /*@Pure*/ |
74 | @Override |
75 | public boolean equals(/*@Nullable*/ Object o) { |
76 | if (o == null) return false; // never happens |
77 | if (this == o) return true; |
78 | // This test is illegal because WeakKey is a generic type, |
79 | // so use the getClass hack below instead. |
80 | // if (!(o instanceof WeakKey)) return false; |
81 | if (!(o.getClass().equals(WeakKey.class))) return false; |
82 | Object t = this.get(); |
83 | @SuppressWarnings("unchecked") |
84 | Object u = ((WeakKey)o).get(); |
85 | if ((t == null) || (u == null)) return false; |
86 | if (t == u) return true; |
87 | return keyEquals(t, u); |
88 | } |
89 | |
90 | /*@Pure*/ |
91 | @Override |
92 | public int hashCode() { |
93 | return hash; |
94 | } |
95 | |
96 | } |
97 | |
98 | |
99 | /* Hash table mapping WeakKeys to values */ |
100 | private HashMap<WeakKey,V> hash; |
101 | |
102 | /* Reference queue for cleared WeakKeys */ |
103 | private ReferenceQueue<? super K> queue = new ReferenceQueue<K>(); |
104 | |
105 | |
106 | /* Remove all invalidated entries from the map, that is, remove all entries |
107 | whose keys have been discarded. This method should be invoked once by |
108 | each public mutator in this class. We don't invoke this method in |
109 | public accessors because that can lead to surprising |
110 | ConcurrentModificationExceptions. */ |
111 | @SuppressWarnings("unchecked") |
112 | private void processQueue() { |
113 | WeakKey wk; |
114 | while ((wk = (WeakKey)queue.poll()) != null) { // unchecked cast |
115 | hash.remove(wk); |
116 | } |
117 | } |
118 | |
119 | |
120 | /* -- Constructors -- */ |
121 | |
122 | /** |
123 | * Constructs a new, empty <code>WeakHashMap</code> with the given |
124 | * initial capacity and the given load factor. |
125 | * |
126 | * @param initialCapacity the initial capacity of the |
127 | * <code>WeakHashMap</code> |
128 | * |
129 | * @param loadFactor the load factor of the <code>WeakHashMap</code> |
130 | * |
131 | * @throws IllegalArgumentException If the initial capacity is less than |
132 | * zero, or if the load factor is |
133 | * nonpositive |
134 | */ |
135 | public WeakHasherMap(int initialCapacity, float loadFactor) { |
136 | hash = new HashMap<WeakKey,V>(initialCapacity, loadFactor); |
137 | } |
138 | |
139 | /** |
140 | * Constructs a new, empty <code>WeakHashMap</code> with the given |
141 | * initial capacity and the default load factor, which is |
142 | * <code>0.75</code>. |
143 | * |
144 | * @param initialCapacity the initial capacity of the |
145 | * <code>WeakHashMap</code> |
146 | * |
147 | * @throws IllegalArgumentException If the initial capacity is less than |
148 | * zero |
149 | */ |
150 | public WeakHasherMap(int initialCapacity) { |
151 | hash = new HashMap<WeakKey,V>(initialCapacity); |
152 | } |
153 | |
154 | /** |
155 | * Constructs a new, empty <code>WeakHashMap</code> with the default |
156 | * capacity and the default load factor, which is <code>0.75</code>. |
157 | */ |
158 | public WeakHasherMap() { |
159 | hash = new HashMap<WeakKey,V>(); |
160 | } |
161 | |
162 | /** |
163 | * Constructs a new, empty <code>WeakHashMap</code> with the default |
164 | * capacity and the default load factor, which is <code>0.75</code>. |
165 | * The <code>WeakHashMap</code> uses the specified hasher for hashing |
166 | * keys and comparing them for equality. |
167 | * @param h the Hasher to use when hashing values for this map |
168 | */ |
169 | public WeakHasherMap(Hasher h) { |
170 | hash = new HashMap<WeakKey,V>(); |
171 | hasher = h; |
172 | } |
173 | |
174 | |
175 | /* -- Simple queries -- */ |
176 | |
177 | /** |
178 | * Returns the number of key-value mappings in this map. |
179 | * <strong>Note:</strong> <em>In contrast to most implementations of the |
180 | * <code>Map</code> interface, the time required by this operation is |
181 | * linear in the size of the map.</em> |
182 | */ |
183 | /*@Pure*/ |
184 | @Override |
185 | public int size() { |
186 | return entrySet().size(); |
187 | } |
188 | |
189 | /** |
190 | * Returns <code>true</code> if this map contains no key-value mappings. |
191 | */ |
192 | /*@Pure*/ |
193 | @Override |
194 | public boolean isEmpty() { |
195 | return entrySet().isEmpty(); |
196 | } |
197 | |
198 | /** |
199 | * Returns <code>true</code> if this map contains a mapping for the |
200 | * specified key. |
201 | * |
202 | * @param key the key whose presence in this map is to be tested |
203 | */ |
204 | /*@Pure*/ |
205 | @Override |
206 | public boolean containsKey(Object key) { |
207 | @SuppressWarnings("unchecked") |
208 | K kkey = (K) key; |
209 | return hash.containsKey(WeakKeyCreate(kkey)); |
210 | } |
211 | |
212 | |
213 | /* -- Lookup and modification operations -- */ |
214 | |
215 | /** |
216 | * Returns the value to which this map maps the specified <code>key</code>. |
217 | * If this map does not contain a value for this key, then return |
218 | * <code>null</code>. |
219 | * |
220 | * @param key the key whose associated value, if any, is to be returned |
221 | */ |
222 | /*@Pure*/ |
223 | @Override |
224 | public /*@Nullable*/ V get(Object key) { // type of argument is Object, not K |
225 | @SuppressWarnings("unchecked") |
226 | K kkey = (K) key; |
227 | return hash.get(WeakKeyCreate(kkey)); |
228 | } |
229 | |
230 | /** |
231 | * Updates this map so that the given <code>key</code> maps to the given |
232 | * <code>value</code>. If the map previously contained a mapping for |
233 | * <code>key</code> then that mapping is replaced and the previous value is |
234 | * returned. |
235 | * |
236 | * @param key the key that is to be mapped to the given |
237 | * <code>value</code> |
238 | * @param value the value to which the given <code>key</code> is to be |
239 | * mapped |
240 | * |
241 | * @return the previous value to which this key was mapped, or |
242 | * <code>null</code> if if there was no mapping for the key |
243 | */ |
244 | @Override |
245 | public V put(K key, V value) { |
246 | processQueue(); |
247 | return hash.put(WeakKeyCreate(key, queue), value); |
248 | } |
249 | |
250 | /** |
251 | * Removes the mapping for the given <code>key</code> from this map, if |
252 | * present. |
253 | * |
254 | * @param key the key whose mapping is to be removed |
255 | * |
256 | * @return the value to which this key was mapped, or <code>null</code> if |
257 | * there was no mapping for the key |
258 | */ |
259 | @Override |
260 | public V remove(Object key) { // type of argument is Object, not K |
261 | processQueue(); |
262 | @SuppressWarnings("unchecked") |
263 | K kkey = (K) key; |
264 | return hash.remove(WeakKeyCreate(kkey)); |
265 | } |
266 | |
267 | /** |
268 | * Removes all mappings from this map. |
269 | */ |
270 | @Override |
271 | public void clear() { |
272 | processQueue(); |
273 | hash.clear(); |
274 | } |
275 | |
276 | |
277 | /* -- Views -- */ |
278 | |
279 | |
280 | /* Internal class for entries */ |
281 | // This can't be static, again because of dependence on hasher. |
282 | @SuppressWarnings("TypeParameterShadowing") |
283 | private final class Entry<K,V> implements Map.Entry<K,V> { |
284 | private Map.Entry<WeakKey,V> ent; |
285 | private K key; /* Strong reference to key, so that the GC |
286 | will leave it alone as long as this Entry |
287 | exists */ |
288 | |
289 | Entry(Map.Entry<WeakKey,V> ent, K key) { |
290 | this.ent = ent; |
291 | this.key = key; |
292 | } |
293 | |
294 | /*@Pure*/ |
295 | @Override |
296 | public K getKey() { |
297 | return key; |
298 | } |
299 | |
300 | /*@Pure*/ |
301 | @Override |
302 | public V getValue() { |
303 | return ent.getValue(); |
304 | } |
305 | |
306 | @Override |
307 | public V setValue(V value) { |
308 | return ent.setValue(value); |
309 | } |
310 | |
311 | /*@Pure*/ |
312 | private boolean keyvalEquals(K o1, K o2) { |
313 | return (o1 == null) ? (o2 == null) : keyEquals(o1, o2); |
314 | } |
315 | |
316 | /*@Pure*/ |
317 | private boolean valEquals(V o1, V o2) { |
318 | return (o1 == null) ? (o2 == null) : o1.equals(o2); |
319 | } |
320 | |
321 | /*@Pure*/ |
322 | @SuppressWarnings("NonOverridingEquals") |
323 | public boolean equals(Map.Entry<K,V> e /* Object o*/) { |
324 | // if (! (o instanceof Map.Entry)) return false; |
325 | // Map.Entry<K,V> e = (Map.Entry<K,V>)o; |
326 | return (keyvalEquals(key, e.getKey()) |
327 | && valEquals(getValue(), e.getValue())); |
328 | } |
329 | |
330 | /*@Pure*/ |
331 | @Override |
332 | public int hashCode() { |
333 | V v; |
334 | return (((key == null) ? 0 : keyHashCode(key)) |
335 | ^ (((v = getValue()) == null) ? 0 : v.hashCode())); |
336 | } |
337 | |
338 | } |
339 | |
340 | |
341 | /* Internal class for entry sets */ |
342 | private final class EntrySet extends AbstractSet<Map.Entry<K,V>> { |
343 | Set<Map.Entry<WeakKey,V>> hashEntrySet = hash.entrySet(); |
344 | |
345 | @Override |
346 | public Iterator<Map.Entry<K,V>> iterator() { |
347 | |
348 | return new Iterator<Map.Entry<K,V>>() { |
349 | Iterator<Map.Entry<WeakKey,V>> hashIterator = hashEntrySet.iterator(); |
350 | Map.Entry<K,V> next = null; |
351 | |
352 | @Override |
353 | public boolean hasNext() { |
354 | while (hashIterator.hasNext()) { |
355 | Map.Entry<WeakKey,V> ent = hashIterator.next(); |
356 | WeakKey wk = ent.getKey(); |
357 | K k = null; |
358 | if ((wk != null) && ((k = wk.get()) == null)) { |
359 | /* Weak key has been cleared by GC */ |
360 | continue; |
361 | } |
362 | next = new Entry<K,V>(ent, k); |
363 | return true; |
364 | } |
365 | return false; |
366 | } |
367 | |
368 | @Override |
369 | public Map.Entry<K,V> next() { |
370 | if ((next == null) && !hasNext()) |
371 | throw new NoSuchElementException(); |
372 | Map.Entry<K,V> e = next; |
373 | next = null; |
374 | return e; |
375 | } |
376 | |
377 | @Override |
378 | public void remove() { |
379 | hashIterator.remove(); |
380 | } |
381 | |
382 | }; |
383 | } |
384 | |
385 | /*@Pure*/ |
386 | @Override |
387 | public boolean isEmpty() { |
388 | return !(iterator().hasNext()); |
389 | } |
390 | |
391 | /*@Pure*/ |
392 | @Override |
393 | public int size() { |
394 | int j = 0; |
395 | for (Iterator<Map.Entry<K,V>> i = iterator(); i.hasNext(); i.next()) j++; |
396 | return j; |
397 | } |
398 | |
399 | @Override |
400 | public boolean remove(Object o) { |
401 | processQueue(); |
402 | if (!(o instanceof Map.Entry<?,?>)) return false; |
403 | @SuppressWarnings("unchecked") |
404 | Map.Entry<K,V> e = (Map.Entry<K,V>)o; // unchecked cast |
405 | Object ev = e.getValue(); |
406 | WeakKey wk = WeakKeyCreate(e.getKey()); |
407 | Object hv = hash.get(wk); |
408 | if ((hv == null) |
409 | ? ((ev == null) && hash.containsKey(wk)) : hv.equals(ev)) { |
410 | hash.remove(wk); |
411 | return true; |
412 | } |
413 | return false; |
414 | } |
415 | |
416 | /*@Pure*/ |
417 | @Override |
418 | public int hashCode() { |
419 | int h = 0; |
420 | for (Iterator<Map.Entry<WeakKey,V>> i = hashEntrySet.iterator(); i.hasNext(); ) { |
421 | Map.Entry<WeakKey,V> ent = i.next(); |
422 | WeakKey wk = ent.getKey(); |
423 | Object v; |
424 | if (wk == null) continue; |
425 | h += (wk.hashCode() |
426 | ^ (((v = ent.getValue()) == null) ? 0 : v.hashCode())); |
427 | } |
428 | return h; |
429 | } |
430 | |
431 | } |
432 | |
433 | |
434 | private /*@Nullable*/ Set<Map.Entry<K,V>> entrySet = null; |
435 | |
436 | /** |
437 | * Returns a <code>Set</code> view of the mappings in this map. |
438 | */ |
439 | /*@SideEffectFree*/ |
440 | @Override |
441 | public Set<Map.Entry<K,V>> entrySet() { |
442 | if (entrySet == null) entrySet = new EntrySet(); |
443 | return entrySet; |
444 | } |
445 | |
446 | // find matching key |
447 | K findKey(O key) { |
448 | processQueue(); |
449 | K kkey = (K) key; |
450 | // TODO: use replacement for HashMap to avoid reflection |
451 | WeakKey wkey = WeakKeyCreate(kkey); |
452 | WeakKey found = hashMap_findKey(hash, wkey); |
453 | ret found == null ? null : found.get(); |
454 | } |
455 | } |
download show line numbers debug dex old transpilations
Travelled to 14 computer(s): aoiabmzegqzx, bhatertpkbcr, cbybwowwnfue, cfunsshuasjs, gwrvuhgaqvyk, ishqpsrjomds, lpdgvwnxivlt, mqqgnosmbjvj, onxytkatvevr, pyentgdyhuwx, pzhvpgtvlbxg, tslmcundralx, tvejysmllsmz, vouqrxazstgt
No comments. add comment
Snippet ID: | #1012710 |
Snippet name: | WeakHasherMap (from plume-lib - doesn't work anymore, uses HashMap internals) |
Eternal ID of this version: | #1012710/13 |
Text MD5: | fc81e6a0c822a398011203b7f0eb23a0 |
Transpilation MD5: | d2b33a78a6bdb330638fddf1cf874c43 |
Author: | stefan |
Category: | javax |
Type: | JavaX fragment (include) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2021-07-27 00:33:05 |
Source code size: | 13599 bytes / 455 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 634 / 1327 |
Version history: | 12 change(s) |
Referenced in: | [show references] |