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