diff --git a/src/java/org/apache/commons/collections/map/AbstractReferenceMap.java b/src/java/org/apache/commons/collections/map/AbstractReferenceMap.java
index 7e665c98d..dfcbbe523 100644
--- a/src/java/org/apache/commons/collections/map/AbstractReferenceMap.java
+++ b/src/java/org/apache/commons/collections/map/AbstractReferenceMap.java
@@ -15,6 +15,9 @@
*/
package org.apache.commons.collections.map;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
@@ -72,8 +75,8 @@ import org.apache.commons.collections.keyvalue.DefaultMapEntry;
* provide synchronized access to a ReferenceMap
.
*
* @see java.lang.ref.Reference
- * @since Commons Collections 3.1 (from ReferenceMap in 3.0)
- * @version $Revision: 1.1 $ $Date: 2004/04/09 22:18:18 $
+ * @since Commons Collections 3.1 (extracted from ReferenceMap in 3.0)
+ * @version $Revision: 1.2 $ $Date: 2004/04/27 21:32:52 $
*
* @author Paul Jack
* @author Stephen Colebourne
@@ -93,24 +96,24 @@ public abstract class AbstractReferenceMap extends AbstractHashedMap {
* The reference type for keys. Must be HARD, SOFT, WEAK.
* @serial
*/
- private int keyType;
+ protected int keyType;
/**
* The reference type for values. Must be HARD, SOFT, WEAK.
* @serial
*/
- private int valueType;
+ protected int valueType;
/**
* Should the value be automatically purged when the associated key has been collected?
*/
- private boolean purgeValues;
+ protected boolean purgeValues;
/**
* ReferenceQueue used to eliminate stale mappings.
* See purge.
*/
- private transient ReferenceQueue queue = new ReferenceQueue();
+ private transient ReferenceQueue queue;
//-----------------------------------------------------------------------
/**
@@ -144,6 +147,13 @@ public abstract class AbstractReferenceMap extends AbstractHashedMap {
this.purgeValues = purgeValues;
}
+ /**
+ * Initialise this subclass during construction, cloning or deserialization.
+ */
+ protected void init() {
+ queue = new ReferenceQueue();
+ }
+
//-----------------------------------------------------------------------
/**
* Checks the type int is a valid value.
@@ -158,49 +168,6 @@ public abstract class AbstractReferenceMap extends AbstractHashedMap {
}
}
- //-----------------------------------------------------------------------
-// /**
-// * Writes this object to the given output stream.
-// *
-// * @param out the output stream to write to
-// * @throws IOException if the stream raises it
-// */
-// private void writeObject(ObjectOutputStream out) throws IOException {
-// out.defaultWriteObject();
-// out.writeInt(data.length);
-//
-// // Have to use null-terminated list because size might shrink
-// // during iteration
-//
-// for (Iterator iter = entrySet().iterator(); iter.hasNext();) {
-// Map.Entry entry = (Map.Entry)iter.next();
-// out.writeObject(entry.getKey());
-// out.writeObject(entry.getValue());
-// }
-// out.writeObject(null);
-// }
-//
-//
-// /**
-// * Reads the contents of this object from the given input stream.
-// *
-// * @param in the input stream to read from
-// * @throws IOException if the stream raises it
-// * @throws ClassNotFoundException if the stream raises it
-// */
-// private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
-// in.defaultReadObject();
-// data = new HashEntry[in.readInt()];
-// threshold = calculateThreshold(data.length, loadFactor);
-// queue = new ReferenceQueue();
-// Object key = in.readObject();
-// while (key != null) {
-// Object value = in.readObject();
-// put(key, value);
-// key = in.readObject();
-// }
-// }
-
//-----------------------------------------------------------------------
/**
* Gets the size of the map.
@@ -438,6 +405,19 @@ public abstract class AbstractReferenceMap extends AbstractHashedMap {
}
}
+ /**
+ * Gets the hash code for a MapEntry.
+ * Subclasses can override this, for example to use the identityHashCode.
+ *
+ * @param key the key to get a hash code for, may be null
+ * @param value the value to get a hash code for, may be null
+ * @return the hash code, as per the MapEntry specification
+ */
+ protected int hashEntry(Object key, Object value) {
+ return (key == null ? 0 : key.hashCode()) ^
+ (value == null ? 0 : value.hashCode());
+ }
+
/**
* Compares two keys, in internal converted form, to see if they are equal.
*
@@ -447,7 +427,6 @@ public abstract class AbstractReferenceMap extends AbstractHashedMap {
* @param key1 the first key to compare passed in from outside
* @param key2 the second key extracted from the entry via entry.key
* @return true if equal
- * @since Commons Collections 3.1
*/
protected boolean isEqualKey(Object key1, Object key2) {
key2 = (keyType > HARD ? ((Reference) key2).get() : key2);
@@ -462,7 +441,6 @@ public abstract class AbstractReferenceMap extends AbstractHashedMap {
* @param key the key to store
* @param value the value to store
* @return the newly created entry
- * @since Commons Collections 3.1
*/
protected HashEntry createEntry(HashEntry next, int hashCode, Object key, Object value) {
return new ReferenceEntry(this, next, hashCode, key, value);
@@ -472,7 +450,6 @@ public abstract class AbstractReferenceMap extends AbstractHashedMap {
* Creates an entry set iterator.
*
* @return the entrySet iterator
- * @since Commons Collections 3.1
*/
protected Iterator createEntrySetIterator() {
return new ReferenceEntrySetIterator(this);
@@ -482,7 +459,6 @@ public abstract class AbstractReferenceMap extends AbstractHashedMap {
* Creates an key set iterator.
*
* @return the keySet iterator
- * @since Commons Collections 3.1
*/
protected Iterator createKeySetIterator() {
return new ReferenceKeySetIterator(this);
@@ -492,7 +468,6 @@ public abstract class AbstractReferenceMap extends AbstractHashedMap {
* Creates an values iterator.
*
* @return the values iterator
- * @since Commons Collections 3.1
*/
protected Iterator createValuesIterator() {
return new ReferenceValuesIterator(this);
@@ -596,17 +571,35 @@ public abstract class AbstractReferenceMap extends AbstractHashedMap {
super(next, hashCode, null, null);
this.parent = parent;
this.key = toReference(parent.keyType, key, hashCode);
- this.value = toReference(parent.valueType, value, hashCode);
+ this.value = toReference(parent.valueType, value, hashCode); // the key hashCode is passed in deliberately
}
+ /**
+ * Gets the key from the entry.
+ * This method dereferences weak and soft keys and thus may return null.
+ *
+ * @return the key, which may be null if it was garbage collected
+ */
public Object getKey() {
return (parent.keyType > HARD) ? ((Reference) key).get() : key;
}
+ /**
+ * Gets the value from the entry.
+ * This method dereferences weak and soft value and thus may return null.
+ *
+ * @return the value, which may be null if it was garbage collected
+ */
public Object getValue() {
return (parent.valueType > HARD) ? ((Reference) value).get() : value;
}
+ /**
+ * Sets the value of the entry.
+ *
+ * @param obj the object to store
+ * @return the previous value
+ */
public Object setValue(Object obj) {
Object old = getValue();
if (parent.valueType > HARD) {
@@ -616,6 +609,15 @@ public abstract class AbstractReferenceMap extends AbstractHashedMap {
return old;
}
+ /**
+ * Compares this map entry to another.
+ *
+ * This implementation uses isEqualKey
and
+ * isEqualValue
on the main map for comparison.
+ *
+ * @param obj the other map entry to compare to
+ * @return true if equal, false if not
+ */
public boolean equals(Object obj) {
if (obj == this) {
return true;
@@ -625,12 +627,26 @@ public abstract class AbstractReferenceMap extends AbstractHashedMap {
}
Map.Entry entry = (Map.Entry)obj;
- Object key = entry.getKey();
- Object value = entry.getValue();
- if ((key == null) || (value == null)) {
+ Object entryKey = entry.getKey(); // convert to hard reference
+ Object entryValue = entry.getValue(); // convert to hard reference
+ if ((entryKey == null) || (entryValue == null)) {
return false;
}
- return key.equals(getKey()) && value.equals(getValue());
+ // compare using map methods, aiding identity subclass
+ // note that key is direct access and value is via method
+ return parent.isEqualKey(entryKey, key) &&
+ parent.isEqualValue(entryValue, getValue());
+ }
+
+ /**
+ * Gets the hashcode of the entry using temporary hard references.
+ *
+ * This implementation uses hashEntry
on the main map.
+ *
+ * @return the hashcode of the entry
+ */
+ public int hashCode() {
+ return parent.hashEntry(getKey(), getValue());
}
/**
@@ -642,7 +658,6 @@ public abstract class AbstractReferenceMap extends AbstractHashedMap {
* @param hash the hash code of the key of the mapping;
* this number might be different from referent.hashCode() if
* the referent represents a value and not a key
- * @since Commons Collections 3.1
*/
protected Object toReference(int type, Object referent, int hash) {
switch (type) {
@@ -860,6 +875,7 @@ public abstract class AbstractReferenceMap extends AbstractHashedMap {
* A soft reference holder.
*/
static class SoftRef extends SoftReference {
+ /** the hashCode of the key (even if the reference points to a value) */
private int hash;
public SoftRef(int hash, Object r, ReferenceQueue q) {
@@ -876,6 +892,7 @@ public abstract class AbstractReferenceMap extends AbstractHashedMap {
* A weak reference holder.
*/
static class WeakRef extends WeakReference {
+ /** the hashCode of the key (even if the reference points to a value) */
private int hash;
public WeakRef(int hash, Object r, ReferenceQueue q) {
@@ -888,5 +905,74 @@ public abstract class AbstractReferenceMap extends AbstractHashedMap {
}
}
+ //-----------------------------------------------------------------------
+ /**
+ * Replaces the superclass method to store the state of this class.
+ *
+ * Serialization is not one of the JDK's nicest topics. Normal serialization will
+ * initialise the superclass before the subclass. Sometimes however, this isn't
+ * what you want, as in this case the put()
method on read can be
+ * affected by subclass state.
+ *
+ * The solution adopted here is to serialize the state data of this class in
+ * this protected method. This method must be called by the
+ * writeObject()
of the first serializable subclass.
+ *
+ * Subclasses may override if they have a specific field that must be present + * on read before this implementation will work. Generally, the read determines + * what must be serialized here, if anything. + * + * @param out the output stream + */ + protected void doWriteObject(ObjectOutputStream out) throws IOException { + out.writeInt(keyType); + out.writeInt(valueType); + out.writeBoolean(purgeValues); + out.writeFloat(loadFactor); + out.writeInt(data.length); + for (MapIterator it = mapIterator(); it.hasNext();) { + out.writeObject(it.next()); + out.writeObject(it.getValue()); + } + out.writeObject(null); // null terminate map + // do not call super.doWriteObject() as code there doesn't work for reference map + } + + /** + * Replaces the superclassm method to read the state of this class. + *
+ * Serialization is not one of the JDK's nicest topics. Normal serialization will
+ * initialise the superclass before the subclass. Sometimes however, this isn't
+ * what you want, as in this case the put()
method on read can be
+ * affected by subclass state.
+ *
+ * The solution adopted here is to deserialize the state data of this class in
+ * this protected method. This method must be called by the
+ * readObject()
of the first serializable subclass.
+ *
+ * Subclasses may override if the subclass has a specific field that must be present
+ * before put()
or calculateThreshold()
will work correctly.
+ *
+ * @param in the input stream
+ */
+ protected void doReadObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ this.keyType = in.readInt();
+ this.valueType = in.readInt();
+ this.purgeValues = in.readBoolean();
+ this.loadFactor = in.readFloat();
+ int capacity = in.readInt();
+ init();
+ data = new HashEntry[capacity];
+ while (true) {
+ Object key = in.readObject();
+ if (key == null) {
+ break;
+ }
+ Object value = in.readObject();
+ put(key, value);
+ }
+ threshold = calculateThreshold(data.length, loadFactor);
+ // do not call super.doReadObject() as code there doesn't work for reference map
+ }
}