/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2008 jOpenDocument, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU
* General Public License Version 3 only ("GPL").
* You may not use this file except in compliance with the License.
* You can obtain a copy of the License at http://www.gnu.org/licenses/gpl-3.0.html
* See the License for the specific language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*
*/
package org.jopendocument.util;
import static java.util.Arrays.asList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.apache.commons.collections.MultiHashMap;
import org.apache.commons.collections.MultiMap;
/**
* Une MultiMap qui permet de ne pas renvoyer <code>null</code>. De plus elle permet de choisir le
* type de Collection utilisé.
*
* @author ILM Informatique 8 sept. 2004
* @param <K> type of the keys
* @param <V> type of elements in collections
*/
@SuppressWarnings("unchecked")
public class CollectionMap<K, V> extends MultiHashMap {
private static final int DEFAULT_CAPACITY = 16;
/**
* Create a map with a single entry.
*
* @param <K> type of key.
* @param <V> type of items.
* @param key the single key.
* @param values the values for <code>key</code>.
* @return a map with one entry.
*/
public static <K, V> CollectionMap<K, V> singleton(K key, Collection<V> values) {
final CollectionMap<K, V> res = new CollectionMap<K, V>();
res.putAll(key, values);
return res;
}
public static <K, V> CollectionMap<K, V> singleton(K key, V... values) {
return singleton(key, asList(values));
}
// to avoid
// "Type safety : A generic array of Tuple2<String,Boolean> is created for a varargs parameter"
public static <K, V> CollectionMap<K, V> singleton(K key, V value) {
return singleton(key, Collections.singleton(value));
}
private final Class<? extends Collection<V>> collectionClass;
private final Collection<V> collectionSpecimen;
/**
* Une nouvelle map avec ArrayList comme collection.
*/
public CollectionMap() {
this(ArrayList.class);
}
/**
* Une nouvelle map. <code>collectionClass</code> doit descendre de Collection, et posséder un
* constructeur prenant une Collection (c'est le cas de la majorité des classes de java.util).
*
* @param aCollectionClass le type de collection utilisé.
*/
public CollectionMap(Class aCollectionClass) {
this(aCollectionClass, DEFAULT_CAPACITY);
}
/**
* Une nouvelle map sans préciser le type de collection. Dans ce cas si vous voulez spécifier
* une collection surchargez {@link #createCollection(Collection)}. Ce constructeur est donc
* utile pour des raisons de performances (évite la réflexion nécessaire avec les autres).
*
* @param initialCapacity the initial capacity.
*/
public CollectionMap(final int initialCapacity) {
this((Class) null, initialCapacity);
}
public CollectionMap(Class aCollectionClass, final int initialCapacity) {
super(initialCapacity);
this.collectionClass = aCollectionClass;
this.collectionSpecimen = null;
}
public CollectionMap(Collection<V> collectionSpecimen) {
this(collectionSpecimen, 16);
}
/**
* A map that creates new collections by cloning collectionSpecimen. Allow one to customize an
* instance, contrary to the constructor which only takes a class.
*
* @param collectionSpecimen the collection from which to all others will be cloned.
* @param initialCapacity the initial capacity
* @throws IllegalArgumentException is not a Cloneable.
*/
public CollectionMap(Collection<V> collectionSpecimen, final int initialCapacity) {
super(initialCapacity);
this.collectionClass = null;
if (!(collectionSpecimen instanceof Cloneable))
throw new IllegalArgumentException(collectionSpecimen + " not a cloneable.");
// allow to pass an existing collection w/o us modifying it
// plus test if copy() succeeds
this.collectionSpecimen = CopyUtils.copy(collectionSpecimen);
this.collectionSpecimen.clear();
}
/**
* Renvoie la collection associée à la clef passée. Si la clef n'existe pas, renvoie une
* collection vide.
*
* @param key la clef.
* @return le collectionClass (par défaut ArrayList) associé à la clef passée.
* @see #getCollectionClass()
*/
public Collection<V> getNonNull(K key) {
final Collection<V> res = getNull(key);
return res == null ? this.createCollection(res) : res;
}
/**
* Just for the generics.
*
* @param key the key whose associated value is to be returned
* @return the value to which the specified key is mapped, or {@code null} if this map contains
* no mapping for the key.
*/
public Collection<V> getNull(K key) {
return (Collection<V>) this.get(key);
}
/*
* (non-Javadoc)
*
* @see org.apache.commons.collections.MultiHashMap#createCollection(java.util.Collection)
*/
public Collection<V> createCollection(Collection coll) {
if (this.collectionClass != null)
try {
if (coll == null) {
return this.collectionClass.newInstance();
} else {
return this.collectionClass.getConstructor(new Class[] { Collection.class }).newInstance(new Object[] { coll });
}
} catch (Exception e) {
throw new RuntimeException(e);
}
else if (this.collectionSpecimen != null) {
try {
final Collection<V> res = CopyUtils.copy(this.collectionSpecimen);
if (coll != null)
res.addAll(coll);
return res;
} catch (Exception e) {
throw ExceptionUtils.createExn(IllegalStateException.class, "clone() failed", e);
}
} else
return super.createCollection(coll);
}
public Class getCollectionClass() {
return this.collectionClass;
}
/**
* Fusionne la MultiMap avec celle-ci. C'est à dire rajoute les valeurs de mm à la suite des
* valeurs de cette map (contrairement à putAll(Map) qui ajoute les valeurs de mm en tant que
* valeur scalaire et non en tant que collection).
*
* @param mm la MultiMap à fusionner.
*/
public void merge(MultiMap mm) {
// copied from super ctor
for (Iterator it = mm.entrySet().iterator(); it.hasNext();) {
final Map.Entry entry = (Map.Entry) it.next();
Collection<V> coll = (Collection<V>) entry.getValue();
Collection newColl = createCollection(coll);
this.putAll(entry.getKey(), newColl);
}
}
/**
* Copies all of the mappings from the specified map to this map. This method is equivalent to
* {@link MultiHashMap#MultiHashMap(Map)}. NOTE: cannot use Map<? extends K, ? extends V> since
* java complains (MultiHashMap not being generic).
*
* @param m mappings to be stored in this map
*/
@Override
public void putAll(Map mapToCopy) {
if (mapToCopy instanceof MultiMap) {
this.merge((MultiMap) mapToCopy);
} else {
super.putAll(mapToCopy);
}
}
public boolean putAll(K key, V... values) {
return this.putAll(key, asList(values));
}
// generics : MultiHashMap is not generic but it extends HashMap who does
// so just override
@Override
public Set<Map.Entry<K, Collection<V>>> entrySet() {
return super.entrySet();
}
@Override
public Set<K> keySet() {
return super.keySet();
}
@Override
public Collection<V> values() {
return super.values();
}
}