001: /*
002: * Copyright 2001-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;
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.AbstractCollection;
026: import java.util.AbstractMap;
027: import java.util.AbstractSet;
028: import java.util.ArrayList;
029: import java.util.Arrays;
030: import java.util.Collection;
031: import java.util.ConcurrentModificationException;
032: import java.util.Iterator;
033: import java.util.Map;
034: import java.util.NoSuchElementException;
035: import java.util.Set;
036:
037: import org.apache.commons.collections.keyvalue.DefaultMapEntry;
038:
039: /**
040: * Hash-based {@link Map} implementation that allows
041: * mappings to be removed by the garbage collector.<p>
042: *
043: * When you construct a <code>ReferenceMap</code>, you can
044: * specify what kind of references are used to store the
045: * map's keys and values. If non-hard references are
046: * used, then the garbage collector can remove mappings
047: * if a key or value becomes unreachable, or if the
048: * JVM's memory is running low. For information on how
049: * the different reference types behave, see
050: * {@link Reference}.<p>
051: *
052: * Different types of references can be specified for keys
053: * and values. The keys can be configured to be weak but
054: * the values hard, in which case this class will behave
055: * like a <a href="http://java.sun.com/j2se/1.4/docs/api/java/util/WeakHashMap.html">
056: * <code>WeakHashMap</code></a>. However, you
057: * can also specify hard keys and weak values, or any other
058: * combination. The default constructor uses hard keys
059: * and soft values, providing a memory-sensitive cache.<p>
060: *
061: * The algorithms used are basically the same as those
062: * in {@link java.util.HashMap}. In particular, you
063: * can specify a load factor and capacity to suit your
064: * needs. All optional {@link Map} operations are
065: * supported.<p>
066: *
067: * However, this {@link Map} implementation does <I>not</I>
068: * allow null elements. Attempting to add a null key or
069: * or a null value to the map will raise a
070: * <code>NullPointerException</code>.<p>
071: *
072: * As usual, this implementation is not synchronized. You
073: * can use {@link java.util.Collections#synchronizedMap} to
074: * provide synchronized access to a <code>ReferenceMap</code>.
075: *
076: * @see java.lang.ref.Reference
077: *
078: * @deprecated Moved to map subpackage. Due to be removed in v4.0.
079: * @since Commons Collections 2.1
080: * @version $Revision: 348444 $ $Date: 2005-11-23 14:06:56 +0000 (Wed, 23 Nov 2005) $
081: *
082: * @author Paul Jack
083: */
084: public class ReferenceMap extends AbstractMap {
085:
086: /**
087: * For serialization.
088: */
089: private static final long serialVersionUID = -3370601314380922368L;
090:
091: /**
092: * Constant indicating that hard references should be used.
093: */
094: final public static int HARD = 0;
095:
096: /**
097: * Constant indicating that soft references should be used.
098: */
099: final public static int SOFT = 1;
100:
101: /**
102: * Constant indicating that weak references should be used.
103: */
104: final public static int WEAK = 2;
105:
106: // --- serialized instance variables:
107:
108: /**
109: * The reference type for keys. Must be HARD, SOFT, WEAK.
110: * Note: I originally marked this field as final, but then this class
111: * didn't compile under JDK1.2.2.
112: * @serial
113: */
114: private int keyType;
115:
116: /**
117: * The reference type for values. Must be HARD, SOFT, WEAK.
118: * Note: I originally marked this field as final, but then this class
119: * didn't compile under JDK1.2.2.
120: * @serial
121: */
122: private int valueType;
123:
124: /**
125: * The threshold variable is calculated by multiplying
126: * table.length and loadFactor.
127: * Note: I originally marked this field as final, but then this class
128: * didn't compile under JDK1.2.2.
129: * @serial
130: */
131: private float loadFactor;
132:
133: /**
134: * Should the value be automatically purged when the associated key has been collected?
135: */
136: private boolean purgeValues = false;
137:
138: // -- Non-serialized instance variables
139:
140: /**
141: * ReferenceQueue used to eliminate stale mappings.
142: * See purge.
143: */
144: private transient ReferenceQueue queue = new ReferenceQueue();
145:
146: /**
147: * The hash table. Its length is always a power of two.
148: */
149: private transient Entry[] table;
150:
151: /**
152: * Number of mappings in this map.
153: */
154: private transient int size;
155:
156: /**
157: * When size reaches threshold, the map is resized.
158: * See resize().
159: */
160: private transient int threshold;
161:
162: /**
163: * Number of times this map has been modified.
164: */
165: private transient volatile int modCount;
166:
167: /**
168: * Cached key set. May be null if key set is never accessed.
169: */
170: private transient Set keySet;
171:
172: /**
173: * Cached entry set. May be null if entry set is never accessed.
174: */
175: private transient Set entrySet;
176:
177: /**
178: * Cached values. May be null if values() is never accessed.
179: */
180: private transient Collection values;
181:
182: /**
183: * Constructs a new <code>ReferenceMap</code> that will
184: * use hard references to keys and soft references to values.
185: */
186: public ReferenceMap() {
187: this (HARD, SOFT);
188: }
189:
190: /**
191: * Constructs a new <code>ReferenceMap</code> that will
192: * use the specified types of references.
193: *
194: * @param keyType the type of reference to use for keys;
195: * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK}
196: * @param valueType the type of reference to use for values;
197: * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK}
198: * @param purgeValues should the value be automatically purged when the
199: * key is garbage collected
200: */
201: public ReferenceMap(int keyType, int valueType, boolean purgeValues) {
202: this (keyType, valueType);
203: this .purgeValues = purgeValues;
204: }
205:
206: /**
207: * Constructs a new <code>ReferenceMap</code> that will
208: * use the specified types of references.
209: *
210: * @param keyType the type of reference to use for keys;
211: * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK}
212: * @param valueType the type of reference to use for values;
213: * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK}
214: */
215: public ReferenceMap(int keyType, int valueType) {
216: this (keyType, valueType, 16, 0.75f);
217: }
218:
219: /**
220: * Constructs a new <code>ReferenceMap</code> with the
221: * specified reference types, load factor and initial
222: * capacity.
223: *
224: * @param keyType the type of reference to use for keys;
225: * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK}
226: * @param valueType the type of reference to use for values;
227: * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK}
228: * @param capacity the initial capacity for the map
229: * @param loadFactor the load factor for the map
230: * @param purgeValues should the value be automatically purged when the
231: * key is garbage collected
232: */
233: public ReferenceMap(int keyType, int valueType, int capacity,
234: float loadFactor, boolean purgeValues) {
235: this (keyType, valueType, capacity, loadFactor);
236: this .purgeValues = purgeValues;
237: }
238:
239: /**
240: * Constructs a new <code>ReferenceMap</code> with the
241: * specified reference types, load factor and initial
242: * capacity.
243: *
244: * @param keyType the type of reference to use for keys;
245: * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK}
246: * @param valueType the type of reference to use for values;
247: * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK}
248: * @param capacity the initial capacity for the map
249: * @param loadFactor the load factor for the map
250: */
251: public ReferenceMap(int keyType, int valueType, int capacity,
252: float loadFactor) {
253: super ();
254:
255: verify("keyType", keyType);
256: verify("valueType", valueType);
257:
258: if (capacity <= 0) {
259: throw new IllegalArgumentException(
260: "capacity must be positive");
261: }
262: if ((loadFactor <= 0.0f) || (loadFactor >= 1.0f)) {
263: throw new IllegalArgumentException(
264: "Load factor must be greater than 0 and less than 1.");
265: }
266:
267: this .keyType = keyType;
268: this .valueType = valueType;
269:
270: int v = 1;
271: while (v < capacity)
272: v *= 2;
273:
274: this .table = new Entry[v];
275: this .loadFactor = loadFactor;
276: this .threshold = (int) (v * loadFactor);
277: }
278:
279: // used by constructor
280: private static void verify(String name, int type) {
281: if ((type < HARD) || (type > WEAK)) {
282: throw new IllegalArgumentException(name
283: + " must be HARD, SOFT, WEAK.");
284: }
285: }
286:
287: /**
288: * Writes this object to the given output stream.
289: *
290: * @param out the output stream to write to
291: * @throws IOException if the stream raises it
292: */
293: private void writeObject(ObjectOutputStream out) throws IOException {
294: out.defaultWriteObject();
295: out.writeInt(table.length);
296:
297: // Have to use null-terminated list because size might shrink
298: // during iteration
299:
300: for (Iterator iter = entrySet().iterator(); iter.hasNext();) {
301: Map.Entry entry = (Map.Entry) iter.next();
302: out.writeObject(entry.getKey());
303: out.writeObject(entry.getValue());
304: }
305: out.writeObject(null);
306: }
307:
308: /**
309: * Reads the contents of this object from the given input stream.
310: *
311: * @param inp the input stream to read from
312: * @throws IOException if the stream raises it
313: * @throws ClassNotFoundException if the stream raises it
314: */
315: private void readObject(ObjectInputStream inp) throws IOException,
316: ClassNotFoundException {
317: inp.defaultReadObject();
318: table = new Entry[inp.readInt()];
319: threshold = (int) (table.length * loadFactor);
320: queue = new ReferenceQueue();
321: Object key = inp.readObject();
322: while (key != null) {
323: Object value = inp.readObject();
324: put(key, value);
325: key = inp.readObject();
326: }
327: }
328:
329: /**
330: * Constructs a reference of the given type to the given
331: * referent. The reference is registered with the queue
332: * for later purging.
333: *
334: * @param type HARD, SOFT or WEAK
335: * @param referent the object to refer to
336: * @param hash the hash code of the <I>key</I> of the mapping;
337: * this number might be different from referent.hashCode() if
338: * the referent represents a value and not a key
339: */
340: private Object toReference(int type, Object referent, int hash) {
341: switch (type) {
342: case HARD:
343: return referent;
344: case SOFT:
345: return new SoftRef(hash, referent, queue);
346: case WEAK:
347: return new WeakRef(hash, referent, queue);
348: default:
349: throw new Error();
350: }
351: }
352:
353: /**
354: * Returns the entry associated with the given key.
355: *
356: * @param key the key of the entry to look up
357: * @return the entry associated with that key, or null
358: * if the key is not in this map
359: */
360: private Entry getEntry(Object key) {
361: if (key == null)
362: return null;
363: int hash = key.hashCode();
364: int index = indexFor(hash);
365: for (Entry entry = table[index]; entry != null; entry = entry.next) {
366: if ((entry.hash == hash) && key.equals(entry.getKey())) {
367: return entry;
368: }
369: }
370: return null;
371: }
372:
373: /**
374: * Converts the given hash code into an index into the
375: * hash table.
376: */
377: private int indexFor(int hash) {
378: // mix the bits to avoid bucket collisions...
379: hash += ~(hash << 15);
380: hash ^= (hash >>> 10);
381: hash += (hash << 3);
382: hash ^= (hash >>> 6);
383: hash += ~(hash << 11);
384: hash ^= (hash >>> 16);
385: return hash & (table.length - 1);
386: }
387:
388: /**
389: * Resizes this hash table by doubling its capacity.
390: * This is an expensive operation, as entries must
391: * be copied from the old smaller table to the new
392: * bigger table.
393: */
394: private void resize() {
395: Entry[] old = table;
396: table = new Entry[old.length * 2];
397:
398: for (int i = 0; i < old.length; i++) {
399: Entry next = old[i];
400: while (next != null) {
401: Entry entry = next;
402: next = next.next;
403: int index = indexFor(entry.hash);
404: entry.next = table[index];
405: table[index] = entry;
406: }
407: old[i] = null;
408: }
409: threshold = (int) (table.length * loadFactor);
410: }
411:
412: /**
413: * Purges stale mappings from this map.
414: * <p>
415: * Ordinarily, stale mappings are only removed during
416: * a write operation, although this method is called for both
417: * read and write operations to maintain a consistent state.
418: * <p>
419: * Note that this method is not synchronized! Special
420: * care must be taken if, for instance, you want stale
421: * mappings to be removed on a periodic basis by some
422: * background thread.
423: */
424: private void purge() {
425: Reference ref = queue.poll();
426: while (ref != null) {
427: purge(ref);
428: ref = queue.poll();
429: }
430: }
431:
432: private void purge(Reference ref) {
433: // The hashCode of the reference is the hashCode of the
434: // mapping key, even if the reference refers to the
435: // mapping value...
436: int hash = ref.hashCode();
437: int index = indexFor(hash);
438: Entry previous = null;
439: Entry entry = table[index];
440: while (entry != null) {
441: if (entry.purge(ref)) {
442: if (previous == null)
443: table[index] = entry.next;
444: else
445: previous.next = entry.next;
446: this .size--;
447: return;
448: }
449: previous = entry;
450: entry = entry.next;
451: }
452:
453: }
454:
455: /**
456: * Returns the size of this map.
457: *
458: * @return the size of this map
459: */
460: public int size() {
461: purge();
462: return size;
463: }
464:
465: /**
466: * Returns <code>true</code> if this map is empty.
467: *
468: * @return <code>true</code> if this map is empty
469: */
470: public boolean isEmpty() {
471: purge();
472: return size == 0;
473: }
474:
475: /**
476: * Returns <code>true</code> if this map contains the given key.
477: *
478: * @return true if the given key is in this map
479: */
480: public boolean containsKey(Object key) {
481: purge();
482: Entry entry = getEntry(key);
483: if (entry == null)
484: return false;
485: return entry.getValue() != null;
486: }
487:
488: /**
489: * Returns the value associated with the given key, if any.
490: *
491: * @return the value associated with the given key, or <code>null</code>
492: * if the key maps to no value
493: */
494: public Object get(Object key) {
495: purge();
496: Entry entry = getEntry(key);
497: if (entry == null)
498: return null;
499: return entry.getValue();
500: }
501:
502: /**
503: * Associates the given key with the given value.<p>
504: * Neither the key nor the value may be null.
505: *
506: * @param key the key of the mapping
507: * @param value the value of the mapping
508: * @return the last value associated with that key, or
509: * null if no value was associated with the key
510: * @throws NullPointerException if either the key or value
511: * is null
512: */
513: public Object put(Object key, Object value) {
514: if (key == null)
515: throw new NullPointerException("null keys not allowed");
516: if (value == null)
517: throw new NullPointerException("null values not allowed");
518:
519: purge();
520: if (size + 1 > threshold)
521: resize();
522:
523: int hash = key.hashCode();
524: int index = indexFor(hash);
525: Entry entry = table[index];
526: while (entry != null) {
527: if ((hash == entry.hash) && key.equals(entry.getKey())) {
528: Object result = entry.getValue();
529: entry.setValue(value);
530: return result;
531: }
532: entry = entry.next;
533: }
534: this .size++;
535: modCount++;
536: key = toReference(keyType, key, hash);
537: value = toReference(valueType, value, hash);
538: table[index] = new Entry(key, hash, value, table[index]);
539: return null;
540: }
541:
542: /**
543: * Removes the key and its associated value from this map.
544: *
545: * @param key the key to remove
546: * @return the value associated with that key, or null if
547: * the key was not in the map
548: */
549: public Object remove(Object key) {
550: if (key == null)
551: return null;
552: purge();
553: int hash = key.hashCode();
554: int index = indexFor(hash);
555: Entry previous = null;
556: Entry entry = table[index];
557: while (entry != null) {
558: if ((hash == entry.hash) && key.equals(entry.getKey())) {
559: if (previous == null)
560: table[index] = entry.next;
561: else
562: previous.next = entry.next;
563: this .size--;
564: modCount++;
565: return entry.getValue();
566: }
567: previous = entry;
568: entry = entry.next;
569: }
570: return null;
571: }
572:
573: /**
574: * Clears this map.
575: */
576: public void clear() {
577: Arrays.fill(table, null);
578: size = 0;
579: while (queue.poll() != null)
580: ; // drain the queue
581: }
582:
583: /**
584: * Returns a set view of this map's entries.
585: *
586: * @return a set view of this map's entries
587: */
588: public Set entrySet() {
589: if (entrySet != null) {
590: return entrySet;
591: }
592: entrySet = new AbstractSet() {
593: public int size() {
594: return ReferenceMap.this .size();
595: }
596:
597: public void clear() {
598: ReferenceMap.this .clear();
599: }
600:
601: public boolean contains(Object o) {
602: if (o == null)
603: return false;
604: if (!(o instanceof Map.Entry))
605: return false;
606: Map.Entry e = (Map.Entry) o;
607: Entry e2 = getEntry(e.getKey());
608: return (e2 != null) && e.equals(e2);
609: }
610:
611: public boolean remove(Object o) {
612: boolean r = contains(o);
613: if (r) {
614: Map.Entry e = (Map.Entry) o;
615: ReferenceMap.this .remove(e.getKey());
616: }
617: return r;
618: }
619:
620: public Iterator iterator() {
621: return new EntryIterator();
622: }
623:
624: public Object[] toArray() {
625: return toArray(new Object[0]);
626: }
627:
628: public Object[] toArray(Object[] arr) {
629: ArrayList list = new ArrayList();
630: Iterator iterator = iterator();
631: while (iterator.hasNext()) {
632: Entry e = (Entry) iterator.next();
633: list.add(new DefaultMapEntry(e.getKey(), e
634: .getValue()));
635: }
636: return list.toArray(arr);
637: }
638: };
639: return entrySet;
640: }
641:
642: /**
643: * Returns a set view of this map's keys.
644: *
645: * @return a set view of this map's keys
646: */
647: public Set keySet() {
648: if (keySet != null)
649: return keySet;
650: keySet = new AbstractSet() {
651: public int size() {
652: return ReferenceMap.this .size();
653: }
654:
655: public Iterator iterator() {
656: return new KeyIterator();
657: }
658:
659: public boolean contains(Object o) {
660: return containsKey(o);
661: }
662:
663: public boolean remove(Object o) {
664: Object r = ReferenceMap.this .remove(o);
665: return r != null;
666: }
667:
668: public void clear() {
669: ReferenceMap.this .clear();
670: }
671:
672: public Object[] toArray() {
673: return toArray(new Object[0]);
674: }
675:
676: public Object[] toArray(Object[] array) {
677: Collection c = new ArrayList(size());
678: for (Iterator it = iterator(); it.hasNext();) {
679: c.add(it.next());
680: }
681: return c.toArray(array);
682: }
683: };
684: return keySet;
685: }
686:
687: /**
688: * Returns a collection view of this map's values.
689: *
690: * @return a collection view of this map's values.
691: */
692: public Collection values() {
693: if (values != null)
694: return values;
695: values = new AbstractCollection() {
696: public int size() {
697: return ReferenceMap.this .size();
698: }
699:
700: public void clear() {
701: ReferenceMap.this .clear();
702: }
703:
704: public Iterator iterator() {
705: return new ValueIterator();
706: }
707:
708: public Object[] toArray() {
709: return toArray(new Object[0]);
710: }
711:
712: public Object[] toArray(Object[] array) {
713: Collection c = new ArrayList(size());
714: for (Iterator it = iterator(); it.hasNext();) {
715: c.add(it.next());
716: }
717: return c.toArray(array);
718: }
719: };
720: return values;
721: }
722:
723: // If getKey() or getValue() returns null, it means
724: // the mapping is stale and should be removed.
725: private class Entry implements Map.Entry, KeyValue {
726:
727: Object key;
728: Object value;
729: int hash;
730: Entry next;
731:
732: public Entry(Object key, int hash, Object value, Entry next) {
733: this .key = key;
734: this .hash = hash;
735: this .value = value;
736: this .next = next;
737: }
738:
739: public Object getKey() {
740: return (keyType > HARD) ? ((Reference) key).get() : key;
741: }
742:
743: public Object getValue() {
744: return (valueType > HARD) ? ((Reference) value).get()
745: : value;
746: }
747:
748: public Object setValue(Object object) {
749: Object old = getValue();
750: if (valueType > HARD)
751: ((Reference) value).clear();
752: value = toReference(valueType, object, hash);
753: return old;
754: }
755:
756: public boolean equals(Object o) {
757: if (o == null)
758: return false;
759: if (o == this )
760: return true;
761: if (!(o instanceof Map.Entry))
762: return false;
763:
764: Map.Entry entry = (Map.Entry) o;
765: Object key = entry.getKey();
766: Object value = entry.getValue();
767: if ((key == null) || (value == null))
768: return false;
769: return key.equals(getKey()) && value.equals(getValue());
770: }
771:
772: public int hashCode() {
773: Object v = getValue();
774: return hash ^ ((v == null) ? 0 : v.hashCode());
775: }
776:
777: public String toString() {
778: return getKey() + "=" + getValue();
779: }
780:
781: boolean purge(Reference ref) {
782: boolean r = (keyType > HARD) && (key == ref);
783: r = r || ((valueType > HARD) && (value == ref));
784: if (r) {
785: if (keyType > HARD)
786: ((Reference) key).clear();
787: if (valueType > HARD) {
788: ((Reference) value).clear();
789: } else if (purgeValues) {
790: value = null;
791: }
792: }
793: return r;
794: }
795: }
796:
797: private class EntryIterator implements Iterator {
798: // These fields keep track of where we are in the table.
799: int index;
800: Entry entry;
801: Entry previous;
802:
803: // These Object fields provide hard references to the
804: // current and next entry; this assures that if hasNext()
805: // returns true, next() will actually return a valid element.
806: Object nextKey, nextValue;
807: Object currentKey, currentValue;
808:
809: int expectedModCount;
810:
811: public EntryIterator() {
812: index = (size() != 0 ? table.length : 0);
813: // have to do this here! size() invocation above
814: // may have altered the modCount.
815: expectedModCount = modCount;
816: }
817:
818: public boolean hasNext() {
819: checkMod();
820: while (nextNull()) {
821: Entry e = entry;
822: int i = index;
823: while ((e == null) && (i > 0)) {
824: i--;
825: e = table[i];
826: }
827: entry = e;
828: index = i;
829: if (e == null) {
830: currentKey = null;
831: currentValue = null;
832: return false;
833: }
834: nextKey = e.getKey();
835: nextValue = e.getValue();
836: if (nextNull())
837: entry = entry.next;
838: }
839: return true;
840: }
841:
842: private void checkMod() {
843: if (modCount != expectedModCount) {
844: throw new ConcurrentModificationException();
845: }
846: }
847:
848: private boolean nextNull() {
849: return (nextKey == null) || (nextValue == null);
850: }
851:
852: protected Entry nextEntry() {
853: checkMod();
854: if (nextNull() && !hasNext())
855: throw new NoSuchElementException();
856: previous = entry;
857: entry = entry.next;
858: currentKey = nextKey;
859: currentValue = nextValue;
860: nextKey = null;
861: nextValue = null;
862: return previous;
863: }
864:
865: public Object next() {
866: return nextEntry();
867: }
868:
869: public void remove() {
870: checkMod();
871: if (previous == null)
872: throw new IllegalStateException();
873: ReferenceMap.this .remove(currentKey);
874: previous = null;
875: currentKey = null;
876: currentValue = null;
877: expectedModCount = modCount;
878: }
879:
880: }
881:
882: private class ValueIterator extends EntryIterator {
883: public Object next() {
884: return nextEntry().getValue();
885: }
886: }
887:
888: private class KeyIterator extends EntryIterator {
889: public Object next() {
890: return nextEntry().getKey();
891: }
892: }
893:
894: // These two classes store the hashCode of the key of
895: // of the mapping, so that after they're dequeued a quick
896: // lookup of the bucket in the table can occur.
897:
898: private static class SoftRef extends SoftReference {
899: private int hash;
900:
901: public SoftRef(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: private static class WeakRef extends WeakReference {
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: }
|