/*
 *  Primitive Collections for Java.
 *  Copyright (C) 2002, 2003  Søren Bak
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package com.uwyn.jhighlight.pcj.map;

import com.uwyn.jhighlight.pcj.CharIterator;
import com.uwyn.jhighlight.pcj.hash.CharHashFunction;
import com.uwyn.jhighlight.pcj.hash.DefaultCharHashFunction;
import com.uwyn.jhighlight.pcj.hash.Primes;
import com.uwyn.jhighlight.pcj.map.AbstractCharKeyMap;
import com.uwyn.jhighlight.pcj.map.CharKeyMap;
import com.uwyn.jhighlight.pcj.map.CharKeyMapIterator;
import com.uwyn.jhighlight.pcj.set.AbstractCharSet;
import com.uwyn.jhighlight.pcj.set.CharSet;
import com.uwyn.jhighlight.pcj.util.Exceptions;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.AbstractCollection;
import java.util.Collection;
import java.util.Iterator;

/**
 *  This class represents open addressing hash table based maps from
 *  char values to objects.
 *
 *  @see        CharKeyChainedHashMap
 *  @see        java.util.Map
 *
 *  @author     S&oslash;ren Bak
 *  @version    1.3     21-08-2003 19:45
 *  @since      1.0
 */
public class CharKeyOpenHashMap extends AbstractCharKeyMap implements CharKeyMap, Cloneable, Serializable
{
	
    /** Constant indicating relative growth policy. */
    private static final int    GROWTH_POLICY_RELATIVE      = 0;
	
    /** Constant indicating absolute growth policy. */
    private static final int    GROWTH_POLICY_ABSOLUTE      = 1;
	
    /**
     *  The default growth policy of this map.
     *  @see    #GROWTH_POLICY_RELATIVE
     *  @see    #GROWTH_POLICY_ABSOLUTE
     */
    private static final int    DEFAULT_GROWTH_POLICY       = GROWTH_POLICY_RELATIVE;
	
    /** The default factor with which to increase the capacity of this map. */
    public static final double DEFAULT_GROWTH_FACTOR        = 1.0;
	
    /** The default chunk size with which to increase the capacity of this map. */
    public static final int    DEFAULT_GROWTH_CHUNK         = 10;
	
    /** The default capacity of this map. */
    public static final int    DEFAULT_CAPACITY             = 11;
	
    /** The default load factor of this map. */
    public static final double DEFAULT_LOAD_FACTOR          = 0.75;
	
    /**
     *  The hash function used to hash keys in this map.
     *  @serial
     */
    private CharHashFunction keyhash;
	
    /**
     *  The size of this map.
     *  @serial
     */
    private int size;
	
    /**
     *  The keys of this map. Contains key values directly.
     *  Due to the use of a secondary hash function, the length of this
     *  array must be a prime.
     */
    private transient char[] keys;
	
    /**
     *  The values of this map. Contains values directly.
     *  Due to the use of a secondary hash function, the length of this
     *  array must be a prime.
     */
    private transient Object[] values;
	
    /** The states of each cell in the keys[] and values[]. */
    private transient byte[] states;
	
    private static final byte EMPTY = 0;
    private static final byte OCCUPIED = 1;
    private static final byte REMOVED = 2;
	
    /** The number of entries in use (removed or occupied). */
    private transient int used;
	
    /**
     *  The growth policy of this map (0 is relative growth, 1 is absolute growth).
     *  @serial
     */
    private int growthPolicy;
	
    /**
     *  The growth factor of this map, if the growth policy is
     *  relative.
     *  @serial
     */
    private double growthFactor;
	
    /**
     *  The growth chunk size of this map, if the growth policy is
     *  absolute.
     *  @serial
     */
    private int growthChunk;
	
    /**
     *  The load factor of this map.
     *  @serial
     */
    private double loadFactor;
	
    /**
     *  The next size at which to expand the data[].
     *  @serial
     */
    private int expandAt;
	
    /** A set view of the keys of this map. */
    private transient CharSet ckeys;
	
