[COLLECTIONS-508] Initial commit of new MultiValuedMap interface + implementations. Thanks to Dipanjan Laha.

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/collections/trunk@1581553 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Thomas Neidhart 2014-03-25 22:01:07 +00:00
parent 56ff5a3481
commit b0e7365e0c
18 changed files with 2517 additions and 0 deletions

View File

@ -0,0 +1,69 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.collections4;
import java.util.List;
/**
* Defines a map that holds a list of values against each key.
* <p>
* A <code>ListValuedMap</code> is a Map with slightly different semantics:
* <ul>
* <li>Putting a value into the map will add the value to a Collection at that key.</li>
* <li>Getting a value will return a Collection, holding all the values put to that key.</li>
* </ul>
*
* @since 4.1
* @version $Id$
*/
public interface ListValuedMap<K, V> extends MultiValuedMap<K, V> {
/**
* Gets the list of values associated with the specified key.
* <p>
* Implementations typically return <code>null</code> if no values have been
* mapped to the key, however the implementation may choose to return an
* empty collection.
* <p>
* Implementations may choose to return a clone of the internal collection.
*
* @param key the key to retrieve
* @return the <code>Collection</code> of values, implementations should
* return <code>null</code> for no mapping, but may return an empty collection
* @throws ClassCastException if the key is of an invalid type
* @throws NullPointerException if the key is null and null keys are invalid
*/
List<V> get(Object key);
/**
* Removes all values associated with the specified key.
* <p>
* Implementations typically return <code>null</code> from a subsequent
* <code>get(Object)</code>, however they may choose to return an empty
* collection.
*
* @param key the key to remove values from
* @return the <code>Collection</code> of values removed, implementations
* should return <code>null</code> for no mapping found, but may
* return an empty collection
* @throws UnsupportedOperationException if the map is unmodifiable
* @throws ClassCastException if the key is of an invalid type
* @throws NullPointerException if the key is null and null keys are invalid
*/
List<V> remove(Object key);
}

View File

@ -0,0 +1,287 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.collections4;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* Defines a map that holds a collection of values against each key.
* <p>
* A <code>MultiValuedMap</code> is a Map with slightly different semantics:
* <ul>
* <li>Putting a value into the map will add the value to a Collection at that key.</li>
* <li>Getting a value will return a Collection, holding all the values put to that key.</li>
* </ul>
* <p>
* For example:
* <pre>
* MultiValuedMap&lt;K, String&gt; map = new MultiValuedHashMap&lt;K, String&gt;();
* map.put(key, &quot;A&quot;);
* map.put(key, &quot;B&quot;);
* map.put(key, &quot;C&quot;);
* Collection&lt;String&gt; coll = map.get(key);
* </pre>
* <p>
* <code>coll</code> will be a collection containing "A", "B", "C".
* <p>
*
* @since 4.1
* @version $Id$
*/
public interface MultiValuedMap<K, V> {
// Query operations
/**
* Gets the total size of the map.
* <p>
* Implementations would return the total size of the map which is the count
* of the values from all keys.
*
* @return the total size of the map
*/
int size();
/**
* Returns <tt>true</tt> if this map contains no key-value mappings.
*
* @return <tt>true</tt> if this map contains no key-value mappings
*/
boolean isEmpty();
/**
* Returns <tt>true</tt> if this map contains a mapping for the specified
* key. More formally, returns <tt>true</tt> if and only if this map
* contains a mapping for a key <tt>k</tt> such that
* <tt>(key==null ? k==null : key.equals(k))</tt>. (There can be at most one
* such mapping.)
*
* @param key key whose presence in this map is to be tested
* @return <tt>true</tt> if this map contains a mapping for the specified key
* @throws ClassCastException if the key is of an inappropriate type for this map (optional)
* @throws NullPointerException if the specified key is null and this map
* does not permit null keys (optional)
*/
boolean containsKey(Object key);
/**
* Checks whether the map contains at least one mapping for the specified value.
*
* @param value the value to search for
* @return true if the map contains the value
* @throws ClassCastException if the value is of an invalid type
* @throws NullPointerException if the value is null and null value are invalid
*/
boolean containsValue(Object value);
/**
* Checks whether the map contains a mapping for the specified key and value.
*
* @param key the key to search for
* @param value the value to search for
* @return true if the map contains the value
*/
boolean containsMapping(Object key, Object value);
/**
* Gets the collection of values associated with the specified key.
* <p>
* Implementations are free to declare that they return
* <code>Collection</code> subclasses such as <code>List</code> or
* <code>Set</code>.
* <p>
* Implementations typically return <code>null</code> if no values have been
* mapped to the key, however the implementation may choose to return an
* empty collection.
* <p>
* Implementations may choose to return a clone of the internal collection.
*
* @param key the key to retrieve
* @return the <code>Collection</code> of values, implementations should
* return <code>null</code> for no mapping, but may return an empty collection
* @throws ClassCastException if the key is of an invalid type
* @throws NullPointerException if the key is null and null keys are invalid
*/
Collection<V> get(Object key);
// Modification operations
/**
* Adds the value to the collection associated with the specified key.
* <p>
* Unlike a normal <code>Map</code> the previous value is not replaced.
* Instead the new value is added to the collection stored against the key.
* The collection may be a <code>List</code>, <code>Set</code> or other
* collection dependent on implementation.
*
* @param key the key to store against
* @param value the value to add to the collection at the key
* @return typically the value added if the map changed and null if the map
* did not change
* @throws UnsupportedOperationException if the map is unmodifiable
* @throws ClassCastException if the key or value is of an invalid type
* @throws NullPointerException if the key or value is null and null is invalid
* @throws IllegalArgumentException if the key or value is invalid
*/
V put(K key, V value);
/**
* Adds Iterable values to the collection associated with the specified key.
*
* @param key the key to store against
* @param values the values to add to the collection at the key, null ignored
* @return true if this map changed
*/
boolean putAll(K key, Iterable<? extends V> values);
/**
* Copies all of the mappings from the specified map to this map (optional
* operation). The effect of this call is equivalent to that of calling
* {@link #put(Object,Object) put(k, v)} on this map once for each mapping
* from key <tt>k</tt> to value <tt>v</tt> in the specified map. The
* behavior of this operation is undefined if the specified map is modified
* while the operation is in progress.
*
* @param m mappings to be stored in this map
* @throws UnsupportedOperationException if the <tt>putAll</tt> operation is
* not supported by this map
* @throws ClassCastException if the class of a key or value in the
* specified map prevents it from being stored in this map
* @throws NullPointerException if the specified map is null, or if this map
* does not permit null keys or values, and the specified map
* contains null keys or values
* @throws IllegalArgumentException if some property of a key or value in
* the specified map prevents it from being stored in this map
*/
void putAll(Map<? extends K, ? extends V> m);
/**
* Copies all of the mappings from the specified MultiValuedMap to this map
* (optional operation). The effect of this call is equivalent to that of
* calling {@link #put(Object,Object) put(k, v)} on this map once for each
* mapping from key <tt>k</tt> to value <tt>v</tt> in the specified map. The
* behavior of this operation is undefined if the specified map is modified
* while the operation is in progress.
*
* @param m mappings to be stored in this map
* @throws UnsupportedOperationException if the <tt>putAll</tt> operation is
* not supported by this map
* @throws ClassCastException if the class of a key or value in the
* specified map prevents it from being stored in this map
* @throws NullPointerException if the specified map is null, or if this map
* does not permit null keys or values, and the specified map
* contains null keys or values
* @throws IllegalArgumentException if some property of a key or value in
* the specified map prevents it from being stored in this map
*/
void putAll(MultiValuedMap<? extends K, ? extends V> m);
/**
* Removes all values associated with the specified key.
* <p>
* Implementations typically return <code>null</code> from a subsequent
* <code>get(Object)</code>, however they may choose to return an empty
* collection.
*
* @param key the key to remove values from
* @return the <code>Collection</code> of values removed, implementations
* should return <code>null</code> for no mapping found, but may
* return an empty collection
* @throws UnsupportedOperationException if the map is unmodifiable
* @throws ClassCastException if the key is of an invalid type
* @throws NullPointerException if the key is null and null keys are invalid
*/
Collection<V> remove(Object key);
/**
* Removes a key-value mapping from the map.
* <p>
* The item is removed from the collection mapped to the specified key.
* Other values attached to that key are unaffected.
* <p>
* If the last value for a key is removed, implementations typically return
* <code>null</code> from a subsequent <code>get(Object)</code>, however
* they may choose to return an empty collection.
*
* @param key the key to remove from
* @param item the item to remove
* @return {@code true} if the mapping was removed, {@code false} otherwise
* @throws UnsupportedOperationException if the map is unmodifiable
* @throws ClassCastException if the key or value is of an invalid type
* @throws NullPointerException if the key or value is null and null is
* invalid
*/
boolean removeMapping(K key, V item);
/**
* Removes all of the mappings from this map (optional operation).
* The map will be empty after this call returns.
*
* @throws UnsupportedOperationException if the map is unmodifiable
*/
void clear();
// Views
/**
* Returns a {@link Collection} view of the mappings contained in this map.
* The collection is backed by the map, so changes to the map are reflected
* in this, and vice-versa.
*
* @return a set view of the mappings contained in this map
*/
Collection<Entry<K, V>> entries();
/**
* Returns a {@link Bag} view of the key mapping contained in this map.
* <p>
* Implementations typically return a Bag of keys with its values count as
* the count of the Bag. This bag is backed by the map, so any changes in
* the map is reflected here.
*
* @return a bag view of the key mapping contained in this map
*/
Bag<K> keys();
/**
* Returns a {@link Set} view of the keys contained in this map. The set is
* backed by the map, so changes to the map are reflected in the set, and
* vice-versa. If the map is modified while an iteration over the set is in
* progress (except through the iterator's own <tt>remove</tt> operation),
* the results of the iteration are undefined. The set supports element
* removal, which removes the corresponding mapping from the map, via the
* <tt>Iterator.remove</tt>, <tt>Set.remove</tt>, <tt>removeAll</tt>,
* <tt>retainAll</tt>, and <tt>clear</tt> operations. It does not support
* the <tt>add</tt> or <tt>addAll</tt> operations.
*
* @return a set view of the keys contained in this map
*/
Set<K> keySet();
/**
* Gets a collection containing all the values in the map.
* <p>
* Implementations typically return a collection containing the combination
* of values from all keys.
*
* @return a collection view of the values contained in this map
*/
Collection<V> values();
}

