001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.commons.collections.map; 018 019 import java.io.IOException; 020 import java.io.ObjectInputStream; 021 import java.io.ObjectOutputStream; 022 import java.lang.ref.Reference; 023 import java.lang.ref.ReferenceQueue; 024 import java.lang.ref.SoftReference; 025 import java.lang.ref.WeakReference; 026 import java.util.ArrayList; 027 import java.util.Collection; 028 import java.util.ConcurrentModificationException; 029 import java.util.Iterator; 030 import java.util.List; 031 import java.util.Map; 032 import java.util.NoSuchElementException; 033 import java.util.Set; 034 035 import org.apache.commons.collections.MapIterator; 036 import org.apache.commons.collections.keyvalue.DefaultMapEntry; 037 038 /** 039 * An abstract implementation of a hash-based map that allows the entries to 040 * be removed by the garbage collector. 041 * <p> 042 * This class implements all the features necessary for a subclass reference 043 * hash-based map. Key-value entries are stored in instances of the 044 * <code>ReferenceEntry</code> class which can be overridden and replaced. 045 * The iterators can similarly be replaced, without the need to replace the KeySet, 046 * EntrySet and Values view classes. 047 * <p> 048 * Overridable methods are provided to change the default hashing behaviour, and 049 * to change how entries are added to and removed from the map. Hopefully, all you 050 * need for unusual subclasses is here. 051 * <p> 052 * When you construct an <code>AbstractReferenceMap</code>, you can specify what 053 * kind of references are used to store the map's keys and values. 054 * If non-hard references are used, then the garbage collector can remove 055 * mappings if a key or value becomes unreachable, or if the JVM's memory is 056 * running low. For information on how the different reference types behave, 057 * see {@link Reference}. 058 * <p> 059 * Different types of references can be specified for keys and values. 060 * The keys can be configured to be weak but the values hard, 061 * in which case this class will behave like a 062 * <a href="http://java.sun.com/j2se/1.4/docs/api/java/util/WeakHashMap.html"> 063 * <code>WeakHashMap</code></a>. However, you can also specify hard keys and 064 * weak values, or any other combination. The default constructor uses 065 * hard keys and soft values, providing a memory-sensitive cache. 066 * <p> 067 * This {@link Map} implementation does <i>not</i> allow null elements. 068 * Attempting to add a null key or value to the map will raise a 069 * <code>NullPointerException</code>. 070 * <p> 071 * All the available iterators can be reset back to the start by casting to 072 * <code>ResettableIterator</code> and calling <code>reset()</code>. 073 * <p> 074 * This implementation is not synchronized. 075 * You can use {@link java.util.Collections#synchronizedMap} to 076 * provide synchronized access to a <code>ReferenceMap</code>. 077 * 078 * @see java.lang.ref.Reference 079 * @since Commons Collections 3.1 (extracted from ReferenceMap in 3.0) 080 * @version $Revision: 646777 $ $Date: 2008-04-10 13:33:15 +0100 (Thu, 10 Apr 2008) $ 081 * 082 * @author Paul Jack 083 * @author Stephen Colebourne 084 */ 085 public abstract class AbstractReferenceMap extends AbstractHashedMap { 086 087 /** Constant indicating that hard references should be used */ 088 public static final int HARD = 0; 089 090 /** Constant indicating that soft references should be used */ 091 public static final int SOFT = 1; 092 093 /** Constant indicating that weak references should be used */ 094 public static final int WEAK = 2; 095 096 /** 097 * The reference type for keys. Must be HARD, SOFT, WEAK. 098 * @serial 099 */ 100 protected int keyType; 101 102 /** 103 * The reference type for values. Must be HARD, SOFT, WEAK. 104 * @serial 105 */ 106 protected int valueType; 107 108 /** 109 * Should the value be automatically purged when the associated key has been collected? 110 */ 111 protected boolean purgeValues; 112 113 /** 114 * ReferenceQueue used to eliminate stale mappings. 115 * See purge. 116 */ 117 private transient ReferenceQueue queue; 118 119 //----------------------------------------------------------------------- 120 /** 121 * Constructor used during deserialization. 122 */ 123 protected AbstractReferenceMap() { 124 super(); 125 } 126 127 /** 128 * Constructs a new empty map with the specified reference types, 129 * load factor and initial capacity. 130 * 131 * @param keyType the type of reference to use for keys; 132 * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK} 133 * @param valueType the type of reference to use for values; 134 * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK} 135 * @param capacity the initial capacity for the map 136 * @param loadFactor the load factor for the map 137 * @param purgeValues should the value be automatically purged when the 138 * key is garbage collected 139 */ 140 protected AbstractReferenceMap( 141 int keyType, int valueType, int capacity, 142 float loadFactor, boolean purgeValues) { 143 super(capacity, loadFactor); 144 verify("keyType", keyType); 145 verify("valueType", valueType); 146 this.keyType = keyType; 147 this.valueType = valueType; 148 this.purgeValues = purgeValues; 149 } 150 151 /** 152 * Initialise this subclass during construction, cloning or deserialization. 153 */ 154 protected void init() { 155 queue = new ReferenceQueue(); 156 } 157 158 //----------------------------------------------------------------------- 159 /** 160 * Checks the type int is a valid value. 161 * 162 * @param name the name for error messages 163 * @param type the type value to check 164 * @throws IllegalArgumentException if the value if invalid 165 */ 166 private static void verify(String name, int type) { 167 if ((type < HARD) || (type > WEAK)) { 168 throw new IllegalArgumentException(name + " must be HARD, SOFT, WEAK."); 169 } 170 } 171 172 //----------------------------------------------------------------------- 173 /** 174 * Gets the size of the map. 175 * 176 * @return the size 177 */ 178 public int size() { 179 purgeBeforeRead(); 180 return super.size(); 181 } 182 183 /** 184 * Checks whether the map is currently empty. 185 * 186 * @return true if the map is currently size zero 187 */ 188 public boolean isEmpty() { 189 purgeBeforeRead(); 190 return super.isEmpty(); 191 } 192 193 /** 194 * Checks whether the map contains the specified key. 195 * 196 * @param key the key to search for 197 * @return true if the map contains the key 198 */ 199 public boolean containsKey(Object key) { 200 purgeBeforeRead(); 201 Entry entry = getEntry(key); 202 if (entry == null) { 203 return false; 204 } 205 return (entry.getValue() != null); 206 } 207 208 /** 209 * Checks whether the map contains the specified value. 210 * 211 * @param value the value to search for 212 * @return true if the map contains the value 213 */ 214 public boolean containsValue(Object value) { 215 purgeBeforeRead(); 216 if (value == null) { 217 return false; 218 } 219 return super.containsValue(value); 220 } 221 222 /** 223 * Gets the value mapped to the key specified. 224 * 225 * @param key the key 226 * @return the mapped value, null if no match 227 */ 228 public Object get(Object key) { 229 purgeBeforeRead(); 230 Entry entry = getEntry(key); 231 if (entry == null) { 232 return null; 233 } 234 return entry.getValue(); 235 } 236 237 238 /** 239 * Puts a key-value mapping into this map. 240 * Neither the key nor the value may be null. 241 * 242 * @param key the key to add, must not be null 243 * @param value the value to add, must not be null 244 * @return the value previously mapped to this key, null if none 245 * @throws NullPointerException if either the key or value is null 246 */ 247 public Object put(Object key, Object value) { 248 if (key == null) { 249 throw new NullPointerException("null keys not allowed"); 250 } 251 if (value == null) { 252 throw new NullPointerException("null values not allowed"); 253 } 254 255 purgeBeforeWrite(); 256 return super.put(key, value); 257 } 258 259 /** 260 * Removes the specified mapping from this map. 261 * 262 * @param key the mapping to remove 263 * @return the value mapped to the removed key, null if key not in map 264 */ 265 public Object remove(Object key) { 266 if (key == null) { 267 return null; 268 } 269 purgeBeforeWrite(); 270 return super.remove(key); 271 } 272 273 /** 274 * Clears this map. 275 */ 276 public void clear() { 277 super.clear(); 278 while (queue.poll() != null) {} // drain the queue 279 } 280 281 //----------------------------------------------------------------------- 282 /** 283 * Gets a MapIterator over the reference map. 284 * The iterator only returns valid key/value pairs. 285 * 286 * @return a map iterator 287 */ 288 public MapIterator mapIterator() { 289 return new ReferenceMapIterator(this); 290 } 291 292 /** 293 * Returns a set view of this map's entries. 294 * An iterator returned entry is valid until <code>next()</code> is called again. 295 * The <code>setValue()</code> method on the <code>toArray</code> entries has no effect. 296 * 297 * @return a set view of this map's entries 298 */ 299 public Set entrySet() { 300 if (entrySet == null) { 301 entrySet = new ReferenceEntrySet(this); 302 } 303 return entrySet; 304 } 305 306 /** 307 * Returns a set view of this map's keys. 308 * 309 * @return a set view of this map's keys 310 */ 311 public Set keySet() { 312 if (keySet == null) { 313 keySet = new ReferenceKeySet(this); 314 } 315 return keySet; 316 } 317 318 /** 319 * Returns a collection view of this map's values. 320 * 321 * @return a set view of this map's values 322 */ 323 public Collection values() { 324 if (values == null) { 325 values = new ReferenceValues(this); 326 } 327 return values; 328 } 329 330 //----------------------------------------------------------------------- 331 /** 332 * Purges stale mappings from this map before read operations. 333 * <p> 334 * This implementation calls {@link #purge()} to maintain a consistent state. 335 */ 336 protected void purgeBeforeRead() { 337 purge(); 338 } 339 340 /** 341 * Purges stale mappings from this map before write operations. 342 * <p> 343 * This implementation calls {@link #purge()} to maintain a consistent state. 344 */ 345 protected void purgeBeforeWrite() { 346 purge(); 347 } 348 349 /** 350 * Purges stale mappings from this map. 351 * <p> 352 * Note that this method is not synchronized! Special 353 * care must be taken if, for instance, you want stale 354 * mappings to be removed on a periodic basis by some 355 * background thread. 356 */ 357 protected void purge() { 358 Reference ref = queue.poll(); 359 while (ref != null) { 360 purge(ref); 361 ref = queue.poll(); 362 } 363 } 364 365 /** 366 * Purges the specified reference. 367 * 368 * @param ref the reference to purge 369 */ 370 protected void purge(Reference ref) { 371 // The hashCode of the reference is the hashCode of the 372 // mapping key, even if the reference refers to the 373 // mapping value... 374 int hash = ref.hashCode(); 375 int index = hashIndex(hash, data.length); 376 HashEntry previous = null; 377 HashEntry entry = data[index]; 378 while (entry != null) { 379 if (((ReferenceEntry) entry).purge(ref)) { 380 if (previous == null) { 381 data[index] = entry.next; 382 } else { 383 previous.next = entry.next; 384 } 385 this.size--; 386 return; 387 } 388 previous = entry; 389 entry = entry.next; 390 } 391 392 } 393 394 //----------------------------------------------------------------------- 395 /** 396 * Gets the entry mapped to the key specified. 397 * 398 * @param key the key 399 * @return the entry, null if no match 400 */ 401 protected HashEntry getEntry(Object key) { 402 if (key == null) { 403 return null; 404 } else { 405 return super.getEntry(key); 406 } 407 } 408 409 /** 410 * Gets the hash code for a MapEntry. 411 * Subclasses can override this, for example to use the identityHashCode. 412 * 413 * @param key the key to get a hash code for, may be null 414 * @param value the value to get a hash code for, may be null 415 * @return the hash code, as per the MapEntry specification 416 */ 417 protected int hashEntry(Object key, Object value) { 418 return (key == null ? 0 : key.hashCode()) ^ 419 (value == null ? 0 : value.hashCode()); 420 } 421 422 /** 423 * Compares two keys, in internal converted form, to see if they are equal. 424 * <p> 425 * This implementation converts the key from the entry to a real reference 426 * before comparison. 427 * 428 * @param key1 the first key to compare passed in from outside 429 * @param key2 the second key extracted from the entry via <code>entry.key</code> 430 * @return true if equal 431 */ 432 protected boolean isEqualKey(Object key1, Object key2) { 433 key2 = (keyType > HARD ? ((Reference) key2).get() : key2); 434 return (key1 == key2 || key1.equals(key2)); 435 } 436 437 /** 438 * Creates a ReferenceEntry instead of a HashEntry. 439 * 440 * @param next the next entry in sequence 441 * @param hashCode the hash code to use 442 * @param key the key to store 443 * @param value the value to store 444 * @return the newly created entry 445 */ 446 protected HashEntry createEntry(HashEntry next, int hashCode, Object key, Object value) { 447 return new ReferenceEntry(this, next, hashCode, key, value); 448 } 449 450 /** 451 * Creates an entry set iterator. 452 * 453 * @return the entrySet iterator 454 */ 455 protected Iterator createEntrySetIterator() { 456 return new ReferenceEntrySetIterator(this); 457 } 458 459 /** 460 * Creates an key set iterator. 461 * 462 * @return the keySet iterator 463 */ 464 protected Iterator createKeySetIterator() { 465 return new ReferenceKeySetIterator(this); 466 } 467 468 /** 469 * Creates an values iterator. 470 * 471 * @return the values iterator 472 */ 473 protected Iterator createValuesIterator() { 474 return new ReferenceValuesIterator(this); 475 } 476 477 //----------------------------------------------------------------------- 478 /** 479 * EntrySet implementation. 480 */ 481 static class ReferenceEntrySet extends EntrySet { 482 483 protected ReferenceEntrySet(AbstractHashedMap parent) { 484 super(parent); 485 } 486 487 public Object[] toArray() { 488 return toArray(new Object[0]); 489 } 490 491 public Object[] toArray(Object[] arr) { 492 // special implementation to handle disappearing entries 493 ArrayList list = new ArrayList(); 494 Iterator iterator = iterator(); 495 while (iterator.hasNext()) { 496 Entry e = (Entry) iterator.next(); 497 list.add(new DefaultMapEntry(e.getKey(), e.getValue())); 498 } 499 return list.toArray(arr); 500 } 501 } 502 503 //----------------------------------------------------------------------- 504 /** 505 * KeySet implementation. 506 */ 507 static class ReferenceKeySet extends KeySet { 508 509 protected ReferenceKeySet(AbstractHashedMap parent) { 510 super(parent); 511 } 512 513 public Object[] toArray() { 514 return toArray(new Object[0]); 515 } 516 517 public Object[] toArray(Object[] arr) { 518 // special implementation to handle disappearing keys 519 List list = new ArrayList(parent.size()); 520 for (Iterator it = iterator(); it.hasNext(); ) { 521 list.add(it.next()); 522 } 523 return list.toArray(arr); 524 } 525 } 526 527 //----------------------------------------------------------------------- 528 /** 529 * Values implementation. 530 */ 531 static class ReferenceValues extends Values { 532 533 protected ReferenceValues(AbstractHashedMap parent) { 534 super(parent); 535 } 536 537 public Object[] toArray() { 538 return toArray(new Object[0]); 539 } 540 541 public Object[] toArray(Object[] arr) { 542 // special implementation to handle disappearing values 543 List list = new ArrayList(parent.size()); 544 for (Iterator it = iterator(); it.hasNext(); ) { 545 list.add(it.next()); 546 } 547 return list.toArray(arr); 548 } 549 } 550 551 //----------------------------------------------------------------------- 552 /** 553 * A MapEntry implementation for the map. 554 * <p> 555 * If getKey() or getValue() returns null, it means 556 * the mapping is stale and should be removed. 557 * 558 * @since Commons Collections 3.1 559 */ 560 protected static class ReferenceEntry extends HashEntry { 561 /** The parent map */ 562 protected final AbstractReferenceMap parent; 563 564 /** 565 * Creates a new entry object for the ReferenceMap. 566 * 567 * @param parent the parent map 568 * @param next the next entry in the hash bucket 569 * @param hashCode the hash code of the key 570 * @param key the key 571 * @param value the value 572 */ 573 public ReferenceEntry(AbstractReferenceMap parent, HashEntry next, int hashCode, Object key, Object value) { 574 super(next, hashCode, null, null); 575 this.parent = parent; 576 this.key = toReference(parent.keyType, key, hashCode); 577 this.value = toReference(parent.valueType, value, hashCode); // the key hashCode is passed in deliberately 578 } 579 580 /** 581 * Gets the key from the entry. 582 * This method dereferences weak and soft keys and thus may return null. 583 * 584 * @return the key, which may be null if it was garbage collected 585 */ 586 public Object getKey() { 587 return (parent.keyType > HARD) ? ((Reference) key).get() : key; 588 } 589 590 /** 591 * Gets the value from the entry. 592 * This method dereferences weak and soft value and thus may return null. 593 * 594 * @return the value, which may be null if it was garbage collected 595 */ 596 public Object getValue() { 597 return (parent.valueType > HARD) ? ((Reference) value).get() : value; 598 } 599 600 /** 601 * Sets the value of the entry. 602 * 603 * @param obj the object to store 604 * @return the previous value 605 */ 606 public Object setValue(Object obj) { 607 Object old = getValue(); 608 if (parent.valueType > HARD) { 609 ((Reference)value).clear(); 610 } 611 value = toReference(parent.valueType, obj, hashCode); 612 return old; 613 } 614 615 /** 616 * Compares this map entry to another. 617 * <p> 618 * This implementation uses <code>isEqualKey</code> and 619 * <code>isEqualValue</code> on the main map for comparison. 620 * 621 * @param obj the other map entry to compare to 622 * @return true if equal, false if not 623 */ 624 public boolean equals(Object obj) { 625 if (obj == this) { 626 return true; 627 } 628 if (obj instanceof Map.Entry == false) { 629 return false; 630 } 631 632 Map.Entry entry = (Map.Entry)obj; 633 Object entryKey = entry.getKey(); // convert to hard reference 634 Object entryValue = entry.getValue(); // convert to hard reference 635 if ((entryKey == null) || (entryValue == null)) { 636 return false; 637 } 638 // compare using map methods, aiding identity subclass 639 // note that key is direct access and value is via method 640 return parent.isEqualKey(entryKey, key) && 641 parent.isEqualValue(entryValue, getValue()); 642 } 643 644 /** 645 * Gets the hashcode of the entry using temporary hard references. 646 * <p> 647 * This implementation uses <code>hashEntry</code> on the main map. 648 * 649 * @return the hashcode of the entry 650 */ 651 public int hashCode() { 652 return parent.hashEntry(getKey(), getValue()); 653 } 654 655 /** 656 * Constructs a reference of the given type to the given referent. 657 * The reference is registered with the queue for later purging. 658 * 659 * @param type HARD, SOFT or WEAK 660 * @param referent the object to refer to 661 * @param hash the hash code of the <i>key</i> of the mapping; 662 * this number might be different from referent.hashCode() if 663 * the referent represents a value and not a key 664 */ 665 protected Object toReference(int type, Object referent, int hash) { 666 switch (type) { 667 case HARD: return referent; 668 case SOFT: return new SoftRef(hash, referent, parent.queue); 669 case WEAK: return new WeakRef(hash, referent, parent.queue); 670 default: throw new Error(); 671 } 672 } 673 674 /** 675 * Purges the specified reference 676 * @param ref the reference to purge 677 * @return true or false 678 */ 679 boolean purge(Reference ref) { 680 boolean r = (parent.keyType > HARD) && (key == ref); 681 r = r || ((parent.valueType > HARD) && (value == ref)); 682 if (r) { 683 if (parent.keyType > HARD) { 684 ((Reference)key).clear(); 685 } 686 if (parent.valueType > HARD) { 687 ((Reference)value).clear(); 688 } else if (parent.purgeValues) { 689 value = null; 690 } 691 } 692 return r; 693 } 694 695 /** 696 * Gets the next entry in the bucket. 697 * 698 * @return the next entry in the bucket 699 */ 700 protected ReferenceEntry next() { 701 return (ReferenceEntry) next; 702 } 703 } 704 705 //----------------------------------------------------------------------- 706 /** 707 * The EntrySet iterator. 708 */ 709 static class ReferenceEntrySetIterator implements Iterator { 710 /** The parent map */ 711 final AbstractReferenceMap parent; 712 713 // These fields keep track of where we are in the table. 714 int index; 715 ReferenceEntry entry; 716 ReferenceEntry previous; 717 718 // These Object fields provide hard references to the 719 // current and next entry; this assures that if hasNext() 720 // returns true, next() will actually return a valid element. 721 Object nextKey, nextValue; 722 Object currentKey, currentValue; 723 724 int expectedModCount; 725 726 public ReferenceEntrySetIterator(AbstractReferenceMap parent) { 727 super(); 728 this.parent = parent; 729 index = (parent.size() != 0 ? parent.data.length : 0); 730 // have to do this here! size() invocation above 731 // may have altered the modCount. 732 expectedModCount = parent.modCount; 733 } 734 735 public boolean hasNext() { 736 checkMod(); 737 while (nextNull()) { 738 ReferenceEntry e = entry; 739 int i = index; 740 while ((e == null) && (i > 0)) { 741 i--; 742 e = (ReferenceEntry) parent.data[i]; 743 } 744 entry = e; 745 index = i; 746 if (e == null) { 747 currentKey = null; 748 currentValue = null; 749 return false; 750 } 751 nextKey = e.getKey(); 752 nextValue = e.getValue(); 753 if (nextNull()) { 754 entry = entry.next(); 755 } 756 } 757 return true; 758 } 759 760 private void checkMod() { 761 if (parent.modCount != expectedModCount) { 762 throw new ConcurrentModificationException(); 763 } 764 } 765 766 private boolean nextNull() { 767 return (nextKey == null) || (nextValue == null); 768 } 769 770 protected ReferenceEntry nextEntry() { 771 checkMod(); 772 if (nextNull() && !hasNext()) { 773 throw new NoSuchElementException(); 774 } 775 previous = entry; 776 entry = entry.next(); 777 currentKey = nextKey; 778 currentValue = nextValue; 779 nextKey = null; 780 nextValue = null; 781 return previous; 782 } 783 784 protected ReferenceEntry currentEntry() { 785 checkMod(); 786 return previous; 787 } 788 789 public Object next() { 790 return nextEntry(); 791 } 792 793 public void remove() { 794 checkMod(); 795 if (previous == null) { 796 throw new IllegalStateException(); 797 } 798 parent.remove(currentKey); 799 previous = null; 800 currentKey = null; 801 currentValue = null; 802 expectedModCount = parent.modCount; 803 } 804 } 805 806 /** 807 * The keySet iterator. 808 */ 809 static class ReferenceKeySetIterator extends ReferenceEntrySetIterator { 810 811 ReferenceKeySetIterator(AbstractReferenceMap parent) { 812 super(parent); 813 } 814 815 public Object next() { 816 return nextEntry().getKey(); 817 } 818 } 819 820 /** 821 * The values iterator. 822 */ 823 static class ReferenceValuesIterator extends ReferenceEntrySetIterator { 824 825 ReferenceValuesIterator(AbstractReferenceMap parent) { 826 super(parent); 827 } 828 829 public Object next() { 830 return nextEntry().getValue(); 831 } 832 } 833 834 /** 835 * The MapIterator implementation. 836 */ 837 static class ReferenceMapIterator extends ReferenceEntrySetIterator implements MapIterator { 838 839 protected ReferenceMapIterator(AbstractReferenceMap parent) { 840 super(parent); 841 } 842 843 public Object next() { 844 return nextEntry().getKey(); 845 } 846 847 public Object getKey() { 848 HashEntry current = currentEntry(); 849 if (current == null) { 850 throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID); 851 } 852 return current.getKey(); 853 } 854 855 public Object getValue() { 856 HashEntry current = currentEntry(); 857 if (current == null) { 858 throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID); 859 } 860 return current.getValue(); 861 } 862 863 public Object setValue(Object value) { 864 HashEntry current = currentEntry(); 865 if (current == null) { 866 throw new IllegalStateException(AbstractHashedMap.SETVALUE_INVALID); 867 } 868 return current.setValue(value); 869 } 870 } 871 872 //----------------------------------------------------------------------- 873 // These two classes store the hashCode of the key of 874 // of the mapping, so that after they're dequeued a quick 875 // lookup of the bucket in the table can occur. 876 877 /** 878 * A soft reference holder. 879 */ 880 static class SoftRef extends SoftReference { 881 /** the hashCode of the key (even if the reference points to a value) */ 882 private int hash; 883 884 public SoftRef(int hash, Object r, ReferenceQueue q) { 885 super(r, q); 886 this.hash = hash; 887 } 888 889 public int hashCode() { 890 return hash; 891 } 892 } 893 894 /** 895 * A weak reference holder. 896 */ 897 static class WeakRef extends WeakReference { 898 /** the hashCode of the key (even if the reference points to a value) */ 899 private int hash; 900 901 public WeakRef(int hash, Object r, ReferenceQueue q) { 902 super(r, q); 903 this.hash = hash; 904 } 905 906 public int hashCode() { 907 return hash; 908 } 909 } 910 911 //----------------------------------------------------------------------- 912 /** 913 * Replaces the superclass method to store the state of this class. 914 * <p> 915 * Serialization is not one of the JDK's nicest topics. Normal serialization will 916 * initialise the superclass before the subclass. Sometimes however, this isn't 917 * what you want, as in this case the <code>put()</code> method on read can be 918 * affected by subclass state. 919 * <p> 920 * The solution adopted here is to serialize the state data of this class in 921 * this protected method. This method must be called by the 922 * <code>writeObject()</code> of the first serializable subclass. 923 * <p> 924 * Subclasses may override if they have a specific field that must be present 925 * on read before this implementation will work. Generally, the read determines 926 * what must be serialized here, if anything. 927 * 928 * @param out the output stream 929 */ 930 protected void doWriteObject(ObjectOutputStream out) throws IOException { 931 out.writeInt(keyType); 932 out.writeInt(valueType); 933 out.writeBoolean(purgeValues); 934 out.writeFloat(loadFactor); 935 out.writeInt(data.length); 936 for (MapIterator it = mapIterator(); it.hasNext();) { 937 out.writeObject(it.next()); 938 out.writeObject(it.getValue()); 939 } 940 out.writeObject(null); // null terminate map 941 // do not call super.doWriteObject() as code there doesn't work for reference map 942 } 943 944 /** 945 * Replaces the superclassm method to read the state of this class. 946 * <p> 947 * Serialization is not one of the JDK's nicest topics. Normal serialization will 948 * initialise the superclass before the subclass. Sometimes however, this isn't 949 * what you want, as in this case the <code>put()</code> method on read can be 950 * affected by subclass state. 951 * <p> 952 * The solution adopted here is to deserialize the state data of this class in 953 * this protected method. This method must be called by the 954 * <code>readObject()</code> of the first serializable subclass. 955 * <p> 956 * Subclasses may override if the subclass has a specific field that must be present 957 * before <code>put()</code> or <code>calculateThreshold()</code> will work correctly. 958 * 959 * @param in the input stream 960 */ 961 protected void doReadObject(ObjectInputStream in) throws IOException, ClassNotFoundException { 962 this.keyType = in.readInt(); 963 this.valueType = in.readInt(); 964 this.purgeValues = in.readBoolean(); 965 this.loadFactor = in.readFloat(); 966 int capacity = in.readInt(); 967 init(); 968 data = new HashEntry[capacity]; 969 while (true) { 970 Object key = in.readObject(); 971 if (key == null) { 972 break; 973 } 974 Object value = in.readObject(); 975 put(key, value); 976 } 977 threshold = calculateThreshold(data.length, loadFactor); 978 // do not call super.doReadObject() as code there doesn't work for reference map 979 } 980 981 }