TTLCache.java [src/csip/utils] Revision: default Date:
/*
* $Id$
*
* This file is part of the Cloud Services Integration Platform (CSIP),
* a Model-as-a-Service framework, API and application suite.
*
* 2012-2022, Olaf David and others, OMSLab, Colorado State University.
*
* OMSLab licenses this file to you under the MIT license.
* See the LICENSE file in the project root for more information.
*/
package csip.utils;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* LRU + TTL cache.
*
* - The cache has a limited size/capacity - Each entry will have a ttl on put()
* - evicted: if expired or capacity has reached.
*
* @author od
*/
public class TTLCache<K, V> {
private static class Item<V> {
long exptime;
long ttl;
V value;
Item(long ttl, V value) {
this.ttl = ttl;
this.exptime = expire(System.currentTimeMillis(), ttl);
this.value = value;
}
void updateExpiration(long systime) {
this.exptime = expire(systime, ttl);
}
boolean isExpired(long systime) {
return systime > exptime;
}
long getExpiration() {
return exptime;
}
@Override
public String toString() {
return value.toString() + "[" + exptime + "]";
}
/**
*
* @param systime
* @param ttl -1 means no expiration
* @return
*/
private static long expire(long systime, long ttl) {
return ttl < 0 ? Long.MAX_VALUE : systime + ttl;
}
}
Map<K, Item<V>> m = new LinkedHashMap<>();
// the last key/values accessed.
int size = 16; // default 16 elements
/**
* The size of the cache
* @param size the new size of the cache
* @return this TTLCache instance
*/
public TTLCache withSize(int size) {
if (size < 0)
throw new IllegalArgumentException("size >= 0!");
this.size = size;
prune();
return this;
}
public V put(K key, V val, long ttl) {
if (size == 0 || key == null)
return null;
Item<V> prev = m.put(key, new Item<V>(ttl, val));
if (m.size() > size * 0.75)
removeAllExpired();
// nothing is expired, evict one if cache is full.
prune();
return prev == null ? null : prev.value;
}
public V get(K key) {
if (size == 0 || key == null)
return null;
if (m.containsKey(key)) {
Item<V> val = m.get(key);
// remove it from wherever it is
m.remove(key);
long now = System.currentTimeMillis();
if (!val.isExpired(now)) {
// not expired, reset expiration
val.updateExpiration(now);
// put it back at the top
m.put(key, val);
return val.value;
} else {
// was expired, now removed.
return null;
}
}
return null;
}
public void clear() {
m.clear();
}
public int getSize() {
return size;
}
private void prune() {
while (m.size() > size) {
// evict element
K first = m.keySet().iterator().next();
m.remove(first);
}
}
private void removeAllExpired() {
long now = System.currentTimeMillis();
Iterator<Item<V>> itr = m.values().iterator();
while (itr.hasNext()) {
if (itr.next().isExpired(now))
itr.remove();
}
}
}