org.codehaus.plexus.util.CachedMap Maven / Gradle / Ivy
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.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
/**
*
* This class provides cache access to Map
collections.
*
*
* Instance of this class can be used as "proxy" for any collection implementing the java.util.Map
* interface.
*
*
* Typically, {@link CachedMap} are used to accelerate access to large collections when the access to the collection is
* not evenly distributed (associative cache). The performance gain is about 50% for the fastest hash map collections
* (e.g. {@link FastMap}). For slower collections such as java.util.TreeMap
, non-resizable {@link FastMap}
* (real-time) or database access, performance can be of several orders of magnitude.
*
*
* Note: The keys used to access elements of a {@link CachedMap} do not need to be immutable as they are not
* stored in the cache (only keys specified by the {@link #put} method are). In other words, access can be performed
* using mutable keys as long as these keys can be compared for equality with the real map's keys (e.g. same
* hashCode
values).
*
*
* This implementation is not synchronized. Multiple threads accessing or modifying the collection must be synchronized
* externally.
*
*
* This class is public domain (not copyrighted).
*
*
* @author Jean-Marie Dautelle
* @version 5.3, October 30, 2003
*/
public final class CachedMap
implements Map
{
/**
* Holds the FastMap backing this collection (null
if generic backing map).
*/
private final FastMap _backingFastMap;
/**
* Holds the generic map backing this collection.
*/
private final Map _backingMap;
/**
* Holds the keys of the backing map (key-to-key mapping). (null
if FastMap backing map).
*/
private final FastMap _keysMap;
/**
* Holds the cache's mask (capacity - 1).
*/
private final int _mask;
/**
* Holds the keys being cached.
*/
private final Object[] _keys;
/**
* Holds the values being cached.
*/
private final Object[] _values;
/**
* Creates a cached map backed by a {@link FastMap}. The default cache size and map capacity is set to
* 256
entries.
*/
public CachedMap()
{
this( 256, new FastMap() );
}
/**
* Creates a cached map backed by a {@link FastMap} and having the specified cache size.
*
* @param cacheSize the cache size, the actual cache size is the first power of 2 greater or equal to
* cacheSize
. This is also the initial capacity of the backing map.
*/
public CachedMap( int cacheSize )
{
this( cacheSize, new FastMap( cacheSize ) );
}
/**
* Creates a cached map backed by the specified map and having the specified cache size. In order to maintain cache
* veracity, it is critical that all update to the backing map is accomplished through the {@link CachedMap}
* instance; otherwise {@link #flush} has to be called.
*
* @param cacheSize the cache size, the actual cache size is the first power of 2 greater or equal to
* cacheSize
.
* @param backingMap the backing map to be "wrapped" in a cached map.
*/
public CachedMap( int cacheSize, Map backingMap )
{
// Find a power of 2 >= minimalCache
int actualCacheSize = 1;
while ( actualCacheSize < cacheSize )
{
actualCacheSize <<= 1;
}
// Sets up cache.
_keys = new Object[actualCacheSize];
_values = new Object[actualCacheSize];
_mask = actualCacheSize - 1;
// Sets backing map references.
if ( backingMap instanceof FastMap )
{
_backingFastMap = (FastMap) backingMap;
_backingMap = _backingFastMap;
_keysMap = null;
}
else
{
_backingFastMap = null;
_backingMap = backingMap;
_keysMap = new FastMap( backingMap.size() );
for ( Object key : backingMap.keySet() )
{
_keysMap.put( key, key );
}
}
}
/**
* Returns the actual cache size.
*
* @return the cache size (power of 2).
*/
public int getCacheSize()
{
return _keys.length;
}
/**
* Returns the backing map. If the backing map is modified directly, this {@link CachedMap} has to be flushed.
*
* @return the backing map.
* @see #flush
*/
public Map getBackingMap()
{
return ( _backingFastMap != null ) ? _backingFastMap : _backingMap;
}
/**
* Flushes the key/value pairs being cached. This method should be called if the backing map is externally modified.
*/
public void flush()
{
for ( int i = 0; i < _keys.length; i++ )
{
_keys[i] = null;
_values[i] = null;
}
if ( _keysMap != null )
{
// Re-populates keys from backing map.
for ( Object key : _backingMap.keySet() )
{
_keysMap.put( key, key );
}
}
}
/**
* Returns the value to which this map maps the specified key. First, the cache is being checked, then if the cache
* does not contains the specified key, the backing map is accessed and the key/value pair is stored in the cache.
*
* @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 the map contains no mapping
* for this key.
* @throws ClassCastException if the key is of an inappropriate type for the backing map (optional).
* @throws NullPointerException if the key is null
.
*/
@Override
public Object get( Object key )
{
int index = key.hashCode() & _mask;
return key.equals( _keys[index] ) ? _values[index] : getCacheMissed( key, index );
}
private Object getCacheMissed( Object key, int index )
{
if ( _backingFastMap != null )
{
Map.Entry entry = _backingFastMap.getEntry( key );
if ( entry != null )
{
_keys[index] = entry.getKey();
Object value = entry.getValue();
_values[index] = value;
return value;
}
else
{
return null;
}
}
else
{ // Generic backing map.
Object mapKey = _keysMap.get( key );
if ( mapKey != null )
{
_keys[index] = mapKey;
Object value = _backingMap.get( key );
_values[index] = value;
return value;
}
else
{
return null;
}
}
}
/**
* Associates the specified value with the specified key in this map.
*
* @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 the
* key.
* @throws UnsupportedOperationException if the put
operation is not supported by the backing map.
* @throws ClassCastException if the class of the specified key or value prevents it from being stored in this map.
* @throws IllegalArgumentException if some aspect of this key or value prevents it from being stored in this map.
* @throws NullPointerException if the key is null
.
*/
@Override
public Object put( Object key, Object value )
{
// Updates the cache.
int index = key.hashCode() & _mask;
if ( key.equals( _keys[index] ) )
{
_values[index] = value;
}
else if ( _keysMap != null )
{ // Possibly a new key.
_keysMap.put( key, key );
}
// Updates the backing map.
return _backingMap.put( key, value );
}
/**
* Removes the mapping for this key from this map if it is present.
*
* @param key 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.
* @throws ClassCastException if the key is of an inappropriate type for the backing map (optional).
* @throws NullPointerException if the key is null
.
* @throws UnsupportedOperationException if the remove
method is not supported by the backing map.
*/
@Override
public Object remove( Object key )
{
// Removes from cache.
int index = key.hashCode() & _mask;
if ( key.equals( _keys[index] ) )
{
_keys[index] = null;
}
// Removes from key map.
if ( _keysMap != null )
{
_keysMap.remove( key );
}
// Removes from backing map.
return _backingMap.remove( key );
}
/**
* Indicates if this map 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.
*/
@Override
public boolean containsKey( Object key )
{
// Checks the cache.
int index = key.hashCode() & _mask;
if ( key.equals( _keys[index] ) )
{
return true;
}
else
{ // Checks the backing map.
return _backingMap.containsKey( key );
}
}
/**
* Returns the number of key-value mappings in this map. If the map contains more than
* Integer.MAX_VALUE
elements, returns Integer.MAX_VALUE
.
*
* @return the number of key-value mappings in this map.
*/
@Override
public int size()
{
return _backingMap.size();
}
/**
* Returns true
if this map contains no key-value mappings.
*
* @return true
if this map contains no key-value mappings.
*/
@Override
public boolean isEmpty()
{
return _backingMap.isEmpty();
}
/**
* Returns true
if this map maps one or more keys to the specified value.
*
* @param value 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 ClassCastException if the value is of an inappropriate type for the backing map (optional).
* @throws NullPointerException if the value is null
and the backing map does not not permit
* null
values.
*/
@Override
public boolean containsValue( Object value )
{
return _backingMap.containsValue( value );
}
/**
* Copies all of the mappings from the specified map to this map (optional operation). This method automatically
* flushes the cache.
*
* @param map the mappings to be stored in this map.
* @throws UnsupportedOperationException if the putAll
method is not supported by the backing map.
* @throws ClassCastException if the class of a key or value in the specified map prevents it from being stored in
* this map.
* @throws IllegalArgumentException some aspect of a key or value in the specified map prevents it from being stored
* in this map.
* @throws NullPointerException the specified map is null
, or if the backing map does not permit
* null
keys or values, and the specified map contains null
keys or values.
*/
@Override
public void putAll( Map map )
{
_backingMap.putAll( map );
flush();
}
/**
* Removes all mappings from this map (optional operation). This method automatically flushes the cache.
*
* @throws UnsupportedOperationException if clear is not supported by the backing map.
*/
@Override
public void clear()
{
_backingMap.clear();
flush();
}
/**
* Returns an unmodifiable view of the keys contained in this map.
*
* @return an unmodifiable view of the keys contained in this map.
*/
@Override
public Set keySet()
{
return Collections.unmodifiableSet( _backingMap.keySet() );
}
/**
* Returns an unmodifiable view of the values contained in this map.
*
* @return an unmodifiable view of the values contained in this map.
*/
@Override
public Collection values()
{
return Collections.unmodifiableCollection( _backingMap.values() );
}
/**
* Returns an unmodifiable view of the mappings contained in this map. Each element in the returned set is a
* Map.Entry
.
*
* @return an unmodifiable view of the mappings contained in this map.
*/
@Override
public Set entrySet()
{
return Collections.unmodifiableSet( _backingMap.entrySet() );
}
/**
* Compares the specified object with this map for equality. Returns true
if the given object is also a map
* and the two Maps represent the same mappings.
*
* @param o object to be compared for equality with this map.
* @return true
if the specified object is equal to this map.
*/
@Override
public boolean equals( Object o )
{
return _backingMap.equals( o );
}
/**
* Returns the hash code value for this map.
*
* @return the hash code value for this map.
*/
@Override
public int hashCode()
{
return _backingMap.hashCode();
}
}