
de.tsl2.nano.collection.ExpiringMap Maven / Gradle / Ivy
/*
* File: $HeadURL$
* Id : $Id$
*
* created by: Tom
* created on: 24.02.2015
*
* Copyright: (c) Thomas Schneider 2015, all rights reserved
*/
package de.tsl2.nano.collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import de.tsl2.nano.core.cls.PrivateAccessor;
import de.tsl2.nano.core.log.LogFactory;
/**
* map holding entries with a lifecycle defined by a {@link #timeout}. on adding elements, old elements may be removed.
* useful for in-memory caches. if you define a timeout of -1, the expiration functionality will be switched off.
*
* Note-1: the {@link LinkedHashMap} implementation provides an accessOrder (see it's constructors) and an unimplemented
* {@link #removeEldestEntry(java.util.Map.Entry)} - these are not usable for our cache.
*
* Note-2: As the capacity() evaluation is only be accessible through pure-performance reflection, we may use our
* timeout variable as {@link #shrink()}ing frequency.
*
* @author Tom
* @version $Revision$
*/
public class ExpiringMap extends LinkedHashMap {
/** serialVersionUID */
private static final long serialVersionUID = 1L;
private static final Log LOG = LogFactory.getLog(ExpiringMap.class);
/** holding touch-times of all entries */
Map touches;
/** maps current capacity (as it is not visible from here, we get it through reflection!) */
int capacity;
/** life cycle of elements */
long timeout;
/**
* constructor
*
* @param timeout
*/
public ExpiringMap(long timeout) {
super();
init(timeout);
}
/**
* constructor
*
* @param initialCapacity
* @param loadFactor
* @param accessOrder
*/
public ExpiringMap(int initialCapacity, float loadFactor, boolean accessOrder, long timeout) {
super(initialCapacity, loadFactor, accessOrder);
init(timeout);
}
/**
* constructor
*
* @param initialCapacity
* @param loadFactor
*/
public ExpiringMap(int initialCapacity, float loadFactor, long timeout) {
super(initialCapacity, loadFactor);
init(timeout);
}
/**
* constructor
*
* @param initialCapacity
*/
public ExpiringMap(int initialCapacity, long timeout) {
super(initialCapacity);
init(timeout);
}
/**
* constructor
*
* @param m
*/
public ExpiringMap(Map extends K, ? extends V> m, long timeout) {
super(m);
init(timeout);
}
@SuppressWarnings("unchecked")
@Override
public V get(Object key) {
if (timeout != -1) {
Long access = touches.get(key);
if (access != null && System.currentTimeMillis() - access > timeout) {
remove(key);
return null;
}
touch((K) key);
}
return super.get(key);
}
private void touch(K key) {
touches.put(key, System.currentTimeMillis());
}
@Override
public V put(K key, V value) {
if (timeout != -1) {
touch(key);
shrink();
}
return super.put(key, value);
}
@Override
public V remove(Object key) {
if (timeout != -1) {
touches.remove(key);
}
return super.remove(key);
}
@Override
public void clear() {
if (timeout != -1) {
touches.clear();
}
super.clear();
}
/**
* does the thing and removes expired objects.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
protected void shrink() {
if (size() >= capacity) {
long now = System.currentTimeMillis();
Set> entries = entrySet();
for (Iterator iterator = entries.iterator(); iterator.hasNext();) {
java.util.Map.Entry entry = (java.util.Map.Entry) iterator.next();
if (now - touches.get(entry.getKey()) > timeout) {
LOG.info("removing expired entry " + entry);
iterator.remove();
}
}
refreshCapacity();
}
}
/**
* creates the touch-time table
*
* @param timeout The timeout to set.
*/
protected void init(long timeout) {
if (timeout != -1) {
touches = new HashMap();
Set ks = keySet();
long now = System.currentTimeMillis();
for (K k : ks) {
touches.put(k, now);
}
refreshCapacity();
}
this.timeout = timeout;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
void refreshCapacity() {
// the capacity() method from Hashtable is implementation specific. other VMs may not have that method
try {
capacity = (Integer) new PrivateAccessor(this).call("capacity", Integer.class);
} catch(Exception ex) {
LOG.warn(ex.toString());
capacity = size();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy