package org.ws4d.coap.tools;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public class TimeoutHashMap<K, V> extends HashMap<Object, Object>{
private static final long serialVersionUID = 4987370276778256858L;
/* chronological list to remove expired elements when update() is called */
LinkedList<TimoutType<K>> timeoutQueue = new LinkedList<TimoutType<K>>();
/* Default Timeout is one minute */
long timeout = 60000;
public TimeoutHashMap(long timeout){
this.timeout = timeout;
}
@Override
public Object put(Object key, Object value) {
long expires = System.currentTimeMillis() + timeout;
TimoutType<V> timeoutValue = new TimoutType<V>((V) value, expires);
TimoutType<K> timeoutKey = new TimoutType<K>((K) key, expires);
timeoutQueue.add(timeoutKey);
timeoutValue = (TimoutType<V>) super.put((K) key, timeoutValue);
if (timeoutValue != null){
return timeoutValue.object;
}
return null;
}
@Override
public Object get(Object key) {
TimoutType<V> timeoutValue = (TimoutType<V>) super.get(key);
if (timeoutValueIsValid(timeoutValue)){
return timeoutValue.object;
}
return null;
}
@Override
public Object remove(Object key) {
TimoutType<V> timeoutValue = (TimoutType<V>) super.remove(key);
if (timeoutValueIsValid(timeoutValue)){
return timeoutValue.object;
}
return null;
}
@Override
public void clear() {
super.clear();
timeoutQueue.clear();
}
/* remove expired elements */
public void update(){
while(true) {
TimoutType<K> timeoutKey = timeoutQueue.peek();
if (timeoutKey == null){
/* if the timeoutKey queue is empty, there must be no more elements in the hashmap
* otherwise there is a bug in the implementation */
if (!super.isEmpty()){
throw new IllegalStateException("Error in TimeoutHashMap. Timeout queue is empty but hashmap not!");
}
return;
}
long now = System.currentTimeMillis();
if (now > timeoutKey.expires){
timeoutQueue.poll();
TimoutType<V> timeoutValue = (TimoutType<V>) super.remove(timeoutKey.object);
if (timeoutValueIsValid(timeoutValue)){
/* This is a very special case which happens if an entry is overridden:
* - put V with K
* - put V2 with K
* - K is expired but V2 not
* because this is expected to be happened very seldom, we "reput" V2 to the hashmap
* wich is better than every time to making a get and than a remove */
super.put(timeoutKey.object, timeoutValue);
}
} else {
/* Key is not expired -> break the loop */
break;
}
}
}
@Override
public Object clone() {
// TODO implement function
throw new IllegalStateException();
// return super.clone();
}
@Override
public boolean containsKey(Object arg0) {
// TODO implement function
throw new IllegalStateException();
// return super.containsKey(arg0);
}
@Override
public boolean containsValue(Object arg0) {
// TODO implement function
throw new IllegalStateException();
// return super.containsValue(arg0);
}
@Override
public Set<Entry<Object, Object>> entrySet() {
// TODO implement function
throw new IllegalStateException();
// return super.entrySet();
}
@Override
public boolean isEmpty() {
// TODO implement function
throw new IllegalStateException();
// return super.isEmpty();
}
@Override
public Set<Object> keySet() {
// TODO implement function
throw new IllegalStateException();
// return super.keySet();
}
@Override
public void putAll(Map<? extends Object, ? extends Object> arg0) {
// TODO implement function
throw new IllegalStateException();
// super.putAll(arg0);
}
@Override
public int size() {
// TODO implement function
throw new IllegalStateException();
// return super.size();
}
@Override
public Collection<Object> values() {
// TODO implement function
throw new IllegalStateException();
// return super.values();
}
/* private classes and methods */
private boolean timeoutValueIsValid(TimoutType<V> timeoutValue){
return timeoutValue != null && System.currentTimeMillis() < timeoutValue.expires;
}
private class TimoutType<T>{
public T object;
public long expires;
public TimoutType(T object, long expires) {
super();
this.object = object;
this.expires = expires;
}
}
}