/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the Common Development
* and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at
* src/com/vodafone360/people/VODAFONE.LICENSE.txt or
* http://github.com/360/360-Engine-for-Android
* See the License for the specific language governing permissions and
* limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each file and
* include the License file at src/com/vodafone360/people/VODAFONE.LICENSE.txt.
* If applicable, add the following below this CDDL HEADER, with the fields
* enclosed by brackets "[]" replaced with your own identifying information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
* Copyright 2010 Vodafone Sales & Services Ltd. All rights reserved.
* Use is subject to license terms.
*/
package com.vodafone360.people.utils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
/**
* LRUHashMap is an extension of {@link HashMap}.
* Cloning and serialization are not implemented, i.e.
* methods of the parent HashMap will be invoked.
* The purpose of the class is to provide a functionality of HashMap
* maintaining its size not bigger than a defined limit.
* For this purpose "Removing Least Recently Used (RLU) Item" algorithm is
* applied. I.e. if adding an key/value pair to the LRUHashMap would
* cause its size to overcome the defined size limit the oldest key/value pair
* in this LRUHashMap will be deleted before the new one is added.
* So LRUHashMap is a hash map of latest added (size limit) elements.
*
* <p>All elements are permitted as keys or values, including null.
*
* <p>Note that the iteration order for LRUHashMap is non-deterministic. If you want
* deterministic iteration, use {@link LinkedHashMap}.
*
* <p>Note: the implementation of {@code LRUHashMap} is synchronized.
*
* @param <K> the type of keys maintained by this map
* @param <V> the type of mapped values
*/
public class LRUHashMap<K, V> extends HashMap<K, V> {
/**
* Id required for serialization (auto-generated here)
*/
private static final long serialVersionUID = 12345L;
/**
* Default size limit for the SizedLinkedHashMap
* (when the map is created by a constructor with no parameters)
* which ensures that SizedLinkedHashMap behaves like a normal HashMap,
* i.e. doesn't maintain any size limit.
*/
private static final int NO_LIMIT = -1;
/**
* This ArrayList saves the order the keys of the LRUHashMap were added to it.
*/
private ArrayList<K> mCacheOrder;
/**
* The maximum size of LRUHashMap. If not defined, no constraint is applied.
*/
private int mSizeLimit = NO_LIMIT;
/**
* Default constructor. Using this constructor makes no sense
* as it initializes the LRUHashMap to behave as a HashMap,
* so using HashMap is preferable then.
*/
public LRUHashMap() {
super();
mCacheOrder = new ArrayList<K>();
}
/**
* Constructs a new {@code LRUHashMap} instance with the specified
* size limit and load factor.
* @param capacity the initial size limit of this hash map.
* If zero is passed as capacity no maximum size
* constraint is applied to the LRUHashMap size,
* using HashMap is preferable then.
* @param loadFactor the initial load factor.
* @throws IllegalArgumentException
* when the capacity is less than zero or the load factor is
* less or equal to zero or NaN.
*/
public LRUHashMap(int capacity, float loadFactor) {
super(capacity, loadFactor);
if (capacity > 0) {
mSizeLimit = capacity;
}
mCacheOrder = new ArrayList<K>();
}
/**
* Constructs a new {@code LRUHashMap} instance
* with the specified size limit.
* @param capacity the initial size limit of this hash map.
* If zero is passed as capacity no maximum size
* constraint is applied to the LRUHashMap size,
* using HashMap is preferable then.
* @throws IllegalArgumentException when the capacity
* is less than zero.
*/
public LRUHashMap(int capacity) {
super(capacity);
if (capacity > 0) {
mSizeLimit = capacity;
}
mCacheOrder = new ArrayList<K>();
}
/**
* Constructs a new {@code LRUHashMap} instance containing the mappings from
* the specified map. If the passed map is zero size no maximum size
* constraint is applied to the LRUHashMap size,
* using HashMap is preferable then.
* @param map the mappings to add.
*/
public LRUHashMap(Map<? extends K, ? extends V> map) {
super(map);
mSizeLimit = map.size();
mCacheOrder = new ArrayList<K>();
}
/***
* Returns the value of the mapping with the specified key.
*
* @param key The key.
* @return Value of the mapping with the specified key, or NULL if no
* mapping for the specified key is found.
*/
synchronized public V get(int key) {
return super.get(key);
}
/**
* Maps the specified key to the specified value. If adding
* a key/value pair to the LRUHashMap would cause its size to overcome
* the defined size limit the oldest key/value pair in this LRUHashMap
* will be deleted before the new one is added.
*
* @param key - The key. If the key is already in the map it will be added as the latest.
*
* @param value - the value.
* @return the value of any previous mapping with the specified key, or the removed mapping
* to the oldest key in this hash (the one that was removed) or {@code null} if there was no such mapping.
*/
synchronized public V put(K key, V value) {
if (mSizeLimit != NO_LIMIT) {
if (mCacheOrder.size() < mSizeLimit) {
if (mCacheOrder.contains(key)) {
mCacheOrder.remove(key);
}
mCacheOrder.add(key);
// return the previous mapping (or null if no such mapping)
return super.put(key, value);
} else {
if (mCacheOrder.contains(key)) {
mCacheOrder.remove(key);
mCacheOrder.add(key);
// return the previous mapping
return super.put(key, value);
} else {
Object oldestKey = mCacheOrder.remove(0);
//return the oldest value (the one that is removed from the hash)
V ret = super.remove(oldestKey);
mCacheOrder.add(key);
super.put(key, value);
return ret;
}
}
} else {
return super.put(key, value);
}
}
/**
* This method returns this LRUHashMap maximum size constraint value.
* @return long maximum size constraint value.
*/
public long getSizeLimit() {
return mSizeLimit;
}
@Override
synchronized public V remove(Object key) {
if (mSizeLimit != NO_LIMIT) {
if (mCacheOrder.contains(key)) {
mCacheOrder.remove(key);
}
}
return super.remove(key);
}
@Override
synchronized public void clear() {
mCacheOrder.clear();
super.clear();
}
@Override
public String toString() {
return "SizedLinkedHashMap:" + super.toString();
}
}