org.codehaus.plexus.util.FastMap Maven / Gradle / Ivy
Show all versions of plexus-utils Show documentation
package org.codehaus.plexus.util;
/*
* J.A.D.E. Java(TM) Addition to Default Environment.
* Latest release available at http://jade.dautelle.com/
* This class is public domain (not copyrighted).
*/
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
*
* This class represents a Map
collection with real-time behavior. Unless the map's size exceeds its
* current capacity, no dynamic memory allocation is ever performed and response time is extremely fast and
* consistent.
*
*
* Our benchmark indicates that {@link FastMap#put
* FastMap.put(key, value)} is up to 5x faster than java.util.HashMap.put(key, value)
. This
* difference is mostly due to the cost of the Map.Entry
allocations that {@link FastMap} avoids by
* recycling its entries (see note below).
*
*
* {@link FastMap} has a predictable iteration order, which is the order in which keys were inserted into the map
* (similar to java.util.LinkedHashMap
collection class).
*
*
* Applications may change the resizing policy of {@link FastMap} by overriding the {@link #sizeChanged} method. For
* example, to improve predictability, automatic resizing can be disabled.
*
*
* This implementation is not synchronized. Multiple threads accessing or modifying the collection must be synchronized
* externally.
*
*
* Note: To avoid dynamic memory allocations, {@link FastMap} maintains an internal pool of
* Map.Entry
objects. The size of the pool is determined by the map's capacity. When an entry is removed
* from the map, it is automatically restored to the pool.
*
*
* This class is public domain (not copyrighted).
*
*
* @author Jean-Marie Dautelle
* @version 5.3, October 31 2003
*/
public class FastMap
implements Map, Cloneable, Serializable
{
/**
* Holds the map's hash table.
*/
private transient EntryImpl[] _entries;
/**
* Holds the map's current capacity.
*/
private transient int _capacity;
/**
* Holds the hash code mask.
*/
private transient int _mask;
/**
* Holds the first pool entry (linked list).
*/
private transient EntryImpl _poolFirst;
/**
* Holds the first map entry (linked list).
*/
private transient EntryImpl _mapFirst;
/**
* Holds the last map entry (linked list).
*/
private transient EntryImpl _mapLast;
/**
* Holds the current size.
*/
private transient int _size;
/**
* Creates a {@link FastMap} with a capacity of 256
entries.
*/
public FastMap()
{
initialize( 256 );
}
/**
* Creates a {@link FastMap}, copy of the specified Map
. If the specified map is not an instance of
* {@link FastMap}, the newly created map has a capacity set to the specified map's size. The copy has the same
* order as the original, regardless of the original map's implementation:
*
*
* TreeMap dictionary = ...;
* FastMap dictionaryLookup = new FastMap(dictionary);
*
*
* @param map the map whose mappings are to be placed in this map.
*/
public FastMap( Map map )
{
int capacity = ( map instanceof FastMap ) ? ( (FastMap) map ).capacity() : map.size();
initialize( capacity );
putAll( map );
}
/**
* Creates a {@link FastMap} with the specified capacity. Unless the capacity is exceeded, operations on this map do
* not allocate entries. For optimum performance, the capacity should be of the same order of magnitude or larger
* than the expected map's size.
*
* @param capacity the number of buckets in the hash table; it also defines the number of pre-allocated entries.
*/
public FastMap( int capacity )
{
initialize( capacity );
}
/**
* Returns the number of key-value mappings in this {@link FastMap}.
*
* @return this map's size.
*/
public int size()
{
return _size;
}
/**
* Returns the capacity of this {@link FastMap}. The capacity defines the number of buckets in the hash table, as
* well as the maximum number of entries the map may contain without allocating memory.
*
* @return this map's capacity.
*/
public int capacity()
{
return _capacity;
}
/**
* Indicates if this {@link FastMap} contains no key-value mappings.
*
* @return true
if this map contains no key-value mappings; false
otherwise.
*/
public boolean isEmpty()
{
return _size == 0;
}
/**
* Indicates if this {@link FastMap} contains a mapping for the specified key.
*
* @param key the key whose presence in this map is to be tested.
* @return true
if this map contains a mapping for the specified key; false
otherwise.
* @throws NullPointerException if the key is null
.
*/
public boolean containsKey( Object key )
{
EntryImpl entry = _entries[keyHash( key ) & _mask];
while ( entry != null )
{
if ( key.equals( entry._key ) )
{
return true;
}
entry = entry._next;
}
return false;
}
/**
* Indicates if this {@link FastMap} maps one or more keys to the specified value.
*
* @param value the value whose presence in this map is to be tested.
* @return true
if this map maps one or more keys to the specified value.
* @throws NullPointerException if the key is null
.
*/
public boolean containsValue( Object value )
{
EntryImpl entry = _mapFirst;
while ( entry != null )
{
if ( value.equals( entry._value ) )
{
return true;
}
entry = entry._after;
}
return false;
}
/**
* Returns the value to which this {@link FastMap} maps the specified key.
*
* @param key the key whose associated value is to be returned.
* @return the value to which this map maps the specified key, or null
if there is no mapping for the
* key.
* @throws NullPointerException if key is null
.
*/
public V get( Object key )
{
EntryImpl entry = _entries[keyHash( key ) & _mask];
while ( entry != null )
{
if ( key.equals( entry._key ) )
{
return entry._value;
}
entry = entry._next;
}
return null;
}
/**
* Returns the entry with the specified key.
*
* @param key the key whose associated entry is to be returned.
* @return the entry for the specified key or null
if none.
*/
public Map.Entry getEntry( Object key )
{
EntryImpl entry = _entries[keyHash( key ) & _mask];
while ( entry != null )
{
if ( key.equals( entry._key ) )
{
return entry;
}
entry = entry._next;
}
return null;
}
/**
* Associates the specified value with the specified key in this {@link FastMap}. If the {@link FastMap} previously
* contained a mapping for this key, the old value is replaced.
*
* @param key the key with which the specified value is to be associated.
* @param value the value to be associated with the specified key.
* @return the previous value associated with specified key, or null
if there was no mapping for key. A
* null
return can also indicate that the map previously associated null
with the
* specified key.
* @throws NullPointerException if the key is null
.
*/
public Object put( Object key, Object value )
{
EntryImpl entry = _entries[keyHash( key ) & _mask];
while ( entry != null )
{
if ( key.equals( entry._key ) )
{
Object prevValue = entry._value;
entry._value = value;
return prevValue;
}
entry = entry._next;
}
// No previous mapping.
addEntry( key, value );
return null;
}
/**
* Copies all of the mappings from the specified map to this {@link FastMap}.
*
* @param map the mappings to be stored in this map.
* @throws NullPointerException the specified map is null
, or the specified map contains
* null
keys.
*/
public void putAll( Map extends K, ? extends V> map )
{
for ( Entry extends K, ? extends V> entry : map.entrySet() )
{
addEntry( entry.getKey(), entry.getValue() );
}
}
/**
* Removes the mapping for this key from this {@link FastMap} if present.
*
* @param key the key whose mapping is to be removed from the map.
* @return previous value associated with specified key, or null
if there was no mapping for key. A
* null
return can also indicate that the map previously associated null
with the
* specified key.
* @throws NullPointerException if the key is null
.
*/
public V remove( Object key )
{
EntryImpl entry = _entries[keyHash( key ) & _mask];
while ( entry != null )
{
if ( key.equals( entry._key ) )
{
V prevValue = entry._value;
removeEntry( entry );
return prevValue;
}
entry = entry._next;
}
return null;
}
/**
* Removes all mappings from this {@link FastMap}.
*/
public void clear()
{
// Clears all keys, values and buckets linked lists.
for ( EntryImpl entry = _mapFirst; entry != null; entry = entry._after )
{
entry._key = null;
entry._value = null;
entry._before = null;
entry._next = null;
if ( entry._previous == null )
{ // First in bucket.
_entries[entry._index] = null;
}
else
{
entry._previous = null;
}
}
// Recycles all entries.
if ( _mapLast != null )
{
_mapLast._after = _poolFirst; // Connects to pool.
_poolFirst = _mapFirst;
_mapFirst = null;
_mapLast = null;
_size = 0;
sizeChanged();
}
}
/**
* Changes the current capacity of this {@link FastMap}. If the capacity is increased, new entries are allocated and
* added to the pool. If the capacity is decreased, entries from the pool are deallocated (and are eventually
* garbage collected). The capacity also determined the number of buckets for the hash table.
*
* @param newCapacity the new capacity of this map.
*/
public void setCapacity( int newCapacity )
{
if ( newCapacity > _capacity )
{ // Capacity increases.
for ( int i = _capacity; i < newCapacity; i++ )
{
EntryImpl entry = new EntryImpl();
entry._after = _poolFirst;
_poolFirst = entry;
}
}
else if ( newCapacity < _capacity )
{ // Capacity decreases.
for ( int i = newCapacity; ( i < _capacity ) && ( _poolFirst != null ); i++ )
{
// Disconnects the entry for gc to do its work.
EntryImpl entry = _poolFirst;
_poolFirst = entry._after;
entry._after = null; // All pointers are now null!
}
}
// Find a power of 2 >= capacity
int tableLength = 16;
while ( tableLength < newCapacity )
{
tableLength <<= 1;
}
// Checks if the hash table has to be re-sized.
if ( _entries.length != tableLength )
{
_entries = new EntryImpl[tableLength];
_mask = tableLength - 1;
// Repopulates the hash table.
EntryImpl entry = _mapFirst;
while ( entry != null )
{
int index = keyHash( entry._key ) & _mask;
entry._index = index;
// Connects to bucket.
entry._previous = null; // Resets previous.
EntryImpl next = _entries[index];
entry._next = next;
if ( next != null )
{
next._previous = entry;
}
_entries[index] = entry;
entry = entry._after;
}
}
_capacity = newCapacity;
}
/**
* Returns a shallow copy of this {@link FastMap}. The keys and the values themselves are not cloned.
*
* @return a shallow copy of this map.
*/
public Object clone()
{
try
{
FastMap clone = (FastMap) super.clone();
clone.initialize( _capacity );
clone.putAll( this );
return clone;
}
catch ( CloneNotSupportedException e )
{
// Should not happen, since we are Cloneable.
throw new InternalError();
}
}
/**
* Compares the specified object with this {@link FastMap} for equality. Returns true
if the given
* object is also a map and the two maps represent the same mappings (regardless of collection iteration order).
*
* @param obj the object to be compared for equality with this map.
* @return true
if the specified object is equal to this map; false
otherwise.
*/
public boolean equals( Object obj )
{
if ( obj == this )
{
return true;
}
else if ( obj instanceof Map )
{
Map that = (Map) obj;
if ( this.size() == that.size() )
{
EntryImpl entry = _mapFirst;
while ( entry != null )
{
if ( !that.entrySet().contains( entry ) )
{
return false;
}
entry = entry._after;
}
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
/**
* Returns the hash code value for this {@link FastMap}.
*
* @return the hash code value for this map.
*/
public int hashCode()
{
int code = 0;
EntryImpl entry = _mapFirst;
while ( entry != null )
{
code += entry.hashCode();
entry = entry._after;
}
return code;
}
/**
* Returns a String
representation of this {@link FastMap}.
*
* @return this.entrySet().toString();
*/
public String toString()
{
return entrySet().toString();
}
/**
* Returns a collection view of the values contained in this {@link FastMap}. The collection is backed by the map,
* so changes to the map are reflected in the collection, and vice-versa. The collection supports element removal,
* which removes the corresponding mapping from this map, via the Iterator.remove
,
* Collection.remove
, removeAll
, retainAll
, and clear
* operations. It does not support the add
or addAll
operations.
*
* @return a collection view of the values contained in this map.
*/
public Collection values()
{
return _values;
}
private transient Values _values;
private class Values
extends AbstractCollection
{
public Iterator iterator()
{
return new Iterator()
{
EntryImpl after = _mapFirst;
EntryImpl before;
public void remove()
{
removeEntry( before );
}
public boolean hasNext()
{
return after != null;
}
public Object next()
{
before = after;
after = after._after;
return before._value;
}
};
}
public int size()
{
return _size;
}
public boolean contains( Object o )
{
return containsValue( o );
}
public void clear()
{
FastMap.this.clear();
}
}
/**
* Returns a collection view of the mappings contained in this {@link FastMap}. Each element in the returned
* collection is a Map.Entry
. The collection is backed by the map, so changes to the map are reflected
* in the collection, and vice-versa. The collection supports element removal, which removes the corresponding
* mapping from this map, via the Iterator.remove
, Collection.remove
,
* removeAll
, retainAll
, and clear
operations. It does not support the
* add
or addAll
operations.
*
* @return a collection view of the mappings contained in this map.
*/
public Set entrySet()
{
return _entrySet;
}
private transient EntrySet _entrySet;
private class EntrySet
extends AbstractSet
{
public Iterator iterator()
{
return new Iterator()
{
EntryImpl after = _mapFirst;
EntryImpl before;
public void remove()
{
removeEntry( before );
}
public boolean hasNext()
{
return after != null;
}
public Object next()
{
before = after;
after = after._after;
return before;
}
};
}
public int size()
{
return _size;
}
public boolean contains( Object obj )
{ // Optimization.
if ( obj instanceof Map.Entry )
{
Map.Entry entry = (Map.Entry) obj;
Map.Entry mapEntry = getEntry( entry.getKey() );
return entry.equals( mapEntry );
}
else
{
return false;
}
}
public boolean remove( Object obj )
{ // Optimization.
if ( obj instanceof Map.Entry )
{
Map.Entry entry = (Map.Entry) obj;
EntryImpl mapEntry = (EntryImpl) getEntry( entry.getKey() );
if ( ( mapEntry != null ) && ( entry.getValue() ).equals( mapEntry._value ) )
{
removeEntry( mapEntry );
return true;
}
}
return false;
}
}
/**
* Returns a set view of the keys contained in this {@link FastMap}. The set is backed by the map, so changes to the
* map are reflected in the set, and vice-versa. The set supports element removal, which removes the corresponding
* mapping from this map, via the Iterator.remove
, Collection.remove
,
* removeAll
, retainAll
, and clear
operations. It does not support the
* add
or addAll
operations.
*
* @return a set view of the keys contained in this map.
*/
public Set keySet()
{
return _keySet;
}
private transient KeySet _keySet;
private class KeySet
extends AbstractSet
{
public Iterator iterator()
{
return new Iterator()
{
EntryImpl after = _mapFirst;
EntryImpl before;
public void remove()
{
removeEntry( before );
}
public boolean hasNext()
{
return after != null;
}
public Object next()
{
before = after;
after = after._after;
return before._key;
}
};
}
public int size()
{
return _size;
}
public boolean contains( Object obj )
{ // Optimization.
return FastMap.this.containsKey( obj );
}
public boolean remove( Object obj )
{ // Optimization.
return FastMap.this.remove( obj ) != null;
}
public void clear()
{ // Optimization.
FastMap.this.clear();
}
}
/**
* This methods is being called when the size of this {@link FastMap} has changed. The default behavior is to double
* the map's capacity when the map's size reaches the current map's capacity. Sub-class may override this method to
* implement custom resizing policies or to disable automatic resizing. For example:
*
*
* Map fixedCapacityMap = new FastMap( 256 )
* {
* protected sizeChanged()
* {
* // Do nothing, automatic resizing disabled.
* }
* };
*
*
* @see #setCapacity
*/
protected void sizeChanged()
{
if ( size() > capacity() )
{
setCapacity( capacity() * 2 );
}
}
/**
* Returns the hash code for the specified key. The formula being used is identical to the formula used by
* java.util.HashMap
(ensures similar behavior for ill-conditioned hashcode keys).
*
* @param key the key to calculate the hashcode for.
* @return the hash code for the specified key.
*/
private static int keyHash( Object key )
{
// From HashMap.hash(Object) function.
int hashCode = key.hashCode();
hashCode += ~( hashCode << 9 );
hashCode ^= ( hashCode >>> 14 );
hashCode += ( hashCode << 4 );
hashCode ^= ( hashCode >>> 10 );
return hashCode;
}
/**
* Adds a new entry for the specified key and value.
*
* @param key the entry's key.
* @param value the entry's value.
*/
private void addEntry( Object key, Object value )
{
EntryImpl entry = _poolFirst;
if ( entry != null )
{
_poolFirst = entry._after;
entry._after = null;
}
else
{ // Pool empty.
entry = new EntryImpl();
}
// Setup entry parameters.
entry._key = key;
entry._value = value;
int index = keyHash( key ) & _mask;
entry._index = index;
// Connects to bucket.
EntryImpl next = _entries[index];
entry._next = next;
if ( next != null )
{
next._previous = entry;
}
_entries[index] = entry;
// Connects to collection.
if ( _mapLast != null )
{
entry._before = _mapLast;
_mapLast._after = entry;
}
else
{
_mapFirst = entry;
}
_mapLast = entry;
// Updates size.
_size++;
sizeChanged();
}
/**
* Removes the specified entry from the map.
*
* @param entry the entry to be removed.
*/
private void removeEntry( EntryImpl entry )
{
// Removes from bucket.
EntryImpl previous = entry._previous;
EntryImpl next = entry._next;
if ( previous != null )
{
previous._next = next;
entry._previous = null;
}
else
{ // First in bucket.
_entries[entry._index] = next;
}
if ( next != null )
{
next._previous = previous;
entry._next = null;
} // Else do nothing, no last pointer.
// Removes from collection.
EntryImpl before = entry._before;
EntryImpl after = entry._after;
if ( before != null )
{
before._after = after;
entry._before = null;
}
else
{ // First in collection.
_mapFirst = after;
}
if ( after != null )
{
after._before = before;
}
else
{ // Last in collection.
_mapLast = before;
}
// Clears value and key.
entry._key = null;
entry._value = null;
// Recycles.
entry._after = _poolFirst;
_poolFirst = entry;
// Updates size.
_size--;
sizeChanged();
}
/**
* Initializes this instance for the specified capacity. Once initialized, operations on this map should not create
* new objects (unless the map's size exceeds the specified capacity).
*
* @param capacity the initial capacity.
*/
private void initialize( int capacity )
{
// Find a power of 2 >= capacity
int tableLength = 16;
while ( tableLength < capacity )
{
tableLength <<= 1;
}
// Allocates hash table.
_entries = new EntryImpl[tableLength];
_mask = tableLength - 1;
_capacity = capacity;
_size = 0;
// Allocates views.
_values = new Values();
_entrySet = new EntrySet();
_keySet = new KeySet();
// Resets pointers.
_poolFirst = null;
_mapFirst = null;
_mapLast = null;
// Allocates entries.
for ( int i = 0; i < capacity; i++ )
{
EntryImpl entry = new EntryImpl();
entry._after = _poolFirst;
_poolFirst = entry;
}
}
/**
* Requires special handling during de-serialization process.
*
* @param stream the object input stream.
* @throws IOException if an I/O error occurs.
* @throws ClassNotFoundException if the class for the object de-serialized is not found.
*/
private void readObject( ObjectInputStream stream )
throws IOException, ClassNotFoundException
{
int capacity = stream.readInt();
initialize( capacity );
int size = stream.readInt();
for ( int i = 0; i < size; i++ )
{
Object key = stream.readObject();
Object value = stream.readObject();
addEntry( key, value );
}
}
/**
* Requires special handling during serialization process.
*
* @param stream the object output stream.
* @throws IOException if an I/O error occurs.
*/
private void writeObject( ObjectOutputStream stream )
throws IOException
{
stream.writeInt( _capacity );
stream.writeInt( _size );
int count = 0;
EntryImpl entry = _mapFirst;
while ( entry != null )
{
stream.writeObject( entry._key );
stream.writeObject( entry._value );
count++;
entry = entry._after;
}
if ( count != _size )
{
throw new IOException( "FastMap Corrupted" );
}
}
/**
* This class represents a {@link FastMap} entry.
*/
private static final class EntryImpl
implements Map.Entry
{
/**
* Holds the entry key (null when in pool).
*/
private K _key;
/**
* Holds the entry value (null when in pool).
*/
private V _value;
/**
* Holds the bucket index (undefined when in pool).
*/
private int _index;
/**
* Holds the previous entry in the same bucket (null when in pool).
*/
private EntryImpl _previous;
/**
* Holds the next entry in the same bucket (null when in pool).
*/
private EntryImpl _next;
/**
* Holds the entry added before this entry (null when in pool).
*/
private EntryImpl _before;
/**
* Holds the entry added after this entry or the next available entry when in pool.
*/
private EntryImpl _after;
/**
* Returns the key for this entry.
*
* @return the entry's key.
*/
public K getKey()
{
return _key;
}
/**
* Returns the value for this entry.
*
* @return the entry's value.
*/
public V getValue()
{
return _value;
}
/**
* Sets the value for this entry.
*
* @param value the new value.
* @return the previous value.
*/
public V setValue( V value )
{
V old = _value;
_value = value;
return old;
}
/**
* Indicates if this entry is considered equals to the specified entry.
*
* @param that the object to test for equality.
* @return true if both entry are considered equal; false otherwise.
*/
public boolean equals( Object that )
{
if ( that instanceof Map.Entry )
{
Map.Entry entry = (Map.Entry) that;
return ( _key.equals( entry.getKey() ) )
&& ( ( _value != null ) ? _value.equals( entry.getValue() ) : ( entry.getValue() == null ) );
}
else
{
return false;
}
}
/**
* Returns the hash code for this entry.
*
* @return this entry's hash code.
*/
public int hashCode()
{
return _key.hashCode() ^ ( ( _value != null ) ? _value.hashCode() : 0 );
}
/**
* Returns the text representation of this entry.
*
* @return this entry's textual representation.
*/
public String toString()
{
return _key + "=" + _value;
}
}
}