git-utils/src/main/java/me/brianlong/git/LRUExpiringHashMap.java
2020-12-07 14:46:57 -05:00

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);
}
}
}
}