scolebourne 2003/11/04 15:36:24
Modified: collections/src/java/org/apache/commons/collections/decorators
OrderedMap.java
collections/src/test/org/apache/commons/collections/decorators
TestOrderedMap.java
Log:
Complete OrderedMap with MapIterator
Ensure fully tested
Revision Changes Path
1.5 +161 -298
jakarta-commons/collections/src/java/org/apache/commons/collections/decorators/OrderedMap.java
Index: OrderedMap.java
===================================================================
RCS file:
/home/cvs/jakarta-commons/collections/src/java/org/apache/commons/collections/decorators/OrderedMap.java,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -r1.4 -r1.5
--- OrderedMap.java 3 Oct 2003 23:19:32 -0000 1.4
+++ OrderedMap.java 4 Nov 2003 23:36:23 -0000 1.5
@@ -57,6 +57,8 @@
*/
package org.apache.commons.collections.decorators;
+import java.util.AbstractCollection;
+import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
@@ -64,21 +66,24 @@
import java.util.Map;
import java.util.Set;
-import org.apache.commons.collections.pairs.DefaultMapEntry;
+import org.apache.commons.collections.iterators.DefaultMapIterator;
+import org.apache.commons.collections.iterators.MapIterator;
+import org.apache.commons.collections.pairs.AbstractMapEntry;
/**
- * Decorates a <code>Map</code> to ensure that the order of addition
- * is retained and used by the values and keySet iterators.
+ * Decorates a <code>Map</code> to ensure that the order of addition is retained.
+ * <p>
+ * The order will be used via the iterators and toArray methods on the views.
+ * The order is also returned by the <code>MapIterator</code>.
* <p>
* If an object is added to the Map for a second time, it will remain in the
* original position in the iteration.
- * <p>
- * The order can be observed via the iterator or toArray methods.
*
* @since Commons Collections 3.0
* @version $Revision$ $Date$
*
* @author Henri Yandell
+ * @author Stephen Colebourne
*/
public class OrderedMap extends AbstractMapDecorator implements Map {
@@ -87,6 +92,8 @@
/**
* Factory method to create an ordered map.
+ * <p>
+ * An <code>ArrayList</code> is used to retain order.
*
* @param map the map to decorate, must not be null
* @throws IllegalArgumentException if map is null
@@ -103,22 +110,10 @@
*/
protected OrderedMap(Map map) {
super(map);
- insertOrder.addAll( getMap().keySet() );
+ insertOrder.addAll(getMap().keySet());
}
//-----------------------------------------------------------------------
- public void clear() {
- getMap().clear();
- insertOrder.clear();
- }
-
- public void putAll(Map m) {
- for (Iterator it = m.entrySet().iterator(); it.hasNext();) {
- Map.Entry entry = (Map.Entry) it.next();
- put(entry.getKey(), entry.getValue());
- }
- }
-
public Object put(Object key, Object value) {
if (getMap().containsKey(key)) {
// re-adding doesn't change order
@@ -131,268 +126,151 @@
}
}
+ public void putAll(Map map) {
+ for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
+ Map.Entry entry = (Map.Entry) it.next();
+ put(entry.getKey(), entry.getValue());
+ }
+ }
+
public Object remove(Object key) {
Object result = getMap().remove(key);
insertOrder.remove(key);
return result;
}
+ public void clear() {
+ getMap().clear();
+ insertOrder.clear();
+ }
+
+ //-----------------------------------------------------------------------
+ public MapIterator mapIterator() {
+ return new DefaultMapIterator(this);
+ }
+
public Set keySet() {
- return new KeyView( this, this.insertOrder );
+ return new KeySetView(this);
}
public Collection values() {
- return new ValuesView( this, this.insertOrder );
+ return new ValuesView(this);
}
- // QUERY: Should a change of value change insertion order?
public Set entrySet() {
- return new EntrySetView( this, this.insertOrder );
+ return new EntrySetView(this, this.insertOrder);
}
-
- // TODO: Code a toString up.
- // It needs to retain the right order, else it will
- // look peculiar.
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns the Map as a string.
+ *
+ * @return the Map as a String
+ */
public String toString() {
- return super.toString();
+ if (isEmpty()) {
+ return "{}";
+ }
+ StringBuffer buf = new StringBuffer();
+ buf.append('{');
+ boolean first = true;
+ Iterator it = entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry entry = (Map.Entry) it.next();
+ Object key = entry.getKey();
+ Object value = entry.getValue();
+ buf.append(key == this ? "(this Map)" : key);
+ buf.append('=');
+ buf.append(value == this ? "(this Map)" : value);
+ if (first) {
+ first = false;
+ } else {
+ buf.append(", ");
+ }
+ }
+ buf.append('}');
+ return buf.toString();
}
- // class for handling the values() method's callback to this Map
- // THESE NEED UNIT TESTING as their own collections
- class ValuesView implements Collection {
- private OrderedMap parent;
- private List insertOrder;
+ //-----------------------------------------------------------------------
+ static class ValuesView extends AbstractCollection {
+ private final OrderedMap parent;
- ValuesView(OrderedMap parent, List insertOrder) {
+ ValuesView(OrderedMap parent) {
+ super();
this.parent = parent;
- this.insertOrder = insertOrder;
- }
-
- // slow to call
- Collection _values() {
- Iterator keys = this.insertOrder.iterator();
- ArrayList list = new ArrayList( insertOrder.size() );
- while( keys.hasNext() ) {
- list.add( this.parent.getMap().get( keys.next() ) );
- }
- return list;
}
public int size() {
return this.parent.size();
}
- public boolean isEmpty() {
- return this.parent.isEmpty();
- }
-
public boolean contains(Object value) {
return this.parent.containsValue(value);
}
- public Iterator iterator() {
- // TODO: Allow this to be backed
- return _values().iterator();
-// return new ValuesViewIterator( who? );
- }
-
- public Object[] toArray() {
- return _values().toArray();
- }
-
- public Object[] toArray(Object[] array) {
- return _values().toArray(array);
- }
-
- public boolean add(Object obj) {
- throw new UnsupportedOperationException("Not allowed. ");
- }
-
- public boolean remove(Object obj) {
- // who?? which value do I choose? first one?
- for(Iterator itr = this.insertOrder.iterator(); itr.hasNext(); ) {
- Object key = itr.next();
- Object value = this.parent.get(key);
-
- // also handles null
- if(value == obj) {
- return (this.parent.remove(key) != null);
- }
-
- if( (value != null) && value.equals(obj) ) {
- return (this.parent.remove(key) != null);
- }
- }
-
- return false;
- }
-
- public boolean containsAll(Collection coll) {
- // TODO: What does Collection spec say about null/empty?
- for(Iterator itr = coll.iterator(); itr.hasNext(); ) {
- if( !this.parent.containsValue( itr.next() ) ) {
- return false;
- }
- }
- return true;
- }
-
- public boolean addAll(Collection coll) {
- throw new UnsupportedOperationException("Not allowed. ");
- }
-
- public boolean removeAll(Collection coll) {
- // not transactional. No idea if removeAll's boolean
- // reply is meant to be
- boolean ret = false;
- for( Iterator itr = coll.iterator(); itr.hasNext(); ) {
- ret = ret && remove(itr.next());
- }
- return ret;
- }
-
- public boolean retainAll(Collection coll) {
- // transactional?
- boolean ret = false;
-
- for( Iterator itr = this.insertOrder.iterator(); itr.hasNext(); ) {
- Object key = itr.next();
- Object value = this.parent.get(key);
- if( coll.contains(value) ) {
- // retain
- } else {
- ret = ret && (parent.remove(key) != null);
- }
- }
-
- return ret;
- }
-
public void clear() {
this.parent.clear();
}
- public boolean equals(Object obj) {
- // exactly what to do here?
- return super.equals(obj);
- }
- public int hashCode() {
- return _values().hashCode();
- }
-
- public String toString() {
- return _values().toString();
+ public Iterator iterator() {
+ return new AbstractIteratorDecorator(parent.entrySet().iterator()) {
+ public Object next() {
+ return ((Map.Entry) iterator.next()).getValue();
+ }
+ };
}
}
+
+ //-----------------------------------------------------------------------
+ static class KeySetView extends AbstractSet {
+ private final OrderedMap parent;
- class KeyView implements Set {
-
- private OrderedMap parent;
- private List insertOrder;
-
- public KeyView(OrderedMap parent, List insertOrder) {
+ KeySetView(OrderedMap parent) {
+ super();
this.parent = parent;
- this.insertOrder = insertOrder;
}
public int size() {
return this.parent.size();
}
- public boolean isEmpty() {
- return this.parent.isEmpty();
- }
- public boolean contains(Object obj) {
- return this.parent.containsKey(obj);
- }
- public Iterator iterator() {
- // TODO: Needs to return a KeyViewIterator, which
- // removes from this and from the Map
- return this.insertOrder.iterator();
- }
- public Object toArray()[] {
- return this.insertOrder.toArray();
- }
- public Object toArray(Object[] array)[] {
- return this.insertOrder.toArray(array);
- }
- public boolean add(Object obj) {
- throw new UnsupportedOperationException("Not allowed. ");
- }
- public boolean remove(Object obj) {
- return (this.parent.remove(obj) != null);
- }
- public boolean containsAll(Collection coll) {
- // TODO: What does Collection spec say about null/empty?
- for(Iterator itr = coll.iterator(); itr.hasNext(); ) {
- if( !this.parent.containsKey( itr.next() ) ) {
- return false;
- }
- }
- return true;
- }
- public boolean addAll(Collection coll) {
- throw new UnsupportedOperationException("Not allowed. ");
- }
- public boolean removeAll(Collection coll) {
- // not transactional. No idea if removeAll's boolean
- // reply is meant to be
- boolean ret = false;
- for( Iterator itr = coll.iterator(); itr.hasNext(); ) {
- ret = ret && remove(itr.next());
- }
- return ret;
- }
- public boolean retainAll(Collection coll) {
- // transactional?
- boolean ret = false;
-
- for( Iterator itr = this.insertOrder.iterator(); itr.hasNext(); ) {
- Object key = itr.next();
- if( coll.contains(key) ) {
- // retain
- } else {
- ret = ret && (parent.remove(key) != null);
- }
- }
- return ret;
+ public boolean contains(Object value) {
+ return this.parent.containsKey(value);
}
+
public void clear() {
this.parent.clear();
}
- public boolean equals(Object obj) {
- // exactly what to do here?
- return super.equals(obj);
- }
- public int hashCode() {
- return this.parent.getMap().keySet().hashCode();
- }
- public String toString() {
- return this.insertOrder.toString();
+ public Iterator iterator() {
+ return new AbstractIteratorDecorator(parent.entrySet().iterator()) {
+ public Object next() {
+ return ((Map.Entry) super.next()).getKey();
+ }
+ };
}
}
- class EntrySetView implements Set {
-
- private OrderedMap parent;
- private List insertOrder;
+ //-----------------------------------------------------------------------
+ static class EntrySetView extends AbstractSet {
+ private final OrderedMap parent;
+ private final List insertOrder;
+ private Set entrySet;
public EntrySetView(OrderedMap parent, List insertOrder) {
+ super();
this.parent = parent;
this.insertOrder = insertOrder;
}
- public Set _entries() {
- Set set = new java.util.HashSet( this.insertOrder.size() );
- set = OrderedSet.decorate( set );
- for (Iterator it = insertOrder.iterator(); it.hasNext();) {
- Object key = it.next();
- set.add( new DefaultMapEntry( key, getMap().get( key ) ) );
+ private Set getEntrySet() {
+ if (entrySet == null) {
+ entrySet = parent.getMap().entrySet();
}
- return set;
+ return entrySet;
}
-
+
public int size() {
return this.parent.size();
}
@@ -401,98 +279,83 @@
}
public boolean contains(Object obj) {
- if(obj instanceof Map.Entry) {
- Map.Entry entry = (Map.Entry) obj;
- if( this.parent.containsKey(entry.getKey()) ) {
- Object value = this.parent.get(entry.getKey());
- if( obj == null && value == null ) {
- return true;
- } else {
- return obj.equals(value);
- }
- } else {
- return false;
- }
- } else {
- throw new IllegalArgumentException("Parameter must be a Map.Entry");
- }
+ return getEntrySet().contains(obj);
}
- public Iterator iterator() {
- // TODO: Needs to return a EntrySetViewIterator, which
- // removes from this and from the Map
- return _entries().iterator();
- }
- public Object toArray()[] {
- return _entries().toArray();
- }
- public Object toArray(Object[] array)[] {
- return _entries().toArray(array);
- }
- public boolean add(Object obj) {
- throw new UnsupportedOperationException("Not allowed. ");
+
+ public boolean containsAll(Collection coll) {
+ return getEntrySet().containsAll(coll);
}
+
public boolean remove(Object obj) {
- if(obj instanceof Map.Entry) {
- Map.Entry entry = (Map.Entry) obj;
- return (this.parent.remove(entry.getKey()) != null);
- } else {
- throw new IllegalArgumentException("Parameter must be a Map.Entry");
+ if (obj instanceof Map.Entry == false) {
+ return false;
}
- }
- // need to decide on IllegalArgument or ClassCast in this class
- // when not Map.Entry
- public boolean containsAll(Collection coll) {
- // TODO: What does Collection spec say about null/empty?
- for(Iterator itr = coll.iterator(); itr.hasNext(); ) {
- Map.Entry entry = (Map.Entry) itr.next();
- if( !this.parent.containsKey( entry.getKey() ) ) {
- return false;
- }
+ Object key = ((Map.Entry) obj).getKey();
+ if (parent.getMap().containsKey(key) == false) {
+ return false;
}
+ parent.remove(key);
return true;
}
- public boolean addAll(Collection coll) {
- throw new UnsupportedOperationException("Not allowed. ");
- }
- public boolean removeAll(Collection coll) {
- // not transactional. No idea if removeAll's boolean
- // reply is meant to be
- boolean ret = false;
- for( Iterator itr = coll.iterator(); itr.hasNext(); ) {
- Map.Entry entry = (Map.Entry) itr.next();
- ret = ret && remove( entry.getKey() );
- }
- return ret;
- }
- public boolean retainAll(Collection coll) {
- // transactional?
- boolean ret = false;
-
- for( Iterator itr = this.insertOrder.iterator(); itr.hasNext(); ) {
- Map.Entry entry = (Map.Entry) itr.next();
- Object key = entry.getKey();
- if( coll.contains(key) ) {
- // retain
- } else {
- ret = ret && (parent.remove(key) != null);
- }
- }
- return ret;
- }
public void clear() {
this.parent.clear();
}
+
public boolean equals(Object obj) {
- // exactly what to do here?
- return super.equals(obj);
+ if (obj == this) {
+ return true;
+ }
+ return getEntrySet().equals(obj);
}
+
public int hashCode() {
- return this.parent.getMap().entrySet().hashCode();
+ return getEntrySet().hashCode();
}
public String toString() {
- return this._entries().toString();
+ return getEntrySet().toString();
+ }
+
+ public Iterator iterator() {
+ return new OrderedIterator(parent, insertOrder);
+ }
+ }
+
+ static class OrderedIterator extends AbstractIteratorDecorator {
+ private final OrderedMap parent;
+ private Object last = null;
+
+ OrderedIterator(OrderedMap parent, List insertOrder) {
+ super(insertOrder.iterator());
+ this.parent = parent;
+ }
+
+ public Object next() {
+ last = super.next();
+ return new OrderedMapEntry(parent, last);
+ }
+
+ public void remove() {
+ super.remove();
+ parent.getMap().remove(last);
+ }
+ }
+
+ static class OrderedMapEntry extends AbstractMapEntry {
+ private final OrderedMap parent;
+
+ OrderedMapEntry(OrderedMap parent, Object key) {
+ super(key, null);
+ this.parent = parent;
+ }
+
+ public Object getValue() {
+ return parent.get(key);
+ }
+
+ public Object setValue(Object value) {
+ return parent.getMap().put(key, value);
}
}
1.3 +124 -4
jakarta-commons/collections/src/test/org/apache/commons/collections/decorators/TestOrderedMap.java
Index: TestOrderedMap.java
===================================================================
RCS file:
/home/cvs/jakarta-commons/collections/src/test/org/apache/commons/collections/decorators/TestOrderedMap.java,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -r1.2 -r1.3
--- TestOrderedMap.java 3 Oct 2003 12:54:54 -0000 1.2
+++ TestOrderedMap.java 4 Nov 2003 23:36:23 -0000 1.3
@@ -65,9 +65,11 @@
import java.util.Set;
import junit.framework.Test;
-import junit.framework.TestSuite;
import org.apache.commons.collections.AbstractTestMap;
+import org.apache.commons.collections.BulkTest;
+import org.apache.commons.collections.iterators.AbstractTestMapIterator;
+import org.apache.commons.collections.iterators.MapIterator;
/**
* Extension of [EMAIL PROTECTED] TestMap} for exercising the [EMAIL PROTECTED]
OrderedMap}
@@ -77,6 +79,7 @@
* @version $Revision$ $Date$
*
* @author Henri Yandell
+ * @author Stephen Colebourne
*/
public class TestOrderedMap extends AbstractTestMap {
@@ -85,7 +88,7 @@
}
public static Test suite() {
- return new TestSuite(TestOrderedMap.class);
+ return BulkTest.makeSuite(TestOrderedMap.class);
}
public static void main(String args[]) {
@@ -97,6 +100,123 @@
return OrderedMap.decorate(new HashMap());
}
+ //-----------------------------------------------------------------------
+ public BulkTest bulkTestMapIterator() {
+ return new TestOrderedMapIterator();
+ }
+
+ public class TestOrderedMapIterator extends AbstractTestMapIterator {
+ public TestOrderedMapIterator() {
+ super("TestOrderedMapIterator");
+ }
+
+ protected Object addSetValue() {
+ return TestOrderedMap.this.getNewSampleValues()[0];
+ }
+
+ protected boolean supportsRemove() {
+ return TestOrderedMap.this.isRemoveSupported();
+ }
+
+ protected boolean supportsSetValue() {
+ return TestOrderedMap.this.isSetValueSupported();
+ }
+
+ protected MapIterator makeEmptyMapIterator() {
+ resetEmpty();
+ return ((OrderedMap) TestOrderedMap.this.map).mapIterator();
+ }
+
+ protected MapIterator makeFullMapIterator() {
+ resetFull();
+ return ((OrderedMap) TestOrderedMap.this.map).mapIterator();
+ }
+
+ protected Map getMap() {
+ // assumes makeFullMapIterator() called first
+ return TestOrderedMap.this.map;
+ }
+ }
+
+ //-----------------------------------------------------------------------
+ public void testMapIteratorRemove() {
+ resetFull();
+ OrderedMap testMap = (OrderedMap) map;
+ MapIterator it = testMap.mapIterator();
+ assertEquals(true, it.hasNext());
+ Object key = it.next();
+
+ if (isRemoveSupported() == false) {
+ try {
+ it.remove();
+ fail();
+ } catch (UnsupportedOperationException ex) {
+ }
+ return;
+ }
+
+ it.remove();
+ confirmed.remove(key);
+ assertEquals(false, testMap.containsKey(key));
+ verify();
+
+ try {
+ it.remove(); // second remove fails
+ } catch (IllegalStateException ex) {
+ }
+ verify();
+ }
+
+ //-----------------------------------------------------------------------
+ public void testMapIteratorSet() {
+ Object newValue1 = getOtherValues()[0];
+ Object newValue2 = getOtherValues()[1];
+
+ resetFull();
+ OrderedMap testMap = (OrderedMap) map;
+ MapIterator it = testMap.mapIterator();
+ assertEquals(true, it.hasNext());
+ Object key1 = it.next();
+
+ if (isSetValueSupported() == false) {
+ try {
+ it.setValue(newValue1);
+ fail();
+ } catch (UnsupportedOperationException ex) {
+ }
+ return;
+ }
+
+ it.setValue(newValue1);
+ confirmed.put(key1, newValue1);
+ assertSame(key1, it.getKey());
+ assertSame(newValue1, it.getValue());
+ assertEquals(true, testMap.containsKey(key1));
+ assertEquals(true, testMap.containsValue(newValue1));
+ assertEquals(newValue1, testMap.get(key1));
+ verify();
+
+ it.setValue(newValue1); // same value - should be OK
+ confirmed.put(key1, newValue1);
+ assertSame(key1, it.getKey());
+ assertSame(newValue1, it.getValue());
+ assertEquals(true, testMap.containsKey(key1));
+ assertEquals(true, testMap.containsValue(newValue1));
+ assertEquals(newValue1, testMap.get(key1));
+ verify();
+
+ Object key2 = it.next();
+ it.setValue(newValue2);
+ confirmed.put(key2, newValue2);
+ assertSame(key2, it.getKey());
+ assertSame(newValue2, it.getValue());
+ assertEquals(true, testMap.containsKey(key2));
+ assertEquals(true, testMap.containsValue(newValue2));
+ assertEquals(newValue2, testMap.get(key2));
+ verify();
+ }
+
+ //-----------------------------------------------------------------------
// Creates a known series of Objects, puts them in
// an OrderedMap and ensures that all three Collection
// methods return in the correct order.
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]