/**
* Copyright (c) 2013 Oculus Info Inc. http://www.oculusinfo.com/
*
* Released under the MIT License.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.oculusinfo.binning.util;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
/**
* Simple, Least Recently Used object cache. This holds onto at most some
* fixed number of objects, dropping any extras according to whichever have
* been accessed the least recently.
*
* @author cbethune, nkronenfeld
*/
public class LRUCache<K, V> {
/**
* The RemovalPolicy allows the user of a cache to affect which elements
* can be removed - i.e., to make the LRU removal not strict.
*
* @author nkronenfeld
*/
public interface RemovalPolicy<K, V> {
/**
* Test if the last entry should be removed. Mostly this is used for
* side effects (in which case it should return true - it is only called
* if the cache is already too big) - but it can determine behavior - if
* it returns false, the entry will not be removed. If the removal
* policy handles removing the element itself, it must return true (see
* {@link LinkedHashMap#removeEldestEntry}
*
* @param entry
* The least recently used entry in the cache
* @param map
* The data map of the cache
* @param suggestedMaxSize
* The intended max size of the LRU cache
* @return True if the entry should be, and has not yet been, removed;
* false if it should be left alone, or if the removal policy
* has already handled removing it.
*/
public boolean shouldRemove (Map.Entry<K, V> entry, Map<K, V> map, int suggestedMaxSize);
/**
* Called whenever an entry has been removed.
* @param key The key of the removed entry
* @param value The value of the removed entry
*/
public void onElementRemoved (K key, V value);
}
protected class LruRemovalHashMap extends LinkedHashMap<K, V> {
private static final long serialVersionUID = 1L;
public RemovalPolicy<K, V> removalPolicy;
public LruRemovalHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {
super(initialCapacity, loadFactor, accessOrder);
}
public LruRemovalHashMap(int initialCapacity, float loadFactor, boolean accessOrder, RemovalPolicy<K, V> removalPolicy) {
super(initialCapacity, loadFactor, accessOrder);
this.removalPolicy = removalPolicy;
}
protected boolean removeEldestEntry (Map.Entry<K, V> entry) {
boolean result = false;
if (size() > LRUCache.this._size) {
result = true;
if (removalPolicy != null) {
result = removalPolicy.shouldRemove(entry, this, LRUCache.this._size);
if (result)
removalPolicy.onElementRemoved(entry.getKey(), entry.getValue());
}
}
return result;
}
}
protected LruRemovalHashMap _map = null;
protected int _size;
public LRUCache() {
_size = 250;
_map = new LruRemovalHashMap(_size, 0.75f, true);
}
public LRUCache(int size) {
_size = size;
_map = new LruRemovalHashMap(_size, 0.75f, true);
}
public LRUCache(int size, RemovalPolicy<K, V> removalPolicy) {
_size = size;
_map = new LruRemovalHashMap(_size, 0.75f, true, removalPolicy);
}
public boolean containsKey (Object key) {
return _map.containsKey(key);
}
public void put(K key, V value) {
V oldValue = _map.put(key, value);
if (oldValue != null && _map.removalPolicy != null)
_map.removalPolicy.onElementRemoved(key, oldValue);
}
public void remove(K key) {
V value = _map.remove(key);
if (_map.removalPolicy != null)
_map.removalPolicy.onElementRemoved(key, value);
}
public V get (Object key) {
return _map.get(key);
}
public int getCurrentSize() {
return _map.size();
}
synchronized public void setSize (int size) {
_size = size;
while (_map.size() > _size) {
K key = _map.keySet().iterator().next();
V value = _map.remove(key);
if (_map.removalPolicy != null)
_map.removalPolicy.onElementRemoved(key, value);
}
}
public void setRemovalPolicy(RemovalPolicy<K, V> removalPolicy) {
_map.removalPolicy = removalPolicy;
}
synchronized public int getSize() {
return _size;
}
public void clear() {
if (_map.removalPolicy != null) {
for (Entry<K, V> entry : _map.entrySet()) {
_map.removalPolicy.onElementRemoved(entry.getKey(), entry.getValue());
}
}
_map.clear();
}
public Collection<V> values() {
return Collections.unmodifiableCollection(_map.values());
}
}