diff --git a/data/test/ReferenceIdentityMap.emptyCollection.version3.1.obj b/data/test/ReferenceIdentityMap.emptyCollection.version3.1.obj
new file mode 100644
index 000000000..64ebde74b
Binary files /dev/null and b/data/test/ReferenceIdentityMap.emptyCollection.version3.1.obj differ
diff --git a/data/test/ReferenceIdentityMap.fullCollection.version3.1.obj b/data/test/ReferenceIdentityMap.fullCollection.version3.1.obj
new file mode 100644
index 000000000..6d42d87b0
Binary files /dev/null and b/data/test/ReferenceIdentityMap.fullCollection.version3.1.obj differ
diff --git a/src/java/org/apache/commons/collections/map/ReferenceIdentityMap.java b/src/java/org/apache/commons/collections/map/ReferenceIdentityMap.java
new file mode 100644
index 000000000..c9bb30d7c
--- /dev/null
+++ b/src/java/org/apache/commons/collections/map/ReferenceIdentityMap.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright 2004 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.collections.map;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.lang.ref.Reference;
+
+/**
+ * A Map
implementation that allows mappings to be
+ * removed by the garbage collector and matches keys and values based
+ * on ==
not equals()
.
+ *
+ *
+ * When you construct a ReferenceIdentityMap
, you can specify what kind
+ * of references are used to store the map's keys and values.
+ * If non-hard references are used, then the garbage collector can remove
+ * mappings if a key or value becomes unreachable, or if the JVM's memory is
+ * running low. For information on how the different reference types behave,
+ * see {@link Reference}.
+ *
+ * Different types of references can be specified for keys and values. + * The default constructor uses hard keys and soft values, providing a + * memory-sensitive cache. + *
+ * This map is similar to
+ * {@link org.apache.commons.collections.map.ReferenceMap ReferenceMap}.
+ * It differs in that keys and values in this class are compared using ==
.
+ *
+ * This map will violate the detail of various Map and map view contracts. + * As a general rule, don't compare this map to other maps. + *
+ * This {@link Map} implementation does not allow null elements.
+ * Attempting to add a null key or value to the map will raise a NullPointerException
.
+ *
+ * This implementation is not synchronized.
+ * You can use {@link java.util.Collections#synchronizedMap} to
+ * provide synchronized access to a ReferenceIdentityMap
.
+ * Remember that synchronization will not stop the garbage collecter removing entries.
+ *
+ * All the available iterators can be reset back to the start by casting to
+ * ResettableIterator
and calling reset()
.
+ *
+ * @see java.lang.ref.Reference
+ *
+ * @since Commons Collections 3.0 (previously in main package v2.1)
+ * @version $Revision: 1.1 $ $Date: 2004/04/27 21:37:32 $
+ *
+ * @author Stephen Colebourne
+ */
+public class ReferenceIdentityMap extends AbstractReferenceMap implements Serializable {
+
+ /** Serialization version */
+ private static final long serialVersionUID = -1266190134568365852L;
+
+ /**
+ * Constructs a new ReferenceIdentityMap
that will
+ * use hard references to keys and soft references to values.
+ */
+ public ReferenceIdentityMap() {
+ super(HARD, SOFT, DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR, false);
+ }
+
+ /**
+ * Constructs a new ReferenceIdentityMap
that will
+ * use the specified types of references.
+ *
+ * @param keyType the type of reference to use for keys;
+ * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK}
+ * @param valueType the type of reference to use for values;
+ * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK}
+ */
+ public ReferenceIdentityMap(int keyType, int valueType) {
+ super(keyType, valueType, DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR, false);
+ }
+
+ /**
+ * Constructs a new ReferenceIdentityMap
that will
+ * use the specified types of references.
+ *
+ * @param keyType the type of reference to use for keys;
+ * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK}
+ * @param valueType the type of reference to use for values;
+ * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK}
+ * @param purgeValues should the value be automatically purged when the
+ * key is garbage collected
+ */
+ public ReferenceIdentityMap(int keyType, int valueType, boolean purgeValues) {
+ super(keyType, valueType, DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR, purgeValues);
+ }
+
+ /**
+ * Constructs a new ReferenceIdentityMap
with the
+ * specified reference types, load factor and initial capacity.
+ *
+ * @param keyType the type of reference to use for keys;
+ * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK}
+ * @param valueType the type of reference to use for values;
+ * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK}
+ * @param capacity the initial capacity for the map
+ * @param loadFactor the load factor for the map
+ */
+ public ReferenceIdentityMap(int keyType, int valueType, int capacity, float loadFactor) {
+ super(keyType, valueType, capacity, loadFactor, false);
+ }
+
+ /**
+ * Constructs a new ReferenceIdentityMap
with the
+ * specified reference types, load factor and initial capacity.
+ *
+ * @param keyType the type of reference to use for keys;
+ * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK}
+ * @param valueType the type of reference to use for values;
+ * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK}
+ * @param capacity the initial capacity for the map
+ * @param loadFactor the load factor for the map
+ * @param purgeValues should the value be automatically purged when the
+ * key is garbage collected
+ */
+ public ReferenceIdentityMap(int keyType, int valueType, int capacity,
+ float loadFactor, boolean purgeValues) {
+ super(keyType, valueType, capacity, loadFactor, purgeValues);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the hash code for the key specified.
+ *
+ * This implementation uses the identity hash code. + * + * @param key the key to get a hash code for + * @return the hash code + */ + protected int hash(Object key) { + return System.identityHashCode(key); + } + + /** + * Gets the hash code for a MapEntry. + *
+ * This implementation uses the identity hash code. + * + * @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 System.identityHashCode(key) ^ + System.identityHashCode(value); + } + + /** + * Compares two keys for equals. + *
+ * This implementation converts the key from the entry to a real reference
+ * before comparison and uses ==
.
+ *
+ * @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 by identity
+ */
+ protected boolean isEqualKey(Object key1, Object key2) {
+ key2 = (keyType > HARD ? ((Reference) key2).get() : key2);
+ return (key1 == key2);
+ }
+
+ /**
+ * Compares two values for equals.
+ *
+ * This implementation uses ==
.
+ *
+ * @param value1 the first value to compare passed in from outside
+ * @param value2 the second value extracted from the entry via getValue()
+ * @return true if equal by identity
+ */
+ protected boolean isEqualValue(Object value1, Object value2) {
+ return (value1 == value2);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Write the map out using a custom routine.
+ */
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ out.defaultWriteObject();
+ doWriteObject(out);
+ }
+
+ /**
+ * Read the map in using a custom routine.
+ */
+ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ in.defaultReadObject();
+ doReadObject(in);
+ }
+
+}
diff --git a/src/test/org/apache/commons/collections/map/TestReferenceIdentityMap.java b/src/test/org/apache/commons/collections/map/TestReferenceIdentityMap.java
new file mode 100644
index 000000000..d9234937f
--- /dev/null
+++ b/src/test/org/apache/commons/collections/map/TestReferenceIdentityMap.java
@@ -0,0 +1,292 @@
+/*
+ * Copyright 2004 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.collections.map;
+
+import java.lang.ref.WeakReference;
+import java.util.Iterator;
+import java.util.Map;
+
+import junit.framework.Test;
+
+import org.apache.commons.collections.BulkTest;
+import org.apache.commons.collections.IterableMap;
+
+/**
+ * Tests for ReferenceIdentityMap.
+ *
+ * @version $Revision: 1.1 $
+ *
+ * @author Paul Jack
+ * @author Stephen Colebourne
+ */
+public class TestReferenceIdentityMap extends AbstractTestIterableMap {
+
+ private static final Integer I1A = new Integer(1);
+ private static final Integer I1B = new Integer(1);
+ private static final Integer I2A = new Integer(2);
+ private static final Integer I2B = new Integer(2);
+
+ public TestReferenceIdentityMap(String testName) {
+ super(testName);
+ }
+
+ public static Test suite() {
+ return BulkTest.makeSuite(TestReferenceIdentityMap.class);
+ }
+
+ public static void main(String args[]) {
+ String[] testCaseName = { TestReferenceIdentityMap.class.getName() };
+ junit.textui.TestRunner.main(testCaseName);
+ }
+
+ public Map makeEmptyMap() {
+ ReferenceIdentityMap map = new ReferenceIdentityMap(ReferenceIdentityMap.WEAK, ReferenceIdentityMap.WEAK);
+ return map;
+ }
+
+ public Map makeConfirmedMap() {
+ // Testing against another [collections] class generally isn't a good idea,
+ // but the alternative is a JDK1.4 dependency in the tests
+ return new IdentityMap();
+ }
+
+ public boolean isAllowNullKey() {
+ return false;
+ }
+
+ public boolean isAllowNullValue() {
+ return false;
+ }
+
+ //-----------------------------------------------------------------------
+ public void testBasics() {
+ IterableMap map = new ReferenceIdentityMap(ReferenceIdentityMap.HARD, ReferenceIdentityMap.HARD);
+ assertEquals(0, map.size());
+
+ map.put(I1A, I2A);
+ assertEquals(1, map.size());
+ assertSame(I2A, map.get(I1A));
+ assertSame(null, map.get(I1B));
+ assertEquals(true, map.containsKey(I1A));
+ assertEquals(false, map.containsKey(I1B));
+ assertEquals(true, map.containsValue(I2A));
+ assertEquals(false, map.containsValue(I2B));
+
+ map.put(I1A, I2B);
+ assertEquals(1, map.size());
+ assertSame(I2B, map.get(I1A));
+ assertSame(null, map.get(I1B));
+ assertEquals(true, map.containsKey(I1A));
+ assertEquals(false, map.containsKey(I1B));
+ assertEquals(false, map.containsValue(I2A));
+ assertEquals(true, map.containsValue(I2B));
+
+ map.put(I1B, I2B);
+ assertEquals(2, map.size());
+ assertSame(I2B, map.get(I1A));
+ assertSame(I2B, map.get(I1B));
+ assertEquals(true, map.containsKey(I1A));
+ assertEquals(true, map.containsKey(I1B));
+ assertEquals(false, map.containsValue(I2A));
+ assertEquals(true, map.containsValue(I2B));
+ }
+
+ //-----------------------------------------------------------------------
+ public void testHashEntry() {
+ IterableMap map = new ReferenceIdentityMap(ReferenceIdentityMap.HARD, ReferenceIdentityMap.HARD);
+
+ map.put(I1A, I2A);
+ map.put(I1B, I2A);
+
+ Map.Entry entry1 = (Map.Entry) map.entrySet().iterator().next();
+ Iterator it = map.entrySet().iterator();
+ Map.Entry entry2 = (Map.Entry) it.next();
+ Map.Entry entry3 = (Map.Entry) it.next();
+
+ assertEquals(true, entry1.equals(entry2));
+ assertEquals(true, entry2.equals(entry1));
+ assertEquals(false, entry1.equals(entry3));
+ }
+
+
+ //-----------------------------------------------------------------------
+ // Unfortunately, these tests all rely on System.gc(), which is
+ // not reliable across platforms. Not sure how to code the tests
+ // without using System.gc() though...
+ // They all passed on my platform though. :)
+/*
+ public void testPurge() {
+ ReferenceIdentityMap map = new ReferenceIdentityMap(ReferenceIdentityMap.WEAK, ReferenceIdentityMap.WEAK);
+ Object[] hard = new Object[10];
+ for (int i = 0; i < hard.length; i++) {
+ hard[i] = new Object();
+ map.put(hard[i], new Object());
+ }
+ System.gc();
+ assertTrue("map should be empty after purge of weak values", map.isEmpty());
+
+ for (int i = 0; i < hard.length; i++) {
+ map.put(new Object(), hard[i]);
+ }
+ System.gc();
+ assertTrue("map should be empty after purge of weak keys", map.isEmpty());
+
+ for (int i = 0; i < hard.length; i++) {
+ map.put(new Object(), hard[i]);
+ map.put(hard[i], new Object());
+ }
+
+ System.gc();
+ assertTrue("map should be empty after purge of weak keys and values", map.isEmpty());
+ }
+
+
+ public void testGetAfterGC() {
+ ReferenceIdentityMap map = new ReferenceIdentityMap(ReferenceIdentityMap.WEAK, ReferenceIdentityMap.WEAK);
+ for (int i = 0; i < 10; i++) {
+ map.put(new Integer(i), new Integer(i));
+ }
+
+ System.gc();
+ for (int i = 0; i < 10; i++) {
+ Integer I = new Integer(i);
+ assertTrue("map.containsKey should return false for GC'd element", !map.containsKey(I));
+ assertTrue("map.get should return null for GC'd element", map.get(I) == null);
+ }
+ }
+
+
+ public void testEntrySetIteratorAfterGC() {
+ ReferenceIdentityMap map = new ReferenceIdentityMap(ReferenceIdentityMap.WEAK, ReferenceIdentityMap.WEAK);
+ Object[] hard = new Object[10];
+ for (int i = 0; i < 10; i++) {
+ hard[i] = new Integer(10 + i);
+ map.put(new Integer(i), new Integer(i));
+ map.put(hard[i], hard[i]);
+ }
+
+ System.gc();
+ Iterator iterator = map.entrySet().iterator();
+ while (iterator.hasNext()) {
+ Map.Entry entry = (Map.Entry)iterator.next();
+ Integer key = (Integer)entry.getKey();
+ Integer value = (Integer)entry.getValue();
+ assertTrue("iterator should skip GC'd keys", key.intValue() >= 10);
+ assertTrue("iterator should skip GC'd values", value.intValue() >= 10);
+ }
+
+ }
+
+ public void testMapIteratorAfterGC() {
+ ReferenceIdentityMap map = new ReferenceIdentityMap(ReferenceIdentityMap.WEAK, ReferenceIdentityMap.WEAK);
+ Object[] hard = new Object[10];
+ for (int i = 0; i < 10; i++) {
+ hard[i] = new Integer(10 + i);
+ map.put(new Integer(i), new Integer(i));
+ map.put(hard[i], hard[i]);
+ }
+
+ System.gc();
+ MapIterator iterator = map.mapIterator();
+ while (iterator.hasNext()) {
+ Object key1 = iterator.next();
+ Integer key = (Integer) iterator.getKey();
+ Integer value = (Integer) iterator.getValue();
+ assertTrue("iterator keys should match", key == key1);
+ assertTrue("iterator should skip GC'd keys", key.intValue() >= 10);
+ assertTrue("iterator should skip GC'd values", value.intValue() >= 10);
+ }
+
+ }
+
+ public void testMapIteratorAfterGC2() {
+ ReferenceIdentityMap map = new ReferenceIdentityMap(ReferenceIdentityMap.WEAK, ReferenceIdentityMap.WEAK);
+ Object[] hard = new Object[10];
+ for (int i = 0; i < 10; i++) {
+ hard[i] = new Integer(10 + i);
+ map.put(new Integer(i), new Integer(i));
+ map.put(hard[i], hard[i]);
+ }
+
+ MapIterator iterator = map.mapIterator();
+ while (iterator.hasNext()) {
+ Object key1 = iterator.next();
+ System.gc();
+ Integer key = (Integer) iterator.getKey();
+ Integer value = (Integer) iterator.getValue();
+ assertTrue("iterator keys should match", key == key1);
+ assertTrue("iterator should skip GC'd keys", key.intValue() >= 10);
+ assertTrue("iterator should skip GC'd values", value.intValue() >= 10);
+ }
+
+ }
+*/
+/*
+ // Uncomment to create test files in /data/test
+ public void testCreateTestFiles() throws Exception {
+ ReferenceIdentityMap m = (ReferenceIdentityMap) makeEmptyMap();
+ writeExternalFormToDisk(m, getCanonicalEmptyCollectionName(m));
+ m = (ReferenceIdentityMap) makeFullMap();
+ writeExternalFormToDisk(m, getCanonicalFullCollectionName(m));
+ }
+*/
+
+
+ public String getCompatibilityVersion() {
+ return "3.1";
+ }
+
+ /** Tests whether purge values setting works */
+ public void testPurgeValues() throws Exception {
+ // many thanks to Juozas Baliuka for suggesting this method
+ Object key = new Object();
+ Object value = new Object();
+
+ WeakReference keyReference = new WeakReference(key);
+ WeakReference valueReference = new WeakReference(value);
+
+ Map testMap = new ReferenceIdentityMap(ReferenceIdentityMap.WEAK, ReferenceIdentityMap.HARD, true);
+ testMap.put(key, value);
+
+ assertEquals("In map", value, testMap.get(key));
+ assertNotNull("Weak reference released early (1)", keyReference.get());
+ assertNotNull("Weak reference released early (2)", valueReference.get());
+
+ // dereference strong references
+ key = null;
+ value = null;
+
+ int iterations = 0;
+ int bytz = 2;
+ while(true) {
+ System.gc();
+ if(iterations++ > 50){
+ fail("Max iterations reached before resource released.");
+ }
+ testMap.isEmpty();
+ if(
+ keyReference.get() == null &&
+ valueReference.get() == null) {
+ break;
+
+ } else {
+ // create garbage:
+ byte[] b = new byte[bytz];
+ bytz = bytz * 2;
+ }
+ }
+ }
+}