    /** A collection view of the values of this map. */
    private transient Collection cvalues;
	
    private CharKeyOpenHashMap(CharHashFunction keyhash, int capacity, int growthPolicy, double growthFactor, int growthChunk, double loadFactor)
	{
        if (keyhash==null)
            Exceptions.nullArgument("hash function");
        if (capacity<0)
            Exceptions.negativeArgument("capacity", String.valueOf(capacity));
        if (growthFactor<=0.0)
            Exceptions.negativeOrZeroArgument("growthFactor", String.valueOf(growthFactor));
        if (growthChunk<=0)
            Exceptions.negativeOrZeroArgument("growthChunk", String.valueOf(growthChunk));
        if (loadFactor<=0.0)
            Exceptions.negativeOrZeroArgument("loadFactor", String.valueOf(loadFactor));
        this.keyhash = keyhash;
        capacity = Primes.nextPrime(capacity);
        keys = new char[capacity];
        values = (Object[])new Object[capacity];
        this.states = new byte[capacity];
        size = 0;
        expandAt = (int)Math.round(loadFactor*capacity);
        this.used = 0;
        this.growthPolicy = growthPolicy;
        this.growthFactor = growthFactor;
        this.growthChunk = growthChunk;
        this.loadFactor = loadFactor;
    }
	
    private CharKeyOpenHashMap(int capacity, int growthPolicy, double growthFactor, int growthChunk, double loadFactor)
	{
        this(DefaultCharHashFunction.INSTANCE, capacity, growthPolicy, growthFactor, growthChunk, loadFactor);
    }
	
    /**
     *  Creates a new hash map with capacity 11, a relative
     *  growth factor of 1.0, and a load factor of 75%.
     */
    public CharKeyOpenHashMap()
	{
        this(DEFAULT_CAPACITY);
    }
	
    /**
     *  Creates a new hash map with the same mappings as a specified map.
     *
     *  @param      map
     *              the map whose mappings to put into the new map.
     *
     *  @throws     NullPointerException
     *              if <tt>map</tt> is <tt>null</tt>.
     */
    public CharKeyOpenHashMap(CharKeyMap map)
	{
        this();
        putAll(map);
    }
	
    /**
     *  Creates a new hash map with a specified capacity, a relative
     *  growth factor of 1.0, and a load factor of 75%.
     *
     *  @param      capacity
     *              the initial capacity of the map.
     *
     *  @throws     IllegalArgumentException
     *              if <tt>capacity</tt> is negative.
     */
    public CharKeyOpenHashMap(int capacity)
	{
        this(capacity, DEFAULT_GROWTH_POLICY, DEFAULT_GROWTH_FACTOR, DEFAULT_GROWTH_CHUNK, DEFAULT_LOAD_FACTOR);
    }
	
    /**
     *  Creates a new hash map with a capacity of 11, a relative
     *  growth factor of 1.0, and a specified load factor.
     *
     *  @param      loadFactor
     *              the load factor of the map.
     *
     *  @throws     IllegalArgumentException
     *              if <tt>capacity</tt> is negative;
     *              if <tt>loadFactor</tt> is not positive.
     */
    public CharKeyOpenHashMap(double loadFactor)
	{
        this(DEFAULT_CAPACITY, DEFAULT_GROWTH_POLICY, DEFAULT_GROWTH_FACTOR, DEFAULT_GROWTH_CHUNK, loadFactor);
    }
	
    /**
     *  Creates a new hash map with a specified capacity and
     *  load factor, and a relative growth factor of 1.0.
     *
     *  @param      capacity
     *              the initial capacity of the map.
     *
     *  @param      loadFactor
     *              the load factor of the map.
     *
     *  @throws     IllegalArgumentException
     *              if <tt>capacity</tt> is negative;
     *              if <tt>loadFactor</tt> is not positive.
     */
    public CharKeyOpenHashMap(int capacity, double loadFactor)
	{
        this(capacity, DEFAULT_GROWTH_POLICY, DEFAULT_GROWTH_FACTOR, DEFAULT_GROWTH_CHUNK, loadFactor);
    }
	