View File

@ -0,0 +1,694 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.collections4.multimap;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.commons.collections4.Bag;
import org.apache.commons.collections4.Factory;
import org.apache.commons.collections4.MultiValuedMap;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.bag.HashBag;
import org.apache.commons.collections4.functors.InstantiateFactory;
import org.apache.commons.collections4.iterators.EmptyIterator;
import org.apache.commons.collections4.iterators.IteratorChain;
import org.apache.commons.collections4.iterators.LazyIteratorChain;
import org.apache.commons.collections4.iterators.TransformIterator;
/**
* Abstract implementation of the {@link MultiValuedMap} interface to simplify
* the creation of subclass implementations.
* <p>
* Subclasses specify a Map implementation to use as the internal storage.
*
* @since 4.1
* @version $Id$
*/
public class AbstractMultiValuedMap<K, V> implements MultiValuedMap<K, V>, Serializable {
/** Serialization Version */
private static final long serialVersionUID = 7994988366330224277L;
/** The factory for creating value collections. */
private final Factory<? extends Collection<V>> collectionFactory;
/** The values view */
private transient Collection<V> valuesView;
/** The EntryValues view */
private transient EntryValues entryValuesView;
/** The KeyBag view */
private transient KeysBag keysBagView;
/** The map used to store the data */
private final Map<K, Collection<V>> map;
/**
* Constructor that wraps (not copies).
*
* @param <C> the collection type
* @param map the map to wrap, must not be null
* @param collectionClazz the collection class
* @throws IllegalArgumentException if the map is null
*/
@SuppressWarnings("unchecked")
protected <C extends Collection<V>> AbstractMultiValuedMap(final Map<K, ? super C> map,
final Class<C> collectionClazz) {
if (map == null) {
throw new IllegalArgumentException("Map must not be null");
}
this.map = (Map<K, Collection<V>>) map;
this.collectionFactory = new InstantiateFactory<C>(collectionClazz);
}
/**
* Gets the map being wrapped.
*
* @return the wrapped map
*/
protected Map<K, Collection<V>> getMap() {
return map;
}
/**
* {@inheritDoc}
*/
public boolean containsKey(Object key) {
return getMap().containsKey(key);
}
/**
* {@inheritDoc}
*/
public boolean containsValue(final Object value) {
final Set<Map.Entry<K, Collection<V>>> pairs = getMap().entrySet();
if (pairs != null) {
for (final Map.Entry<K, Collection<V>> entry : pairs) {
if (entry.getValue().contains(value)) {
return true;
}
}
}
return false;
}
/**
* {@inheritDoc}
*/
public boolean containsMapping(Object key, Object value) {
final Collection<V> col = get(key);
if (col == null) {
return false;
}
return col.contains(value);
}
/**
* {@inheritDoc}
*/
public Collection<Entry<K, V>> entries() {
return entryValuesView != null ? entryValuesView : (entryValuesView = new EntryValues());
}
/**
* Gets the collection of values associated with the specified key.
*
* @param key the key to retrieve
* @return the <code>Collection</code> of values, will return
* <code>null</code> for no mapping
* @throws ClassCastException if the key is of an invalid type
*/
public Collection<V> get(Object key) {
return getMap().get(key);
}
/**
* Removes all values associated with the specified key.
* <p>
* A subsequent <code>get(Object)</code> would return null collection.
*
* @param key the key to remove values from
* @return the <code>Collection</code> of values removed, will return
* <code>null</code> for no mapping found.
* @throws ClassCastException if the key is of an invalid type
*/
public Collection<V> remove(Object key) {
return getMap().remove(key);
}
/**
* Removes a specific value from map.
* <p>
* The item is removed from the collection mapped to the specified key.
* Other values attached to that key are unaffected.
* <p>
* If the last value for a key is removed, <code>null</code> would be
* returned from a subsequent <code>get(Object)</code>.
*
* @param key the key to remove from
* @param item the item to remove
* @return {@code true} if the mapping was removed, {@code false} otherwise
*/
public boolean removeMapping(K key, V item) {
boolean result = false;
final Collection<V> col = get(key);
if (col == null) {
return false;
}
result = col.remove(item);
if (!result) {
return false;
}
if (col.isEmpty()) {
remove(key);
}
return true;
}
/**
* {@inheritDoc}
*/
public boolean isEmpty() {
return getMap().isEmpty();
}
/**
* {@inheritDoc}
*/
public Set<K> keySet() {
return getMap().keySet();
}
/**
* {@inheritDoc}
*/
public int size() {
int size = 0;
for (Collection<V> col : getMap().values()) {
size += col.size();
}
return size;
}
/**
* Gets a collection containing all the values in the map.
* <p>
* Returns a collection containing all the values from all keys.
*
* @return a collection view of the values contained in this map
*/
public Collection<V> values() {
final Collection<V> vs = valuesView;
return vs != null ? vs : (valuesView = new Values());
}
/**
* {@inheritDoc}
*/
public void clear() {
getMap().clear();
}
/**
* Adds the value to the collection associated with the specified key.
* <p>
* Unlike a normal <code>Map</code> the previous value is not replaced.
* Instead the new value is added to the collection stored against the key.
*
* @param key the key to store against
* @param value the value to add to the collection at the key
* @return the value added if the map changed and null if the map did not
* change
*/
public V put(K key, V value) {
boolean result = false;
Collection<V> coll = get(key);
if (coll == null) {
coll = createCollection();
coll.add(value);
if (coll.size() > 0) {
// only add if non-zero size to maintain class state
getMap().put(key, coll);
result = true; // map definitely changed
}
} else {
result = coll.add(value);
}
return result ? value : null;
}
/**
* Copies all of the mappings from the specified map to this map. The effect
* of this call is equivalent to that of calling {@link #put(Object,Object)
* put(k, v)} on this map once for each mapping from key <tt>k</tt> to value
* <tt>v</tt> in the specified map. The behavior of this operation is
* undefined if the specified map is modified while the operation is in
* progress.
*
* @param map mappings to be stored in this map
*/
public void putAll(final Map<? extends K, ? extends V> map) {
if (map != null) {
for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
put((K) entry.getKey(), (V) entry.getValue());
}
}
}
/**
* Copies all of the mappings from the specified MultiValuedMap to this map.
* The effect of this call is equivalent to that of calling
* {@link #put(Object,Object) put(k, v)} on this map once for each mapping
* from key <tt>k</tt> to value <tt>v</tt> in the specified map. The
* behavior of this operation is undefined if the specified map is modified
* while the operation is in progress.
*
* @param map mappings to be stored in this map
*/
@SuppressWarnings("unchecked")
public void putAll(MultiValuedMap<? extends K, ? extends V> map) {
if (map != null) {
for (final K key : map.keySet()) {
putAll(key, (Collection<V>) map.get(key));
}
}
}
/**
* Returns a {@link Bag} view of the key mapping contained in this map.
* <p>
* Returns a Bag of keys with its values count as the count of the Bag. This
* bag is backed by the map, so any changes in the map is reflected here.
* Any method which modifies this bag like <tt>add</tt>, <tt>remove</tt>,
* <tt>Iterator.remove</tt> etc throws
* <code>UnsupportedOperationException</code>
*
* @return a bag view of the key mapping contained in this map
*/
public Bag<K> keys() {
return keysBagView != null ? keysBagView : (keysBagView = new KeysBag());
}
/**
* Adds Iterable values to the collection associated with the specified key.
*
* @param key the key to store against
* @param values the values to add to the collection at the key, null
* ignored
* @return true if this map changed
*/
public boolean putAll(final K key, final Iterable<? extends V> values) {
if (values == null || values.iterator() == null || !values.iterator().hasNext()) {
return false;
}
Iterator<? extends V> it = values.iterator();
boolean result = false;
Collection<V> coll = get(key);
if (coll == null) {
coll = createCollection(); // might produce a non-empty collection
while (it.hasNext()) {
coll.add(it.next());
}
if (coll.size() > 0) {
// only add if non-zero size to maintain class state
getMap().put(key, coll);
result = true; // map definitely changed
}
} else {
while (it.hasNext()) {
boolean tmpResult = coll.add(it.next());
if (!result && tmpResult) {
// If any one of the values have been added, the map has
// changed
result = true;
}
}
}
return result;
}
/**
* Gets an iterator for the collection mapped to the specified key.
*
* @param key the key to get an iterator for
* @return the iterator of the collection at the key, empty iterator if key
* not in map
*/
public Iterator<V> iterator(final Object key) {
if (!containsKey(key)) {
return EmptyIterator.<V> emptyIterator();
}
return new ValuesIterator(key);
}
/**
* Gets the size of the collection mapped to the specified key.
*
* @param key the key to get size for
* @return the size of the collection at the key, zero if key not in map
*/
public int size(final Object key) {
final Collection<V> coll = get(key);
if (coll == null) {
return 0;
}
return coll.size();
}
@SuppressWarnings("rawtypes")
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (obj instanceof MultiValuedMap == false) {
return false;
}
MultiValuedMap<?, ?> other = (MultiValuedMap<?, ?>) obj;
if (other.size() != size()) {
return false;
}
Iterator it = keySet().iterator();
while (it.hasNext()) {
Object key = it.next();
Collection<?> col = get(key);
Collection<?> otherCol = other.get(key);
if (otherCol == null) {
return false;
}
if (col.size() != otherCol.size()) {
return false;
}
for (Object value : col) {
if (!otherCol.contains(value)) {
return false;
}
}
}
return true;
}
@Override
public int hashCode() {
return getMap().hashCode();
}
@Override
public String toString() {
return getMap().toString();
}
// -----------------------------------------------------------------------
protected Collection<V> createCollection() {
return collectionFactory.create();
}
// -----------------------------------------------------------------------
/**
* Inner class that provides a Bag<K> keys view
*/
private class KeysBag implements Bag<K> {
public boolean addAll(Collection<? extends K> c) {
throw new UnsupportedOperationException();
}
public void clear() {
throw new UnsupportedOperationException();
}
public boolean contains(Object o) {
return getMap().containsKey(o);
}
public boolean isEmpty() {
return getMap().isEmpty();
}
public Object[] toArray() {
final Object[] result = new Object[size()];
int i = 0;
final Iterator<K> it = getMap().keySet().iterator();
while (it.hasNext()) {
final K current = it.next();
for (int index = getCount(current); index > 0; index--) {
result[i++] = current;
}
}
return result;
}
public <T> T[] toArray(T[] array) {
final int size = size();
if (array.length < size) {
@SuppressWarnings("unchecked")
// safe as both are of type T
final T[] unchecked = (T[]) Array.newInstance(array.getClass().getComponentType(), size);
array = unchecked;
}
int i = 0;
final Iterator<K> it = getMap().keySet().iterator();
while (it.hasNext()) {
final K current = it.next();
for (int index = getCount(current); index > 0; index--) {
// unsafe, will throw ArrayStoreException if types are not
// compatible, see javadoc
@SuppressWarnings("unchecked")
final T unchecked = (T) current;
array[i++] = unchecked;
}
}
while (i < array.length) {
array[i++] = null;
}
return array;
}
public int getCount(Object object) {
int count = 0;
Collection<V> col = AbstractMultiValuedMap.this.getMap().get(object);
if (col != null) {
count = col.size();
}
return count;
}
public boolean add(K object) {
throw new UnsupportedOperationException();
}
public boolean add(K object, int nCopies) {
throw new UnsupportedOperationException();
}
public boolean remove(Object object) {
throw new UnsupportedOperationException();
}
public boolean remove(Object object, int nCopies) {
throw new UnsupportedOperationException();
}
public Set<K> uniqueSet() {
return keySet();
}
public int size() {
return AbstractMultiValuedMap.this.size();
}
public boolean containsAll(Collection<?> coll) {
if (coll instanceof Bag) {
return containsAll((Bag<?>) coll);
}
return containsAll(new HashBag<Object>(coll));
}
private boolean containsAll(final Bag<?> other) {
final Iterator<?> it = other.uniqueSet().iterator();
while (it.hasNext()) {
final Object current = it.next();
if (getCount(current) < other.getCount(current)) {
return false;
}
}
return true;
}
public boolean removeAll(Collection<?> coll) {
throw new UnsupportedOperationException();
}
public boolean retainAll(Collection<?> coll) {
throw new UnsupportedOperationException();
}
public Iterator<K> iterator() {
return new LazyIteratorChain<K>() {
final Iterator<K> keyIterator = getMap().keySet().iterator();
@Override
protected Iterator<? extends K> nextIterator(int count) {
if (!keyIterator.hasNext()) {
return null;
}
final K key = keyIterator.next();
final Iterator<V> colIterator = getMap().get(key).iterator();
Iterator<K> nextIt = new Iterator<K>() {
public boolean hasNext() {
return colIterator.hasNext();
}
public K next() {
colIterator.next();// Increment the iterator
// The earlier statement would throw
// NoSuchElementException anyway in case it ends
return key;
}
public void remove() {
throw new UnsupportedOperationException();
}
};
return nextIt;
}
};
}
}
/**
* Inner class that provides the Entry<K, V> view
*/
private class EntryValues extends AbstractCollection<Entry<K, V>> {
@Override
public Iterator<Entry<K, V>> iterator() {
return new LazyIteratorChain<Entry<K, V>>() {
final Collection<K> keysCol = new ArrayList<K>(getMap().keySet());
final Iterator<K> keyIterator = keysCol.iterator();
@Override
protected Iterator<? extends Entry<K, V>> nextIterator(int count) {
if (!keyIterator.hasNext()) {
return null;
}
final K key = keyIterator.next();
final Transformer<V, Entry<K, V>> entryTransformer = new Transformer<V, Entry<K, V>>() {
public Entry<K, V> transform(final V input) {
return new Entry<K, V>() {
public K getKey() {
return key;
}
public V getValue() {
return input;
}
public V setValue(V value) {
throw new UnsupportedOperationException();
}
};
}
};
return new TransformIterator<V, Entry<K, V>>(new ValuesIterator(key), entryTransformer);
}
};
}
@Override
public int size() {
return AbstractMultiValuedMap.this.size();
}
}
/**
* Inner class that provides the values view.
*/
private class Values extends AbstractCollection<V> {
@Override
public Iterator<V> iterator() {
final IteratorChain<V> chain = new IteratorChain<V>();
for (final K k : keySet()) {
chain.addIterator(new ValuesIterator(k));
}
return chain;
}
@Override
public int size() {
return AbstractMultiValuedMap.this.size();
}
@Override
public void clear() {
AbstractMultiValuedMap.this.clear();
}
}
/**
* Inner class that provides the values iterator.
*/
private class ValuesIterator implements Iterator<V> {
private final Object key;
private final Collection<V> values;
private final Iterator<V> iterator;
public ValuesIterator(final Object key) {
this.key = key;
this.values = get(key);
this.iterator = values.iterator();
}
public void remove() {
iterator.remove();
if (values.isEmpty()) {
AbstractMultiValuedMap.this.remove(key);
}
}
public boolean hasNext() {
return iterator.hasNext();
}
public V next() {
return iterator.next();
}
}
}

