package de.skuzzle.polly.tools.collections;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import de.skuzzle.polly.tools.Check;
import de.skuzzle.polly.tools.concurrent.ThreadFactoryBuilder;
public class TemporaryValueMap<K, V> implements Map<K, V> {
private class DeletionTask implements Runnable {
private final K key;
public DeletionTask(K key) {
this.key = key;
}
@Override
public void run() {
synchronized (backend) {
backend.remove(this.key);
}
}
}
private final class TaskValuePair {
private final ScheduledFuture<?> future;
private final V value;
public TaskValuePair(ScheduledFuture<?> future, V value) {
this.future = future;
this.value = value;
}
@Override
public int hashCode() {
return this.value == null ? 0 : this.value.hashCode();
}
@Override
public boolean equals(Object obj) {
return this.value == null ? obj == null : this.value.equals(obj);
}
}
private final static int DEFAULT_SIZE = 16;
private final Map<K, TaskValuePair> backend;
private final ScheduledExecutorService deletionService;
private final long defaultCacheTime;
public TemporaryValueMap(long defaultCacheTime, int size) {
Check.number(size).isPositiveOrZero();
this.defaultCacheTime = defaultCacheTime;
this.backend = new HashMap<>(size);
this.deletionService = Executors.newSingleThreadScheduledExecutor(
new ThreadFactoryBuilder().setDaemon(true));
}
public TemporaryValueMap(long defaultCacheTime) {
this(defaultCacheTime, DEFAULT_SIZE);
}
public TemporaryValueMap(long defaultCacheTime, Map<? extends K, ? extends V> m) {
this(defaultCacheTime, m.size());
this.putAll(m);
}
public V put(K key, V value, long cacheTimeMs) {
synchronized (this.backend) {
final ScheduledFuture<?> future;
if (cacheTimeMs > 0) {
future = this.deletionService.schedule(
new DeletionTask(key), cacheTimeMs, TimeUnit.MILLISECONDS);
} else {
future = null;
}
final TaskValuePair tvp = new TaskValuePair(future, value);
final TaskValuePair result = this.backend.put(key, tvp);
if (result != null) {
if (result.future != null) {
result.future.cancel(true);
}
return result.value;
}
return null;
}
}
@Override
public V put(K key, V value) {
return this.put(key, value, this.defaultCacheTime);
}
@Override
public void clear() {
synchronized (this.backend) {
for (final TaskValuePair tvp : this.backend.values()) {
if (tvp.future != null) {
tvp.future.cancel(true);
}
}
this.backend.clear();
}
}
@Override
public boolean containsKey(Object key) {
synchronized (this.backend) {
return this.backend.containsKey(key);
}
}
@Override
public boolean containsValue(Object value) {
synchronized (this.backend) {
return this.backend.containsValue(value);
}
}
@Override
public Set<java.util.Map.Entry<K, V>> entrySet() {
synchronized (this.backend) {
final Set<Entry<K, TaskValuePair>> cpy = new HashSet<>(
this.backend.entrySet());
final Iterator<Entry<K, TaskValuePair>> it = cpy.iterator();
final Iterator<Entry<K, V>> newIt = new Iterator<Map.Entry<K,V>>() {
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public java.util.Map.Entry<K, V> next() {
final Entry<K, TaskValuePair> e = it.next();
return new Entry<K, V>() {
@Override
public K getKey() {
return e.getKey();
}
@Override
public V getValue() {
return e.getValue().value;
}
@Override
public V setValue(V value) {
throw new UnsupportedOperationException();
}
};
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
return new AbstractSet<Map.Entry<K,V>>() {
@Override
public Iterator<java.util.Map.Entry<K, V>> iterator() {
return newIt;
}
@Override
public int size() {
return cpy.size();
}
};
}
}
@Override
public Collection<V> values() {
synchronized (this.backend) {
final Collection<TaskValuePair> cpy = new HashSet<>(this.backend.values());
final Iterator<TaskValuePair> it = cpy.iterator();
final Iterator<V> newIt = new Iterator<V>() {
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public V next() {
return it.next().value;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
return new AbstractSet<V>() {
@Override
public Iterator<V> iterator() {
return newIt;
}
@Override
public int size() {
return backend.size();
}
};
}
}
@Override
public V get(Object key) {
synchronized (this.backend) {
final TaskValuePair tvp = this.backend.get(key);
return tvp == null ? null : tvp.value;
}
}
@Override
public boolean isEmpty() {
synchronized (this.backend) {
return this.backend.isEmpty();
}
}
@Override
public Set<K> keySet() {
synchronized (this.backend) {
return new HashSet<>(this.backend.keySet());
}
}
@Override
public void putAll(Map<? extends K, ? extends V> m) {
synchronized (this.backend) {
m.forEach(this::put);
}
}
@Override
public V remove(Object key) {
synchronized (this.backend) {
final TaskValuePair tvp = this.backend.remove(key);
if (tvp != null) {
if (tvp.future != null) {
tvp.future.cancel(true);
}
return tvp.value;
}
}
return null;
}
@Override
public int size() {
synchronized (this.backend) {
return this.backend.size();
}
}
}