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