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