    /**
     *  Creates a new hash map with a specified capacity,
     *  load factor, and relative growth factor.
     *
     *  <p>The map capacity increases to <tt>capacity()*(1+growthFactor)</tt>.
     *  This strategy is good for avoiding many capacity increases, but
     *  the amount of wasted memory is approximately the size of the map.
     *
     *  @param      capacity
     *              the initial capacity of the map.
     *
     *  @param      loadFactor
     *              the load factor of the map.
     *
     *  @param      growthFactor
     *              the relative amount with which to increase the
     *              the capacity when a capacity increase is needed.
     *
     *  @throws     IllegalArgumentException
     *              if <tt>capacity</tt> is negative;
     *              if <tt>loadFactor</tt> is not positive;
     *              if <tt>growthFactor</tt> is not positive.
     */
    public CharKeyOpenHashMap(int capacity, double loadFactor, double growthFactor)
	{
        this(capacity, GROWTH_POLICY_RELATIVE, growthFactor, DEFAULT_GROWTH_CHUNK, loadFactor);
    }
	
    /**
     *  Creates a new hash map with a specified capacity,
     *  load factor, and absolute growth factor.
     *
     *  <p>The map capacity increases to <tt>capacity()+growthChunk</tt>.
     *  This strategy is good for avoiding wasting memory. However, an
     *  overhead is potentially introduced by frequent capacity increases.
     *
     *  @param      capacity
     *              the initial capacity of the map.
     *
     *  @param      loadFactor
     *              the load factor of the map.
     *
     *  @param      growthChunk
     *              the absolute amount with which to increase the
     *              the capacity when a capacity increase is needed.
     *
     *  @throws     IllegalArgumentException
     *              if <tt>capacity</tt> is negative;
     *              if <tt>loadFactor</tt> is not positive;
     *              if <tt>growthChunk</tt> is not positive.
     */
    public CharKeyOpenHashMap(int capacity, double loadFactor, int growthChunk)
	{
        this(capacity, GROWTH_POLICY_ABSOLUTE, DEFAULT_GROWTH_FACTOR, growthChunk, loadFactor);
    }
	
    // ---------------------------------------------------------------
    //      Constructors with hash function argument
    // ---------------------------------------------------------------
	
    /**
     *  Creates a new hash map with capacity 11, a relative
     *  growth factor of 1.0, and a load factor of 75%.
     *
     *  @param      keyhash
     *              the hash function to use when hashing keys.
     *
     *  @throws     NullPointerException
     *              if <tt>keyhash</tt> is <tt>null</tt>.
     */
    public CharKeyOpenHashMap(CharHashFunction keyhash)
	{
        this(keyhash, DEFAULT_CAPACITY, DEFAULT_GROWTH_POLICY, DEFAULT_GROWTH_FACTOR, DEFAULT_GROWTH_CHUNK, DEFAULT_LOAD_FACTOR);
    }
	
    /**
     *  Creates a new hash map with a specified capacity, a relative
     *  growth factor of 1.0, and a load factor of 75%.
     *
     *  @param      keyhash
     *              the hash function to use when hashing keys.
     *
     *  @param      capacity
     *              the initial capacity of the map.
     *
     *  @throws     IllegalArgumentException
     *              if <tt>capacity</tt> is negative.
     *
     *  @throws     NullPointerException
     *              if <tt>keyhash</tt> is <tt>null</tt>.
     */
    public CharKeyOpenHashMap(CharHashFunction keyhash, int capacity)
	{
        this(keyhash, capacity, DEFAULT_GROWTH_POLICY, DEFAULT_GROWTH_FACTOR, DEFAULT_GROWTH_CHUNK, DEFAULT_LOAD_FACTOR);
    }
	
