package com.supaham.commons.utils;
import com.google.common.base.Preconditions;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.RemovalNotification;
import org.jetbrains.annotations.NotNull;
import java.util.AbstractSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Represents a {@link Set} with expiring elements. The expiry countdown starts when an item has
* been added to this set using {@link #add(Object)}.
*
* @param <E> type of element this set contains
*/
public class ExpiringSet<E> extends AbstractSet<E> implements Set<E> {
// Dummy value to associate with an Object in the backing Cache
private static final Object PRESENT = new Object();
private final Cache<E, Object> cache;
public ExpiringSet(long duration, @Nonnull TimeUnit unit) {
this(duration, unit, null);
}
public ExpiringSet(long duration, @Nonnull TimeUnit unit,
@Nullable final RemovalListener<E> removalListener) {
Preconditions.checkNotNull(unit, "unit cannot be null.");
Preconditions.checkArgument(duration > 0, "duration must be positive: %s %s", duration, unit);
CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder()
.expireAfterWrite(duration, unit);
if (removalListener != null) {
builder.removalListener(new com.google.common.cache.RemovalListener<E, Object>() {
@Override public void onRemoval(@Nonnull RemovalNotification<E, Object> notification) {
removalListener.onRemoval(notification.getKey());
}
});
}
cache = builder.build();
}
@Override public int size() {
return (int) cache.size();
}
@Override public boolean isEmpty() {
return size() == 0;
}
@Override public boolean contains(Object o) {
return cache.getIfPresent(o) != null;
}
@NotNull @Override public Iterator<E> iterator() {
return cache.asMap().keySet().iterator();
}
@NotNull @Override public Object[] toArray() {
return cache.asMap().keySet().toArray();
}
@NotNull @Override public <T> T[] toArray(@Nonnull T[] a) {
return cache.asMap().keySet().toArray(a);
}
@Override public boolean add(E e) {
int size = size();
cache.put(e, PRESENT);
return size != size();
}
@Override public boolean remove(Object o) {
int size = size();
cache.invalidate(o);
return size != size();
}
@Override public void clear() {
cache.invalidateAll();
}
/**
* Cleans up this set. This refreshes this set and expires outdated elements. Otherwise, expiries
* occur during read and write operations.
*/
public void cleanUp() {
this.cache.cleanUp();
}
/**
* Interface used for notifying when an element has been removed from an {@link ExpiringSet}.
*/
public interface RemovalListener<E> {
/**
* Called when an element has been removed from an {@link ExpiringSet}.
*
* @param e element, nonnull
*/
void onRemoval(@Nonnull E e);
}
}