scolebourne 2002/11/24 11:36:48
Modified: collections/src/java/org/apache/commons/collections
MultiHashMap.java
Log:
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
Revision Changes Path
1.8 +273 -105
jakarta-commons/collections/src/java/org/apache/commons/collections/MultiHashMap.java
Index: MultiHashMap.java
===================================================================
RCS file:
/home/cvs/jakarta-commons/collections/src/java/org/apache/commons/collections/MultiHashMap.java,v
retrieving revision 1.7
retrieving revision 1.8
diff -u -r1.7 -r1.8
--- MultiHashMap.java 1 Nov 2002 19:41:00 -0000 1.7
+++ MultiHashMap.java 24 Nov 2002 19:36:48 -0000 1.8
@@ -60,165 +60,333 @@
*/
package org.apache.commons.collections;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+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.NoSuchElementException;
import java.util.Set;
/**
* <code>MultiHashMap</code> is the default implementation of the
* {@link org.apache.commons.collections.MultiMap MultiMap} interface.
+ * <p>
* A <code>MultiMap</code> is a Map with slightly different semantics.
- * Instead of returning an Object, it returns a Collection.
- * So for example, you can put( key, new Integer(1) );
- * and then a Object get( key ); will return you a Collection
- * instead of an Integer.
+ * Putting a value into the map will add the value to a Collection at that
+ * key. Getting a value will always return a Collection, holding all the
+ * values put to that key. This implementation uses an ArrayList as the
+ * 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
* @author Christopher Berry
* @author <a href="mailto:[EMAIL PROTECTED]">James Strachan</a>
* @author Steve Downey
* @author Stephen Colebourne
+ * @author <a href="mailto:[EMAIL PROTECTED]">Julien Buret</a>
*/
-public class MultiHashMap extends HashMap implements MultiMap
-{
- //----------------- Data
+public class MultiHashMap extends HashMap implements MultiMap {
+ // deprecated name concept
private static int sCount = 0;
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();
setName();
}
-
- public MultiHashMap( int initialCapacity )
- {
- super( initialCapacity );
+
+ /**
+ * Constructor.
+ *
+ * @param initialCapacity the initial map capacity
+ */
+ public MultiHashMap(int initialCapacity) {
+ super(initialCapacity);
setName();
}
-
- public MultiHashMap(int initialCapacity, float loadFactor )
- {
- super( initialCapacity, 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);
setName();
}
-
- public MultiHashMap( Map mapToCopy )
- {
- super( mapToCopy );
+
+ /**
+ * Constructor.
+ *
+ * @param mapToCopy a Map to copy
+ */
+ public MultiHashMap(Map mapToCopy) {
+ super(mapToCopy);
+ }
+
+ /**
+ * 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());
+ }
+ }
}
- private void setName()
- {
+ /**
+ * Create a name for the map.
+ */
+ private void setName() {
sCount++;
mName = "MultiMap-" + sCount;
}
- public String getName()
- { return mName; }
-
- public Object put( Object key, Object value )
- {
- // NOTE:: put might be called during deserialization !!!!!!
- // so we must provide a hook to handle this case
- // This means that we cannot make MultiMaps of ArrayLists !!!
-
- if ( value instanceof ArrayList ) {
- return ( super.put( key, value ) );
- }
-
- ArrayList keyList = (ArrayList)(super.get( key ));
- if ( keyList == null ) {
- keyList = new ArrayList(10);
-
- super.put( key, keyList );
+ /**
+ * Gets the name of the map.
+ *
+ * @deprecated no replacement. There is no good reason for a MultiMap to have a
name
+ * @return the name
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Put a key and value into the map.
+ * <p>
+ * The value is added to a collection mapped to the key instead of
+ * 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 = keyList.add( value );
-
- return ( results ? value : null );
+ boolean results = coll.add(value);
+
+ 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();
-
- if ( pairs == null )
+
+ if (pairs == null) {
return false;
-
+ }
Iterator pairsIterator = pairs.iterator();
- while ( pairsIterator.hasNext() ) {
- Map.Entry keyValuePair = (Map.Entry)(pairsIterator.next());
- ArrayList list = (ArrayList)(keyValuePair.getValue());
- if( list.contains( value ) )
+ while (pairsIterator.hasNext()) {
+ Map.Entry keyValuePair = (Map.Entry) pairsIterator.next();
+ Collection coll = (Collection) keyValuePair.getValue();
+ if (coll.contains(value)) {
return true;
+ }
}
return false;
}
-
- public Object remove( Object key, Object item )
- {
- ArrayList valuesForKey = (ArrayList) super.get( key );
-
- if ( valuesForKey == null )
+
+ /**
+ * Removes a specific value from map.
+ * <p>
+ * The item is removed from the collection mapped to the specified key.
+ *
+ * @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;
-
- valuesForKey.remove( item );
+ }
+ valuesForKey.remove(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();
Iterator pairsIterator = pairs.iterator();
- while ( pairsIterator.hasNext() ) {
- Map.Entry keyValuePair = (Map.Entry)(pairsIterator.next());
- ArrayList list = (ArrayList)(keyValuePair.getValue());
- list.clear();
+ while (pairsIterator.hasNext()) {
+ Map.Entry keyValuePair = (Map.Entry) pairsIterator.next();
+ Collection coll = (Collection) keyValuePair.getValue();
+ coll.clear();
}
super.clear();
}
-
- public void putAll( Map mapToPut )
- {
- super.putAll( mapToPut );
- }
-
+
/**
- * Note: Currently the returned {@link Collection} is <i>not</i>
- * backed by this map.
- * @see Map#values
- * @see http://issues.apache.org/bugzilla/show_bug.cgi?id=9573
- */
- public Collection values()
- {
- ArrayList returnList = new ArrayList( super.size() );
-
- Set pairs = super.entrySet();
- Iterator pairsIterator = pairs.iterator();
- 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] );
+ * Gets a view over all the values in the map.
+ * <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()));
+ }
+
+ /**
+ * Inner class to view the elements.
+ */
+ private class Values extends AbstractCollection {
+
+ public Iterator iterator() {
+ return new ValueIterator();
+ }
+
+ public int size() {
+ int compt = 0;
+ Iterator it = iterator();
+ while (it.hasNext()) {
+ it.next();
+ compt++;
}
+ return compt;
+ }
+
+ public void clear() {
+ MultiHashMap.this.clear();
}
- return returnList;
+
}
-
- // FIXME:: do we need to implement this??
- // public boolean equals( Object obj ) {}
-
- // --------------- From Cloneable
- public Object clone()
- {
- 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;
+
+ // 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;
}
+ /**
+ * 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);
+ }
+ }
+
}
--
To unsubscribe, e-mail: <mailto:[EMAIL PROTECTED]>
For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>