scolebourne 2003/09/27 03:07:14
Modified: collections/src/java/org/apache/commons/collections
CursorableLinkedList.java
collections/src/test/org/apache/commons/collections
TestCursorableLinkedList.java
Log:
Change CursorableLinkedList to use weak references to avoid memory leaks
from Simon Kitching
Revision Changes Path
1.18 +72 -19
jakarta-commons/collections/src/java/org/apache/commons/collections/CursorableLinkedList.java
Index: CursorableLinkedList.java
===================================================================
RCS file:
/home/cvs/jakarta-commons/collections/src/java/org/apache/commons/collections/CursorableLinkedList.java,v
retrieving revision 1.17
retrieving revision 1.18
diff -u -r1.17 -r1.18
--- CursorableLinkedList.java 20 Sep 2003 14:03:57 -0000 1.17
+++ CursorableLinkedList.java 27 Sep 2003 10:07:14 -0000 1.18
@@ -69,6 +69,7 @@
import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;
+import java.lang.ref.WeakReference;
/**
* A doubly-linked list implementation of the [EMAIL PROTECTED] List} interface,
@@ -88,10 +89,9 @@
*
* @author Rodney Waldhoff
* @author Janek Bogucki
+ * @author Simon Kitching
*/
public class CursorableLinkedList implements List, Serializable {
- // TODO: use weak references to cursors in case they aren't closed directly
-
/** Ensure serialization compatability */
private static final long serialVersionUID = 8836393098519411393L;
@@ -304,11 +304,9 @@
* [EMAIL PROTECTED] ListIterator#nextIndex} and [EMAIL PROTECTED]
ListIterator#previousIndex}
* methods (they throw [EMAIL PROTECTED] UnsupportedOperationException} when
invoked.
* <p>
- * Clients must close the cursor when they are done using it.
- * The returned [EMAIL PROTECTED] ListIterator} will be an instance of
- * [EMAIL PROTECTED] CursorableLinkedList.Cursor}. To close the cursor,
- * cast the [EMAIL PROTECTED] ListIterator} to [EMAIL PROTECTED]
CursorableLinkedList.Cursor}
- * and invoke the [EMAIL PROTECTED] CursorableLinkedList.Cursor#close} method.
+ * Historical Note: In previous versions of this class, the object
+ * returned from this method was required to be explicitly closed. This
+ * is no longer necessary.
*
* @see #cursor(int)
* @see #listIterator
@@ -839,7 +837,16 @@
* of changes to this list.
*/
protected void registerCursor(Cursor cur) {
- _cursors.add(cur);
+ // We take this opportunity to clean the _cursors list
+ // of WeakReference objects to garbage-collected cursors.
+ for (Iterator it = _cursors.iterator(); it.hasNext(); ) {
+ WeakReference ref = (WeakReference) it.next();
+ if (ref.get() == null) {
+ it.remove();
+ }
+ }
+
+ _cursors.add( new WeakReference(cur) );
}
/**
@@ -847,7 +854,21 @@
* the set of cursors to be notified of changes to this list.
*/
protected void unregisterCursor(Cursor cur) {
- _cursors.remove(cur);
+ for (Iterator it = _cursors.iterator(); it.hasNext(); ) {
+ WeakReference ref = (WeakReference) it.next();
+ Cursor cursor = (Cursor) ref.get();
+ if (cursor == null) {
+ // some other unrelated cursor object has been
+ // garbage-collected; let's take the opportunity to
+ // clean up the cursors list anyway..
+ it.remove();
+
+ } else if (cursor == cur) {
+ ref.clear();
+ it.remove();
+ break;
+ }
+ }
}
/**
@@ -856,8 +877,14 @@
*/
protected void invalidateCursors() {
Iterator it = _cursors.iterator();
- while(it.hasNext()) {
- ((Cursor)it.next()).invalidate();
+ while (it.hasNext()) {
+ WeakReference ref = (WeakReference) it.next();
+ Cursor cursor = (Cursor) ref.get();
+ if (cursor != null) {
+ // cursor is null if object has been garbage-collected
+ cursor.invalidate();
+ ref.clear();
+ }
it.remove();
}
}
@@ -869,8 +896,14 @@
*/
protected void broadcastListableChanged(Listable elt) {
Iterator it = _cursors.iterator();
- while(it.hasNext()) {
- ((Cursor)it.next()).listableChanged(elt);
+ while (it.hasNext()) {
+ WeakReference ref = (WeakReference) it.next();
+ Cursor cursor = (Cursor) ref.get();
+ if (cursor == null) {
+ it.remove(); // clean up list
+ } else {
+ cursor.listableChanged(elt);
+ }
}
}
@@ -880,8 +913,14 @@
*/
protected void broadcastListableRemoved(Listable elt) {
Iterator it = _cursors.iterator();
- while(it.hasNext()) {
- ((Cursor)it.next()).listableRemoved(elt);
+ while (it.hasNext()) {
+ WeakReference ref = (WeakReference) it.next();
+ Cursor cursor = (Cursor) ref.get();
+ if (cursor == null) {
+ it.remove(); // clean up list
+ } else {
+ cursor.listableRemoved(elt);
+ }
}
}
@@ -891,8 +930,14 @@
*/
protected void broadcastListableInserted(Listable elt) {
Iterator it = _cursors.iterator();
- while(it.hasNext()) {
- ((Cursor)it.next()).listableInserted(elt);
+ while (it.hasNext()) {
+ WeakReference ref = (WeakReference) it.next();
+ Cursor cursor = (Cursor) ref.get();
+ if (cursor == null) {
+ it.remove(); // clean up list
+ } else {
+ cursor.listableInserted(elt);
+ }
}
}
@@ -1171,6 +1216,14 @@
_valid = false;
}
+ /**
+ * Mark this cursor as no longer being needed. Any resources
+ * associated with this cursor are immediately released.
+ * In previous versions of this class, it was mandatory to close
+ * all cursor objects to avoid memory leaks. It is <i>no longer</i>
+ * necessary to call this close method; an instance of this class
+ * can now be treated exactly like a normal iterator.
+ */
public void close() {
if(_valid) {
_valid = false;
1.11 +80 -4
jakarta-commons/collections/src/test/org/apache/commons/collections/TestCursorableLinkedList.java
Index: TestCursorableLinkedList.java
===================================================================
RCS file:
/home/cvs/jakarta-commons/collections/src/test/org/apache/commons/collections/TestCursorableLinkedList.java,v
retrieving revision 1.10
retrieving revision 1.11
diff -u -r1.10 -r1.11
--- TestCursorableLinkedList.java 20 Sep 2003 14:03:57 -0000 1.10
+++ TestCursorableLinkedList.java 27 Sep 2003 10:07:14 -0000 1.11
@@ -70,7 +70,10 @@
import junit.framework.Test;
/**
+ * Test class.
+ *
* @author Rodney Waldhoff
+ * @author Simon Kitching
* @version $Id$
*/
public class TestCursorableLinkedList extends TestList {
@@ -334,6 +337,79 @@
it.close();
}
+ public void testCursorConcurrentModification() {
+ // this test verifies that cursors remain valid when the list
+ // is modified via other means.
+ list.add("1");
+ list.add("2");
+ list.add("3");
+ list.add("5");
+ list.add("7");
+ list.add("9");
+
+ CursorableLinkedList.Cursor c1 = list.cursor();
+ CursorableLinkedList.Cursor c2 = list.cursor();
+ ListIterator li = list.listIterator();
+
+ // test cursors remain valid when list modified by std ListIterator
+ // test cursors skip elements removed via ListIterator
+ assertEquals("1",li.next());
+ assertEquals("2",li.next());
+ li.remove();
+ assertEquals("3",li.next());
+ assertEquals("1",c1.next());
+ assertEquals("3",c1.next());
+ assertEquals("1",c2.next());
+
+ // test cursor c1 can remove elements from previously modified list
+ // test cursor c2 skips elements removed via different cursor
+ c1.remove();
+ assertEquals("5",c2.next());
+ c2.add("6");
+ assertEquals("5",c1.next());
+ assertEquals("6",c1.next());
+ assertEquals("7",c1.next());
+
+ // test cursors remain valid when list mod via CursorableLinkedList
+ // test cursor remains valid when elements inserted into list before
+ // the current position of the cursor.
+ list.add(0, "0");
+
+ // test cursor remains valid when element inserted immediately after
+ // current element of a cursor, and the element is seen on the
+ // next call to the next method of that cursor.
+ list.add(5, "8");
+
+ assertEquals("8",c1.next());
+ assertEquals("9",c1.next());
+ c1.add("10");
+ assertEquals("7",c2.next());
+ assertEquals("8",c2.next());
+ assertEquals("9",c2.next());
+ assertEquals("10",c2.next());
+
+ boolean nosuch = false;
+ try {
+ c2.next();
+ }
+ catch (java.util.NoSuchElementException nse) {
+ nosuch = true; // expected
+ }
+ assertTrue(nosuch);
+
+ boolean listIteratorInvalid = false;
+ try {
+ li.next();
+ }
+ catch(java.util.ConcurrentModificationException cme) {
+ listIteratorInvalid = true; // expected
+ }
+ assertTrue(listIteratorInvalid);
+
+ c1.close(); // not necessary
+ c2.close(); // not necessary
+ }
+
public void testEqualsAndHashCode() {
assertTrue(list.equals(list));
assertEquals(list.hashCode(),list.hashCode());
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]