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]>