package weiss.util;

/**
 * MapImpl implements the Map on top of a set.
 * It should be extended by TreeMap and HashMap, with
 * chained calls to the constructor.
 */
abstract class MapImpl implements Map
{
    private Set theSet;
    
    protected abstract Map.Entry makePair( Object key, Object value );
    protected abstract Set makeEmptyKeySet( );
    protected abstract Set clonePairSet( Set pairSet );
    
    private Map.Entry makePair( Object key )
    {
        return makePair( key, null );
    }
        
    protected MapImpl( Set s )
    {
        theSet = s;
    }
    
    protected MapImpl( Map m )
    {
        theSet = m.entrySet( );
    }
    
    /**
     * Returns the number of keys in this map.
     * @return the number of keys in this collection.
     */
    public int size( )
    {
        return theSet.size( );
    }
    
    /**
     * Tests if this map is empty.
     * @return true if the size of this map is zero.
     */
    public boolean isEmpty( )
    {
        return theSet.isEmpty( );
    }

    /**
     * Tests if this map contains a given key.
     * @param key the key to search for.
     * @return true if the map contains the key.
     */
    public boolean containsKey( Object key )
    {
        return theSet.contains( makePair( key ) );
    }

    /**
     * Returns the value in the map associated with the key.
     * @param key the key to search for.
     * @return the value that matches the key or null
     * if the key is not found. Since null values are allowed,
     * checking if the return value is null may not
     * be a safe way to ascertain if the key is present in the map.
     */
    public Object get( Object key )
    {
        Object match = theSet.getMatch( makePair( key ) );
        
        if( match == null )
            return null;
        else
            return ( (Map.Entry) match ).getValue( );    
    }

    /**
     * Adds the key value pair to the map, overriding the
     * original value if the key was already present.
     * @param key the key to insert.
     * @param value the value to insert.
     * @return the old value associated with the key, or
     * null if the key was not present prior to this call.
     */
    public Object put( Object key, Object value )
    {
    
        Object match = theSet.getMatch( makePair( key ) );
        
        if( match == null )
        {
            theSet.add( makePair( key, value ) );
            return null;
        }
        else
        {
            Map.Entry pair = (Map.Entry) match;
            return pair.setValue( value );
        }
    }
        

    /**
     * Remove the key and its value from the map.
     * @param key the key to remove.
     * @return the previous value associated with the key,
     * or null if the key was not present prior to this call.
     */
    public Object remove( Object key )
    {
        Object oldValue = get( key );
        if( oldValue != null )
            theSet.remove( makePair( key ) );
        
        return oldValue;    
    }

    /**
     * Removes all key value pairs from the map. 
     */
    public void clear( )
    {
        theSet.clear( );
    }

    /**
     * Returns the keys in the map.
     * These semantics are different from those in java.util because
     * in this class, changes made to the returned key set do not cause
     * changes to be reflected in the map.
     * @return the keys in the map.
     */
    public Set keySet( )
    {
        Iterator itr = theSet.iterator( );
        Set result = makeEmptyKeySet( );
        
        while( itr.hasNext( ) )
            result.add( ( (Map.Entry) itr.next( ) ).getKey( ) );

        return result;
    }

    /**
     * Returns the values in the map. There may be duplicates.
     * These semantics are different from those in java.util because
     * in this class, changes made to the returned value collection
     * do not cause changes to be reflected in the map.
     * @return the values in the map.
     */
    public Collection values( )
    {
        Iterator itr = theSet.iterator( );
        Collection result = new ArrayList( );
        
        while( itr.hasNext( ) )  
            result.add( ( (Map.Entry) itr.next( ) ).getValue( ) );

        return result;
    
    }

    /**
     * Return a set of Map.Entry objects corresponding to
     * the key/value pairs in the map.
     * These semantics are different from those in java.util because
     * in this class, changes made to the returned key/value set do not cause
     * changes to be reflected in the map.
     * @return the key/value pairs in the map.
     */ 
    public Set entrySet( )
    {
        return clonePairSet( theSet );
    }

    /**
     * Return a reference to the underlying set.
     */
    protected Set getSet( )
    {
        return theSet;
    }
}