    /**
     *  Creates a new hash map with a capacity of 11, a relative
     *  growth factor of 1.0, and a specified load factor.
     *
     *  @param      keyhash
     *              the hash function to use when hashing keys.
     *
     *  @param      loadFactor
     *              the load factor of the map.
     *
     *  @throws     IllegalArgumentException
     *              if <tt>capacity</tt> is negative;
     *              if <tt>loadFactor</tt> is not positive.
     *
     *  @throws     NullPointerException
     *              if <tt>keyhash</tt> is <tt>null</tt>.
     */
    public CharKeyOpenHashMap(CharHashFunction keyhash, double loadFactor)
	{
        this(keyhash, DEFAULT_CAPACITY, DEFAULT_GROWTH_POLICY, DEFAULT_GROWTH_FACTOR, DEFAULT_GROWTH_CHUNK, loadFactor);
    }
	
    /**
     *  Creates a new hash map with a specified capacity and
     *  load factor, and a relative growth factor of 1.0.
     *
     *  @param      keyhash
     *              the hash function to use when hashing keys.
     *
     *  @param      capacity
     *              the initial capacity of the map.
     *
     *  @param      loadFactor
     *              the load factor of the map.
     *
     *  @throws     IllegalArgumentException
     *              if <tt>capacity</tt> is negative;
     *              if <tt>loadFactor</tt> is not positive.
     *
     *  @throws     NullPointerException
     *              if <tt>keyhash</tt> is <tt>null</tt>.
     */
    public CharKeyOpenHashMap(CharHashFunction keyhash, int capacity, double loadFactor)
	{
        this(keyhash, capacity, DEFAULT_GROWTH_POLICY, DEFAULT_GROWTH_FACTOR, DEFAULT_GROWTH_CHUNK, loadFactor);
    }
	
    /**
     *  Creates a new hash map with a specified capacity,
     *  load factor, and relative growth factor.
     *
     *  <p>The map capacity increases to <tt>capacity()*(1+growthFactor)</tt>.
     *  This strategy is good for avoiding many capacity increases, but
     *  the amount of wasted memory is approximately the size of the map.
     *
     *  @param      keyhash
     *              the hash function to use when hashing keys.
     *
     *  @param      capacity
     *              the initial capacity of the map.
     *
     *  @param      loadFactor
     *              the load factor of the map.
     *
     *  @param      growthFactor
     *              the relative amount with which to increase the
     *              the capacity when a capacity increase is needed.
     *
     *  @throws     IllegalArgumentException
     *              if <tt>capacity</tt> is negative;
     *              if <tt>loadFactor</tt> is not positive;
     *              if <tt>growthFactor</tt> is not positive.
     *
     *  @throws     NullPointerException
     *              if <tt>keyhash</tt> is <tt>null</tt>.
     */
    public CharKeyOpenHashMap(CharHashFunction keyhash, int capacity, double loadFactor, double growthFactor)
	{
        this(keyhash, capacity, GROWTH_POLICY_RELATIVE, growthFactor, DEFAULT_GROWTH_CHUNK, loadFactor);
    }
	
    /**
     *  Creates a new hash map with a specified capacity,
     *  load factor, and absolute growth factor.
     *
     *  <p>The map capacity increases to <tt>capacity()+growthChunk</tt>.
     *  This strategy is good for avoiding wasting memory. However, an
     *  overhead is potentially introduced by frequent capacity increases.
     *
     *  @param      keyhash
     *              the hash function to use when hashing keys.
     *
     *  @param      capacity
     *              the initial capacity of the map.
     *
     *  @param      loadFactor
     *              the load factor of the map.
     *
     *  @param      growthChunk
     *              the absolute amount with which to increase the
     *              the capacity when a capacity increase is needed.
     *
     *  @throws     IllegalArgumentException
     *              if <tt>capacity</tt> is negative;
     *              if <tt>loadFactor</tt> is not positive;
     *              if <tt>growthChunk</tt> is not positive.
     *
     *  @throws     NullPointerException
     *              if <tt>keyhash</tt> is <tt>null</tt>.
     */
    public CharKeyOpenHashMap(CharHashFunction keyhash, int capacity, double loadFactor, int growthChunk)
	{
        this(keyhash, capacity, GROWTH_POLICY_ABSOLUTE, DEFAULT_GROWTH_FACTOR, growthChunk, loadFactor);
    }
	
