| 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 | } |