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 }