/**
* VMware Continuent Tungsten Replicator
* Copyright (C) 2015 VMware, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Initial developer(s): Robert Hodges
* Contributor(s):
*/
package com.continuent.tungsten.common.cache;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Implements a self-managing cache suitable for database metadata or other
* resources that clients wish to manage in a bounded fashion. The cache has a
* specific capacity which is maintained by invalidating cache nodes implicitly
* using an LRU (least recently used) algorithm. Clients can also invalidate
* individual keys, key ranges, and the entire cache.
* <p/>
* NOTE: The cache is designed for use by a single thread and implements basic
* cache semantics semantics only. Clients must add synchronization as well as
* higher level semantics like notions of loans on cached object.
*
* @author <a href="mailto:robert.hodges@continuent.com">Robert Hodges</a>
*/
public class IndexedLRUCache<T>
{
// Hash index on cache nodes.
private Map<String, CacheNode<T>> pmap = new HashMap<String, CacheNode<T>>();
// Most and leads recently used node.
private CacheNode<T> lruFirst;
private CacheNode<T> lruLast;
// Maximum size of the cache.
private int capacity;
// Call-back to release values.
private CacheResourceManager<T> resourceManager;
/**
* Creates a new prepared statement cache.
*
* @param capacity Maximum capacity of the cache, after which nodes are
* removed in LRU order
* @param resourceManager Name of the cache manager
*/
public IndexedLRUCache(int capacity,
CacheResourceManager<T> resourceManager)
{
this.capacity = capacity;
this.resourceManager = resourceManager;
}
/**
* Return current size of list.
*/
public int size()
{
return pmap.size();
}
/**
* Store a value in the cache.
*/
public void put(String key, T value)
{
// If there is a previous node, unlink and release it.
CacheNode<T> old = pmap.get(key);
if (old != null)
remove(old);
// If the index is at capacity, unlink the least recently used node.
if (pmap.size() >= capacity)
remove(lruLast);
// Add the new node.
CacheNode<T> node = new CacheNode<T>(key, value);
add(node);
}
/**
* Retrieves a value or returns null if it is not in the cache.
*/
public T get(String key)
{
CacheNode<T> node = pmap.get(key);
if (node == null)
return null;
else
{
// Relink node in the LRU.
unlink(node);
link(node);
return node.get();
}
}
/**
* Returns all current keys.
*/
public Set<String> keys()
{
Set<String> keys = new HashSet<String>();
keys.addAll(pmap.keySet());
return keys;
}
/**
* Returns values in LRU order.
*/
public List<T> lruValues()
{
ArrayList<T> lruValues = new ArrayList<T>(pmap.size());
CacheNode<T> next = lruFirst;
while (next != null)
{
lruValues.add(next.get());
next = next.getAfter();
}
return lruValues;
}
/**
* Invalidate all values in the cache, returning number of items deleted.
*/
public int invalidateAll()
{
int deleted = this.pmap.size();
for (String key : keys())
invalidate(key);
return deleted;
}
/**
* Invalidate all values that start with the given key prefix, returning
* number deleted.
*/
public int invalidateByPrefix(String prefix)
{
int deleted = 0;
for (String key : keys())
{
if (key.startsWith(prefix))
{
invalidate(key);
deleted++;
}
}
return deleted;
}
/**
* Release the value corresponding to a specific key, returning the number
* of items deleted.
*/
public int invalidate(String key)
{
CacheNode<T> node = pmap.get(key);
if (node != null)
{
remove(node);
return 1;
}
else
return 0;
}
// Link a node into the index, which means to insert the same in the
// hash table and add it to the head of the LRU list.
private void add(CacheNode<T> node)
{
pmap.put(node.getKey(), node);
link(node);
}
// Free a node from the LRU chain, which means remove from hash index,
// unlink from LRU chain, and free the resource.
private void remove(CacheNode<T> node)
{
pmap.remove(node.getKey());
unlink(node);
if (resourceManager != null)
resourceManager.release(node.get());
}
// Link node into the head of the LRU list.
private void link(CacheNode<T> node)
{
if (lruFirst == null)
{
// First node in an empty list.
node.setAfter(null);
node.setBefore(null);
lruFirst = node;
lruLast = node;
}
else
{
// Adding to list with >=1 members.
lruFirst.setBefore(node);
node.setAfter(lruFirst);
node.setBefore(null);
lruFirst = node;
}
}
// Free a node from the LRU list.
private void unlink(CacheNode<T> node)
{
// Unlink from the LRU list.
CacheNode<T> before = node.getBefore();
CacheNode<T> after = node.getAfter();
if (before == null)
lruFirst = after;
else
before.setAfter(after);
if (after == null)
lruLast = before;
else
after.setBefore(before);
}
}