Applied fixes from Julien Buret to improve MultiMap including

- values() backed by real map
- clone works properly
- ArrayLists can be added to a MultiMap
Javadoc class


git-svn-id: https://svn.apache.org/repos/asf/jakarta/commons/proper/collections/trunk@130874 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Stephen Colebourne 2002-11-24 19:36:48 +00:00
parent 7c12e16836
commit c8de9e8066
1 changed files with 274 additions and 106 deletions

View File

@ -1,7 +1,7 @@
/* /*
* $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/java/org/apache/commons/collections/MultiHashMap.java,v 1.7 2002/11/01 19:41:00 rwaldhoff Exp $ * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/java/org/apache/commons/collections/MultiHashMap.java,v 1.8 2002/11/24 19:36:48 scolebourne Exp $
* $Revision: 1.7 $ * $Revision: 1.8 $
* $Date: 2002/11/01 19:41:00 $ * $Date: 2002/11/24 19:36:48 $
* *
* ==================================================================== * ====================================================================
* *
@ -60,165 +60,333 @@
*/ */
package org.apache.commons.collections; package org.apache.commons.collections;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.AbstractCollection;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set; import java.util.Set;
/** /**
* <code>MultiHashMap</code> is the default implementation of the * <code>MultiHashMap</code> is the default implementation of the
* {@link org.apache.commons.collections.MultiMap MultiMap} interface. * {@link org.apache.commons.collections.MultiMap MultiMap} interface.
* <p>
* A <code>MultiMap</code> is a Map with slightly different semantics. * A <code>MultiMap</code> is a Map with slightly different semantics.
* Instead of returning an Object, it returns a Collection. * Putting a value into the map will add the value to a Collection at that
* So for example, you can put( key, new Integer(1) ); * key. Getting a value will always return a Collection, holding all the
* and then a Object get( key ); will return you a Collection * values put to that key. This implementation uses an ArrayList as the
* instead of an Integer. * collection.
* <p>
* For example:
* <pre>
* MultiMap mhm = new MultiHashMap();
* mhm.put(key, "A");
* mhm.put(key, "B");
* mhm.put(key, "C");
* Collection coll = mhm.get(key);</pre>
* <p>
* <code>coll</code> will be a list containing "A", "B", "C".
* *
* @since 2.0 * @since 2.0
* @author Christopher Berry * @author Christopher Berry
* @author <a href="mailto:jstrachan@apache.org">James Strachan</a> * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
* @author Steve Downey * @author Steve Downey
* @author Stephen Colebourne * @author Stephen Colebourne
* @author <a href="mailto:jburet@yahoo.com">Julien Buret</a>
*/ */
public class MultiHashMap extends HashMap implements MultiMap public class MultiHashMap extends HashMap implements MultiMap {
{ // deprecated name concept
//----------------- Data
private static int sCount = 0; private static int sCount = 0;
private String mName = null; private String mName = null;
public MultiHashMap() //backed values collection
{ private transient Collection values = null;
// compatibility with commons-collection releases 2.0/2.1
private static final long serialVersionUID = 1943563828307035349L;
/**
* Constructor.
*/
public MultiHashMap() {
super(); super();
setName(); setName();
} }
public MultiHashMap( int initialCapacity ) /**
{ * Constructor.
*
* @param initialCapacity the initial map capacity
*/
public MultiHashMap(int initialCapacity) {
super(initialCapacity); super(initialCapacity);
setName(); setName();
} }
public MultiHashMap(int initialCapacity, float loadFactor ) /**
{ * Constructor.
*
* @param initialCapacity the initial map capacity
* @param loadFactor the amount 0.0-1.0 at which to resize the map
*/
public MultiHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor); super(initialCapacity, loadFactor);
setName(); setName();
} }
public MultiHashMap( Map mapToCopy ) /**
{ * Constructor.
*
* @param mapToCopy a Map to copy
*/
public MultiHashMap(Map mapToCopy) {
super(mapToCopy); super(mapToCopy);
} }
private void setName() /**
{ * Read the object during deserialization.
*/
private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
// This method is needed because the 1.2/1.3 Java deserialisation called
// put and thus messed up that method
// default read object
s.defaultReadObject();
// problem only with jvm <1.4
String version = "1.2";
try {
version = System.getProperty("java.version");
} catch (SecurityException ex) {
// ignore and treat as 1.2/1.3
}
if (version.startsWith("1.2") || version.startsWith("1.3")) {
for (Iterator iterator = entrySet().iterator(); iterator.hasNext();) {
Map.Entry entry = (Map.Entry) iterator.next();
// put has created a extra collection level, remove it
super.put(entry.getKey(), ((Collection) entry.getValue()).iterator().next());
}
}
}
/**
* Create a name for the map.
*/
private void setName() {
sCount++; sCount++;
mName = "MultiMap-" + sCount; mName = "MultiMap-" + sCount;
} }
public String getName() /**
{ return mName; } * Gets the name of the map.
*
public Object put( Object key, Object value ) * @deprecated no replacement. There is no good reason for a MultiMap to have a name
{ * @return the name
// NOTE:: put might be called during deserialization !!!!!! */
// so we must provide a hook to handle this case public String getName() {
// This means that we cannot make MultiMaps of ArrayLists !!! return mName;
if ( value instanceof ArrayList ) {
return ( super.put( key, value ) );
} }
ArrayList keyList = (ArrayList)(super.get( key )); /**
if ( keyList == null ) { * Put a key and value into the map.
keyList = new ArrayList(10); * <p>
* The value is added to a collection mapped to the key instead of
super.put( key, keyList ); * replacing the previous value.
*
* @param key the key to set
* @param value the value to set the key to
* @return the value added if the add is successful, <code>null</code> otherwise
*/
public Object put(Object key, Object value) {
// NOTE:: put is called during deserialization in JDK < 1.4 !!!!!!
// so we must have a readObject()
Collection coll = (Collection) super.get(key);
if (coll == null) {
coll = createCollection(null);
super.put(key, coll);
} }
boolean results = coll.add(value);
boolean results = keyList.add( value );
return (results ? value : null); return (results ? value : null);
} }
public boolean containsValue( Object value ) /**
{ * Does the map contain a specific value.
* <p>
* This searches the collection mapped to each key, and thus could be slow.
*
* @param value the value to search for
* @return true if the list contains the value
*/
public boolean containsValue(Object value) {
Set pairs = super.entrySet(); Set pairs = super.entrySet();
if ( pairs == null ) if (pairs == null) {
return false; return false;
}
Iterator pairsIterator = pairs.iterator(); Iterator pairsIterator = pairs.iterator();
while (pairsIterator.hasNext()) { while (pairsIterator.hasNext()) {
Map.Entry keyValuePair = (Map.Entry)(pairsIterator.next()); Map.Entry keyValuePair = (Map.Entry) pairsIterator.next();
ArrayList list = (ArrayList)(keyValuePair.getValue()); Collection coll = (Collection) keyValuePair.getValue();
if( list.contains( value ) ) if (coll.contains(value)) {
return true; return true;
} }
}
return false; return false;
} }
public Object remove( Object key, Object item ) /**
{ * Removes a specific value from map.
ArrayList valuesForKey = (ArrayList) super.get( key ); * <p>
* The item is removed from the collection mapped to the specified key.
if ( valuesForKey == null ) *
* @param key the key to remove from
* @param value the value to remove
* @return the value removed (which was passed in)
*/
public Object remove(Object key, Object item) {
Collection valuesForKey = (Collection) super.get(key);
if (valuesForKey == null) {
return null; return null;
}
valuesForKey.remove(item); valuesForKey.remove(item);
return item; return item;
} }
public void clear() /**
{ * Clear the map.
* <p>
* This clears each collection in the map, and so may be slow.
*/
public void clear() {
// For gc, clear each list in the map
Set pairs = super.entrySet(); Set pairs = super.entrySet();
Iterator pairsIterator = pairs.iterator(); Iterator pairsIterator = pairs.iterator();
while (pairsIterator.hasNext()) { while (pairsIterator.hasNext()) {
Map.Entry keyValuePair = (Map.Entry)(pairsIterator.next()); Map.Entry keyValuePair = (Map.Entry) pairsIterator.next();
ArrayList list = (ArrayList)(keyValuePair.getValue()); Collection coll = (Collection) keyValuePair.getValue();
list.clear(); coll.clear();
} }
super.clear(); super.clear();
} }
public void putAll( Map mapToPut ) /**
{ * Gets a view over all the values in the map.
super.putAll( mapToPut ); * <p>
* The values view includes all the entries in the collections at each map key.
*
* @return the collection view of all the values in the map
*/
public Collection values() {
Collection vs = values;
return (vs != null ? vs : (values = new Values()));
} }
/** /**
* Note: Currently the returned {@link Collection} is <i>not</i> * Inner class to view the elements.
* backed by this map.
* @see Map#values
* @see http://issues.apache.org/bugzilla/show_bug.cgi?id=9573
*/ */
public Collection values() private class Values extends AbstractCollection {
{
ArrayList returnList = new ArrayList( super.size() );
Set pairs = super.entrySet(); public Iterator iterator() {
Iterator pairsIterator = pairs.iterator(); return new ValueIterator();
while ( pairsIterator.hasNext() ) {
Map.Entry keyValuePair = (Map.Entry)(pairsIterator.next());
ArrayList list = (ArrayList)(keyValuePair.getValue());
Object[] values = list.toArray();
for ( int ii=0; ii < values.length; ii++ ) {
returnList.add( values[ii] );
}
}
return returnList;
} }
// FIXME:: do we need to implement this?? public int size() {
// public boolean equals( Object obj ) {} int compt = 0;
Iterator it = iterator();
while (it.hasNext()) {
it.next();
compt++;
}
return compt;
}
// --------------- From Cloneable public void clear() {
public Object clone() MultiHashMap.this.clear();
{ }
MultiHashMap obj = (MultiHashMap)(super.clone());
}
/**
* Inner iterator to view the elements.
*/
private class ValueIterator implements Iterator {
private Iterator backedIterator;
private Iterator tempIterator;
private ValueIterator() {
backedIterator = MultiHashMap.super.values().iterator();
}
private boolean searchNextIterator() {
while (tempIterator == null || tempIterator.hasNext() == false) {
if (backedIterator.hasNext() == false) {
return false;
}
tempIterator = ((Collection) backedIterator.next()).iterator();
}
return true;
}
public boolean hasNext() {
return searchNextIterator();
}
public Object next() {
if (searchNextIterator() == false) {
throw new NoSuchElementException();
}
return tempIterator.next();
}
public void remove() {
if (tempIterator == null) {
throw new IllegalStateException();
}
tempIterator.remove();
}
}
/**
* Clone the map.
* <p>
* The clone will shallow clone the collections as well as the map.
*
* @return the cloned map
*/
public Object clone() {
MultiHashMap obj = (MultiHashMap) super.clone();
obj.mName = mName; obj.mName = mName;
// clone each Collection container
for (Iterator it = entrySet().iterator(); it.hasNext();) {
Map.Entry entry = (Map.Entry) it.next();
Collection coll = (Collection) entry.getValue();
Collection newColl = createCollection(coll);
entry.setValue(newColl);
}
return obj; return obj;
} }
/**
* Creates a new instance of the map value Collection container.
* <p>
* This method can be overridden to use your own collection type.
*
* @param coll the collection to copy, may be null
* @return the new collection
*/
protected Collection createCollection(Collection coll) {
if (coll == null) {
return new ArrayList();
} else {
return new ArrayList(coll);
}
}
} }