253 lines
6.1 KiB
Java
253 lines
6.1 KiB
Java
package me.brianlong.git;
|
|
|
|
import java.io.Serializable;
|
|
import java.util.Collection;
|
|
import java.util.Iterator;
|
|
import java.util.LinkedHashMap;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
// TODO need to implement the actual expiration schedule
|
|
public class LRUExpiringHashMap<K extends Serializable, V> implements ListeningMap<K, V> {
|
|
|
|
private final Logger logger = LoggerFactory.getLogger(LRUExpiringHashMap.class);
|
|
|
|
private final Map<ExpiringHashKey, V> map;
|
|
private final List<MapListener<K, V>> listeners = new LinkedList<MapListener<K, V>>();
|
|
private final long expirationTimeMillis;
|
|
|
|
public LRUExpiringHashMap(int expirationTimeInMinutes) {
|
|
this.map = new LinkedHashMap<ExpiringHashKey, V>();
|
|
this.expirationTimeMillis = expirationTimeInMinutes * 60L + 1000L;
|
|
}
|
|
|
|
public LRUExpiringHashMap(int expirationTimeInMinutes, int initialCapacity) {
|
|
this.map = new LinkedHashMap<ExpiringHashKey, V>(initialCapacity);
|
|
this.expirationTimeMillis = expirationTimeInMinutes * 60L + 1000L;
|
|
}
|
|
|
|
@Override
|
|
public void addListener(MapListener<K, V> listener) {
|
|
if (this.logger.isDebugEnabled())
|
|
this.logger.debug("adding listener");
|
|
this.listeners.add(listener);
|
|
}
|
|
|
|
@Override
|
|
public boolean containsKey(Object key) {
|
|
return this.map.containsKey(key);
|
|
}
|
|
|
|
@Override
|
|
public boolean containsValue(Object value) {
|
|
return this.map.containsValue(value);
|
|
}
|
|
|
|
@Override
|
|
public Set<Entry<K, V>> entrySet() {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
|
|
@Override
|
|
public Set<K> keySet() {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
|
|
@Override
|
|
public Collection<V> values() {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
|
|
@Override
|
|
public boolean isEmpty() {
|
|
return this.map.isEmpty();
|
|
}
|
|
|
|
@Override
|
|
public int size() {
|
|
return this.map.size();
|
|
}
|
|
|
|
@Override
|
|
@SuppressWarnings("unchecked")
|
|
public V get(Object key) {
|
|
if (!this.map.containsKey(key))
|
|
return null;
|
|
|
|
// remove and put to move the entry to the end of the map; helping with finding expired entries
|
|
V value = this.map.remove(key);
|
|
this.map.put(new ExpiringHashKey((K)key, System.currentTimeMillis() + this.expirationTimeMillis), value);
|
|
|
|
for (MapListener<K, V> listener : this.listeners)
|
|
listener.accessed(new ExpiringHashMapEntry((K)key, value));
|
|
return value;
|
|
}
|
|
|
|
@Override
|
|
public V put(K key, V value) {
|
|
ExpiringHashKey ehkey = new ExpiringHashKey(key, System.currentTimeMillis() + this.expirationTimeMillis);
|
|
V oldValue = this.map.put(ehkey, value);
|
|
for (MapListener<K, V> listener : this.listeners)
|
|
listener.added(new ExpiringHashMapEntry(key, value));
|
|
return oldValue;
|
|
}
|
|
|
|
@Override
|
|
public void clear() {
|
|
for (Entry<ExpiringHashKey, V> entry : this.map.entrySet()) {
|
|
for (MapListener<K, V> listener : this.listeners)
|
|
listener.cleared(new ExpiringHashMapEntry(entry.getKey().getKey(), entry.getValue()));
|
|
}
|
|
|
|
this.map.clear();
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
@Override
|
|
public V remove(Object key) {
|
|
if (!this.map.containsKey(key))
|
|
return null;
|
|
|
|
V value = this.map.remove(key);
|
|
for (MapListener<K, V> listener : this.listeners)
|
|
listener.removed(new ExpiringHashMapEntry((K)key, value));
|
|
return value;
|
|
}
|
|
|
|
@Override
|
|
public void putAll(Map<? extends K, ? extends V> m) {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return this.map.hashCode();
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
return this.map.equals(obj);
|
|
}
|
|
|
|
public void exipriationCheck() {
|
|
Iterator<Entry<ExpiringHashKey, V>> i = this.map.entrySet().iterator();
|
|
for (Entry<ExpiringHashKey, V> entry = i.next(); i.hasNext(); entry = i.next()) {
|
|
if (entry.getKey().isExpired()) {
|
|
i.remove();
|
|
for (MapListener<K, V> listener : this.listeners)
|
|
if (listener instanceof ExpiringMapListener)
|
|
((ExpiringMapListener<K, V>)listener).expired(new ExpiringHashMapEntry(entry.getKey().getKey(), entry.getValue()));
|
|
}
|
|
}
|
|
}
|
|
|
|
public void expire(K key) {
|
|
if (this.logger.isDebugEnabled())
|
|
this.logger.debug("expiring key from map: " + key);
|
|
|
|
if (!this.map.containsKey(key))
|
|
return;
|
|
|
|
V value = this.map.remove(key);
|
|
for (MapListener<K, V> listener : this.listeners)
|
|
if (listener instanceof ExpiringMapListener)
|
|
((ExpiringMapListener<K, V>)listener).expired(new ExpiringHashMapEntry(key, value));
|
|
}
|
|
|
|
|
|
|
|
private class ExpiringHashMapEntry implements Entry<K, V> {
|
|
|
|
private final K key;
|
|
private V value;
|
|
|
|
public ExpiringHashMapEntry(K key, V value) {
|
|
this.key = key;
|
|
this.value = value;
|
|
}
|
|
|
|
@Override
|
|
public K getKey() {
|
|
return this.key;
|
|
}
|
|
|
|
@Override
|
|
public V getValue() {
|
|
return this.value;
|
|
}
|
|
|
|
@Override
|
|
public V setValue(V value) {
|
|
return this.value = value;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (obj instanceof Entry<?, ?>) {
|
|
return this.key.equals(((Entry<?, ?>)obj).getKey()) && this.value.equals(((Entry<?, ?>)obj).getValue());
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return this.key.hashCode() + this.value.hashCode();
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "{" + this.key + ", " + this.value + "}";
|
|
}
|
|
|
|
}
|
|
|
|
private class ExpiringHashKey implements Serializable {
|
|
|
|
private static final long serialVersionUID = -6511298315143655313L;
|
|
|
|
private long expirationTimeInMillis;
|
|
private K key;
|
|
|
|
public ExpiringHashKey(K key, long expirationTimeInMillis) {
|
|
this.key = key;
|
|
this.expirationTimeInMillis = expirationTimeInMillis;
|
|
}
|
|
|
|
public K getKey() {
|
|
return this.key;
|
|
}
|
|
|
|
public boolean isExpired() {
|
|
return this.expirationTimeInMillis <= System.currentTimeMillis();
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return this.key.hashCode();
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return this.key.toString();
|
|
}
|
|
|
|
@Override
|
|
@SuppressWarnings("unchecked")
|
|
public boolean equals(Object obj) {
|
|
if (obj instanceof LRUExpiringHashMap.ExpiringHashKey) {
|
|
return this.key.equals(((ExpiringHashKey)obj).key);
|
|
} else {
|
|
return this.key.equals(obj);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|