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 implements ListeningMap { private final Logger logger = LoggerFactory.getLogger(LRUExpiringHashMap.class); private final Map map; private final List> listeners = new LinkedList>(); private final long expirationTimeMillis; public LRUExpiringHashMap(int expirationTimeInMinutes) { this.map = new LinkedHashMap(); this.expirationTimeMillis = expirationTimeInMinutes * 60L + 1000L; } public LRUExpiringHashMap(int expirationTimeInMinutes, int initialCapacity) { this.map = new LinkedHashMap(initialCapacity); this.expirationTimeMillis = expirationTimeInMinutes * 60L + 1000L; } @Override public void addListener(MapListener 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> entrySet() { throw new UnsupportedOperationException(); } @Override public Set keySet() { throw new UnsupportedOperationException(); } @Override public Collection 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 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 listener : this.listeners) listener.added(new ExpiringHashMapEntry(key, value)); return oldValue; } @Override public void clear() { for (Entry entry : this.map.entrySet()) { for (MapListener 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 listener : this.listeners) listener.removed(new ExpiringHashMapEntry((K)key, value)); return value; } @Override public void putAll(Map 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> i = this.map.entrySet().iterator(); for (Entry entry = i.next(); i.hasNext(); entry = i.next()) { if (entry.getKey().isExpired()) { i.remove(); for (MapListener listener : this.listeners) if (listener instanceof ExpiringMapListener) ((ExpiringMapListener)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 listener : this.listeners) if (listener instanceof ExpiringMapListener) ((ExpiringMapListener)listener).expired(new ExpiringHashMapEntry(key, value)); } private class ExpiringHashMapEntry implements Entry { 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); } } } }