[COLLECTIONS-275] Finished IndexedCollection by supporting non-uniqueness of keys, fixes tests.
git-svn-id: https://svn.apache.org/repos/asf/commons/proper/collections/trunk@1451883 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
77309d9e0d
commit
d1ba17365f
|
@ -22,6 +22,10 @@
|
|||
<body>
|
||||
|
||||
<release version="4.0" date="TBA" description="Next release">
|
||||
<action issue="COLLECTIONS-275" dev="tn" type="add" due-to="Stephen Kestle">
|
||||
Added "IndexedCollection" collection decorator which provides a map-like
|
||||
view on an existing collection.
|
||||
</action>
|
||||
<action issue="COLLECTIONS-258" dev="tn" type="add" due-to="Nathan Blomquist">
|
||||
Added "DualLinkedHashBidiMap" bidi map implementation.
|
||||
</action>
|
||||
|
|
|
@ -18,9 +18,10 @@ package org.apache.commons.collections.collection;
|
|||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.collections.MultiMap;
|
||||
import org.apache.commons.collections.Transformer;
|
||||
import org.apache.commons.collections.map.MultiValueMap;
|
||||
|
||||
/**
|
||||
* An IndexedCollection is a Map-like view onto a Collection. It accepts a
|
||||
|
@ -40,7 +41,6 @@ import org.apache.commons.collections.Transformer;
|
|||
* @since 4.0
|
||||
* @version $Id$
|
||||
*/
|
||||
// TODO support MultiMap/non-unique index behavior
|
||||
public class IndexedCollection<K, C> extends AbstractCollectionDecorator<C> {
|
||||
|
||||
/** Serialization version */
|
||||
|
@ -50,10 +50,16 @@ public class IndexedCollection<K, C> extends AbstractCollectionDecorator<C> {
|
|||
private final Transformer<C, K> keyTransformer;
|
||||
|
||||
/** The map of indexes to collected objects. */
|
||||
private final Map<K, C> index;
|
||||
private final MultiMap<K, C> index;
|
||||
|
||||
/** The uniqueness constraint for the index. */
|
||||
private final boolean uniqueIndex;
|
||||
|
||||
/**
|
||||
* Create an {@link IndexedCollection} for a unique index.
|
||||
* <p>
|
||||
* If an element is added, which maps to an existing key, an {@link IllegalArgumentException}
|
||||
* will be thrown.
|
||||
*
|
||||
* @param <K> the index object type.
|
||||
* @param <C> the collection type.
|
||||
|
@ -63,24 +69,50 @@ public class IndexedCollection<K, C> extends AbstractCollectionDecorator<C> {
|
|||
*/
|
||||
public static <K, C> IndexedCollection<K, C> uniqueIndexedCollection(final Collection<C> coll,
|
||||
final Transformer<C, K> keyTransformer) {
|
||||
return new IndexedCollection<K, C>(coll, keyTransformer, new HashMap<K, C>());
|
||||
return new IndexedCollection<K, C>(coll, keyTransformer,
|
||||
MultiValueMap.<K, C>multiValueMap(new HashMap<K, Collection<C>>()),
|
||||
true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link IndexedCollection} for a unique index.
|
||||
* Create an {@link IndexedCollection} for a non-unique index.
|
||||
*
|
||||
* @param <K> the index object type.
|
||||
* @param <C> the collection type.
|
||||
* @param coll the decorated {@link Collection}.
|
||||
* @param keyTransformer the {@link Transformer} for generating index keys.
|
||||
* @return the created {@link IndexedCollection}.
|
||||
*/
|
||||
public static <K, C> IndexedCollection<K, C> nonUniqueIndexedCollection(final Collection<C> coll,
|
||||
final Transformer<C, K> keyTransformer) {
|
||||
return new IndexedCollection<K, C>(coll, keyTransformer,
|
||||
MultiValueMap.<K, C>multiValueMap(new HashMap<K, Collection<C>>()),
|
||||
false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link IndexedCollection}.
|
||||
*
|
||||
* @param coll decorated {@link Collection}
|
||||
* @param keyTransformer {@link Transformer} for generating index keys
|
||||
* @param map map to use as index
|
||||
* @param uniqueIndex if the index shall enforce uniqueness of index keys
|
||||
*/
|
||||
public IndexedCollection(final Collection<C> coll, final Transformer<C, K> keyTransformer,
|
||||
final HashMap<K, C> map) {
|
||||
final MultiMap<K, C> map, final boolean uniqueIndex) {
|
||||
super(coll);
|
||||
this.keyTransformer = keyTransformer;
|
||||
this.index = new HashMap<K, C>();
|
||||
this.index = map;
|
||||
this.uniqueIndex = uniqueIndex;
|
||||
reindex();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @throws IllegalArgumentException if the object maps to an existing key and the index
|
||||
* enforces a uniqueness constraint
|
||||
*/
|
||||
@Override
|
||||
public boolean add(final C object) {
|
||||
final boolean added = super.add(object);
|
||||
|
@ -133,12 +165,30 @@ public class IndexedCollection<K, C> extends AbstractCollectionDecorator<C> {
|
|||
|
||||
/**
|
||||
* Get the element associated with the given key.
|
||||
* <p>
|
||||
* In case of a non-unique index, this method will return the first
|
||||
* value associated with the given key. To retrieve all elements associated
|
||||
* with a key, use {@link #values(Object)}.
|
||||
*
|
||||
* @param key key to look up
|
||||
* @return element found
|
||||
* @see #values(Object)
|
||||
*/
|
||||
public C get(final K key) {
|
||||
return index.get(key);
|
||||
@SuppressWarnings("unchecked") // index is a MultiMap which returns a Collection
|
||||
Collection<C> coll = (Collection<C>) index.get(key);
|
||||
return coll == null ? null : coll.iterator().next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all elements associated with the given key.
|
||||
*
|
||||
* @param key key to look up
|
||||
* @return a collection of elements found, or null if {@code contains(key) == false}
|
||||
*/
|
||||
@SuppressWarnings("unchecked") // index is a MultiMap which returns a Collection
|
||||
public Collection<C> values(final K key) {
|
||||
return (Collection<C>) index.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -185,12 +235,15 @@ public class IndexedCollection<K, C> extends AbstractCollectionDecorator<C> {
|
|||
* Provides checking for adding the index.
|
||||
*
|
||||
* @param object the object to index
|
||||
* @throws IllegalArgumentException if the object maps to an existing key and the index
|
||||
* enforces a uniqueness constraint
|
||||
*/
|
||||
private void addToIndex(final C object) {
|
||||
final C existingObject = index.put(keyTransformer.transform(object), object);
|
||||
if (existingObject != null) {
|
||||
final K key = keyTransformer.transform(object);
|
||||
if (uniqueIndex && index.containsKey(key)) {
|
||||
throw new IllegalArgumentException("Duplicate key in uniquely indexed collection.");
|
||||
}
|
||||
index.put(key, object);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -26,7 +26,6 @@ import java.util.List;
|
|||
|
||||
import org.apache.commons.collections.Transformer;
|
||||
import org.apache.commons.collections.collection.IndexedCollection;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Extension of {@link AbstractCollectionTest} for exercising the
|
||||
|
@ -45,6 +44,10 @@ public class IndexedCollectionTest extends AbstractCollectionTest<String> {
|
|||
//------------------------------------------------------------------------
|
||||
|
||||
protected Collection<String> decorateCollection(final Collection<String> collection) {
|
||||
return IndexedCollection.nonUniqueIndexedCollection(collection, new IntegerTransformer());
|
||||
}
|
||||
|
||||
protected IndexedCollection<Integer, String> decorateUniqueCollection(final Collection<String> collection) {
|
||||
return IndexedCollection.uniqueIndexedCollection(collection, new IntegerTransformer());
|
||||
}
|
||||
|
||||
|
@ -90,6 +93,14 @@ public class IndexedCollectionTest extends AbstractCollectionTest<String> {
|
|||
return list;
|
||||
}
|
||||
|
||||
public Collection<String> makeTestCollection() {
|
||||
return decorateCollection(new ArrayList<String>());
|
||||
}
|
||||
|
||||
public Collection<String> makeUniqueTestCollection() {
|
||||
return decorateUniqueCollection(new ArrayList<String>());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean skipSerializedCanonicalTests() {
|
||||
// FIXME: support canonical tests
|
||||
|
@ -98,22 +109,15 @@ public class IndexedCollectionTest extends AbstractCollectionTest<String> {
|
|||
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void testCollectionAddAll() {
|
||||
// FIXME: does not work as we do not support multi-keys yet
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addedObjectsCanBeRetrievedByKey() throws Exception {
|
||||
final Collection<String> coll = getCollection();
|
||||
public void testAddedObjectsCanBeRetrievedByKey() throws Exception {
|
||||
final Collection<String> coll = makeTestCollection();
|
||||
coll.add("12");
|
||||
coll.add("16");
|
||||
coll.add("1");
|
||||
coll.addAll(asList("2","3","4"));
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
final
|
||||
IndexedCollection<Integer, String> indexed = (IndexedCollection<Integer, String>) coll;
|
||||
final IndexedCollection<Integer, String> indexed = (IndexedCollection<Integer, String>) coll;
|
||||
assertEquals("12", indexed.get(12));
|
||||
assertEquals("16", indexed.get(16));
|
||||
assertEquals("1", indexed.get(1));
|
||||
|
@ -122,40 +126,43 @@ public class IndexedCollectionTest extends AbstractCollectionTest<String> {
|
|||
assertEquals("4", indexed.get(4));
|
||||
}
|
||||
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void ensureDuplicateObjectsCauseException() throws Exception {
|
||||
getCollection().add("1");
|
||||
getCollection().add("1");
|
||||
public void testEnsureDuplicateObjectsCauseException() throws Exception {
|
||||
final Collection<String> coll = makeUniqueTestCollection();
|
||||
|
||||
coll.add("1");
|
||||
try {
|
||||
coll.add("1");
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
// @Test
|
||||
// public void decoratedCollectionIsIndexedOnCreation() throws Exception {
|
||||
// original.add("1");
|
||||
// original.add("2");
|
||||
// original.add("3");
|
||||
//
|
||||
// indexed = IndexedCollection.uniqueIndexedCollection(original, new Transformer<String, Integer>() {
|
||||
// public Integer transform(String input) {
|
||||
// return Integer.parseInt(input);
|
||||
// }
|
||||
// });
|
||||
// assertEquals("1", indexed.get(1));
|
||||
// assertEquals("2", indexed.get(2));
|
||||
// assertEquals("3", indexed.get(3));
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void reindexUpdatesIndexWhenTheDecoratedCollectionIsModifiedSeparately() throws Exception {
|
||||
// original.add("1");
|
||||
// original.add("2");
|
||||
// original.add("3");
|
||||
//
|
||||
// assertNull(indexed.get(1));
|
||||
// assertNull(indexed.get(2));
|
||||
// assertNull(indexed.get(3));
|
||||
// indexed.reindex();
|
||||
// assertEquals("1", indexed.get(1));
|
||||
// assertEquals("2", indexed.get(2));
|
||||
// assertEquals("3", indexed.get(3));
|
||||
// }
|
||||
public void testDecoratedCollectionIsIndexedOnCreation() throws Exception {
|
||||
Collection<String> original = makeFullCollection();
|
||||
IndexedCollection<Integer, String> indexed = decorateUniqueCollection(original);
|
||||
|
||||
assertEquals("1", indexed.get(1));
|
||||
assertEquals("2", indexed.get(2));
|
||||
assertEquals("3", indexed.get(3));
|
||||
}
|
||||
|
||||
public void testReindexUpdatesIndexWhenDecoratedCollectionIsModifiedSeparately() throws Exception {
|
||||
Collection<String> original = new ArrayList<String>();
|
||||
IndexedCollection<Integer, String> indexed = decorateUniqueCollection(original);
|
||||
|
||||
original.add("1");
|
||||
original.add("2");
|
||||
original.add("3");
|
||||
|
||||
assertNull(indexed.get(1));
|
||||
assertNull(indexed.get(2));
|
||||
assertNull(indexed.get(3));
|
||||
|
||||
indexed.reindex();
|
||||
|
||||
assertEquals("1", indexed.get(1));
|
||||
assertEquals("2", indexed.get(2));
|
||||
assertEquals("3", indexed.get(3));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue