001/* 002 * jDTAUS Core RI Client Container 003 * Copyright (C) 2005 Christian Schulte 004 * <cs@schulte.it> 005 * 006 * This library is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 2.1 of the License, or any later version. 010 * 011 * This library is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public 017 * License along with this library; if not, write to the Free Software 018 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 019 * 020 */ 021package org.jdtaus.core.container.ri.client; 022 023import java.lang.ref.ReferenceQueue; 024import java.lang.ref.WeakReference; 025import java.util.AbstractCollection; 026import java.util.AbstractSet; 027import java.util.Collection; 028import java.util.Collections; 029import java.util.ConcurrentModificationException; 030import java.util.HashMap; 031import java.util.IdentityHashMap; 032import java.util.Iterator; 033import java.util.Map; 034import java.util.NoSuchElementException; 035import java.util.Set; 036import java.util.WeakHashMap; 037 038/** 039 * Hash-table based {@code Map} implementation with weak keys, using 040 * object-identity in place of object-equality when comparing keys. 041 * 042 * <p>In a {@code WeakIdentityHashMap} two keys {@code k1} and {@code k2} are 043 * considered equal if and only if {@code k1==k2} evaluates to {@code true}. 044 * An entry will automatically be removed when its key is no longer in ordinary 045 * use. More precisely, the presence of a mapping for a given key will not 046 * prevent the key from being discarded by the garbage collector, that is, made 047 * finalizable, finalized, and then reclaimed. When a key has been discarded its 048 * entry is effectively removed from the map, so this class behaves somewhat 049 * differently from other {@code Map} implementations and is not a 050 * general-purpose {@code Map} implementation! Although it implements the 051 * {@code Map} interface, it intentionally violates {@code Map}'s general 052 * contract, which mandates the use of the {@code equals} method when comparing 053 * objects.</p> 054 * 055 * <p>This class has performance characteristics similar to those of the 056 * {@code HashMap} class, and has the same efficiency parameters of 057 * {@code initialCapacity} and {@code loadFactor}. All of the optional map 058 * operations are provided. It does not support {@code null} values and the 059 * {@code null} key. All methods taking a key or value will throw a 060 * {@code NullPointerException} when given a {@code null} reference. No 061 * guarantees are made as to the order of the map; in particular, there is no 062 * guarantee that the order will remain constant over time. Like most collection 063 * classes, this class is not synchronized. A synchronized 064 * {@code WeakIdentityHashMap} may be constructed using the 065 * {@code Collections.synchronizedMap} method.</p> 066 * 067 * <p>The behavior of the {@code WeakIdentityHashMap} class depends in part upon 068 * the actions of the garbage collector, so several {@code Map} invariants do 069 * not hold for this class. Because the garbage collector may discard keys at 070 * any time, a {@code WeakIdentityHashMap} may behave as though an unknown 071 * thread is silently removing entries. In particular, even if synchronizing on 072 * a {@code WeakIdentityHashMap} instance and invoking none of its mutator 073 * methods, it is possible for the {@code size} method to return smaller values 074 * over time, for the {@code isEmpty} method to return {@code false} and then 075 * {@code true}, for the {@code containsKey} method to return {@code true} and 076 * later {@code false} for a given key, for the {@code get} method to return a 077 * value for a given key but later return {@code null}, for the {@code put} 078 * method to return {@code null} and the {@code remove} method to return 079 * {@code false} for a key that previously appeared to be in the map, and 080 * for successive examinations of the key set, the value collection, and 081 * the entry set to yield successively smaller numbers of elements. To protect 082 * against such situations all {@code Iterator}s and {@code Entry}s created by 083 * this class throw an {@code IllegalStateException} when a key becomes 084 * {@code null} or a mapping got removed.</p> 085 * 086 * <p>The iterators returned by the {@code iterator} method of the collections 087 * returned by all of this class's "collection view methods" are 088 * fail-fast: if the map is structurally modified at any time after the iterator 089 * is created, in any way except through the iterator's own {@code remove} 090 * method, the iterator will throw a {@code ConcurrentModificationException}. 091 * Note that the fail-fast behavior of an iterator cannot be guaranteed as it 092 * is, generally speaking, impossible to make any hard guarantees in the 093 * presence of unsynchronized concurrent modification. Fail-fast iterators throw 094 * {@code ConcurrentModificationException}s on a best-effort basis. Therefore, 095 * it would be wrong to write a program that depends on this exception for its 096 * correctness: <i>the fail-fast behavior of iterators should be used only to 097 * detect bugs.</i></p> 098 * 099 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a> 100 * @version $JDTAUS: WeakIdentityHashMap.java 8743 2012-10-07 03:06:20Z schulte $ 101 * 102 * @see HashMap 103 * @see WeakHashMap 104 * @see IdentityHashMap 105 * @see Collections#synchronizedMap 106 */ 107public 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 volatile 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}