Fixed to have SequencedHashMap throw a ConcurrentModificationException

from its iterators if the map is modified through something other than
the iterator.


git-svn-id: https://svn.apache.org/repos/asf/jakarta/commons/proper/collections/trunk@130700 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Michael Smith 2002-05-09 03:20:59 +00:00
parent 09e7a4b86e
commit 6b320e8afe
3 changed files with 62 additions and 26 deletions

View File

@ -1,7 +1,7 @@
/*
* $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/java/org/apache/commons/collections/SequencedHashMap.java,v 1.8 2002/02/22 04:58:17 mas Exp $
* $Revision: 1.8 $
* $Date: 2002/02/22 04:58:17 $
* $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/java/org/apache/commons/collections/SequencedHashMap.java,v 1.9 2002/05/09 03:20:59 mas Exp $
* $Revision: 1.9 $
* $Date: 2002/05/09 03:20:59 $
*
* ====================================================================
*
@ -77,6 +77,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.NoSuchElementException;
import java.util.ConcurrentModificationException;
/**
* A map of objects whose mapping entries are sequenced based on the order in
@ -191,6 +192,14 @@ public class SequencedHashMap implements Map, Cloneable, Externalizable {
**/
private HashMap entries;
/**
* Holds the number of modifications that have occurred to the map,
* excluding modifications made through a collection view's iterator
* (e.g. entrySet().iterator().remove()). This is used to create a
* fail-fast behavior with the iterators.
**/
private transient long modCount = 0;
/**
* Construct a new sequenced hash map with default initial size and load
* factor.
@ -434,6 +443,7 @@ public class SequencedHashMap implements Map, Cloneable, Externalizable {
// per Map.put(Object,Object)
public Object put(Object key, Object value) {
modCount++;
Object oldValue = null;
@ -468,6 +478,15 @@ public class SequencedHashMap implements Map, Cloneable, Externalizable {
// per Map.remove(Object)
public Object remove(Object key) {
modCount++;
return removeImpl(key);
}
/**
* Removed an entry without changing the map's modification count. This
* method should only be called from a collection view's iterator
**/
private Object removeImpl(Object key) {
Entry e = (Entry)entries.remove(key);
if(e == null) return null;
removeEntry(e);
@ -494,6 +513,8 @@ public class SequencedHashMap implements Map, Cloneable, Externalizable {
// per Map.clear()
public void clear() {
modCount++;
// remove all from the underlying map
entries.clear();
@ -633,10 +654,17 @@ public class SequencedHashMap implements Map, Cloneable, Externalizable {
private int returnType;
/**
* Holds the "current" position in the iterator. when pos.next is the
* Holds the "current" position in the iterator. When pos.next is the
* sentinel, we've reached the end of the list.
**/
private Entry pos = sentinel;
/**
* Holds the expected modification count. If the actual modification
* count of the map differs from this value, then a concurrent
* modification has occurred.
**/
private transient long expectedModCount = modCount;
/**
* Construct an iterator over the sequenced elements in the order in which
@ -676,8 +704,14 @@ public class SequencedHashMap implements Map, Cloneable, Externalizable {
*
* @exception NoSuchElementException if there are no more elements in the
* iterator.
*
* @exception ConcurrentModificationException if a modification occurs in
* the underlying map.
**/
public Object next() {
if(modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
if(pos.next == sentinel) {
throw new NoSuchElementException();
}
@ -707,14 +741,21 @@ public class SequencedHashMap implements Map, Cloneable, Externalizable {
* @exception IllegalStateException if there isn't a "last element" to be
* removed. That is, if {@link #next()} has never been called, or if
* {@link #remove()} was already called on the element.
*
* @exception ConcurrentModificationException if a modification occurs in
* the underlying map.
**/
public void remove() {
if((returnType & REMOVED_MASK) != 0) {
throw new IllegalStateException("remove() must follow next()");
}
if(modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
// remove the entry
SequencedHashMap.this.remove(pos.getKey());
// remove the entry by calling the removeImpl method which does not
// update the mod count. This allows the iterator to remain valid.
SequencedHashMap.this.removeImpl(pos.getKey());
// set the removed flag
returnType = returnType | REMOVED_MASK;

View File

@ -1,7 +1,7 @@
/*
* $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/test/org/apache/commons/collections/TestLRUMap.java,v 1.19 2002/05/08 17:34:17 morgand Exp $
* $Revision: 1.19 $
* $Date: 2002/05/08 17:34:17 $
* $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/test/org/apache/commons/collections/TestLRUMap.java,v 1.20 2002/05/09 03:20:59 mas Exp $
* $Revision: 1.20 $
* $Date: 2002/05/09 03:20:59 $
*
* ====================================================================
*
@ -73,7 +73,7 @@ import java.util.HashMap;
*
* @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
* @author <a href="mailto:morgand@apache.org">Morgan Delagrange</a>
* @version $Id: TestLRUMap.java,v 1.19 2002/05/08 17:34:17 morgand Exp $
* @version $Id: TestLRUMap.java,v 1.20 2002/05/09 03:20:59 mas Exp $
*/
public class TestLRUMap extends TestSequencedHashMap
{
@ -95,13 +95,6 @@ public class TestLRUMap extends TestSequencedHashMap
return map;
}
// had to override from TestSequencedHashMap, because the test performs a get
// inside a loop. Since get() alter the Map in this class, an infinite loop
// is produced
public void testSequenceMap() {
fail("trying to work out an infinite loop bug");
}
public void testRemoveLRU() {
LRUMap map2 = new LRUMap(3);
map2.put(new Integer(1),"foo");

View File

@ -137,18 +137,20 @@ implements TestMap.SupportsPut, TestMap.EntrySetSupportsRemove
SequencedHashMap clone = (SequencedHashMap) labRat.clone();
assertEquals("Size of clone does not match original",
labRat.size(), clone.size());
Iterator origKeys = labRat.keySet().iterator();
Iterator copiedKeys = clone.keySet().iterator();
while (origKeys.hasNext()) {
Object origKey = origKeys.next();
Object copiedKey = copiedKeys.next();
assertEquals("Cloned key does not match orginal",
origKey, copiedKey);
Iterator origEntries = labRat.entrySet().iterator();
Iterator copiedEntries = clone.entrySet().iterator();
while (origEntries.hasNext()) {
Map.Entry origEntry = (Map.Entry)origEntries.next();
Map.Entry copiedEntry = (Map.Entry)copiedEntries.next();
assertEquals("Cloned key does not match original",
origEntry.getKey(), copiedEntry.getKey());
assertEquals("Cloned value does not match original",
labRat.get(origKey), clone.get(copiedKey));
origEntry.getValue(), copiedEntry.getValue());
assertEquals("Cloned entry does not match orginal",
origEntry, copiedEntry);
}
assertTrue("iterator() returned different number of elements than keys()",
!copiedKeys.hasNext());
!copiedEntries.hasNext());
// Test sequence()
List seq = labRat.sequence();