View File

@ -0,0 +1,153 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.collections4.multimap;
import java.io.Serializable;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.commons.collections4.Bag;
import org.apache.commons.collections4.MultiValuedMap;
/**
* Decorates another <code>MultiValuedMap</code> to provide additional behaviour.
* <p>
* Each method call made on this <code>MultiValuedMap</code> is forwarded to the
* decorated <code>MultiValuedMap</code>. This class is used as a framework to
* build to extensions such as synchronized and unmodifiable behaviour.
*
* @param <K> the type of key elements
* @param <V> the type of value elements
*
* @since 4.1
* @version $Id$
*/
public class AbstractMultiValuedMapDecorator<K, V>
implements MultiValuedMap<K, V>, Serializable {
/** Serialization version */
private static final long serialVersionUID = -9184930955231260637L;
/** MultiValuedMap to decorate */
private final MultiValuedMap<K, V> map;
/**
* Constructor that wraps (not copies).
*
* @param map the map to decorate, must not be null
* @throws IllegalArgumentException if the map is null
*/
protected AbstractMultiValuedMapDecorator(final MultiValuedMap<K, V> map) {
if (map == null) {
throw new IllegalArgumentException("MultiValuedMap must not be null");
}
this.map = map;
}
protected MultiValuedMap<K, V> decorated() {
return map;
}
public int size() {
return decorated().size();
}
public boolean isEmpty() {
return decorated().isEmpty();
}
public boolean containsKey(Object key) {
return decorated().containsKey(key);
}
public boolean containsValue(Object value) {
return decorated().containsValue(value);
}
public boolean containsMapping(Object key, Object value) {
return decorated().containsMapping(key, value);
}
public Collection<V> get(Object key) {
return decorated().get(key);
}
public Collection<V> remove(Object key) {
return decorated().remove(key);
}
public boolean removeMapping(K key, V item) {
return decorated().removeMapping(key, item);
}
public void clear() {
decorated().clear();
}
public V put(K key, V value) {
return decorated().put(key, value);
}
public Set<K> keySet() {
return decorated().keySet();
}
public Collection<Entry<K, V>> entries() {
return decorated().entries();
}
public Bag<K> keys() {
return decorated().keys();
}
public Collection<V> values() {
return decorated().values();
}
public boolean putAll(K key, Iterable<? extends V> values) {
return decorated().putAll(key, values);
}
public void putAll(Map<? extends K, ? extends V> m) {
decorated().putAll(m);
}
public void putAll(MultiValuedMap<? extends K, ? extends V> m) {
decorated().putAll(m);
}
@Override
public boolean equals(final Object object) {
if (object == this) {
return true;
}
return decorated().equals(object);
}
@Override
public int hashCode() {
return decorated().hashCode();
}
@Override
public String toString() {
return decorated().toString();
}
}