    // ---------------------------------------------------------------
    //      Hash table management
    // ---------------------------------------------------------------
	
    private void ensureCapacity(int elements)
	{
        if (elements>=expandAt)
		{
            int newcapacity;
            if (growthPolicy==GROWTH_POLICY_RELATIVE)
                newcapacity = (int)(keys.length*(1.0+growthFactor));
            else
                newcapacity = keys.length+growthChunk;
            if (newcapacity*loadFactor<elements)
                newcapacity = (int)Math.round(((double)elements/loadFactor));
            newcapacity = Primes.nextPrime(newcapacity);
            expandAt = (int)Math.round(loadFactor*newcapacity);
			
            char[] newkeys = new char[newcapacity];
            Object[] newvalues = (Object[])new Object[newcapacity];
            byte[] newstates = new byte[newcapacity];
			
            used = 0;
            //  re-hash
            for (int i = 0; i<keys.length; i++)
			{
                if (states[i]==OCCUPIED)
				{
                    used++;
                    char k = keys[i];
                    Object v = values[i];
                    //  first hash
                    int h = Math.abs(keyhash.hash(k));
                    int n = h%newcapacity;
                    if (newstates[n]==OCCUPIED)
					{
                        //  second hash
                        int c = 1+(h%(newcapacity-2));
                        for (;;)
						{
                            n -= c;
                            if (n<0)
                                n += newcapacity;
                            if (newstates[n]==EMPTY)
                                break;
                        }
                    }
                    newstates[n] = OCCUPIED;
                    newvalues[n] = v;
                    newkeys[n] = k;
                }
            }
			
            keys = newkeys;
            values = newvalues;
            states = newstates;
        }
    }
	
    // ---------------------------------------------------------------
    //      Operations not supported by abstract implementation
    // ---------------------------------------------------------------
	
    public CharSet keySet()
	{
        if (ckeys==null)
            ckeys = new KeySet();
        return ckeys;
    }
	
    public Object put(char key, Object value)
	{
        Object result;
		
        //  first hash
        int h = Math.abs(keyhash.hash(key));
        int i = h%keys.length;
        if (states[i]==OCCUPIED)
		{
            if (keys[i]==key)
			{
                Object oldValue = values[i];
                values[i] = value;
                return oldValue;
            }
            //  second hash
            int c = 1+(h%(keys.length-2));
            for (;;)
			{
                i -= c;
                if (i<0)
                    i += keys.length;
                //  Empty entries are re-used
                if (states[i]==EMPTY||states[i]==REMOVED)
                    break;
                if (states[i]==OCCUPIED&&keys[i]==key)
				{
                    Object oldValue = values[i];
                    values[i] = value;
                    return oldValue;
                }
            }
        }
		
        if (states[i]==EMPTY)
            used++;
        states[i] = OCCUPIED;
        keys[i] = key;
        values[i] = value;
        size++;
        ensureCapacity(used);
        return null;
    }
	
    public Collection values()
	{
        if (cvalues==null)
            cvalues = new ValueCollection();
        return cvalues;
    }
	
    /**
     *  Returns a clone of this hash map.
     *
     *  @return     a clone of this hash map.
     *
     *  @since      1.1
     */
    public Object clone()
	{
        try
		{
            CharKeyOpenHashMap c = (CharKeyOpenHashMap)super.clone();
            c.keys = new char[keys.length];
            System.arraycopy(keys, 0, c.keys, 0, keys.length);
            c.values = (Object[])new Object[values.length];
            System.arraycopy(values, 0, c.values, 0, values.length);
            c.states = new byte[states.length];
            System.arraycopy(states, 0, c.states, 0, states.length);
            //  The views should not refer to this map's views
            c.cvalues = null;
            c.ckeys = null;
            return c;
        }
		catch (CloneNotSupportedException e)
		{
            Exceptions.cloning(); return null;
        }
    }
	
