com.caucho.util.WeakLruCache Maven / Gradle / Ivy
Show all versions of resin-kernel Show documentation
/*
* Copyright (c) 1998-2012 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source 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, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
* Free SoftwareFoundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package com.caucho.util;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
/**
* Fixed length cache with a LRU replacement policy. If cache items
* implement CacheListener, they will be informed when they're removed
* from the cache.
*
* Null keys are not allowed. LruCache is synchronized.
*/
public class WeakLruCache {
// hash table containing the entries. Its size is twice the capacity
// so it will always remain at least half empty
private CacheItem []_entries;
// maximum allowed entries
private int _capacity;
// number of items in the cache
private int _size;
private int _mask;
// head of the LRU list
private CacheItem _head;
// tail of the LRU list
private CacheItem _tail;
private static Integer NULL = new Integer(0);
/**
* Create the LRU cache with a specific capacity.
*
* @param initialCapacity minimum capacity of the cache
*/
public WeakLruCache(int initialCapacity)
{
int capacity;
for (capacity = 16; capacity < 2 * initialCapacity; capacity *= 2) {
}
_entries = new CacheItem[capacity];
_mask = capacity - 1;
_capacity = initialCapacity;
}
/**
* Returns the current number of entries in the cache.
*/
public int size()
{
return _size;
}
/**
* Clears the cache
*/
public void clear()
{
ArrayList listeners = null;
synchronized (this) {
for (int i = 0; i < _entries.length; i++) {
CacheItem item = _entries[i];
if (item != null) {
V value = item.getValue();
if (value instanceof CacheListener) {
if (listeners == null)
listeners = new ArrayList();
listeners.add((CacheListener) value);
}
}
_entries[i] = null;
}
_size = 0;
_head = null;
_tail = null;
}
for (int i = listeners == null ? -1 : listeners.size() - 1; i >= 0; i--) {
CacheListener listener = listeners.get(i);
listener.removeEvent();
}
}
/**
* Get an item from the cache and make it most recently used.
*
* @param key key to lookup the item
* @return the matching object in the cache
*/
public V get(K key)
{
Object okey = key;
if (okey == null)
okey = NULL;
int hash = okey.hashCode() & _mask;
int count = _size + 1;
synchronized (this) {
for (; count > 0; count--) {
CacheItem item = _entries[hash];
if (item == null)
return null;
if (item._key == key || item._key.equals(key)) {
updateLru(item);
return item.getValue();
}
hash = (hash + 1) & _mask;
}
}
return null;
}
/**
* Puts a new item in the cache. If the cache is full, remove the
* LRU item.
*
* @param key key to store data
* @param value value to be stored
*
* @return old value stored under the key
*/
public V put(K key, V value)
{
Object okey = key;
if (okey == null)
okey = NULL;
// remove LRU items until we're below capacity
while (_capacity <= _size) {
remove(_tail._key);
}
V oldValue = null;
int hash = key.hashCode() & _mask;
int count = _size + 1;
synchronized (this) {
for (; count > 0; count--) {
CacheItem item = _entries[hash];
// No matching item, so create one
if (item == null) {
item = new CacheItem(key, value);
_entries[hash] = item;
_size++;
item._next = _head;
if (_head != null)
_head._prev = item;
else
_tail = item;
_head = item;
return null;
}
// matching item gets replaced
if (item._key == okey || item._key.equals(okey)) {
updateLru(item);
oldValue = item.getValue();
item.setValue(value);
break;
}
hash = (hash + 1) & _mask;
}
}
if (oldValue instanceof CacheListener && oldValue != value)
((CacheListener) oldValue).removeEvent();
return oldValue;
}
/**
* Put item at the head of the lru list. This is always called while
* synchronized.
*/
private void updateLru(CacheItem item)
{
CacheItem prev = item._prev;
CacheItem next = item._next;
if (prev != null) {
prev._next = next;
item._prev = null;
item._next = _head;
_head._prev = item;
_head = item;
if (next != null)
next._prev = prev;
else
_tail = prev;
}
}
/**
* Remove the last item in the LRU
*/
public boolean removeTail()
{
CacheItem last = _tail;
if (last == null)
return false;
else {
remove(last._key);
return true;
}
}
/**
* Removes an item from the cache
*
* @param key the key to remove
*
* @return the value removed
*/
public V remove(K key)
{
Object okey = key;
if (okey == null)
okey = NULL;
int hash = key.hashCode() & _mask;
int count = _size + 1;
V value = null;
synchronized (this) {
for (; count > 0; count--) {
CacheItem item = _entries[hash];
if (item == null)
return null;
if (item._key == okey || item._key.equals(okey)) {
_entries[hash] = null;
_size--;
CacheItem prev = item._prev;
CacheItem next = item._next;
if (prev != null)
prev._next = next;
else
_head = next;
if (next != null)
next._prev = prev;
else
_tail = prev;
// Shift colliding entries down
for (int i = 1; i <= count; i++) {
int nextHash = (hash + i) & _mask;
CacheItem nextItem = _entries[nextHash];
if (nextItem == null)
break;
_entries[nextHash] = null;
refillEntry(nextItem);
}
value = item.getValue();
break;
}
hash = (hash + 1) & _mask;
}
}
if (count < 0)
throw new RuntimeException("internal cache error");
if (value instanceof CacheListener)
((CacheListener) value).removeEvent();
return value;
}
/**
* Put the item in the best location available in the hash table.
*/
private void refillEntry(CacheItem item)
{
int baseHash = item._key.hashCode();
for (int count = 0; count < _size + 1; count++) {
int hash = (baseHash + count) & _mask;
if (_entries[hash] == null) {
_entries[hash] = item;
return;
}
}
}
/**
* Returns the keys stored in the cache
*/
public Iterator keys()
{
KeyIterator iter = new KeyIterator();
iter.init(this);
return iter;
}
/**
* Returns keys stored in the cache using an old iterator
*/
public Iterator keys(Iterator oldIter)
{
KeyIterator iter = (KeyIterator) oldIter;
iter.init(this);
return oldIter;
}
/**
* Returns the values in the cache
*/
public Iterator values()
{
ValueIterator iter = new ValueIterator();
iter.init(this);
return iter;
}
public Iterator values(Iterator oldIter)
{
ValueIterator iter = (ValueIterator) oldIter;
iter.init(this);
return oldIter;
}
/**
* A cache item
*/
static class CacheItem {
CacheItem _prev;
CacheItem _next;
K _key;
private WeakReference _value;
int _index;
CacheItem(K key, V value)
{
_key = key;
if (value == null)
_value = null;
else
_value = new WeakReference(value);
}
public final V getValue()
{
WeakReference ref = _value;
if (ref == null)
return null;
else
return ref.get();
}
public final void setValue(V value)
{
if (value == null)
_value = null;
else
_value = new WeakReference(value);
}
}
/**
* Iterator of cache keys
*/
static class KeyIterator implements Iterator {
CacheItem _item;
void init(WeakLruCache cache)
{
_item = cache._head;
}
public boolean hasNext()
{
return _item != null;
}
public K next()
{
K key = _item._key;
_item = _item._next;
return key;
}
public void remove()
{
throw new UnsupportedOperationException();
}
}
/**
* Iterator of cache values
*/
static class ValueIterator implements Iterator {
CacheItem _item;
void init(WeakLruCache cache)
{
_item = cache._head;
}
public boolean hasNext()
{
return _item != null;
}
public V next()
{
V value = _item.getValue();
_item = _item._next;
return value;
}
public void remove()
{
throw new UnsupportedOperationException();
}
}
}