View File

@ -0,0 +1,111 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.collections4.multimap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.collections4.MultiValuedMap;
/**
* Implements a {@link MultiValuedMap}, using a {@link HashMap} to provide data
* storage. This is the standard implementation of a MultiValuedMap
* <p>
* A <code>MultiValuedMap</code> is a Map with slightly different semantics.
* Putting a value into the map will add the value to a Collection at that key.
* Getting a value will return a Collection, holding all the values put to that
* key
* <p>
* In addition, this implementation allows the type of collection used for the
* values to be controlled. By default, an <code>ArrayList</code> is used,
* however a <code>Class<? extends Collection></code> to instantiate the value
* collection may be specified.
* <p>
* <strong>Note that MultiValuedHashMap is not synchronized and is not
* thread-safe.</strong> If you wish to use this map from multiple threads
* concurrently, you must use appropriate synchronization. This class may throw
* exceptions when accessed by concurrent threads without synchronization.
*
* @since 4.1
* @version $Id$
*/
public class MultiValuedHashMap<K, V> extends AbstractMultiValuedMap<K, V> implements MultiValuedMap<K, V> {
/** Serialization Version */
private static final long serialVersionUID = -5845183518195365857L;
/**
* Creates a MultiValuedHashMap which maps keys to collections of type
* <code>collectionClass</code>.
*
* @param <K> the key type
* @param <V> the value type
* @param <C> the collection class type
* @param collectionClass the type of the collection class
* @return a new MultiValuedMap
*/
public static <K, V, C extends Collection<V>> MultiValuedMap<K, V> multiValuedMap(
final Class<C> collectionClass) {
return new MultiValuedHashMap<K, V>(collectionClass);
}
/**
* Creates a MultiValueMap based on a <code>HashMap</code> which stores the
* multiple values in an <code>ArrayList</code>.
*/
@SuppressWarnings("unchecked")
public MultiValuedHashMap() {
this(ArrayList.class);
}
/**
* Creates a MultiValuedHashMap copying all the mappings of the given map.
*
* @param map a <code>MultiValuedMap</code> to copy into this map
*/
@SuppressWarnings("unchecked")
public MultiValuedHashMap(final MultiValuedMap<? extends K, ? extends V> map) {
this(ArrayList.class);
super.putAll(map);
}
/**
* Creates a MultiValuedHashMap copying all the mappings of the given map.
*
* @param map a <code>Map</code> to copy into this map
*/
@SuppressWarnings("unchecked")
public MultiValuedHashMap(final Map<? extends K, ? extends V> map) {
this(ArrayList.class);
super.putAll(map);
}
/**
* Creates a MultiValuedHashMap which creates the value collections using
* the supplied <code>collectionClazz</code>.
*
* @param <C> the collection type
* @param collectionClazz the class of the <code>Collection</code> to use to
* create the value collections
*/
protected <C extends Collection<V>> MultiValuedHashMap(final Class<C> collectionClazz) {
super(new HashMap<K, Collection<V>>(), collectionClazz);
}
}

View File

