You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by tn...@apache.org on 2013/03/02 13:54:48 UTC

svn commit: r1451883 - in /commons/proper/collections/trunk/src: changes/changes.xml main/java/org/apache/commons/collections/collection/IndexedCollection.java test/java/org/apache/commons/collections/collection/IndexedCollectionTest.java

Author: tn
Date: Sat Mar  2 12:54:47 2013
New Revision: 1451883

URL: http://svn.apache.org/r1451883
Log:
[COLLECTIONS-275] Finished IndexedCollection by supporting non-uniqueness of keys, fixes tests.

Modified:
    commons/proper/collections/trunk/src/changes/changes.xml
    commons/proper/collections/trunk/src/main/java/org/apache/commons/collections/collection/IndexedCollection.java
    commons/proper/collections/trunk/src/test/java/org/apache/commons/collections/collection/IndexedCollectionTest.java

Modified: commons/proper/collections/trunk/src/changes/changes.xml
URL: http://svn.apache.org/viewvc/commons/proper/collections/trunk/src/changes/changes.xml?rev=1451883&r1=1451882&r2=1451883&view=diff
==============================================================================
--- commons/proper/collections/trunk/src/changes/changes.xml (original)
+++ commons/proper/collections/trunk/src/changes/changes.xml Sat Mar  2 12:54:47 2013
@@ -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>

Modified: commons/proper/collections/trunk/src/main/java/org/apache/commons/collections/collection/IndexedCollection.java
URL: http://svn.apache.org/viewvc/commons/proper/collections/trunk/src/main/java/org/apache/commons/collections/collection/IndexedCollection.java?rev=1451883&r1=1451882&r2=1451883&view=diff
==============================================================================
--- commons/proper/collections/trunk/src/main/java/org/apache/commons/collections/collection/IndexedCollection.java (original)
+++ commons/proper/collections/trunk/src/main/java/org/apache/commons/collections/collection/IndexedCollection.java Sat Mar  2 12:54:47 2013
@@ -18,9 +18,10 @@ package org.apache.commons.collections.c
 
 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.Tr
  * @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> ext
     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> ext
      */
     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 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} for a unique index.
+     * 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> ext
 
     /**
      * 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> ext
      * 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);
     }
 
     /**

Modified: commons/proper/collections/trunk/src/test/java/org/apache/commons/collections/collection/IndexedCollectionTest.java
URL: http://svn.apache.org/viewvc/commons/proper/collections/trunk/src/test/java/org/apache/commons/collections/collection/IndexedCollectionTest.java?rev=1451883&r1=1451882&r2=1451883&view=diff
==============================================================================
--- commons/proper/collections/trunk/src/test/java/org/apache/commons/collections/collection/IndexedCollectionTest.java (original)
+++ commons/proper/collections/trunk/src/test/java/org/apache/commons/collections/collection/IndexedCollectionTest.java Sat Mar  2 12:54:47 2013
@@ -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 exten
    //------------------------------------------------------------------------
 
     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 exten
         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 exten
 
     //------------------------------------------------------------------------
 
-    @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 exten
         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
+        }
+    }
+    
+    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));
     }
     
-//    @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 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));
+    }
 }