///////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2009, Rob Eden All Rights Reserved.
//
// 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 General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
///////////////////////////////////////////////////////////////////////////////
package gnu.trove.map.hash;
import gnu.trove.strategy.HashingStrategy;
import gnu.trove.function.TObjectFunction;
import gnu.trove.impl.HashFunctions;
import gnu.trove.impl.hash.TCustomObjectHash;
import gnu.trove.iterator.hash.TObjectHashIterator;
import gnu.trove.procedure.TObjectObjectProcedure;
import gnu.trove.procedure.TObjectProcedure;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.*;
/**
* An implementation of the Map interface which uses an open addressed
* hash table to store its contents.
*
* @author Rob Eden
*/
public class TCustomHashMap<K, V> extends TCustomObjectHash<K>
implements Map<K, V>, Externalizable {
static final long serialVersionUID = 1L;
/** the values of the map */
protected transient V[] _values;
/** FOR EXTERNALIZATION ONLY!!! */
public TCustomHashMap() {
super();
}
/**
* Creates a new <code>TCustomHashMap</code> instance with the default
* capacity and load factor.
*/
public TCustomHashMap( HashingStrategy<K> strategy ) {
super( strategy );
}
/**
* Creates a new <code>TCustomHashMap</code> instance with a prime
* capacity equal to or greater than <tt>initialCapacity</tt> and
* with the default load factor.
*
* @param initialCapacity an <code>int</code> value
*/
public TCustomHashMap( HashingStrategy<K> strategy, int initialCapacity ) {
super( strategy, initialCapacity );
}
/**
* Creates a new <code>TCustomHashMap</code> instance with a prime
* capacity equal to or greater than <tt>initialCapacity</tt> and
* with the specified load factor.
*
* @param initialCapacity an <code>int</code> value
* @param loadFactor a <code>float</code> value
*/
public TCustomHashMap( HashingStrategy<K> strategy, int initialCapacity,
float loadFactor ) {
super( strategy, initialCapacity, loadFactor );
}
/**
* Creates a new <code>TCustomHashMap</code> instance which contains the
* key/value pairs in <tt>map</tt>.
*
* @param map a <code>Map</code> value
*/
public TCustomHashMap( HashingStrategy<K> strategy, Map<K, V> map ) {
this( strategy, map.size() );
putAll( map );
}
/**
* Creates a new <code>TCustomHashMap</code> instance which contains the
* key/value pairs in <tt>map</tt>.
*
* @param map a <code>Map</code> value
*/
public TCustomHashMap( HashingStrategy<K> strategy, TCustomHashMap<K, V> map ) {
this( strategy, map.size() );
putAll( map );
}
/**
* initialize the value array of the map.
*
* @param initialCapacity an <code>int</code> value
* @return an <code>int</code> value
*/
public int setUp( int initialCapacity ) {
int capacity;
capacity = super.setUp( initialCapacity );
//noinspection unchecked
_values = (V[]) new Object[capacity];
return capacity;
}
/**
* Inserts a key/value pair into the map.
*
* @param key an <code>Object</code> value
* @param value an <code>Object</code> value
* @return the previous value associated with <tt>key</tt>,
* or {@code null} if none was found.
*/
public V put( K key, V value ) {
int index = insertionIndex( key );
return doPut( key, value, index );
}
/**
* Inserts a key/value pair into the map if the specified key is not already
* associated with a value.
*
* @param key an <code>Object</code> value
* @param value an <code>Object</code> value
* @return the previous value associated with <tt>key</tt>,
* or {@code null} if none was found.
*/
public V putIfAbsent( K key, V value ) {
int index = insertionIndex( key );
if ( index < 0 ) {
return _values[-index - 1];
}
return doPut( key, value, index );
}
private V doPut( K key, V value, int index ) {
V previous = null;
Object oldKey;
boolean isNewMapping = true;
if ( index < 0 ) {
index = -index - 1;
previous = _values[index];
isNewMapping = false;
}
oldKey = _set[index];
_set[index] = key;
_values[index] = value;
if ( isNewMapping ) {
postInsertHook( oldKey == FREE );
}
return previous;
}
/**
* Compares this map with another map for equality of their stored
* entries.
*
* @param other an <code>Object</code> value
* @return a <code>boolean</code> value
*/
@SuppressWarnings({"unchecked", "SimplifiableIfStatement"})
public boolean equals( Object other ) {
if ( !( other instanceof Map ) ) {
return false;
}
Map<K, V> that = (Map<K, V>) other;
if ( that.size() != this.size() ) {
return false;
}
return forEachEntry( new EqProcedure<K, V>( that ) );
}
public int hashCode() {
HashProcedure p = new HashProcedure();
forEachEntry( p );
return p.getHashCode();
}
public String toString() {
final StringBuilder buf = new StringBuilder( "{" );
forEachEntry( new TObjectObjectProcedure<K, V>() {
private boolean first = true;
public boolean execute( K key, V value ) {
if ( first ) {
first = false;
} else {
buf.append( ", " );
}
buf.append( key );
buf.append( "=" );
buf.append( value );
return true;
}
} );
buf.append( "}" );
return buf.toString();
}
private final class HashProcedure implements TObjectObjectProcedure<K, V> {
private int h = 0;
public int getHashCode() {
return h;
}
public final boolean execute( K key, V value ) {
h += HashFunctions.hash( key ) ^ ( value == null ? 0 : value.hashCode() );
return true;
}
}
private static final class EqProcedure<K, V> implements TObjectObjectProcedure<K, V> {
private final Map<K, V> _otherMap;
EqProcedure( Map<K, V> otherMap ) {
_otherMap = otherMap;
}
public final boolean execute( K key, V value ) {
// Check to make sure the key is there. This avoids problems that come up with
// null values. Since it is only caused in that cause, only do this when the
// value is null (to avoid extra work).
if ( value == null && !_otherMap.containsKey( key ) ) {
return false;
}
V oValue = _otherMap.get( key );
return oValue == value || ( oValue != null && oValue.equals( value ) );
}
}
/**
* Executes <tt>procedure</tt> for each key in the map.
*
* @param procedure a <code>TObjectProcedure</code> value
* @return false if the loop over the keys terminated because
* the procedure returned false for some key.
*/
public boolean forEachKey( TObjectProcedure<K> procedure ) {
return forEach( procedure );
}
/**
* Executes <tt>procedure</tt> for each value in the map.
*
* @param procedure a <code>TObjectProcedure</code> value
* @return false if the loop over the values terminated because
* the procedure returned false for some value.
*/
public boolean forEachValue( TObjectProcedure<V> procedure ) {
V[] values = _values;
Object[] set = _set;
for ( int i = values.length; i-- > 0; ) {
if ( set[i] != FREE
&& set[i] != REMOVED
&& !procedure.execute( values[i] ) ) {
return false;
}
}
return true;
}
/**
* Executes <tt>procedure</tt> for each key/value entry in the
* map.
*
* @param procedure a <code>TObjectObjectProcedure</code> value
* @return false if the loop over the entries terminated because
* the procedure returned false for some entry.
*/
@SuppressWarnings({"unchecked"})
public boolean forEachEntry( TObjectObjectProcedure<K, V> procedure ) {
Object[] keys = _set;
V[] values = _values;
for ( int i = keys.length; i-- > 0; ) {
if ( keys[i] != FREE
&& keys[i] != REMOVED
&& !procedure.execute( (K) keys[i], values[i] ) ) {
return false;
}
}
return true;
}
/**
* Retains only those entries in the map for which the procedure
* returns a true value.
*
* @param procedure determines which entries to keep
* @return true if the map was modified.
*/
@SuppressWarnings({"unchecked"})
public boolean retainEntries( TObjectObjectProcedure<K, V> procedure ) {
boolean modified = false;
Object[] keys = _set;
V[] values = _values;
// Temporarily disable compaction. This is a fix for bug #1738760
tempDisableAutoCompaction();
try {
for ( int i = keys.length; i-- > 0; ) {
if ( keys[i] != FREE
&& keys[i] != REMOVED
&& !procedure.execute( (K) keys[i], values[i] ) ) {
removeAt( i );
modified = true;
}
}
}
finally {
reenableAutoCompaction( true );
}
return modified;
}
/**
* Transform the values in this map using <tt>function</tt>.
*
* @param function a <code>TObjectFunction</code> value
*/
public void transformValues( TObjectFunction<V, V> function ) {
V[] values = _values;
Object[] set = _set;
for ( int i = values.length; i-- > 0; ) {
if ( set[i] != FREE && set[i] != REMOVED ) {
values[i] = function.execute( values[i] );
}
}
}
/**
* rehashes the map to the new capacity.
*
* @param newCapacity an <code>int</code> value
*/
@SuppressWarnings({"unchecked"})
protected void rehash( int newCapacity ) {
int oldCapacity = _set.length;
Object oldKeys[] = _set;
V oldVals[] = _values;
_set = new Object[ newCapacity ];
Arrays.fill( _set, FREE );
_values = ( V[] ) new Object[ newCapacity ];
// Process entries from the old array, skipping free and removed slots. Put the
// values into the appropriate place in the new array.
for ( int i = oldCapacity; i-- > 0; ) {
if ( oldKeys[ i ] == FREE || oldKeys[ i ] == REMOVED ) continue;
Object o = oldKeys[ i ];
int index = insertionIndex( ( K ) o );
if ( index < 0 ) {
throwObjectContractViolation( _set[ ( -index - 1 ) ], o );
}
_set[ index ] = o;
_values[ index ] = oldVals[ i ];
}
}
/**
* retrieves the value for <tt>key</tt>
*
* @param key an <code>Object</code> value
* @return the value of <tt>key</tt> or null if no such mapping exists.
*/
@SuppressWarnings({"unchecked"})
public V get( Object key ) {
int index = index( key );
if ( index < 0 || ! strategy.equals( ( K ) _set[index], ( K ) key ) ) {
return null;
}
return _values[index];
}
/** Empties the map. */
public void clear() {
if ( size() == 0 ) {
return; // optimization
}
super.clear();
Arrays.fill( _set, 0, _set.length, FREE );
Arrays.fill( _values, 0, _values.length, null );
}
/**
* Deletes a key/value pair from the map.
*
* @param key an <code>Object</code> value
* @return an <code>Object</code> value
*/
@SuppressWarnings({"unchecked"})
public V remove( Object key ) {
V prev = null;
int index = index( key );
if ( index >= 0 ) {
prev = _values[index];
removeAt( index ); // clear key,state; adjust size
}
return prev;
}
/**
* removes the mapping at <tt>index</tt> from the map.
*
* @param index an <code>int</code> value
*/
public void removeAt( int index ) {
_values[index] = null;
super.removeAt( index ); // clear key, state; adjust size
}
/**
* Returns a view on the values of the map.
*
* @return a <code>Collection</code> value
*/
public Collection<V> values() {
return new ValueView();
}
/**
* returns a Set view on the keys of the map.
*
* @return a <code>Set</code> value
*/
public Set<K> keySet() {
return new KeyView();
}
/**
* Returns a Set view on the entries of the map.
*
* @return a <code>Set</code> value
*/
public Set<Map.Entry<K, V>> entrySet() {
return new EntryView();
}
/**
* checks for the presence of <tt>val</tt> in the values of the map.
*
* @param val an <code>Object</code> value
* @return a <code>boolean</code> value
*/
public boolean containsValue( Object val ) {
Object[] set = _set;
V[] vals = _values;
// special case null values so that we don't have to
// perform null checks before every call to equals()
if ( null == val ) {
for ( int i = vals.length; i-- > 0; ) {
if ( ( set[i] != FREE && set[i] != REMOVED ) &&
val == vals[i] ) {
return true;
}
}
} else {
for ( int i = vals.length; i-- > 0; ) {
if ( ( set[i] != FREE && set[i] != REMOVED ) &&
( val == vals[i] || strategy.equals( ( K ) val, ( K ) vals[i] ) ) ) {
return true;
}
}
} // end of else
return false;
}
/**
* checks for the present of <tt>key</tt> in the keys of the map.
*
* @param key an <code>Object</code> value
* @return a <code>boolean</code> value
*/
public boolean containsKey( Object key ) {
return contains( key );
}
/**
* copies the key/value mappings in <tt>map</tt> into this map.
*
* @param map a <code>Map</code> value
*/
public void putAll( Map<? extends K, ? extends V> map ) {
ensureCapacity( map.size() );
// could optimize this for cases when map instanceof TCustomHashMap
for ( Map.Entry<? extends K, ? extends V> e : map.entrySet() ) {
put( e.getKey(), e.getValue() );
}
}
/** a view onto the values of the map. */
protected class ValueView extends MapBackedView<V> {
@SuppressWarnings({"unchecked"})
public Iterator<V> iterator() {
return new TObjectHashIterator( TCustomHashMap.this ) {
protected V objectAtIndex( int index ) {
return _values[index];
}
};
}
public boolean containsElement( V value ) {
return containsValue( value );
}
public boolean removeElement( V value ) {
Object[] values = _values;
Object[] set = _set;
for ( int i = values.length; i-- > 0; ) {
if ( ( set[i] != FREE && set[i] != REMOVED ) &&
value == values[i] ||
( null != values[i] && strategy.equals( ( K ) values[i], ( K ) value ) ) ) {
removeAt( i );
return true;
}
}
return false;
}
}
/** a view onto the entries of the map. */
protected class EntryView extends MapBackedView<Map.Entry<K, V>> {
private final class EntryIterator extends TObjectHashIterator {
EntryIterator( TCustomHashMap<K, V> map ) {
super( map );
}
@SuppressWarnings({"unchecked"})
public Entry objectAtIndex( final int index ) {
return new Entry( (K) _set[index], _values[index], index );
}
}
@SuppressWarnings({"unchecked"})
public Iterator<Map.Entry<K, V>> iterator() {
return new EntryIterator( TCustomHashMap.this );
}
public boolean removeElement( Map.Entry<K, V> entry ) {
// have to effectively reimplement Map.remove here
// because we need to return true/false depending on
// whether the removal took place. Since the Entry's
// value can be null, this means that we can't rely
// on the value of the object returned by Map.remove()
// to determine whether a deletion actually happened.
//
// Note also that the deletion is only legal if
// both the key and the value match.
Object val;
int index;
K key = keyForEntry( entry );
index = index( key );
if ( index >= 0 ) {
val = valueForEntry( entry );
if ( val == _values[index] ||
( null != val && strategy.equals( ( K ) val, ( K ) _values[index] ) ) ) {
removeAt( index ); // clear key,state; adjust size
return true;
}
}
return false;
}
public boolean containsElement( Map.Entry<K, V> entry ) {
Object val = get( keyForEntry( entry ) );
Object entryValue = entry.getValue();
return entryValue == val ||
( null != val && strategy.equals( ( K ) val, ( K ) entryValue ) );
}
protected V valueForEntry( Map.Entry<K, V> entry ) {
return entry.getValue();
}
protected K keyForEntry( Map.Entry<K, V> entry ) {
return entry.getKey();
}
}
private abstract class MapBackedView<E> extends AbstractSet<E>
implements Set<E>, Iterable<E> {
public abstract Iterator<E> iterator();
public abstract boolean removeElement( E key );
public abstract boolean containsElement( E key );
@SuppressWarnings({"unchecked"})
public boolean contains( Object key ) {
return containsElement( (E) key );
}
@SuppressWarnings({"unchecked"})
public boolean remove( Object o ) {
return removeElement( (E) o );
}
// public boolean containsAll( Collection<?> collection ) {
// for ( Object element : collection ) {
// if ( !contains( element ) ) {
// return false;
// }
// }
// return true;
// }
public void clear() {
TCustomHashMap.this.clear();
}
public boolean add( E obj ) {
throw new UnsupportedOperationException();
}
public int size() {
return TCustomHashMap.this.size();
}
public Object[] toArray() {
Object[] result = new Object[size()];
Iterator<E> e = iterator();
for ( int i = 0; e.hasNext(); i++ ) {
result[i] = e.next();
}
return result;
}
@SuppressWarnings({"unchecked"})
public <T> T[] toArray( T[] a ) {
int size = size();
if ( a.length < size ) {
a = (T[]) java.lang.reflect.Array.newInstance( a.getClass().getComponentType(), size );
}
Iterator<E> it = iterator();
Object[] result = a;
for ( int i = 0; i < size; i++ ) {
result[i] = it.next();
}
if ( a.length > size ) {
a[size] = null;
}
return a;
}
public boolean isEmpty() {
return TCustomHashMap.this.isEmpty();
}
public boolean addAll( Collection<? extends E> collection ) {
throw new UnsupportedOperationException();
}
@SuppressWarnings({"SuspiciousMethodCalls"})
public boolean retainAll( Collection<?> collection ) {
boolean changed = false;
Iterator<E> i = iterator();
while ( i.hasNext() ) {
if ( !collection.contains( i.next() ) ) {
i.remove();
changed = true;
}
}
return changed;
}
public String toString() {
Iterator<E> i = iterator();
if ( !i.hasNext() ) return "{}";
StringBuilder sb = new StringBuilder();
sb.append( '{' );
for (; ; ) {
E e = i.next();
sb.append( e == this ? "(this Collection)" : e );
if ( !i.hasNext() ) return sb.append( '}' ).toString();
sb.append( ", " );
}
}
}
/** a view onto the keys of the map. */
protected class KeyView extends MapBackedView<K> {
@SuppressWarnings({"unchecked"})
public Iterator<K> iterator() {
return new TObjectHashIterator( TCustomHashMap.this );
}
public boolean removeElement( K key ) {
return null != TCustomHashMap.this.remove( key );
}
public boolean containsElement( K key ) {
return TCustomHashMap.this.contains( key );
}
}
final class Entry implements Map.Entry<K, V> {
private K key;
private V val;
private final int index;
Entry( final K key, V value, final int index ) {
this.key = key;
this.val = value;
this.index = index;
}
public K getKey() {
return key;
}
public V getValue() {
return val;
}
public V setValue( V o ) {
if ( _values[index] != val ) {
throw new ConcurrentModificationException();
}
// need to return previous value
V retval = val;
// update this entry's value, in case setValue is called again
_values[index] = o;
val = o;
return retval;
}
public boolean equals( Object o ) {
if ( o instanceof Map.Entry ) {
Map.Entry<K, V> e1 = this;
Map.Entry e2 = (Map.Entry) o;
return ( e1.getKey() == null ? e2.getKey() == null :
strategy.equals( e1.getKey(), ( K ) e2.getKey() ) ) &&
( e1.getValue() == null ? e2.getValue() == null :
e1.getValue().equals( e2.getValue() ) );
}
return false;
}
public int hashCode() {
return ( getKey() == null ? 0 : getKey().hashCode() ) ^ ( getValue() == null ? 0 : getValue().hashCode() );
}
@Override
public String toString() {
return key + "=" + val;
}
}
public void writeExternal( ObjectOutput out ) throws IOException {
// VERSION
out.writeByte( 1 );
// NOTE: Super was not written in version 0
super.writeExternal( out );
// NUMBER OF ENTRIES
out.writeInt( _size );
// ENTRIES
for ( int i = _set.length; i-- > 0; ) {
if ( _set[i] != REMOVED && _set[i] != FREE ) {
out.writeObject( _set[i] );
out.writeObject( _values[i] );
}
}
}
public void readExternal( ObjectInput in )
throws IOException, ClassNotFoundException {
// VERSION
byte version = in.readByte();
// NOTE: super was not written in version 0
if ( version != 0 ) {
super.readExternal( in );
}
// NUMBER OF ENTRIES
int size = in.readInt();
setUp( size );
// ENTRIES
while ( size-- > 0 ) {
//noinspection unchecked
K key = (K) in.readObject();
//noinspection unchecked
V val = (V) in.readObject();
put( key, val );
}
}
} // TCustomHashMap