/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
package org.apache.cassandra.utils;
import java.util.*;
import java.util.Map.Entry;
import org.apache.log4j.Logger;
import org.apache.cassandra.cache.ICacheExpungeHook;
public class ExpiringMap<K, V>
{
private class CacheableObject
{
private V value_;
public long age;
CacheableObject(V o)
{
value_ = o;
age = System.currentTimeMillis();
}
@Override
public boolean equals(Object o)
{
return value_.equals(o);
}
@Override
public int hashCode()
{
return value_.hashCode();
}
V getValue()
{
return value_;
}
boolean isReadyToDie(long expiration)
{
return ((System.currentTimeMillis() - age) > expiration);
}
}
private class CacheMonitor extends TimerTask
{
private long expiration_;
CacheMonitor(long expiration)
{
expiration_ = expiration;
}
@Override
public void run()
{
Map<K, V> expungedValues = new HashMap<K, V>();
synchronized (cache_)
{
Enumeration<K> e = cache_.keys();
while (e.hasMoreElements())
{
K key = e.nextElement();
CacheableObject co = cache_.get(key);
if (co != null && co.isReadyToDie(expiration_))
{
V v = co.getValue();
if (null != v)
{
expungedValues.put(key, v);
}
cache_.remove(key);
}
}
}
/* Calling the hooks on the keys that have been expunged */
for (Entry<K, V> entry : expungedValues.entrySet())
{
K key = entry.getKey();
V value = entry.getValue();
ICacheExpungeHook<K, V> hook = hooks_.remove(key);
if (hook != null)
{
hook.callMe(key, value);
}
}
expungedValues.clear();
}
}
private Hashtable<K, CacheableObject> cache_;
private Map<K, ICacheExpungeHook<K, V>> hooks_;
private Timer timer_;
private static int counter_ = 0;
private static final Logger LOGGER = Logger.getLogger(ExpiringMap.class);
private void init(long expiration)
{
if (expiration <= 0)
{
throw new IllegalArgumentException("Argument specified must be a positive number");
}
cache_ = new Hashtable<K, CacheableObject>();
hooks_ = new Hashtable<K, ICacheExpungeHook<K, V>>();
timer_ = new Timer("CACHETABLE-TIMER-" + (++counter_), true);
timer_.schedule(new CacheMonitor(expiration), expiration, expiration);
}
/*
* Specify the TTL for objects in the cache
* in milliseconds.
*/
public ExpiringMap(long expiration)
{
init(expiration);
}
public void shutdown()
{
timer_.cancel();
}
public void put(K key, V value)
{
cache_.put(key, new CacheableObject(value));
}
public void put(K key, V value, ICacheExpungeHook<K, V> hook)
{
put(key, value);
hooks_.put(key, hook);
}
public V get(K key)
{
V result = null;
CacheableObject co = cache_.get(key);
if (co != null)
{
result = co.getValue();
}
return result;
}
public V remove(K key)
{
CacheableObject co = cache_.remove(key);
V result = null;
if (co != null)
{
result = co.getValue();
}
return result;
}
public long getAge(K key)
{
long age = 0;
CacheableObject co = cache_.get(key);
if (co != null)
{
age = co.age;
}
return age;
}
public int size()
{
return cache_.size();
}
public boolean containsKey(K key)
{
return cache_.containsKey(key);
}
public boolean isEmpty()
{
return cache_.isEmpty();
}
public Set<K> keySet()
{
return cache_.keySet();
}
}