1 | /* |
2 | * Copyright (c) 2009 The JOMC Project |
3 | * Copyright (c) 2005 Christian Schulte <cs@jomc.org> |
4 | * All rights reserved. |
5 | * |
6 | * Redistribution and use in source and binary forms, with or without |
7 | * modification, are permitted provided that the following conditions |
8 | * are met: |
9 | * |
10 | * o Redistributions of source code must retain the above copyright |
11 | * notice, this list of conditions and the following disclaimer. |
12 | * |
13 | * o Redistributions in binary form must reproduce the above copyright |
14 | * notice, this list of conditions and the following disclaimer in |
15 | * the documentation and/or other materials provided with the |
16 | * distribution. |
17 | * |
18 | * THIS SOFTWARE IS PROVIDED BY THE JOMC PROJECT AND CONTRIBUTORS "AS IS" |
19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
20 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
21 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE JOMC PROJECT OR |
22 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
23 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
24 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; |
25 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
26 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR |
27 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
28 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
29 | * |
30 | * $Id: WeakIdentityHashMap.java 891 2009-11-02 03:40:00Z schulte2005 $ |
31 | * |
32 | */ |
33 | package org.jomc.util; |
34 | |
35 | import java.lang.ref.ReferenceQueue; |
36 | import java.lang.ref.WeakReference; |
37 | import java.util.AbstractCollection; |
38 | import java.util.AbstractSet; |
39 | import java.util.Collection; |
40 | import java.util.ConcurrentModificationException; |
41 | import java.util.Iterator; |
42 | import java.util.Map; |
43 | import java.util.NoSuchElementException; |
44 | import java.util.Set; |
45 | |
46 | /** |
47 | * Hash-table based {@code Map} implementation with weak keys, using object-identity in place of object-equality when |
48 | * comparing keys. |
49 | * |
50 | * <p>In a {@code WeakIdentityHashMap} two keys {@code k1} and {@code k2} are considered equal if and only if |
51 | * {@code k1==k2} evaluates to {@code true}. An entry will automatically be removed when its key is no longer in |
52 | * ordinary use. More precisely, the presence of a mapping for a given key will not prevent the key from being discarded |
53 | * by the garbage collector, that is, made finalizable, finalized, and then reclaimed. When a key has been discarded its |
54 | * entry is effectively removed from the map, so this class behaves somewhat differently from other {@code Map} |
55 | * implementations and is not a general-purpose {@code Map} implementation! Although it implements the {@code Map} |
56 | * interface, it intentionally violates {@code Map}'s general contract, which mandates the use of the {@code equals} |
57 | * method when comparing objects.</p> |
58 | * |
59 | * <p>This class has performance characteristics similar to those of the {@code HashMap} class, and has the same |
60 | * efficiency parameters of {@code initialCapacity} and {@code loadFactor}. All of the optional map operations are |
61 | * provided. It does not support {@code null} values and the {@code null} key. All methods taking a key or value will |
62 | * throw a {@code NullPointerException} when given a {@code null} reference. No guarantees are made as to the order of |
63 | * the map; in particular, there is no guarantee that the order will remain constant over time. Like most collection |
64 | * classes, this class is not synchronized. A synchronized {@code WeakIdentityHashMap} may be constructed using the |
65 | * {@code Collections.synchronizedMap} method.</p> |
66 | * |
67 | * <p>The behavior of the {@code WeakIdentityHashMap} class depends in part upon the actions of the garbage collector, |
68 | * so several {@code Map} invariants do not hold for this class. Because the garbage collector may discard keys at |
69 | * any time, a {@code WeakIdentityHashMap} may behave as though an unknown thread is silently removing entries. In |
70 | * particular, even if synchronizing on a {@code WeakIdentityHashMap} instance and invoking none of its mutator |
71 | * methods, it is possible for the {@code size} method to return smaller values over time, for the {@code isEmpty} |
72 | * method to return {@code false} and then {@code true}, for the {@code containsKey} method to return {@code true} and |
73 | * later {@code false} for a given key, for the {@code get} method to return a value for a given key but later return |
74 | * {@code null}, for the {@code put} method to return {@code null} and the {@code remove} method to return {@code false} |
75 | * for a key that previously appeared to be in the map, and for successive examinations of the key set, the value |
76 | * collection, and the entry set to yield successively smaller numbers of elements. To protect against such situations |
77 | * all {@code Iterator}s and {@code Entry}s created by this class throw an {@code IllegalStateException} when a key |
78 | * becomes {@code null} or a mapping got removed.</p> |
79 | * |
80 | * <p>The iterators returned by the {@code iterator} method of the collections returned by all of this class's |
81 | * "collection view methods" are fail-fast: if the map is structurally modified at any time after the iterator is |
82 | * created, in any way except through the iterator's own {@code remove} method, the iterator will throw a |
83 | * {@code ConcurrentModificationException}. Note that the fail-fast behavior of an iterator cannot be guaranteed as it |
84 | * is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent |
85 | * modification. Fail-fast iterators throw {@code ConcurrentModificationException}s on a best-effort basis. Therefore, |
86 | * it would be wrong to write a program that depends on this exception for its correctness: <i>the fail-fast behavior of |
87 | * iterators should be used only to detect bugs.</i></p> |
88 | * |
89 | * @author <a href="mailto:cs@jomc.org">Christian Schulte</a> |
90 | * @version $Id: WeakIdentityHashMap.java 891 2009-11-02 03:40:00Z schulte2005 $ |
91 | * |
92 | * @see java.util.HashMap |
93 | * @see java.util.WeakHashMap |
94 | * @see java.util.IdentityHashMap |
95 | * @see java.util.Collections#synchronizedMap |
96 | */ |
97 | public final class WeakIdentityHashMap implements Map |
98 | { |
99 | |
100 | /** Maximum capacity of the hash-table backing the implementation ({@code 2^30}). */ |
101 | private static final int MAXIMUM_CAPACITY = 0x40000000; |
102 | |
103 | /** Default initial capacity ({@code 2^4}). */ |
104 | private static final int DEFAULT_INITIAL_CAPACITY = 16; |
105 | |
106 | /** Default load factor ({@code 0.75}). */ |
107 | private static final float DEFAULT_LOAD_FACTOR = 0.75F; |
108 | |
109 | /** The number of times the map got structurally modified. */ |
110 | private volatile int modifications; |
111 | |
112 | /** The number of mappings held by the map. */ |
113 | private int size; |
114 | |
115 | /** The size value at which the hash table needs resizing. */ |
116 | private int resizeThreshold; |
117 | |
118 | /** The hash-table backing the map. */ |
119 | private Entry[] hashTable; |
120 | |
121 | /** Queue, to which weak keys are appended to. */ |
122 | private final ReferenceQueue referenceQueue = new ReferenceQueue(); |
123 | |
124 | /** The key set view of the map. */ |
125 | private transient Set keySet; |
126 | |
127 | /** The entry set view of the map. */ |
128 | private transient Set entrySet; |
129 | |
130 | /** The value collection view of the map. */ |
131 | private transient Collection valueCollection; |
132 | |
133 | /** The initial capacity of the hash table. */ |
134 | private final int initialCapacity; |
135 | |
136 | /** The load factor for the hash table. */ |
137 | private final float loadFactor; |
138 | |
139 | /** Null value returned by method {@link #getEntry(Object)}. */ |
140 | private final Entry NULL_ENTRY = new Entry( null, null, 0, this.referenceQueue ); |
141 | |
142 | /** |
143 | * Constructs a new, empty {@code WeakIdentityHashMap} with the default initial capacity ({@code 16}) and load |
144 | * factor ({@code 0.75}). |
145 | */ |
146 | public WeakIdentityHashMap() |
147 | { |
148 | this( DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR ); |
149 | } |
150 | |
151 | /** |
152 | * Constructs a new, empty {@code WeakIdentityHashMap} with the given initial capacity and the default load factor |
153 | * ({@code 0.75}). |
154 | * |
155 | * @param initialCapacity The initial capacity of the {@code WeakIdentityHashMap}. |
156 | * |
157 | * @throws IllegalArgumentException if {@code initialCapacity} is negative or greater than the maximum supported |
158 | * capacity ({@code 2^30}). |
159 | */ |
160 | public WeakIdentityHashMap( final int initialCapacity ) |
161 | { |
162 | this( initialCapacity, DEFAULT_LOAD_FACTOR ); |
163 | } |
164 | |
165 | /** |
166 | * Constructs a new, empty {@code WeakIdentityHashMap} with the default initial capacity ({@code 16}) and the given |
167 | * load factor. |
168 | * |
169 | * @param loadFactor The load factor of the {@code WeakIdentityHashMap}. |
170 | * |
171 | * @throws IllegalArgumentException if {@code loadFactor} is nonpositive. |
172 | */ |
173 | public WeakIdentityHashMap( final float loadFactor ) |
174 | { |
175 | this( DEFAULT_INITIAL_CAPACITY, loadFactor ); |
176 | } |
177 | |
178 | /** |
179 | * Constructs a new, empty {@code WeakIdentityHashMap} with the given initial capacity and the given load factor. |
180 | * |
181 | * @param initialCapacity The initial capacity of the {@code WeakIdentityHashMap}. |
182 | * @param loadFactor The load factor of the {@code WeakIdentityHashMap}. |
183 | * |
184 | * @throws IllegalArgumentException if {@code initialCapacity} is negative or greater than the maximum supported |
185 | * capacity ({@code 2^30}), or if {@code loadFactor} is nonpositive. |
186 | */ |
187 | public WeakIdentityHashMap( final int initialCapacity, final float loadFactor ) |
188 | { |
189 | if ( initialCapacity < 0 || initialCapacity > MAXIMUM_CAPACITY ) |
190 | { |
191 | throw new IllegalArgumentException( Integer.toString( initialCapacity ) ); |
192 | } |
193 | if ( loadFactor <= 0 || Float.isNaN( loadFactor ) ) |
194 | { |
195 | throw new IllegalArgumentException( Float.toString( loadFactor ) ); |
196 | } |
197 | |
198 | this.initialCapacity = initialCapacity; |
199 | this.loadFactor = loadFactor; |
200 | this.resizeThreshold = initialCapacity; |
201 | this.size = 0; |
202 | this.hashTable = new Entry[ initialCapacity ]; |
203 | } |
204 | |
205 | /** |
206 | * Gets the number of key-value mappings in this map. |
207 | * <p>If the map contains more than {@code Integer.MAX_VALUE} elements, returns {@code Integer.MAX_VALUE}.</p> |
208 | * |
209 | * @return The number of key-value mappings in this map. |
210 | */ |
211 | public int size() |
212 | { |
213 | if ( this.size > 0 ) |
214 | { |
215 | this.purge(); |
216 | } |
217 | |
218 | return this.size; |
219 | } |
220 | |
221 | /** |
222 | * Gets a flag indicating if this map is empty. |
223 | * |
224 | * @return {@code true} if this map contains no key-value mappings; {@code false} if this map contains at least one |
225 | * mapping. |
226 | */ |
227 | public boolean isEmpty() |
228 | { |
229 | return this.size() == 0; |
230 | } |
231 | |
232 | /** |
233 | * Gets a flag indicating if this map contains a mapping for a given key. |
234 | * <p>More formally, returns {@code true} if and only if this map contains a mapping for a key {@code k} such that |
235 | * {@code key==k}. There can be at most one such mapping.</p> |
236 | * |
237 | * @param key The key whose presence in this map is to be tested. |
238 | * |
239 | * @return {@code true} if this map contains a mapping for {@code key}; {@code false} if this map does not contain a |
240 | * mapping for {@code key}. |
241 | * |
242 | * @throws NullPointerException if {@code key} is {@code null}. |
243 | */ |
244 | public boolean containsKey( final Object key ) |
245 | { |
246 | if ( key == null ) |
247 | { |
248 | throw new NullPointerException( "key" ); |
249 | } |
250 | |
251 | return this.getEntry( key ).value != null; |
252 | } |
253 | |
254 | /** |
255 | * Gets a flag indicating if this map maps one or more keys to the specified value. |
256 | * <p>More formally, this method returns {@code true} if and only if this map contains at least one mapping to a |
257 | * value {@code v} such that {@code value.equals(v)}. This operation requires time linear in the map size.</p> |
258 | * |
259 | * @param value The value whose presence in this map is to be tested. |
260 | * |
261 | * @return {@code true} if this map maps one or more keys to {@code value}; {@code false} if this map does not map |
262 | * any key to {@code value}. |
263 | * |
264 | * @throws NullPointerException if {@code value} is {@code null}. |
265 | */ |
266 | public boolean containsValue( final Object value ) |
267 | { |
268 | if ( value == null ) |
269 | { |
270 | throw new NullPointerException( "value" ); |
271 | } |
272 | |
273 | final Entry[] table = this.getHashTable(); |
274 | |
275 | for ( int i = table.length - 1; i >= 0; i-- ) |
276 | { |
277 | for ( Entry e = table[i]; e != null; e = e.next ) |
278 | { |
279 | if ( value.equals( e.value ) ) |
280 | { |
281 | return true; |
282 | } |
283 | } |
284 | } |
285 | |
286 | return false; |
287 | } |
288 | |
289 | /** |
290 | * Gets the value to which a given key is mapped, or {@code null} if this map contains no mapping for that key. |
291 | * <p>More formally, if this map contains a mapping from a key {@code k} to a value {@code v} such that |
292 | * {@code key==k}, then this method returns {@code v}; otherwise it returns {@code null}. There can be at most one |
293 | * such mapping.</p> |
294 | * |
295 | * @param key The key whose associated value is to be returned. |
296 | * |
297 | * @return the value to which {@code key} is mapped, or {@code null} if this map contains no mapping for |
298 | * {@code key}. |
299 | * |
300 | * @throws NullPointerException if {@code key} is {@code null}. |
301 | */ |
302 | public Object get( final Object key ) |
303 | { |
304 | if ( key == null ) |
305 | { |
306 | throw new NullPointerException( "key" ); |
307 | } |
308 | |
309 | return this.getEntry( key ).value; |
310 | } |
311 | |
312 | /** |
313 | * Associates a given value with a given key in this map. |
314 | * <p>If the map previously contained a mapping for that key, the old value is replaced by the given value.</p> |
315 | * |
316 | * @param key The key with which {@code value} is to be associated. |
317 | * @param value The value to be associated with {@code key}. |
318 | * |
319 | * @return the value previously associated with {@code key}, or {@code null} if there was no mapping for |
320 | * {@code key}. |
321 | * |
322 | * @throws NullPointerException if {@code key} or {@code value} is {@code null}. |
323 | */ |
324 | public Object put( final Object key, final Object value ) |
325 | { |
326 | if ( key == null ) |
327 | { |
328 | throw new NullPointerException( "key" ); |
329 | } |
330 | if ( value == null ) |
331 | { |
332 | throw new NullPointerException( "value" ); |
333 | } |
334 | |
335 | final int hashCode = getHashCode( key ); |
336 | final Entry[] table = this.getHashTable(); |
337 | final int index = getHashTableIndex( hashCode, table.length ); |
338 | |
339 | for ( Entry e = table[index]; e != null; e = e.next ) |
340 | { |
341 | if ( e.hashCode == hashCode && e.get() == key ) |
342 | { |
343 | final Object oldValue = e.value; |
344 | e.value = value; |
345 | return oldValue; |
346 | } |
347 | } |
348 | |
349 | final Entry entry = new Entry( key, value, hashCode, this.referenceQueue ); |
350 | entry.next = table[index]; |
351 | table[index] = entry; |
352 | |
353 | this.increaseSize(); |
354 | |
355 | return null; |
356 | } |
357 | |
358 | /** |
359 | * Removes the mapping for a given key from this map if it is present. |
360 | * <p>More formally, if this map contains a mapping from key {@code k} to value {@code v} such that {@code key==k}, |
361 | * that mapping is removed. The map can contain at most one such mapping. The map will not contain a mapping for the |
362 | * given key once the call returns.</p> |
363 | * |
364 | * @param key The key whose mapping is to be removed from the map. |
365 | * |
366 | * @return the value previously associated with {@code key}, or {@code null} if there was no mapping for |
367 | * {@code key}. |
368 | * |
369 | * @throws NullPointerException if {@code key} is {@code null}. |
370 | */ |
371 | public Object remove( final Object key ) |
372 | { |
373 | if ( key == null ) |
374 | { |
375 | throw new NullPointerException( "key" ); |
376 | } |
377 | |
378 | final Entry[] table = this.getHashTable(); |
379 | final int hashCode = getHashCode( key ); |
380 | final int index = getHashTableIndex( hashCode, table.length ); |
381 | |
382 | for ( Entry e = table[index], pre = null; e != null; pre = e, e = e.next ) |
383 | { |
384 | if ( e.hashCode == hashCode && e.get() == key ) |
385 | { |
386 | if ( pre != null ) |
387 | { |
388 | pre.next = e.next; |
389 | } |
390 | else |
391 | { |
392 | table[index] = e.next; |
393 | } |
394 | |
395 | this.decreaseSize(); |
396 | this.modifications++; |
397 | |
398 | final Object removed = e.value; |
399 | e.removed = true; |
400 | e.value = null; |
401 | e.next = null; |
402 | return removed; |
403 | } |
404 | } |
405 | |
406 | return null; |
407 | } |
408 | |
409 | /** |
410 | * Copies all of the mappings from a given map to this map. |
411 | * <p>The effect of this call is equivalent to that of calling {@link #put(Object,Object) put(k, v)} on this map |
412 | * once for each mapping from key {@code k} to value {@code v} in the given map. The behavior of this operation is |
413 | * undefined if the given map is modified while the operation is in progress.</p> |
414 | * |
415 | * @param m The mappings to be stored in this map. |
416 | * |
417 | * @throws NullPointerException if {@code map} is {@code null}, or if {@code map} contains {@code null} keys or |
418 | * values. |
419 | */ |
420 | public void putAll( final Map m ) |
421 | { |
422 | if ( m == null ) |
423 | { |
424 | throw new NullPointerException( "m" ); |
425 | } |
426 | |
427 | for ( final Iterator it = m.entrySet().iterator(); it.hasNext(); ) |
428 | { |
429 | final Map.Entry entry = (Map.Entry) it.next(); |
430 | this.put( entry.getKey(), entry.getValue() ); |
431 | } |
432 | } |
433 | |
434 | /** Removes all of the mappings from this map so that the map will be empty after this call returns. */ |
435 | @SuppressWarnings( "empty-statement" ) |
436 | public void clear() |
437 | { |
438 | this.purge(); |
439 | this.hashTable = new Entry[ this.initialCapacity ]; |
440 | this.size = 0; |
441 | this.resizeThreshold = this.initialCapacity; |
442 | this.modifications++; |
443 | while ( this.referenceQueue.poll() != null ); |
444 | } |
445 | |
446 | /** |
447 | * Gets a {@code Set} view of the keys contained in this map. |
448 | * <p>The set is backed by the map, so changes to the map are reflected in the set, and vice-versa. If the map is |
449 | * modified while an iteration over the set is in progress (except through the iterator's own {@code remove} |
450 | * operation), the results of the iteration are undefined, that is, the iterator may throw an |
451 | * {@code IllegalStateException}. The set supports element removal, which removes the corresponding mapping from the |
452 | * map, via the {@code Iterator.remove}, {@code Set.remove}, {@code removeAll}, {@code retainAll}, and {@code clear} |
453 | * operations. It does not support the {@code add} or {@code addAll} operations.</p> |
454 | * |
455 | * @return a set view of the keys contained in this map. |
456 | */ |
457 | public Set keySet() |
458 | { |
459 | if ( this.keySet == null ) |
460 | { |
461 | this.keySet = new AbstractSet() |
462 | { |
463 | |
464 | public Iterator iterator() |
465 | { |
466 | return new EntryIterator() |
467 | { |
468 | |
469 | @Override |
470 | public Object next() |
471 | { |
472 | return ( (Entry) super.next() ).getKey(); |
473 | } |
474 | |
475 | }; |
476 | } |
477 | |
478 | public int size() |
479 | { |
480 | return WeakIdentityHashMap.this.size(); |
481 | } |
482 | |
483 | }; |
484 | |
485 | } |
486 | |
487 | return this.keySet; |
488 | } |
489 | |
490 | /** |
491 | * Gets a {@code Collection} view of the values contained in this map. |
492 | * <p>The collection is backed by the map, so changes to the map are reflected in the collection, and vice-versa. |
493 | * If the map is modified while an iteration over the collection is in progress (except through the iterator's own |
494 | * {@code remove} operation), the results of the iteration are undefined, that is, the iterator may throw an |
495 | * {@code IllegalStateException}. The collection supports element removal, which removes the corresponding mapping |
496 | * from the map, via the {@code Iterator.remove}, {@code Collection.remove}, {@code removeAll}, {@code retainAll} |
497 | * and {@code clear} operations. It does not support the {@code add} or {@code addAll} operations.</p> |
498 | * |
499 | * @return a collection view of the values contained in this map. |
500 | */ |
501 | public Collection values() |
502 | { |
503 | if ( this.valueCollection == null ) |
504 | { |
505 | this.valueCollection = new AbstractCollection() |
506 | { |
507 | |
508 | public Iterator iterator() |
509 | { |
510 | return new EntryIterator() |
511 | { |
512 | |
513 | @Override |
514 | public Object next() |
515 | { |
516 | return ( (Entry) super.next() ).getValue(); |
517 | } |
518 | |
519 | }; |
520 | } |
521 | |
522 | public int size() |
523 | { |
524 | return WeakIdentityHashMap.this.size(); |
525 | } |
526 | |
527 | }; |
528 | } |
529 | |
530 | return this.valueCollection; |
531 | } |
532 | |
533 | /** |
534 | * Gets a {@code Set} view of the mappings contained in this map. |
535 | * <p>The set is backed by the map, so changes to the map are reflected in the set, and vice-versa. If the map is |
536 | * modified while an iteration over the set is in progress (except through the iterator's own {@code remove} |
537 | * operation, or through the {@code setValue} operation on a map entry returned by the iterator) the results of the |
538 | * iteration are undefined, that is, the iterator may throw an {@code IllegalStateException}. The set supports |
539 | * element removal, which removes the corresponding mapping from the map, via the {@code Iterator.remove}, |
540 | * {@code Set.remove}, {@code removeAll}, {@code retainAll} and {@code clear} operations. It does not support the |
541 | * {@code add} or {@code addAll} operations.</p> |
542 | * |
543 | * @return a set view of the mappings contained in this map. |
544 | */ |
545 | public Set entrySet() |
546 | { |
547 | if ( this.entrySet == null ) |
548 | { |
549 | this.entrySet = new AbstractSet() |
550 | { |
551 | |
552 | public Iterator iterator() |
553 | { |
554 | return new EntryIterator(); |
555 | } |
556 | |
557 | public int size() |
558 | { |
559 | return WeakIdentityHashMap.this.size(); |
560 | } |
561 | |
562 | }; |
563 | } |
564 | |
565 | return this.entrySet; |
566 | } |
567 | |
568 | /** |
569 | * Returns a string representation of the object. |
570 | * |
571 | * @return a string representation of the object. |
572 | */ |
573 | @Override |
574 | public String toString() |
575 | { |
576 | return super.toString() + this.internalString(); |
577 | } |
578 | |
579 | /** |
580 | * Compares the specified object with this map for equality. |
581 | * <p>Returns {@code true} if the given object is also a map and the two maps represent the same mappings. More |
582 | * formally, two maps {@code m1} and {@code m2} represent the same mappings if |
583 | * {@code m1.entrySet().equals(m2.entrySet())}.</p> |
584 | * |
585 | * @param o The object to be compared for equality with this map. |
586 | * |
587 | * @return {@code true} if {@code o} is equal to this map; {@code false} if {@code o} is not equal to this map. |
588 | */ |
589 | @Override |
590 | public boolean equals( final Object o ) |
591 | { |
592 | boolean equal = this == o; |
593 | |
594 | if ( !equal && o instanceof Map ) |
595 | { |
596 | final Map that = (Map) o; |
597 | equal = this.entrySet().equals( that.entrySet() ); |
598 | } |
599 | |
600 | return equal; |
601 | } |
602 | |
603 | /** |
604 | * Gets the hash code value for this map. |
605 | * <p>The hash code of a map is defined to be the sum of the hash codes of each entry in the map's |
606 | * {@code entrySet()} view.</p> |
607 | * |
608 | * @return the hash code value for this map. |
609 | */ |
610 | @Override |
611 | public int hashCode() |
612 | { |
613 | return this.entrySet().hashCode(); |
614 | } |
615 | |
616 | /** |
617 | * Creates a string representing the mappings of the instance. |
618 | * |
619 | * @return a string representing the mappings of the instance. |
620 | */ |
621 | private String internalString() |
622 | { |
623 | final StringBuilder buf = new StringBuilder( 12 * this.size() ).append( '{' ); |
624 | final Entry[] table = this.getHashTable(); |
625 | for ( int i = table.length - 1, index = 0; i >= 0; i-- ) |
626 | { |
627 | for ( Entry e = table[i]; e != null; e = e.next ) |
628 | { |
629 | if ( buf.length() > 1 ) |
630 | { |
631 | buf.append( ", " ); |
632 | } |
633 | |
634 | buf.append( '[' ).append( index++ ).append( "]=" ).append( e ); |
635 | } |
636 | } |
637 | |
638 | return buf.append( '}' ).toString(); |
639 | } |
640 | |
641 | /** |
642 | * Gets a hash-code value for a given key. |
643 | * |
644 | * @param key The key to get a hash-code value for. |
645 | * |
646 | * @return Hash-code value of {@code key}. |
647 | */ |
648 | private static int getHashCode( final Object key ) |
649 | { |
650 | return System.identityHashCode( key ); |
651 | } |
652 | |
653 | /** |
654 | * Gets the index of a hash code value. |
655 | * |
656 | * @param hashCode The hash code value to return the index of. |
657 | * @param capacity The capacity to return an index for. |
658 | * |
659 | * @return the index of {@code hashCode} for {@code capacity}. |
660 | */ |
661 | private static int getHashTableIndex( final int hashCode, final int capacity ) |
662 | { |
663 | return hashCode & ( capacity - 1 ); |
664 | } |
665 | |
666 | /** |
667 | * Gets the hash-table backing the instance. |
668 | * <p>This method creates a new hash-table and re-hashes any mappings whenever the size of the map gets greater than |
669 | * the capacity of the internal hash-table times the load factor value.</p> |
670 | * |
671 | * @return the hash-table backing the instance. |
672 | */ |
673 | private Entry[] getHashTable() |
674 | { |
675 | if ( this.hashTable.length < this.resizeThreshold ) |
676 | { |
677 | final Entry[] table = new Entry[ this.calculateCapacity() ]; |
678 | |
679 | for ( int i = this.hashTable.length - 1; i >= 0; i-- ) |
680 | { |
681 | Entry entry = this.hashTable[i]; |
682 | |
683 | while ( entry != null ) |
684 | { |
685 | final Entry next = entry.next; |
686 | final int index = getHashTableIndex( entry.hashCode, table.length ); |
687 | |
688 | entry.next = table[index]; |
689 | table[index] = entry; |
690 | entry = next; |
691 | } |
692 | } |
693 | |
694 | this.hashTable = table; |
695 | this.modifications++; |
696 | } |
697 | |
698 | this.purge(); |
699 | return this.hashTable; |
700 | } |
701 | |
702 | /** Removes any garbage collected entries. */ |
703 | private void purge() |
704 | { |
705 | Entry purge; |
706 | |
707 | while ( ( purge = (Entry) this.referenceQueue.poll() ) != null ) |
708 | { |
709 | final int index = getHashTableIndex( purge.hashCode, this.hashTable.length ); |
710 | |
711 | for ( Entry e = this.hashTable[index], pre = null; e != null; pre = e, e = e.next ) |
712 | { |
713 | if ( e == purge ) |
714 | { |
715 | if ( pre != null ) |
716 | { |
717 | pre.next = purge.next; |
718 | } |
719 | else |
720 | { |
721 | this.hashTable[index] = purge.next; |
722 | } |
723 | |
724 | purge.removed = true; |
725 | purge.next = null; |
726 | purge.value = null; |
727 | |
728 | this.decreaseSize(); |
729 | |
730 | break; |
731 | } |
732 | } |
733 | } |
734 | } |
735 | |
736 | private void increaseSize() |
737 | { |
738 | if ( this.size < Integer.MAX_VALUE ) |
739 | { |
740 | this.size++; |
741 | this.resizeThreshold = (int) ( this.size * this.loadFactor ); |
742 | } |
743 | |
744 | this.modifications++; |
745 | } |
746 | |
747 | private void decreaseSize() |
748 | { |
749 | if ( this.size > 0 ) |
750 | { |
751 | this.size--; |
752 | } |
753 | } |
754 | |
755 | private int calculateCapacity() |
756 | { |
757 | int maxCapacity = this.initialCapacity; |
758 | if ( maxCapacity < this.resizeThreshold ) |
759 | { |
760 | maxCapacity = this.resizeThreshold > MAXIMUM_CAPACITY ? MAXIMUM_CAPACITY : this.resizeThreshold; |
761 | } |
762 | |
763 | int capacity = 1; |
764 | while ( capacity < maxCapacity ) |
765 | { |
766 | capacity <<= 1; |
767 | } |
768 | |
769 | return capacity; |
770 | } |
771 | |
772 | private Entry getEntry( final Object key ) |
773 | { |
774 | final int hashCode = getHashCode( key ); |
775 | for ( Entry e = this.hashTable[getHashTableIndex( hashCode, this.hashTable.length )]; e != null; e = e.next ) |
776 | { |
777 | if ( e.hashCode == hashCode && e.get() == key ) |
778 | { |
779 | return e; |
780 | } |
781 | } |
782 | |
783 | return NULL_ENTRY; |
784 | } |
785 | |
786 | /** |
787 | * A map entry (key-value pair) with weakly referenced key. |
788 | * <p>The {@code WeakIdentityHashMap.entrySet} method returns a collection-view of the map, whose elements are of |
789 | * this class. The only way to obtain a reference to a map entry is from the iterator of this collection-view. These |
790 | * {@code Map.Entry} objects are valid only for the duration of the iteration; more formally, the behavior of a map |
791 | * entry is undefined if the backing map has been modified after the entry was returned by the iterator, except |
792 | * through the {@code setValue} operation on the map entry.</p> |
793 | * |
794 | * @see WeakIdentityHashMap#entrySet() |
795 | */ |
796 | private static class Entry extends WeakReference implements Map.Entry |
797 | { |
798 | |
799 | /** The value of the entry. */ |
800 | private Object value; |
801 | |
802 | /** The next entry in the bucket. */ |
803 | private Entry next; |
804 | |
805 | /** Flag indicating that this entry got removed from the map. */ |
806 | private boolean removed; |
807 | |
808 | /** The hash code value of the key. */ |
809 | private final int hashCode; |
810 | |
811 | Entry( final Object key, final Object value, final int hashCode, final ReferenceQueue queue ) |
812 | { |
813 | super( key, queue ); |
814 | this.hashCode = hashCode; |
815 | this.value = value; |
816 | } |
817 | |
818 | /** |
819 | * Gets the key corresponding to this entry. |
820 | * |
821 | * @return The key corresponding to this entry. |
822 | * |
823 | * @throws IllegalStateException if the entry got removed from the backing map (either due to an iterator's |
824 | * {@code remove} operation or due to the key having been garbage collected). |
825 | */ |
826 | public Object getKey() |
827 | { |
828 | final Object key = this.get(); |
829 | |
830 | if ( key == null || this.removed ) |
831 | { |
832 | throw new IllegalStateException(); |
833 | } |
834 | |
835 | return key; |
836 | } |
837 | |
838 | /** |
839 | * Gets the value corresponding to this entry. |
840 | * |
841 | * @return the value corresponding to this entry. |
842 | * |
843 | * @throws IllegalStateException if the entry got removed from the backing map (either due to an iterator's |
844 | * {@code remove} operation or due to the key having been garbage collected). |
845 | */ |
846 | public Object getValue() |
847 | { |
848 | if ( this.get() == null || this.removed ) |
849 | { |
850 | throw new IllegalStateException(); |
851 | } |
852 | |
853 | return this.value; |
854 | } |
855 | |
856 | /** |
857 | * Replaces the value corresponding to this entry with the specified value. |
858 | * |
859 | * @param value The new value to be stored in this entry. |
860 | * |
861 | * @return old value corresponding to the entry. |
862 | * |
863 | * @throws NullPointerException if {@code value} is {@code null}. |
864 | * @throws IllegalStateException if the entry got removed from the backing map (either due to an iterator's |
865 | * {@code remove} operation or due to the key having been garbage collected). |
866 | */ |
867 | public Object setValue( final Object value ) |
868 | { |
869 | if ( value == null ) |
870 | { |
871 | throw new NullPointerException( "value" ); |
872 | } |
873 | if ( this.get() == null || this.removed ) |
874 | { |
875 | throw new IllegalStateException(); |
876 | } |
877 | |
878 | final Object oldValue = this.getValue(); |
879 | |
880 | if ( value != oldValue && !value.equals( oldValue ) ) |
881 | { |
882 | this.value = value; |
883 | } |
884 | |
885 | return oldValue; |
886 | } |
887 | |
888 | /** |
889 | * Returns a string representation of the object. |
890 | * |
891 | * @return a string representation of the object. |
892 | */ |
893 | @Override |
894 | public String toString() |
895 | { |
896 | return super.toString() + this.internalString(); |
897 | } |
898 | |
899 | /** |
900 | * Compares a given object with this entry for equality. |
901 | * <p>Returns {@code true} if the given object is also a map entry and the two entries represent the same |
902 | * mapping. More formally, two entries {@code e1} and {@code e2} represent the same mapping if |
903 | * <pre><blockquote> |
904 | * ( e1.getKey() == e2.getKey() ) && |
905 | * ( e1.getValue().equals( e2.getValue() ) ) |
906 | * </blockquote></pre></p> |
907 | * |
908 | * @param o The object to be compared for equality with this map entry. |
909 | * |
910 | * @return {@code true} if {@code o} is equal to this map entry; {@code false} if {@code o} is not equal to this |
911 | * map entry. |
912 | */ |
913 | @Override |
914 | public boolean equals( final Object o ) |
915 | { |
916 | boolean equal = this == o; |
917 | |
918 | if ( !equal && o instanceof Map.Entry ) |
919 | { |
920 | final Map.Entry that = (Map.Entry) o; |
921 | equal = this.getKey() == that.getKey() && this.getValue().equals( that.getValue() ); |
922 | } |
923 | |
924 | return equal; |
925 | } |
926 | |
927 | /** |
928 | * Gets the hash code value for this map entry. |
929 | * <p>The hash code of a map entry {@code e} is defined to be: |
930 | * <pre><blockquote> |
931 | * ( e.getKey() == null ? 0 : e.getKey().hashCode() ) ^ |
932 | * ( e.getValue() == null ? 0 : e.getValue().hashCode() ) |
933 | * </blockquote></pre></p> |
934 | * |
935 | * @return the hash code value for this map entry. |
936 | */ |
937 | @Override |
938 | public int hashCode() |
939 | { |
940 | return ( this.hashCode ) ^ ( this.getValue().hashCode() ); |
941 | } |
942 | |
943 | /** |
944 | * Creates a string representing the properties of the instance. |
945 | * |
946 | * @return a string representing the properties of the instance. |
947 | */ |
948 | private String internalString() |
949 | { |
950 | final StringBuilder buf = new StringBuilder( 50 ).append( '{' ); |
951 | buf.append( "key=" ).append( this.getKey() ).append( ", value=" ).append( this.getValue() ); |
952 | return buf.append( '}' ).toString(); |
953 | } |
954 | |
955 | } |
956 | |
957 | /** An iterator over the hash-table backing the implementation. */ |
958 | private class EntryIterator implements Iterator |
959 | { |
960 | |
961 | /** The next element in the iteration. */ |
962 | private Entry next; |
963 | |
964 | /** The current element in the iteration. */ |
965 | private Entry current; |
966 | |
967 | /** The current index into the hash-table. */ |
968 | private int index; |
969 | |
970 | /** The number of modifications when this iterator got created. */ |
971 | private int modifications; |
972 | |
973 | /** Creates a new {@code EntryIterator} instance. */ |
974 | EntryIterator() |
975 | { |
976 | final Entry[] table = getHashTable(); |
977 | for ( this.index = table.length - 1; this.index >= 0; this.index-- ) |
978 | { |
979 | if ( table[this.index] != null ) |
980 | { |
981 | this.next = table[this.index--]; |
982 | break; |
983 | } |
984 | } |
985 | |
986 | this.modifications = WeakIdentityHashMap.this.modifications; |
987 | } |
988 | |
989 | /** |
990 | * Gets a flag indicating that the iteration has more elements. |
991 | * |
992 | * @return {@code true} if the iterator has more elements; {@code false} if the iterator does not have more |
993 | * elements. |
994 | */ |
995 | public boolean hasNext() |
996 | { |
997 | if ( this.modifications != WeakIdentityHashMap.this.modifications ) |
998 | { |
999 | throw new ConcurrentModificationException(); |
1000 | } |
1001 | |
1002 | return this.next != null; |
1003 | } |
1004 | |
1005 | /** |
1006 | * Gets the next element in the iteration. |
1007 | * |
1008 | * @return the next element in the iteration. |
1009 | * |
1010 | * @throws NoSuchElementException if the iterator does not have more elements. |
1011 | */ |
1012 | public Object next() |
1013 | { |
1014 | if ( this.modifications != WeakIdentityHashMap.this.modifications ) |
1015 | { |
1016 | throw new ConcurrentModificationException(); |
1017 | } |
1018 | if ( this.next == null ) |
1019 | { |
1020 | throw new NoSuchElementException(); |
1021 | } |
1022 | |
1023 | this.current = this.next; |
1024 | |
1025 | if ( this.next.next != null ) |
1026 | { |
1027 | this.next = this.next.next; |
1028 | } |
1029 | else |
1030 | { |
1031 | this.next = null; |
1032 | final Entry[] table = getHashTable(); |
1033 | for ( ; this.index >= 0; this.index-- ) |
1034 | { |
1035 | if ( table[this.index] != null ) |
1036 | { |
1037 | this.next = table[this.index--]; |
1038 | break; |
1039 | } |
1040 | } |
1041 | } |
1042 | |
1043 | return this.current; |
1044 | } |
1045 | |
1046 | /** |
1047 | * Removes from the underlying hash-table the last element returned by the iterator. |
1048 | * |
1049 | * @throws IllegalStateException if the {@code next} method has not yet been called, or the {@code remove} |
1050 | * method has already been called after the last call to the {@code next} method. |
1051 | */ |
1052 | public void remove() |
1053 | { |
1054 | if ( this.modifications != WeakIdentityHashMap.this.modifications ) |
1055 | { |
1056 | throw new ConcurrentModificationException(); |
1057 | } |
1058 | if ( this.current == null ) |
1059 | { |
1060 | throw new IllegalStateException(); |
1061 | } |
1062 | |
1063 | final Object key = this.current.getKey(); |
1064 | |
1065 | if ( key == null ) |
1066 | { |
1067 | throw new IllegalStateException(); |
1068 | } |
1069 | |
1070 | WeakIdentityHashMap.this.remove( key ); |
1071 | this.modifications = WeakIdentityHashMap.this.modifications; |
1072 | this.current = null; |
1073 | } |
1074 | |
1075 | } |
1076 | |
1077 | } |