    public CharKeyMapIterator entries()
	{
        return new CharKeyMapIterator() {
            int nextEntry = nextEntry(0);
            int lastEntry = -1;
			
            int nextEntry(int index)
			{
                while (index<keys.length&&states[index]!=OCCUPIED)
                    index++;
                return index;
            }
			
            public boolean hasNext()
			{
                return nextEntry<keys.length;
            }
			
            public void next()
			{
                if (!hasNext())
                    Exceptions.endOfIterator();
                lastEntry = nextEntry;
                nextEntry = nextEntry(nextEntry+1);
            }
			
            public void remove()
			{
                if (lastEntry==-1)
                    Exceptions.noElementToRemove();
                states[lastEntry] = REMOVED;
                values[lastEntry] = null;   //  GC
                size--;
                lastEntry = -1;
            }
			
            public char getKey()
			{
                if (lastEntry==-1)
                    Exceptions.noElementToGet();
                return keys[lastEntry];
            }
			
            public Object getValue()
			{
                if (lastEntry==-1)
                    Exceptions.noElementToGet();
                return values[lastEntry];
            }
        };
    }
	
    private class KeySet extends AbstractCharSet
	{
		
        public void clear()
        { CharKeyOpenHashMap.this.clear(); }
		
        public boolean contains(char v)
		{
            return containsKey(v);
        }
		
        public CharIterator iterator()
		{
            return new CharIterator() {
                int nextEntry = nextEntry(0);
                int lastEntry = -1;
				
                int nextEntry(int index)
				{
                    while (index<keys.length&&states[index]!=OCCUPIED)
                        index++;
                    return index;
                }
				
                public boolean hasNext()
				{
                    return nextEntry<keys.length;
                }
				
                public char next()
				{
                    if (!hasNext())
                        Exceptions.endOfIterator();
                    lastEntry = nextEntry;
                    nextEntry = nextEntry(nextEntry+1);
                    return keys[lastEntry];
                }
				
                public void remove()
				{
                    if (lastEntry==-1)
                        Exceptions.noElementToRemove();
                    states[lastEntry] = REMOVED;
                    values[lastEntry] = null;   //  GC
                    size--;
                    lastEntry = -1;
                }
            };
        }
		
        public boolean remove(char v)
		{
            boolean result = containsKey(v);
            if (result)
                CharKeyOpenHashMap.this.remove(v);
            return result;
        }
		
        public int size()
        { return size; }
		
    }
	
	
    private class ValueCollection extends AbstractCollection
	{
		
        public void clear()
        { CharKeyOpenHashMap.this.clear(); }
		
        public boolean contains(Object v)
		{
            return containsValue(v);
        }
		
        public Iterator iterator()
		{
            return new Iterator() {
                int nextEntry = nextEntry(0);
                int lastEntry = -1;
				
                int nextEntry(int index)
				{
                    while (index<keys.length&&states[index]!=OCCUPIED)
                        index++;
                    return index;
                }
				
                public boolean hasNext()
				{
                    return nextEntry<keys.length;
                }
				
                public Object next()
				{
                    if (!hasNext())
                        Exceptions.endOfIterator();
                    lastEntry = nextEntry;
                    nextEntry = nextEntry(nextEntry+1);
                    return values[lastEntry];
                }
				
                public void remove()
				{
                    if (lastEntry==-1)
                        Exceptions.noElementToRemove();
                    states[lastEntry] = REMOVED;
                    values[lastEntry] = null;   //  GC
                    size--;
                    lastEntry = -1;
                }
            };
        }
		
        public int size()
        { return size; }
		
    }
	
    // ---------------------------------------------------------------
    //      Operations overwritten for efficiency
    // ---------------------------------------------------------------
	
    public void clear()
	{
        java.util.Arrays.fill(states, EMPTY);
        java.util.Arrays.fill(values, null);    // GC
        size = 0;
        used = 0;
    }
	
