Author: tn Date: Sun Nov 22 21:11:49 2015 New Revision: 1715695 URL: http://svn.apache.org/viewvc?rev=1715695&view=rev Log: [COLLECTIONS-508] Added asMap() implementation for MultiValuedMaps.
Modified: commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/MultiValuedMap.java commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/AbstractListValuedMap.java commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMap.java commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/AbstractSetValuedMap.java commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/map/AbstractMapTest.java commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMapTest.java Modified: commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/MultiValuedMap.java URL: http://svn.apache.org/viewvc/commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/MultiValuedMap.java?rev=1715695&r1=1715694&r2=1715695&view=diff ============================================================================== --- commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/MultiValuedMap.java (original) +++ commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/MultiValuedMap.java Sun Nov 22 21:11:49 2015 @@ -17,6 +17,7 @@ package org.apache.commons.collections4; import java.util.Collection; +import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; @@ -289,8 +290,18 @@ public interface MultiValuedMap<K, V> { Collection<V> values(); /** - * Returns a {@link Map} view of this MultiValuedMap with a Collection as - * its value. The Collection holds all the values mapped to that key. + * Returns a view of this multi-valued map as a {@code Map} from each distinct + * key to the non-empty collection of that key's associated values. + * <p> + * Note that {@code this.asMap().get(k)} is equivalent to {@code this.get(k)} + * only when {@code k} is a key contained in the multi-valued map; otherwise it + * returns {@code null} as opposed to an empty collection. + * <p> + * Changes to the returned map or the collections that serve as its values + * will update the underlying multi-valued map, and vice versa. The map does + * not support {@code put} or {@code putAll}, nor do its entries support + * {@link Map.Entry#setValue setValue} and iterators support + * {@link Iterator#remove remove}. * * @return a map view of the mappings in this multi-valued map */ Modified: commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/AbstractListValuedMap.java URL: http://svn.apache.org/viewvc/commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/AbstractListValuedMap.java?rev=1715695&r1=1715694&r2=1715695&view=diff ============================================================================== --- commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/AbstractListValuedMap.java (original) +++ commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/AbstractListValuedMap.java Sun Nov 22 21:11:49 2015 @@ -79,6 +79,11 @@ public abstract class AbstractListValued */ @Override public List<V> get(final K key) { + return wrappedCollection(key); + } + + @Override + List<V> wrappedCollection(final K key) { return new WrappedList(key); } Modified: commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMap.java URL: http://svn.apache.org/viewvc/commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMap.java?rev=1715695&r1=1715694&r2=1715695&view=diff ============================================================================== --- commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMap.java (original) +++ commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMap.java Sun Nov 22 21:11:49 2015 @@ -20,6 +20,8 @@ import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.AbstractCollection; +import java.util.AbstractMap; +import java.util.AbstractSet; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; @@ -33,11 +35,13 @@ import org.apache.commons.collections4.M import org.apache.commons.collections4.MultiSet; import org.apache.commons.collections4.MultiValuedMap; import org.apache.commons.collections4.Transformer; +import org.apache.commons.collections4.iterators.AbstractIteratorDecorator; import org.apache.commons.collections4.iterators.EmptyMapIterator; import org.apache.commons.collections4.iterators.IteratorChain; import org.apache.commons.collections4.iterators.LazyIteratorChain; import org.apache.commons.collections4.iterators.TransformIterator; import org.apache.commons.collections4.keyvalue.AbstractMapEntry; +import org.apache.commons.collections4.keyvalue.UnmodifiableMapEntry; import org.apache.commons.collections4.multiset.AbstractMultiSet; import org.apache.commons.collections4.multiset.UnmodifiableMultiSet; @@ -61,6 +65,9 @@ public abstract class AbstractMultiValue /** The KeyMultiSet view */ private transient MultiSet<K> keysMultiSetView; + /** The AsMap view */ + private transient AsMap asMapView; + /** The map used to store the data */ private transient Map<K, Collection<V>> map; @@ -140,6 +147,10 @@ public abstract class AbstractMultiValue */ @Override public Collection<V> get(final K key) { + return wrappedCollection(key); + } + + Collection<V> wrappedCollection(final K key) { return new WrappedCollection(key); } @@ -324,10 +335,8 @@ public abstract class AbstractMultiValue } @Override - @SuppressWarnings("unchecked") public Map<K, Collection<V>> asMap() { - // TODO: return a view of the map - return (Map<K, Collection<V>>) getMap(); + return asMapView != null ? asMapView : (asMapView = new AsMap(map)); } /** @@ -767,6 +776,131 @@ public abstract class AbstractMultiValue } } + /** + * Inner class that provides the AsMap view. + */ + private class AsMap extends AbstractMap<K, Collection<V>> { + final transient Map<K, Collection<V>> decoratedMap; + + AsMap(final Map<K, Collection<V>> map) { + this.decoratedMap = map; + } + + @Override + public Set<Map.Entry<K, Collection<V>>> entrySet() { + return new AsMapEntrySet(); + } + + @Override + public boolean containsKey(Object key) { + return decoratedMap.containsKey(key); + } + + @Override + public Collection<V> get(Object key) { + Collection<V> collection = decoratedMap.get(key); + if (collection == null) { + return null; + } + @SuppressWarnings("unchecked") + K k = (K) key; + return wrappedCollection(k); + } + + @Override + public Set<K> keySet() { + return AbstractMultiValuedMap.this.keySet(); + } + + @Override + public int size() { + return decoratedMap.size(); + } + + @Override + public Collection<V> remove(Object key) { + Collection<V> collection = decoratedMap.remove(key); + if (collection == null) { + return null; + } + + final Collection<V> output = createCollection(); + output.addAll(collection); + collection.clear(); + return output; + } + + @Override + public boolean equals(Object object) { + return this == object || decoratedMap.equals(object); + } + + @Override + public int hashCode() { + return decoratedMap.hashCode(); + } + + @Override + public String toString() { + return decoratedMap.toString(); + } + + @Override + public void clear() { + AbstractMultiValuedMap.this.clear(); + } + + class AsMapEntrySet extends AbstractSet<Map.Entry<K, Collection<V>>> { + + @Override + public Iterator<Map.Entry<K, Collection<V>>> iterator() { + return new AsMapEntrySetIterator(decoratedMap.entrySet().iterator()); + } + + @Override + public int size() { + return AsMap.this.size(); + } + + @Override + public void clear() { + AsMap.this.clear(); + } + + @Override + public boolean contains(Object o) { + return decoratedMap.entrySet().contains(o); + } + + @Override + public boolean remove(Object o) { + if (!contains(o)) { + return false; + } + Map.Entry<?, ?> entry = (Map.Entry<?, ?>) o; + AbstractMultiValuedMap.this.remove(entry.getKey()); + return true; + } + } + + /** + * EntrySet iterator for the asMap view. + */ + class AsMapEntrySetIterator extends AbstractIteratorDecorator<Map.Entry<K, Collection<V>>> { + + AsMapEntrySetIterator(Iterator<Map.Entry<K, Collection<V>>> iterator) { + super(iterator); + } + + @Override + public Map.Entry<K, Collection<V>> next() { + final Map.Entry<K, Collection<V>> entry = super.next(); + final K key = entry.getKey(); + return new UnmodifiableMapEntry<K, Collection<V>>(key, wrappedCollection(key)); + } + } + } + //----------------------------------------------------------------------- /** * Write the map out using a custom routine. Modified: commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/AbstractSetValuedMap.java URL: http://svn.apache.org/viewvc/commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/AbstractSetValuedMap.java?rev=1715695&r1=1715694&r2=1715695&view=diff ============================================================================== --- commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/AbstractSetValuedMap.java (original) +++ commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/multimap/AbstractSetValuedMap.java Sun Nov 22 21:11:49 2015 @@ -78,6 +78,11 @@ public abstract class AbstractSetValuedM */ @Override public Set<V> get(final K key) { + return wrappedCollection(key); + } + + @Override + Set<V> wrappedCollection(final K key) { return new WrappedSet(key); } Modified: commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/map/AbstractMapTest.java URL: http://svn.apache.org/viewvc/commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/map/AbstractMapTest.java?rev=1715695&r1=1715694&r2=1715695&view=diff ============================================================================== --- commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/map/AbstractMapTest.java (original) +++ commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/map/AbstractMapTest.java Sun Nov 22 21:11:49 2015 @@ -287,6 +287,10 @@ public abstract class AbstractMapTest<K, return true; } + public boolean areEqualElementsDistinguishable() { + return false; + } + /** * Returns the set of keys in the mappings used to test the map. This * method must return an array with the same length as {@link @@ -1588,6 +1592,11 @@ public abstract class AbstractMapTest<K, } @Override + public boolean areEqualElementsDistinguishable() { + return AbstractMapTest.this.areEqualElementsDistinguishable(); + } + + @Override public boolean isTestSerialization() { return false; } Modified: commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMapTest.java URL: http://svn.apache.org/viewvc/commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMapTest.java?rev=1715695&r1=1715694&r2=1715695&view=diff ============================================================================== --- commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMapTest.java (original) +++ commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMapTest.java Sun Nov 22 21:11:49 2015 @@ -683,30 +683,6 @@ public abstract class AbstractMultiValue assertTrue(col.contains("uno")); } - @SuppressWarnings("unchecked") - public void testAsMapPut() { - if (!isAddSupported()) { - return; - } - resetEmpty(); - Map<K, Collection<V>> mapCol = getMap().asMap(); - Collection<V> col = (Collection<V>) Arrays.asList("un", "uno"); - mapCol.put((K) "one", col); - assertEquals(2, getMap().size()); - assertTrue(getMap().containsKey("one")); - assertTrue(getMap().containsValue("un")); - assertTrue(getMap().containsValue("uno")); - - resetFull(); - mapCol = getMap().asMap(); - col = mapCol.get("one"); - col.add((V) "one"); - assertEquals(7, getMap().size()); - assertTrue(getMap().containsValue("one")); - assertTrue(getMap().containsValue("un")); - assertTrue(getMap().containsValue("uno")); - } - public void testAsMapRemove() { if (!isRemoveSupported()) { return; @@ -1141,23 +1117,31 @@ public abstract class AbstractMultiValue @Override public boolean isPutAddSupported() { - return AbstractMultiValuedMapTest.this.isAddSupported(); + return false; } @Override public boolean isPutChangeSupported() { - return AbstractMultiValuedMapTest.this.isAddSupported(); + return false; } @Override public boolean isRemoveSupported() { return AbstractMultiValuedMapTest.this.isRemoveSupported(); } + + @Override + public boolean areEqualElementsDistinguishable() { + // work-around for a problem with the EntrySet: the entries contain + // the wrapped collection, which will be automatically cleared + // when the associated key is removed from the map as the collection + // is not cached atm. + return true; + } @Override public boolean isTestSerialization() { return false; } - } }