@ -0,0 +1,236 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.collections4.multimap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.commons.collections4.MultiValuedMap;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.map.LinkedMap;
/**
* Decorates another <code>MultiValuedMap</code> to transform objects that are added.
* <p>
* This class affects the MultiValuedMap put methods. Thus objects must be
* removed or searched for using their transformed form. For example, if the
* transformation converts Strings to Integers, you must use the Integer form to
* remove objects.
* <p>
* <strong>Note that TransformedMultiValuedMap is not synchronized and is not thread-safe.</strong>
*
* @since 4.1
* @version $Id$
*/
public class TransformedMultiValuedMap<K, V> extends AbstractMultiValuedMapDecorator<K, V> {
/** Serialization Version */
private static final long serialVersionUID = -1254147899086470720L;
private final Transformer<? super K, ? extends K> keyTransformer;
private final Transformer<? super V, ? extends V> valueTransformer;
/**
* Factory method to create a transforming MultiValuedMap.
* <p>
* If there are any elements already in the map being decorated, they are
* NOT transformed. Contrast this with
* {@link #transformedMap(MultiValuedMap, Transformer, Transformer)}.
*
* @param <K> the key type
* @param <V> the value type
* @param map the MultiValuedMap to decorate, must not be null
* @param keyTransformer the transformer to use for key conversion, null
* means no transformation
* @param valueTransformer the transformer to use for value conversion, null
* means no transformation
* @return a new transformed MultiValuedMap
* @throws IllegalArgumentException if map is null
*/
public static <K, V> TransformedMultiValuedMap<K, V> transformingMap(final MultiValuedMap<K, V> map,
final Transformer<? super K, ? extends K> keyTransformer,
final Transformer<? super V, ? extends V> valueTransformer) {
return new TransformedMultiValuedMap<K, V>(map, keyTransformer, valueTransformer);
}
/**
* Factory method to create a transforming MultiValuedMap that will
* transform existing contents of the specified map.
* <p>
* If there are any elements already in the map being decorated, they will
* be transformed by this method. Contrast this with
* {@link #transformingMap(MultiValuedMap, Transformer, Transformer)}.
*
* @param <K> the key type
* @param <V> the value type
* @param map the MultiValuedMap to decorate, must not be null
* @param keyTransformer the transformer to use for key conversion, null
* means no transformation
* @param valueTransformer the transformer to use for value conversion, null
* means no transformation
* @return a new transformed MultiValuedMap
* @throws IllegalArgumentException if map is null
*/
public static <K, V> TransformedMultiValuedMap<K, V> transformedMap(final MultiValuedMap<K, V> map,
final Transformer<? super K, ? extends K> keyTransformer,
final Transformer<? super V, ? extends V> valueTransformer) {
final TransformedMultiValuedMap<K, V> decorated =
new TransformedMultiValuedMap<K, V>(map, keyTransformer, valueTransformer);
if (map.size() > 0) {
MultiValuedMap<K, V> transformed = decorated.transformMultiValuedMap(map);
decorated.clear();
// to avoid double transform
decorated.decorated().putAll(transformed);
}
return decorated;
}
// -----------------------------------------------------------------------
/**
* Constructor that wraps (not copies).
* <p>
* If there are any elements already in the collection being decorated, they
* are NOT transformed.
*
* @param map the MultiValuedMap to decorate, must not be null
* @param keyTransformer the transformer to use for key conversion, null
* means no conversion
* @param valueTransformer the transformer to use for value conversion, null
* means no conversion
* @throws IllegalArgumentException if map is null
*/
protected TransformedMultiValuedMap(MultiValuedMap<K, V> map,
Transformer<? super K, ? extends K> keyTransformer, Transformer<? super V, ? extends V> valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
/**
* Transforms a key.
* <p>
* The transformer itself may throw an exception if necessary.
*
* @param object the object to transform
* @return the transformed object
*/
protected K transformKey(final K object) {
if (keyTransformer == null) {
return object;
}
return keyTransformer.transform(object);
}
/**
* Transforms a value.
* <p>
* The transformer itself may throw an exception if necessary.
*
* @param object the object to transform
* @return the transformed object
*/
protected V transformValue(final V object) {
if (valueTransformer == null) {
return object;
}
return valueTransformer.transform(object);
}
/**
* Transforms a map.
* <p>
* The transformer itself may throw an exception if necessary.
*
* @param map the map to transform
* @return the transformed object
*/
@SuppressWarnings("unchecked")
protected Map<K, V> transformMap(final Map<? extends K, ? extends V> map) {
if (map.isEmpty()) {
return (Map<K, V>) map;
}
final Map<K, V> result = new LinkedMap<K, V>(map.size());
for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
result.put(transformKey(entry.getKey()), transformValue(entry.getValue()));
}
return result;
}
/**
* Transforms a MultiValuedMap.
* <p>
* The transformer itself may throw an exception if necessary.
*
* @param map the MultiValuedMap to transform
* @return the transformed object
*/
@SuppressWarnings("unchecked")
protected MultiValuedMap<K, V> transformMultiValuedMap(final MultiValuedMap<? extends K, ? extends V> map) {
if (map.isEmpty()) {
return (MultiValuedMap<K, V>) map;
}
final MultiValuedMap<K, V> result = new MultiValuedHashMap<K, V>();
for (final Map.Entry<? extends K, ? extends V> entry : map.entries()) {
result.put(transformKey(entry.getKey()), transformValue(entry.getValue()));
}
return result;
}
@Override
public V put(K key, V value) {
K transformedKey = transformKey(key);
V transformedValue = transformValue(value);
return decorated().put(transformedKey, transformedValue);
}
@Override
@SuppressWarnings("unchecked")
public boolean putAll(K key, Iterable<? extends V> values) {
if (values == null || values.iterator() == null || !values.iterator().hasNext()) {
return false;
}
K transformedKey = transformKey(key);
List<V> transformedValues = new LinkedList<V>();
Iterator<V> it = (Iterator<V>) values.iterator();
while (it.hasNext()) {
transformedValues.add(transformValue(it.next()));
}
return decorated().putAll(transformedKey, transformedValues);
}
@Override
public void putAll(Map<? extends K, ? extends V> m) {
if (m == null) {
return;
}
decorated().putAll(transformMap(m));
}
@Override
public void putAll(MultiValuedMap<? extends K, ? extends V> m) {
if (m == null) {
return;
}
decorated().putAll(transformMultiValuedMap(m));
}
}

View File

