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

Reply via email to