/**
* 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 com.facebook.infrastructure.utils;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.log4j.Logger;
/**
* Author : Avinash Lakshman ( alakshman@facebook.com) & Prashant Malik ( pmalik@facebook.com )
*/
public class Cachetable<K,V> implements ICachetable<K,V>
{
private class CacheableObject<V>
{
private V value_;
private long age_;
CacheableObject(V o)
{
value_ = o;
age_ = System.currentTimeMillis();
}
public boolean equals(Object o)
{
return value_.equals(o);
}
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;
}
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<V> 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 */
Set<K> keys = expungedValues.keySet();
for ( K key : keys )
{
V value = expungedValues.get(key);
ICacheExpungeHook<K,V> hook = hooks_.remove(key);
try
{
if ( hook != null )
{
hook.callMe(key, value);
}
else if ( globalHook_ != null )
{
globalHook_.callMe(key, value);
}
}
catch(Throwable e)
{
logger_.info(LogUtil.throwableToString(e));
}
}
expungedValues.clear();
}
}
private ICacheExpungeHook<K,V> globalHook_;
private Hashtable<K, CacheableObject<V>> cache_;
private Map<K, ICacheExpungeHook<K,V>> hooks_;
private Timer timer_;
private static int counter_ = 0;
private static Logger logger_ = Logger.getLogger(Cachetable.class);
private void init(long expiration)
{
if ( expiration <= 0 )
throw new IllegalArgumentException("Argument specified must be a positive number");
cache_ = new Hashtable<K, CacheableObject<V>>();
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 Cachetable(long expiration)
{
init(expiration);
}
/*
* Specify the TTL for objects in the cache
* in milliseconds and a global expunge hook. If
* a key has a key-specific hook installed invoke that
* instead.
*/
public Cachetable(long expiration, ICacheExpungeHook<K,V> global)
{
init(expiration);
globalHook_ = global;
}
public void shutdown()
{
timer_.cancel();
}
public void put(K key, V value)
{
cache_.put(key, new CacheableObject<V>(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<V> co = cache_.get(key);
if ( co != null )
{
result = co.getValue();
}
return result;
}
public V remove(K key)
{
CacheableObject<V> co = cache_.remove(key);
V result = null;
if ( co != null )
{
result = co.getValue();
}
return result;
}
public int size()
{
return cache_.size();
}
public boolean containsKey(K key)
{
return cache_.containsKey(key);
}
public boolean containsValue(V value)
{
return cache_.containsValue( new CacheableObject<V>(value) );
}
public boolean isEmpty()
{
return cache_.isEmpty();
}
public Set<K> keySet()
{
return cache_.keySet();
}
}