/*
* 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ø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
*/
@Override
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
{
@Override
public void clear()
{ CharKeyOpenHashMap.this.clear(); }
@Override
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;
}
};
}
@Override
public boolean remove(char v)
{
boolean result = containsKey(v);
if (result)
CharKeyOpenHashMap.this.remove(v);
return result;
}
@Override
public int size()
{ return size; }
}
private class ValueCollection extends AbstractCollection
{
@Override
public void clear()
{ CharKeyOpenHashMap.this.clear(); }
@Override
public boolean contains(Object v)
{
return containsValue(v);
}
@Override
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;
}
};
}
@Override
public int size()
{ return size; }
}
// ---------------------------------------------------------------
// Operations overwritten for efficiency
// ---------------------------------------------------------------
@Override
public void clear()
{
java.util.Arrays.fill(states, EMPTY);
java.util.Arrays.fill(values, null); // GC
size = 0;
used = 0;
}
@Override
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;
}
@Override
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;
}
@Override
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;
}
@Override
public boolean isEmpty()
{ return size==0; }
@Override
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;
}
@Override
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;
}
}
}