package org.limewire.collection;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* Maintains, at most, a fixed size of objects in a <code>Set</code>
* for a specified time. <code>FixedSizeExpiringSet</code> never
* holds more entries than specified in the constructor. This class
* is a hash-based <code>Set</code> and therefore, objects must correctly
* contain a {@link #hashCode()} and {@link #equals(Object)}.
* <p>
* Note: expiration times longer than Long.MAX_VALUE / 10^6 will be truncated.
<pre>
try{
FixedSizeExpiringSet<String> fses = new FixedSizeExpiringSet<String>(4, 2000);
fses.add("Abby");
fses.add("Bob");
fses.add("Chris");
fses.add("Dan");
fses.add("Eric");
System.out.println("Size: " + fses.size());
Thread.sleep(1000);
System.out.println("Size: " + fses.size());
Thread.sleep(2000);
System.out.println("Size (after expiration): " + fses.size());
} catch(Exception e) {
e.printStackTrace();
}
Output:
Size: 4
Size: 4
Size (after expiration): 0
</pre>
* @author Gregorio Roper
*/
public class FixedSizeExpiringSet<T> implements Set<T>, Collection<T> {
private static final long MAX_EXPIRE_TIME = Long.MAX_VALUE / 1000000;
/*
* Default size for the FixedSizExpiringSet
*/
private static final int DEFAULT_SIZE = 50;
/*
* Default time after which the entries expire 10 minutes
*/
private static final long DEFAULT_EXPIRE_TIME = 10 * 60 * 1000;
private final int _maxSize;
private final long _expireTime;
private Map<T,Long> _map;
/**
* Simple constructor for the FixedSizeExpiringSet. Takes no arguments.
*/
public FixedSizeExpiringSet() {
this(DEFAULT_SIZE);
}
/**
* Constructor for the FixedSizeExpiringSet.
*
* @param size the max size of the set
*/
public FixedSizeExpiringSet(int size) {
this(size, DEFAULT_EXPIRE_TIME);
}
/**
* Constructor for the FixedSizeExpiringSet.
*
* @param size the max size of the set
* @param expireTime the time to keep an entry
*/
public FixedSizeExpiringSet(int size, long expireTime) {
_maxSize = size;
expireTime = Math.min(MAX_EXPIRE_TIME, expireTime);
_expireTime = expireTime * 1000 * 1000;
_map = new HashMap<T,Long>();
}
/*
* (non-Javadoc)
*
* @see java.util.Collection#size()
*/
public int size() {
expire(false);
return _map.size();
}
/*
* (non-Javadoc)
*
* @see java.util.Collection#isEmpty()
*/
public boolean isEmpty() {
return _map.isEmpty();
}
/*
* (non-Javadoc)
*
* @see java.util.Collection#contains(java.lang.Object)
*/
@SuppressWarnings({"SuspiciousMethodCalls"})
public boolean contains(Object arg0) {
Long time = _map.get(arg0);
if (time == null)
return false;
else if (time < System.nanoTime()) {
_map.remove(arg0);
return false;
} else
return true;
}
/*
* (non-Javadoc)
*
* @see java.util.Collection#iterator()
*/
public Iterator<T> iterator() {
expire(false);
return _map.keySet().iterator();
}
/*
* (non-Javadoc)
*
* @see java.util.Collection#toArray()
*/
public Object[] toArray() {
expire(false);
return _map.keySet().toArray();
}
/*
* (non-Javadoc)
*
* @see java.util.Collection#toArray(java.lang.Object[])
*/
@SuppressWarnings({"SuspiciousToArrayCall"})
public <B>B[] toArray(B[] arg0) {
expire(false);
return _map.keySet().toArray(arg0);
}
/*
* (non-Javadoc)
*
* @see java.util.Collection#add(java.lang.Object)
*/
public boolean add(T arg0) {
if (arg0 == null)
return false;
expire(size() >= _maxSize);
if (_map.containsKey(arg0)) //contract requires it!
return false;
_map.put(arg0, System.nanoTime() + _expireTime);
return true;
}
/*
* (non-Javadoc)
*
* @see java.util.Collection#remove(java.lang.Object)
*/
public boolean remove(Object arg0) {
if (_map.remove(arg0) != null)
return true;
return false;
}
/*
* (non-Javadoc)
*
* @see java.util.Collection#containsAll
* (java.util.Collection)
*/
public boolean containsAll(Collection<?> arg0) {
return _map.keySet().containsAll(arg0);
}
/**
* Adds all the elements in collection to this. If the size of the
* collection is bigger than _maxSize only the first _maxSize elements are
* added.
*
* @see java.util.Collection#addAll
* (java.util.Collection) */
public boolean addAll(Collection<? extends T> coll) {
if (coll.isEmpty())
return false;
int i = 0;
for (Iterator<? extends T> iter=coll.iterator(); i < _maxSize && iter.hasNext(); i++)
add(iter.next());
return true;
}
/**
* @see java.util.Collection#retainAll
* (java.util.Collection)
*/
@SuppressWarnings({"SuspiciousMethodCalls"})
public boolean retainAll(Collection<?> arg0) {
Map<T,Long> map = new HashMap<T,Long>();
boolean ret = false;
for (T o : _map.keySet()) {
if (arg0.contains(o))
map.put(o, _map.get(o));
else
ret = true;
}
if (ret)
_map = map;
return ret;
}
/*
* (non-Javadoc)
*
* @see java.util.Collection#removeAll
* (java.util.Collection)
*/
public boolean removeAll(Collection<?> arg0) {
if (arg0.isEmpty())
return false;
boolean ret = false;
for (Object anArg0 : arg0) {
ret |= remove(anArg0);
}
return ret;
}
/*
* (non-Javadoc)
*
* @see java.util.Collection#clear()
*/
public void clear() {
_map.clear();
}
private void expire(boolean forceRemove) {
if (_map.size() == 0)
return;
long now = System.nanoTime();
long min = Long.MAX_VALUE;
T oldest = null;
Collection<T> expired = new HashSet<T>();
for (T key : _map.keySet()) {
long time = _map.get(key);
if (time < now) {
expired.add(key);
forceRemove = false;
} else if (forceRemove && time < min) {
min = time;
oldest = key;
}
}
if (expired.size() > 0)
removeAll(expired);
if (forceRemove)
remove(oldest);
}
}