    public boolean containsKey(char key)
	{
        int h = Math.abs(keyhash.hash(key));
        int i = h%keys.length;
        if (states[i]!=EMPTY)
		{
            if (states[i]==OCCUPIED&&keys[i]==key)
                return true;
			
            //  second hash
            int c = 1+(h%(keys.length-2));
            for (;;)
			{
                i -= c;
                if (i<0)
                    i += keys.length;
                if (states[i]==EMPTY)
                    return false;
                if (states[i]==OCCUPIED&&keys[i]==key)
                    return true;
            }
        }
        return false;
    }
	
    public boolean containsValue(Object value)
	{
        if (value==null)
		{
            for (int i = 0; i<states.length; i++)
                if (states[i]==OCCUPIED&&values[i]==null)
                    return true;
        }
		else
		{
            for (int i = 0; i<states.length; i++)
                if (states[i]==OCCUPIED&&value.equals(values[i]))
                    return true;
        }
        return false;
    }
	
    public Object get(char key)
	{
        int h = Math.abs(keyhash.hash(key));
        int i = h%keys.length;
        if (states[i]!=EMPTY)
		{
            if (states[i]==OCCUPIED&&keys[i]==key)
                return values[i];
            //  second hash
			
            int c = 1+(h%(keys.length-2));
            for (;;)
			{
                i -= c;
                if (i<0)
                    i += keys.length;
                if (states[i]==EMPTY)
                    return null;
                if (states[i]==OCCUPIED&&keys[i]==key)
                    return values[i];
            }
        }
        return null;
    }
	
    public boolean isEmpty()
    { return size==0; }
	
    public Object remove(char key)
	{
        int h = Math.abs(keyhash.hash(key));
        int i = h%keys.length;
        if (states[i]!=EMPTY)
		{
            if (states[i]==OCCUPIED&&keys[i]==key)
			{
                Object oldValue = values[i];
                values[i] = null;   // GC
                states[i] = REMOVED;
                size--;
                return oldValue;
            }
            //  second hash
            int c = 1+(h%(keys.length-2));
            for (;;)
			{
                i -= c;
                if (i<0)
                    i += keys.length;
                if (states[i]==EMPTY)
				{
                    return null;
                }
                if (states[i]==OCCUPIED&&keys[i]==key)
				{
                    Object oldValue = values[i];
                    values[i] = null;   //  GC
                    states[i] = REMOVED;
                    size--;
                    return oldValue;
                }
            }
        }
        return null;
    }
	
    public int size()
    { return size; }
	
    // ---------------------------------------------------------------
    //      Serialization
    // ---------------------------------------------------------------
	
    /**
     *  @serialData     Default fields; the capacity of the
     *                  map (<tt>int</tt>); the maps's entries
     *                  (<tt>char</tt>, <tt>Object</tt>).
     *
     *  @since          1.1
     */
    private void writeObject(ObjectOutputStream s) throws IOException
	{
        s.defaultWriteObject();
        s.writeInt(keys.length);
        CharKeyMapIterator i = entries();
        while (i.hasNext())
		{
            i.next();
            s.writeChar(i.getKey());
            s.writeObject(i.getValue());
        }
    }
	
    /**
     *  @since          1.1
     */
    private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException
	{
        s.defaultReadObject();
        keys = new char[s.readInt()];
        states = new byte[keys.length];
        values = (Object[])new Object[keys.length];
        used = size;
		
        for (int n = 0; n<size; n++)
		{
            char key = s.readChar();
            Object value = s.readObject();
			
            //  first hash
            int h = Math.abs(keyhash.hash(key));
            int i = h%keys.length;
            if (states[i]!=EMPTY)
			{
                //  second hash
                int c = 1+(h%(keys.length-2));
                for (;;)
				{
                    i -= c;
                    if (i<0)
                        i += keys.length;
                    if (states[i]==EMPTY)
                        break;
                }
            }
            states[i] = OCCUPIED;
            keys[i] = key;
            values[i] = value;
        }
    }
	
}