@ -0,0 +1,134 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.collections4.multimap;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.commons.collections4.Bag;
import org.apache.commons.collections4.MultiValuedMap;
import org.apache.commons.collections4.Unmodifiable;
import org.apache.commons.collections4.bag.UnmodifiableBag;
import org.apache.commons.collections4.collection.UnmodifiableCollection;
import org.apache.commons.collections4.set.UnmodifiableSet;
/**
* Decorates another {@link MultiValuedMap} to ensure it can't be altered.
* <p>
* Attempts to modify it will result in an UnsupportedOperationException.
*
* @param <K> the type of key elements
* @param <V> the type of value elements
*
* @since 4.1
* @version $Id$
*/
public class UnmodifiableMultiValuedMap<K, V>
extends AbstractMultiValuedMapDecorator<K, V> implements Unmodifiable {
/** Serialization version */
private static final long serialVersionUID = 1418669828214151566L;
/**
* Factory method to create an unmodifiable MultiValuedMap.
* <p>
* If the map passed in is already unmodifiable, it is returned.
*
* @param <K> the type of key elements
* @param <V> the type of value elements
* @param map the map to decorate, must not be null
* @return an unmodifiable MultiValuedMap
* @throws IllegalArgumentException if map is null
*/
@SuppressWarnings("unchecked")
public static <K, V> UnmodifiableMultiValuedMap<K, V>
unmodifiableMultiValuedMap(MultiValuedMap<? extends K, ? extends V> map) {
if (map instanceof Unmodifiable) {
return (UnmodifiableMultiValuedMap<K, V>) map;
}
return new UnmodifiableMultiValuedMap<K, V>(map);
}
/**
* Constructor that wraps (not copies).
*
* @param map the MultiValuedMap to decorate, must not be null
* @throws IllegalArgumentException if the map is null
*/
@SuppressWarnings("unchecked")
private UnmodifiableMultiValuedMap(final MultiValuedMap<? extends K, ? extends V> map) {
super((MultiValuedMap<K, V>) map);
}
@Override
public Collection<V> remove(Object key) {
throw new UnsupportedOperationException();
}
@Override
public boolean removeMapping(K key, V item) {
throw new UnsupportedOperationException();
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
@Override
public V put(K key, V value) {
throw new UnsupportedOperationException();
}
@Override
public Set<K> keySet() {
return UnmodifiableSet.<K>unmodifiableSet(decorated().keySet());
}
@Override
public Collection<Entry<K, V>> entries() {
return UnmodifiableCollection.<Entry<K, V>>unmodifiableCollection(decorated().entries());
}
@Override
public Bag<K> keys() {
return UnmodifiableBag.<K>unmodifiableBag(decorated().keys());
}
@Override
public Collection<V> values() {
return UnmodifiableCollection.<V>unmodifiableCollection(decorated().values());
}
@Override
public boolean putAll(K key, Iterable<? extends V> values) {
throw new UnsupportedOperationException();
}
@Override
public void putAll(Map<? extends K, ? extends V> m) {
throw new UnsupportedOperationException();
}
@Override
public void putAll(MultiValuedMap<? extends K, ? extends V> m) {
throw new UnsupportedOperationException();
}
}

View File

@ -0,0 +1,34 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* This package contains implementations of the {@link org.apache.commons.collections4.MultiValuedMap} interfaces.
* A MultiValuedMap holds a collection of values against each key.
* <p>
* The following implementations are provided in the package:
* <ul>
* <li>MultiValuedHashMap - implementation that uses a HashMap to store the data
* </ul>
* <p>
* The following decorators are provided in the package:
* <ul>
* <li>Transformed - transforms elements added to the MultiValuedMap
* <li>Unmodifiable - ensures the collection cannot be altered
* </ul>
*
* @version $Id$
*/
package org.apache.commons.collections4.multimap;

View File

@ -0,0 +1,494 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.collections4.multimap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import org.apache.commons.collections4.AbstractObjectTest;
import org.apache.commons.collections4.Bag;
import org.apache.commons.collections4.MultiValuedMap;
import org.apache.commons.collections4.bag.HashBag;
/**
* Abstract test class for {@link MultiValuedMap} contract and methods.
* <p>
* To use, extend this class and implement the {@link #makeObject} method and if
* necessary override the {@link #makeFullMap()} method.
*
* @since 4.1
* @version $Id$
*/
public abstract class AbstractMultiValuedMapTest<K, V> extends AbstractObjectTest {
public AbstractMultiValuedMapTest(String testName) {
super(testName);
}
@Override
abstract public MultiValuedMap<K, V> makeObject();
@Override
public String getCompatibilityVersion() {
return "4.1"; // MultiValuedMap has been added in version 4.1
}
/**
* Returns true if the maps produced by {@link #makeObject()} and
* {@link #makeFullMap()} support the <code>put</code> and
* <code>putAll</code> operations adding new mappings.
* <p>
* Default implementation returns true. Override if your collection class
* does not support put adding.
*/
public boolean isAddSupported() {
return true;
}
/**
* Returns true if the maps produced by {@link #makeObject()} and
* {@link #makeFullMap()} support the <code>remove</code> and
* <code>clear</code> operations.
* <p>
* Default implementation returns true. Override if your collection class
* does not support removal operations.
*/
public boolean isRemoveSupported() {
return true;
}
protected MultiValuedMap<K, V> makeFullMap() {
final MultiValuedMap<K, V> map = makeObject();
addSampleMappings(map);
return map;
}
@SuppressWarnings("unchecked")
protected void addSampleMappings(MultiValuedMap<? super K, ? super V> map) {
map.put((K) "one", (V) "uno");
map.put((K) "one", (V) "un");
map.put((K) "two", (V) "dos");
map.put((K) "two", (V) "deux");
map.put((K) "three", (V) "tres");
map.put((K) "three", (V) "trois");
}
public void testNoMappingReturnsNull() {
final MultiValuedMap<K, V> map = makeFullMap();
assertNull(map.get("whatever"));
}
public void testMultipleValues() {
final MultiValuedMap<K, V> map = makeFullMap();
Collection<V> col = map.get("one");
assertTrue(col.contains("uno"));
assertTrue(col.contains("un"));
}
public void testGet() {
final MultiValuedMap<K, V> map = makeFullMap();
assertTrue(map.get("one").contains("uno"));
assertTrue(map.get("one").contains("un"));
assertTrue(map.get("two").contains("dos"));
assertTrue(map.get("two").contains("deux"));
assertTrue(map.get("three").contains("tres"));
assertTrue(map.get("three").contains("trois"));
}
public void testContainsValue() {
final MultiValuedMap<K, V> map = makeFullMap();
assertTrue(map.containsValue("uno"));
assertTrue(map.containsValue("un"));
assertTrue(map.containsValue("dos"));
assertTrue(map.containsValue("deux"));
assertTrue(map.containsValue("tres"));
assertTrue(map.containsValue("trois"));
assertFalse(map.containsValue("quatro"));
}
public void testKeyContainsValue() {
final MultiValuedMap<K, V> map = makeFullMap();
assertTrue(map.containsMapping("one", "uno"));
assertTrue(map.containsMapping("one", "un"));
assertTrue(map.containsMapping("two", "dos"));
assertTrue(map.containsMapping("two", "deux"));
assertTrue(map.containsMapping("three", "tres"));
assertTrue(map.containsMapping("three", "trois"));
assertFalse(map.containsMapping("four", "quatro"));
}
@SuppressWarnings("unchecked")
public void testValues() {
final MultiValuedMap<K, V> map = makeFullMap();
final HashSet<V> expected = new HashSet<V>();
expected.add((V) "uno");
expected.add((V) "dos");
expected.add((V) "tres");
expected.add((V) "un");
expected.add((V) "deux");
expected.add((V) "trois");
final Collection<V> c = map.values();
assertEquals(6, c.size());
assertEquals(expected, new HashSet<V>(c));
}
// public void testKeyedIterator() {
// final MultiValuedMap<K, V> map = makeFullMap();
// final ArrayList<Object> actual = new ArrayList<Object>(IteratorUtils.toList(map.iterator("one")));
// final ArrayList<Object> expected = new ArrayList<Object>(Arrays.asList("uno", "un"));
// assertEquals(expected, actual);
// }
public void testRemoveAllViaIterator() {
if (!isRemoveSupported()) {
return;
}
final MultiValuedMap<K, V> map = makeFullMap();
for (final Iterator<?> i = map.values().iterator(); i.hasNext();) {
i.next();
i.remove();
}
assertNull(map.get("one"));
assertTrue(map.isEmpty());
}
// public void testRemoveAllViaKeyedIterator() {
// if (!isRemoveSupported()) {
// return;
// }
// final MultiValuedMap<K, V> map = makeFullMap();
// for (final Iterator<?> i = map.iterator("one"); i.hasNext();) {
// i.next();
// i.remove();
// }
// assertNull(map.get("one"));
// assertEquals(4, map.size());
// }
public void testEntriesCollectionIterator() {
final MultiValuedMap<K, V> map = makeFullMap();
Collection<V> values = new ArrayList<V>(map.values());
Iterator<Map.Entry<K, V>> iterator = map.entries().iterator();
while (iterator.hasNext()) {
Map.Entry<K, V> entry = iterator.next();
assertTrue(map.containsMapping(entry.getKey(), entry.getValue()));
assertTrue(values.contains(entry.getValue()));
if (isRemoveSupported()) {
assertTrue(values.remove(entry.getValue()));
}
}
if (isRemoveSupported()) {
assertTrue(values.isEmpty());
}
}
public void testRemoveAllViaEntriesIterator() {
if (!isRemoveSupported()) {
return;
}
final MultiValuedMap<K, V> map = makeFullMap();
for (final Iterator<?> i = map.entries().iterator(); i.hasNext();) {
i.next();
i.remove();
}
assertNull(map.get("one"));
assertEquals(0, map.size());
}
public void testSize() {
assertEquals(6, makeFullMap().size());
}
// -----------------------------------------------------------------------
@SuppressWarnings("unchecked")
public void testMapEquals() {
if (!isAddSupported()) {
return;
}
final MultiValuedMap<K, V> one = makeObject();
final Integer value = Integer.valueOf(1);
one.put((K) "One", (V) value);
one.removeMapping((K) "One", (V) value);
final MultiValuedMap<K, V> two = makeObject();
assertEquals(two, one);
}
@SuppressWarnings("unchecked")
public void testSizeWithPutRemove() {
if (!isRemoveSupported() || !isAddSupported()) {
return;
}
final MultiValuedMap<K, V> map = makeObject();
assertEquals(0, map.size());
map.put((K) "A", (V) "AA");
assertEquals(1, map.size());
map.put((K) "B", (V) "BA");
assertEquals(2, map.size());
map.put((K) "B", (V) "BB");
assertEquals(3, map.size());
map.put((K) "B", (V) "BC");
assertEquals(4, map.size());
map.remove("A");
assertEquals(3, map.size());
map.removeMapping((K) "B", (V) "BC");
assertEquals(2, map.size());
}
public void testKeySetSize() {
final MultiValuedMap<K, V> map = makeFullMap();
assertEquals(3, map.keySet().size());
}
@SuppressWarnings("unchecked")
public void testSize_Key() {
final MultiValuedMap<K, V> map = makeFullMap();
assertEquals(2, map.get("one").size());
assertEquals(2, map.get("two").size());
assertEquals(2, map.get("three").size());
if (!isAddSupported()) {
return;
}
map.put((K) "A", (V) "AA");
assertEquals(1, map.get("A").size());
//assertEquals(0, map.get("B").size());
map.put((K) "B", (V) "BA");
assertEquals(1, map.get("A").size());
assertEquals(1, map.get("B").size());
map.put((K) "B", (V) "BB");
assertEquals(1, map.get("A").size());
assertEquals(2, map.get("B").size());
map.put((K) "B", (V) "BC");
assertEquals(1, map.get("A").size());
assertEquals(3, map.get("B").size());
if (!isRemoveSupported()) {
return;
}
map.remove("A");
//assertEquals(0, map.get("A").size());
assertEquals(3, map.get("B").size());
map.removeMapping((K) "B", (V) "BC");
//assertEquals(0, map.get("A").size());
assertEquals(2, map.get("B").size());
}
// @SuppressWarnings("unchecked")
// public void testIterator_Key() {
// final MultiValuedMap<K, V> map = makeFullMap();
// Iterator<V> it = map.iterator("one");
// assertEquals(true, it.hasNext());
// Set<V> values = new HashSet<V>();
// while (it.hasNext()) {
// values.add(it.next());
// }
// assertEquals(true, values.contains("un"));
// assertEquals(true, values.contains("uno"));
// assertEquals(false, map.iterator("A").hasNext());
// assertEquals(false, map.iterator("A").hasNext());
// if (!isAddSupported()) {
// return;
// }
// map.put((K) "A", (V) "AA");
// it = map.iterator("A");
// assertEquals(true, it.hasNext());
// it.next();
// assertEquals(false, it.hasNext());
// }
@SuppressWarnings("unchecked")
public void testContainsValue_Key() {
final MultiValuedMap<K, V> map = makeFullMap();
assertEquals(true, map.containsMapping("one", (V) "uno"));
assertEquals(false, map.containsMapping("two", (V) "2"));
if (!isAddSupported()) {
return;
}
map.put((K) "A", (V) "AA");
assertEquals(true, map.containsMapping("A", (V) "AA"));
assertEquals(false, map.containsMapping("A", (V) "AB"));
}
@SuppressWarnings("unchecked")
public void testPutAll_Map1() {
if (!isAddSupported()) {
return;
}
final MultiValuedMap<K, V> original = makeObject();
original.put((K) "key", (V) "object1");
original.put((K) "key", (V) "object2");
final MultiValuedMap<K, V> test = makeObject();
test.put((K) "keyA", (V) "objectA");
test.put((K) "key", (V) "object0");
test.putAll(original);
assertEquals(2, test.keySet().size());
assertEquals(4, test.size());
assertEquals(1, test.get("keyA").size());
assertEquals(3, test.get("key").size());
assertEquals(true, test.containsValue("objectA"));
assertEquals(true, test.containsValue("object0"));
assertEquals(true, test.containsValue("object1"));
assertEquals(true, test.containsValue("object2"));
}
@SuppressWarnings("unchecked")
public void testPutAll_Map2() {
if (!isAddSupported()) {
return;
}
final Map<K, V> original = new HashMap<K, V>();
original.put((K) "keyX", (V) "object1");
original.put((K) "keyY", (V) "object2");
final MultiValuedMap<K, V> test = makeObject();
test.put((K) "keyA", (V) "objectA");
test.put((K) "keyX", (V) "object0");
test.putAll(original);
assertEquals(3, test.keySet().size());
assertEquals(4, test.size());
assertEquals(1, test.get("keyA").size());
assertEquals(2, test.get("keyX").size());
assertEquals(1, test.get("keyY").size());
assertEquals(true, test.containsValue("objectA"));
assertEquals(true, test.containsValue("object0"));
assertEquals(true, test.containsValue("object1"));
assertEquals(true, test.containsValue("object2"));
}
@SuppressWarnings("unchecked")
public void testPutAll_KeyIterable() {
if (!isAddSupported()) {
return;
}
final MultiValuedMap<K, V> map = makeObject();
Collection<V> coll = (Collection<V>) Arrays.asList("X", "Y", "Z");
assertEquals(true, map.putAll((K) "A", coll));
assertEquals(3, map.get("A").size());
assertEquals(true, map.containsMapping("A", (V) "X"));
assertEquals(true, map.containsMapping("A", (V) "Y"));
assertEquals(true, map.containsMapping("A", (V) "Z"));
assertEquals(false, map.putAll((K) "A", null));
assertEquals(3, map.get("A").size());
assertEquals(true, map.containsMapping("A", (V) "X"));
assertEquals(true, map.containsMapping("A", (V) "Y"));
assertEquals(true, map.containsMapping("A", (V) "Z"));
assertEquals(false, map.putAll((K) "A", new ArrayList<V>()));
assertEquals(3, map.get("A").size());
assertEquals(true, map.containsMapping("A", (V) "X"));
assertEquals(true, map.containsMapping("A", (V) "Y"));
assertEquals(true, map.containsMapping("A", (V) "Z"));
coll = (Collection<V>) Arrays.asList("M");
assertEquals(true, map.putAll((K) "A", coll));
assertEquals(4, map.get("A").size());
assertEquals(true, map.containsMapping("A", (V) "X"));
assertEquals(true, map.containsMapping("A", (V) "Y"));
assertEquals(true, map.containsMapping("A", (V) "Z"));
assertEquals(true, map.containsMapping("A", (V) "M"));
}
@SuppressWarnings("unchecked")
public void testRemove_KeyItem() {
if (!isRemoveSupported() || !isAddSupported()) {
return;
}
final MultiValuedMap<K, V> map = makeObject();
map.put((K) "A", (V) "AA");
map.put((K) "A", (V) "AB");
map.put((K) "A", (V) "AC");
assertEquals(false, map.removeMapping((K) "C", (V) "CA"));
assertEquals(false, map.removeMapping((K) "A", (V) "AD"));
assertEquals(true, map.removeMapping((K) "A", (V) "AC"));
assertEquals(true, map.removeMapping((K) "A", (V) "AB"));
assertEquals(true, map.removeMapping((K) "A", (V) "AA"));
//assertEquals(new MultiValuedHashMap<K, V>(), map);
}
public void testKeysBag() {
MultiValuedMap<K, V> map = makeFullMap();
Bag<K> keyBag = map.keys();
assertEquals(2, keyBag.getCount("one"));
assertEquals(2, keyBag.getCount("two"));
assertEquals(2, keyBag.getCount("three"));
assertEquals(6, keyBag.size());
}
public void testKeysBagIterator() {
MultiValuedMap<K, V> map = makeFullMap();
Collection<K> col = new ArrayList<K>();
Iterator<K> it = map.keys().iterator();
while (it.hasNext()) {
col.add(it.next());
}
Bag<K> bag = new HashBag<K>(col);
assertEquals(2, bag.getCount("one"));
assertEquals(2, bag.getCount("two"));
assertEquals(2, bag.getCount("three"));
assertEquals(6, bag.size());
}
@SuppressWarnings("unchecked")
public void testKeysBagContainsAll() {
MultiValuedMap<K, V> map = makeFullMap();
Bag<K> keyBag = map.keys();
Collection<K> col = (Collection<K>) Arrays.asList("one", "two", "three", "one", "two", "three");
assertTrue(keyBag.containsAll(col));
}
// public void testMapEqulas() {
// MultiValuedMap<K, V> map1 = makeFullMap();
// MultiValuedMap<K, V> map2 = makeFullMap();
// assertEquals(true, map1.equals(map2));
// }
// -----------------------------------------------------------------------
// Manual serialization testing as this class cannot easily
// extend the AbstractTestMap
// -----------------------------------------------------------------------
public void testEmptyMapCompatibility() throws Exception {
final MultiValuedMap<?, ?> map = makeObject();
final MultiValuedMap<?, ?> map2 = (MultiValuedMap<?, ?>) readExternalFormFromDisk(getCanonicalEmptyCollectionName(map));
assertEquals("Map is empty", 0, map2.size());
}
public void testFullMapCompatibility() throws Exception {
final MultiValuedMap<?, ?> map = (MultiValuedMap<?, ?>) makeFullMap();
final MultiValuedMap<?, ?> map2 = (MultiValuedMap<?, ?>) readExternalFormFromDisk(getCanonicalFullCollectionName(map));
assertEquals("Map is the right size", map.size(), map2.size());
for (final Object key : map.keySet()) {
assertEquals("Map had inequal elements", map.get(key), map2.get(key));
if (isRemoveSupported()) {
map2.remove(key);
}
}
if (isRemoveSupported()) {
assertEquals("Map had extra values", 0, map2.size());
}
}
}

View File

@ -0,0 +1,85 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.collections4.multimap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import org.apache.commons.collections4.MultiValuedMap;
/**
* Test MultValuedHashMap
*
* @since 4.1
* @version $Id$
*/
public class MultiValuedHashMapTest<K, V> extends AbstractMultiValuedMapTest<K, V> {
public MultiValuedHashMapTest(String testName) {
super(testName);
}
@Override
public MultiValuedMap<K, V> makeObject() {
final MultiValuedMap<K, V> m = new MultiValuedHashMap<K, V>();
return m;
}
private <C extends Collection<V>> MultiValuedHashMap<K, V> createTestMap(final Class<C> collectionClass) {
final MultiValuedHashMap<K, V> map =
(MultiValuedHashMap<K, V>) MultiValuedHashMap.<K, V, C> multiValuedMap(collectionClass);
addSampleMappings(map);
return map;
}
@SuppressWarnings("unchecked")
public void testValueCollectionType() {
final MultiValuedHashMap<K, V> map = createTestMap(LinkedList.class);
assertTrue(map.get("one") instanceof LinkedList);
}
@SuppressWarnings("unchecked")
public void testPutWithList() {
final MultiValuedHashMap<K, V> test = (MultiValuedHashMap<K, V>) MultiValuedHashMap.multiValuedMap(ArrayList.class);
assertEquals("a", test.put((K) "A", (V) "a"));
assertEquals("b", test.put((K) "A", (V) "b"));
assertEquals(1, test.keySet().size());
assertEquals(2, test.size("A"));
assertEquals(2, test.size());
}
@SuppressWarnings("unchecked")
public void testPutWithSet() {
final MultiValuedHashMap<K, V> test = (MultiValuedHashMap<K, V>) MultiValuedHashMap.multiValuedMap(HashSet.class);
assertEquals("a", test.put((K) "A", (V) "a"));
assertEquals("b", test.put((K) "A", (V) "b"));
assertEquals(null, test.put((K) "A", (V) "a"));
assertEquals(1, test.keySet().size());
assertEquals(2, test.size("A"));
assertEquals(2, test.size());
}
// public void testCreate() throws Exception {
// writeExternalFormToDisk((java.io.Serializable) makeObject(),
// "src/test/resources/data/test/MultiValuedHashMap.emptyCollection.version4.1.obj");
// writeExternalFormToDisk((java.io.Serializable) makeFullMap(),
// "src/test/resources/data/test/MultiValuedHashMap.fullCollection.version4.1.obj");
// }
}

View File

@ -0,0 +1,132 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.collections4.multimap;
import org.apache.commons.collections4.MultiValuedMap;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.TransformerUtils;
import org.apache.commons.collections4.collection.TransformedCollectionTest;
import org.apache.commons.collections4.multimap.TransformedMultiValuedMap;
/**
* Tests for TransformedMultiValuedMap
*
* @since 4.1
* @version $Id$
*/
public class TransformedMultiValuedMapTest<K, V> extends AbstractMultiValuedMapTest<K, V> {
public TransformedMultiValuedMapTest(String testName) {
super(testName);
}
@Override
public MultiValuedMap<K, V> makeObject() {
return TransformedMultiValuedMap.transformingMap(new MultiValuedHashMap<K, V>(),
TransformerUtils.<K> nopTransformer(), TransformerUtils.<V> nopTransformer());
}
@SuppressWarnings("unchecked")
public void testKeyTransformedMap() {
final Object[] els = new Object[] { "1", "3", "5", "7", "2", "4", "6" };
MultiValuedMap<K, V> map = TransformedMultiValuedMap.transformingMap(
new MultiValuedHashMap<K, V>(),
(Transformer<? super K, ? extends K>) TransformedCollectionTest.STRING_TO_INTEGER_TRANSFORMER,
null);
assertEquals(0, map.size());
for (int i = 0; i < els.length; i++) {
map.put((K) els[i], (V) els[i]);
assertEquals(i + 1, map.size());
assertEquals(true, map.containsKey(Integer.valueOf((String) els[i])));
assertEquals(false, map.containsKey(els[i]));
assertEquals(true, map.containsValue(els[i]));
assertEquals(true, map.get(Integer.valueOf((String) els[i])).contains(els[i]));
}
assertEquals(null, map.remove(els[0]));
assertEquals(true, map.remove(Integer.valueOf((String) els[0])).contains(els[0]));
}
@SuppressWarnings("unchecked")
public void testValueTransformedMap() {
final Object[] els = new Object[] { "1", "3", "5", "7", "2", "4", "6" };
MultiValuedMap<K, V> map = TransformedMultiValuedMap.transformingMap(
new MultiValuedHashMap<K, V>(), null,
(Transformer<? super V, ? extends V>) TransformedCollectionTest.STRING_TO_INTEGER_TRANSFORMER);
assertEquals(0, map.size());
for (int i = 0; i < els.length; i++) {
map.put((K) els[i], (V) els[i]);
assertEquals(i + 1, map.size());
assertEquals(true, map.containsValue(Integer.valueOf((String) els[i])));
assertEquals(false, map.containsValue(els[i]));
assertEquals(true, map.containsKey(els[i]));
assertEquals(true, map.get(els[i]).contains(Integer.valueOf((String) els[i])));
}
assertEquals(true, map.remove(els[0]).contains(Integer.valueOf((String) els[0])));
}
// -----------------------------------------------------------------------
@SuppressWarnings("unchecked")
public void testFactory_Decorate() {
final MultiValuedMap<K, V> base = new MultiValuedHashMap<K, V>();
base.put((K) "A", (V) "1");
base.put((K) "B", (V) "2");
base.put((K) "C", (V) "3");
final MultiValuedMap<K, V> trans = TransformedMultiValuedMap
.transformingMap(
base,
null,
(Transformer<? super V, ? extends V>) TransformedCollectionTest.STRING_TO_INTEGER_TRANSFORMER);
assertEquals(3, trans.size());
assertEquals(true, trans.get("A").contains("1"));
assertEquals(true, trans.get("B").contains("2"));
assertEquals(true, trans.get("C").contains("3"));
trans.put((K) "D", (V) "4");
assertEquals(true, trans.get("D").contains(Integer.valueOf(4)));
}
@SuppressWarnings("unchecked")
public void testFactory_decorateTransform() {
final MultiValuedMap<K, V> base = new MultiValuedHashMap<K, V>();
base.put((K) "A", (V) "1");
base.put((K) "B", (V) "2");
base.put((K) "C", (V) "3");
final MultiValuedMap<K, V> trans = TransformedMultiValuedMap
.transformedMap(
base,
null,
(Transformer<? super V, ? extends V>) TransformedCollectionTest.STRING_TO_INTEGER_TRANSFORMER);
assertEquals(3, trans.size());
assertEquals(true, trans.get("A").contains(Integer.valueOf(1)));
assertEquals(true, trans.get("B").contains(Integer.valueOf(2)));
assertEquals(true, trans.get("C").contains(Integer.valueOf(3)));
trans.put((K) "D", (V) "4");
assertEquals(true, trans.get("D").contains(Integer.valueOf(4)));
}
// public void testCreate() throws Exception {
// writeExternalFormToDisk((java.io.Serializable) makeObject(),
// "src/test/resources/data/test/TransformedMultiValuedMap.emptyCollection.version4.1.obj");
// writeExternalFormToDisk((java.io.Serializable) makeFullMap(),
// "src/test/resources/data/test/TransformedMultiValuedMap.fullCollection.version4.1.obj");
// }
}

View File

@ -0,0 +1,88 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.collections4.multimap;
import org.apache.commons.collections4.MultiValuedMap;
import org.apache.commons.collections4.Unmodifiable;
/**
* Tests for UnmodifiableMultiValuedMap
*
* @since 4.1
* @version $Id$
*/
public class UnmodifiableMultiValuedMapTest<K, V> extends AbstractMultiValuedMapTest<K, V> {
public UnmodifiableMultiValuedMapTest(String testName) {
super(testName);
}
public boolean isAddSupported() {
return false;
}
public boolean isRemoveSupported() {
return false;
}
@Override
public MultiValuedMap<K, V> makeObject() {
return UnmodifiableMultiValuedMap.<K, V> unmodifiableMultiValuedMap(new MultiValuedHashMap<K, V>());
}
protected MultiValuedMap<K, V> makeFullMap() {
final MultiValuedMap<K, V> map = new MultiValuedHashMap<K, V>();
addSampleMappings(map);
return UnmodifiableMultiValuedMap.<K, V> unmodifiableMultiValuedMap(map);
}
public void testUnmodifiable() {
assertTrue(makeObject() instanceof Unmodifiable);
assertTrue(makeFullMap() instanceof Unmodifiable);
}
public void testDecorateFactory() {
final MultiValuedMap<K, V> map = makeFullMap();
assertSame(map, UnmodifiableMultiValuedMap.unmodifiableMultiValuedMap(map));
}
public void testDecoratorFactoryNullMap() {
try {
UnmodifiableMultiValuedMap.unmodifiableMultiValuedMap(null);
fail();
} catch (IllegalArgumentException e) {
}
}
@SuppressWarnings("unchecked")
public void testAddException() {
MultiValuedMap<K, V> map = makeObject();
try {
map.put((K) "one", (V) "uno");
fail();
} catch (UnsupportedOperationException e) {
}
}
// public void testCreate() throws Exception {
// writeExternalFormToDisk((java.io.Serializable) makeObject(),
// "src/test/resources/data/test/UnmodifiableMultiValuedMap.emptyCollection.version4.1.obj");
// writeExternalFormToDisk((java.io.Serializable) makeFullMap(),
// "src/test/resources/data/test/UnmodifiableMultiValuedMap.fullCollection.version4.1.obj");
// }
}