org.apache.commons.collections4.map.MultiValueMap Maven / Gradle / Ivy
/*
* 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.map;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.Factory;
import org.apache.commons.collections4.FunctorException;
import org.apache.commons.collections4.MultiMap;
import org.apache.commons.collections4.Transformer;
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;
/**
* A MultiValueMap decorates another map, allowing it to have
* more than one value for a key.
*
* A MultiMap
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.
*
* This implementation is a decorator, allowing any Map implementation
* to be used as the base.
*
* In addition, this implementation allows the type of collection used
* for the values to be controlled. By default, an ArrayList
* is used, however a Class
to instantiate may be specified,
* or a factory that returns a Collection
instance.
*
* Note that MultiValueMap is not synchronized and is not thread-safe.
* 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.
*
* @param the type of the keys in this map
* @param the type of the values in this map
* @since 3.2
* @deprecated since 4.1, use {@link org.apache.commons.collections4.MultiValuedMap MultiValuedMap} instead
*/
@Deprecated
public class MultiValueMap extends AbstractMapDecorator implements MultiMap, Serializable {
/** Serialization version */
private static final long serialVersionUID = -2214159910087182007L;
/** The factory for creating value collections. */
private final Factory extends Collection> collectionFactory;
/** The cached values. */
private transient Collection valuesView;
/**
* Creates a map which wraps the given map and
* maps keys to ArrayLists.
*
* @param the key type
* @param the value type
* @param map the map to wrap
* @return a new multi-value map
* @since 4.0
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public static MultiValueMap multiValueMap(final Map> map) {
return MultiValueMap. multiValueMap((Map) map, ArrayList.class);
}
/**
* Creates a map which decorates the given map
and
* maps keys to collections of type collectionClass
.
*
* @param the key type
* @param the value type
* @param the collection class type
* @param map the map to wrap
* @param collectionClass the type of the collection class
* @return a new multi-value map
* @since 4.0
*/
public static > MultiValueMap multiValueMap(final Map map,
final Class collectionClass) {
return new MultiValueMap<>(map, new ReflectionFactory<>(collectionClass));
}
/**
* Creates a map which decorates the given map
and
* creates the value collections using the supplied collectionFactory
.
*
* @param the key type
* @param the value type
* @param the collection class type
* @param map the map to decorate
* @param collectionFactory the collection factory (must return a Collection object).
* @return a new multi-value map
* @since 4.0
*/
public static > MultiValueMap multiValueMap(final Map map,
final Factory collectionFactory) {
return new MultiValueMap<>(map, collectionFactory);
}
//-----------------------------------------------------------------------
/**
* Creates a MultiValueMap based on a HashMap
and
* storing the multiple values in an ArrayList
.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public MultiValueMap() {
this(new HashMap(), new ReflectionFactory(ArrayList.class));
}
/**
* Creates a MultiValueMap which decorates the given map
and
* creates the value collections using the supplied collectionFactory
.
*
* @param the collection class type
* @param map the map to decorate
* @param collectionFactory the collection factory which must return a Collection instance
*/
@SuppressWarnings("unchecked")
protected > MultiValueMap(final Map map,
final Factory collectionFactory) {
super((Map) map);
if (collectionFactory == null) {
throw new IllegalArgumentException("The factory must not be null");
}
this.collectionFactory = collectionFactory;
}
//-----------------------------------------------------------------------
/**
* Write the map out using a custom routine.
*
* @param out the output stream
* @throws IOException if an error occurs while writing to the stream
* @since 4.0
*/
private void writeObject(final ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeObject(map);
}
/**
* Read the map in using a custom routine.
*
* @param in the input stream
* @throws IOException if an error occurs while reading from the stream
* @throws ClassNotFoundException if an object read from the stream can not be loaded
* @since 4.0
*/
@SuppressWarnings("unchecked") // (1) should only fail if input stream is incorrect
private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
map = (Map) in.readObject(); // (1)
}
//-----------------------------------------------------------------------
/**
* Clear the map.
*/
@Override
public void clear() {
// If you believe that you have GC issues here, try uncommenting this code
// Set pairs = getMap().entrySet();
// Iterator pairsIterator = pairs.iterator();
// while (pairsIterator.hasNext()) {
// Map.Entry keyValuePair = (Map.Entry) pairsIterator.next();
// Collection coll = (Collection) keyValuePair.getValue();
// coll.clear();
// }
decorated().clear();
}
/**
* Removes a specific value from map.
*
* The item is removed from the collection mapped to the specified key.
* Other values attached to that key are unaffected.
*
* If the last value for a key is removed, null
will be returned
* from a subsequent get(key)
.
*
* @param key the key to remove from
* @param value the value to remove
* @return {@code true} if the mapping was removed, {@code false} otherwise
*/
@Override
public boolean removeMapping(final Object key, final Object value) {
final Collection valuesForKey = getCollection(key);
if (valuesForKey == null) {
return false;
}
final boolean removed = valuesForKey.remove(value);
if (removed == false) {
return false;
}
if (valuesForKey.isEmpty()) {
remove(key);
}
return true;
}
/**
* Checks whether the map contains the value specified.
*
* This checks all collections against all keys for the value, and thus could be slow.
*
* @param value the value to search for
* @return true if the map contains the value
*/
@Override
@SuppressWarnings("unchecked")
public boolean containsValue(final Object value) {
final Set> pairs = decorated().entrySet();
if (pairs != null) {
for (final Map.Entry entry : pairs) {
if (((Collection) entry.getValue()).contains(value)) {
return true;
}
}
}
return false;
}
/**
* Adds the value to the collection associated with the specified key.
*
* Unlike a normal Map
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
*/
@Override
@SuppressWarnings("unchecked")
public Object put(final K key, final Object value) {
boolean result = false;
Collection coll = getCollection(key);
if (coll == null) {
coll = createCollection(1); // might produce a non-empty collection
coll.add((V) value);
if (coll.size() > 0) {
// only add if non-zero size to maintain class state
decorated().put(key, coll);
result = true; // map definitely changed
}
} else {
result = coll.add((V) value);
}
return result ? value : null;
}
/**
* Override superclass to ensure that MultiMap instances are
* correctly handled.
*
* If you call this method with a normal map, each entry is
* added using put(Object,Object)
.
* If you call this method with a multi map, each entry is
* added using putAll(Object,Collection)
.
*
* @param map the map to copy (either a normal or multi map)
*/
@Override
@SuppressWarnings("unchecked")
public void putAll(final Map extends K, ?> map) {
if (map instanceof MultiMap) {
for (final Map.Entry extends K, Object> entry : ((MultiMap extends K, V>) map).entrySet()) {
putAll(entry.getKey(), (Collection) entry.getValue());
}
} else {
for (final Map.Entry extends K, ?> entry : map.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}
}
/**
* {@inheritDoc}
*
* NOTE: the returned Entry objects will contain as value a {@link Collection}
* of all values that are mapped to the given key. To get a "flattened" version
* of all mappings contained in this map, use {@link #iterator()}.
*
* @see #iterator()
*/
@Override
public Set> entrySet() { // NOPMD
return super.entrySet();
}
/**
* Gets a collection containing all the values in the map.
*
* This returns a collection containing the combination of values from all keys.
*
* @return a collection view of the values contained in this map
*/
@Override
@SuppressWarnings("unchecked")
public Collection