package com.limegroup.gnutella.util;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import com.limegroup.gnutella.Assert;
/**
* A simple fixed size sorted set. Uses two structures internally, a SortedSet
* and a Map, in order to efficiently look things up and keep them sorted.
* This class is NOT SYNCHRONIZED. Synchronization should be done externally.
*/
public class FixedSizeSortedSet {
/**
* The underlying set that efficiently
* keeps this FixedSizeSortedSet sorted.
* INVARIANT: The elements of this set must be mirrored
* by values in _map.
* INVARIANT: The size of this set must be equal to the size
* of _map.
*/
private SortedSet _sortedSet;
/**
* The map that allows us to treat this FixedSizeSortedSet
* with equality of equals() instead of compareTo().
* INVARIANT: The values of this map must point to an element
* in the _sortedSet.
* INVARIANT: The size of this map must be equal to the size
* of _sortedSet.
*/
private Map /*Object -> Object*/ _map;
/**
* The maximum size of this, defaults to 50
*/
private int _maxSize;
///////////////////////////////constructors////////////////////
/**
* Constructs a FixedSizeSortedSet with a maximum size of 50.
*/
public FixedSizeSortedSet() {
this(50);
}
/**
* Constructs a FixedSizeSortedSet with a specified maximum size.
*/
public FixedSizeSortedSet(int size) {
_maxSize = size;
_sortedSet = new TreeSet();
_map = new HashMap();
}
/**
* Constructs a FixedSizeSortedSet with the specified comparator
* for the SortedSet and a maximum size of 50.
*/
public FixedSizeSortedSet(Comparator c) {
this(c,50);
}
/**
* Constructs a FixedSizeSortedSet with the specified comparator
* and maximum size.
*/
public FixedSizeSortedSet(Comparator c, int maxSize) {
_maxSize = maxSize;
_sortedSet = new TreeSet(c);
_map = new HashMap();
}
////////////////////////Sorted Set methods///////////////////////
public Object clone() {
FixedSizeSortedSet ret = new FixedSizeSortedSet(_maxSize);
ret._sortedSet = (SortedSet)((TreeSet)_sortedSet).clone();
ret._map = (Map)((HashMap)_map).clone();
return ret;
}
/////////////////////Set Interface methods ///////////////////
/**
* Adds the object to the set. If the object is already present,
* (as specified by the Map's equals comparison), then it is ejected
* and this newer version is used.
*/
public boolean add(Object o) {
if(o==null)
return false;
Object val = _map.get(o);
if(val != null) {//we have the object
boolean removed = _sortedSet.remove(val);
if(!removed)
invariantsBroken(o, val);
_sortedSet.add(o);
_map.put(o,o);//replace the old entry
return false;
}
else {//we need to add it
if(_map.size() >= _maxSize) { //need to remove highest element
Object highest = _sortedSet.last();
boolean removed = (_map.remove(highest)!=null);
if(!removed)
invariantsBroken(highest, highest);
removed = _sortedSet.remove(highest);
if(!removed)
invariantsBroken(highest, highest);
}
_map.put(o,o);
boolean added = _sortedSet.add(o);
if(!added)
invariantsBroken(o, o);
return true;
}
}
/**
* Adds all the elements of the specified collection to this set.
*/
public boolean addAll(Collection c) {
boolean ret = false;
Iterator iter = c.iterator();
while(iter.hasNext())
ret |= add(iter.next());
return ret;
}
/**
* Retrieves the element that has an equals comparison with this
* object and is in this FixedSizeSortedSet.
*/
public Object get(Object o) {
return _map.get(o);
}
/**
* Returns the last element in the sorted set.
*/
public Object last() {
return _sortedSet.last();
}
/**
* Returns the first element in the sorted set.
*/
public Object first() {
return _sortedSet.first();
}
/**
* Removes the specified object from this sorted set.
* Equality is determined by equals, not compareTo.
*/
public boolean remove(Object o) {
Object obj = _map.remove(o);
boolean b1 = (obj!=null);
boolean b2 = _sortedSet.remove(obj);
if(b1 != b2)
invariantsBroken(o, obj);
return b1;
}
/**
* Clears this FixedSizeSortedSet.
*/
public void clear() {
_sortedSet.clear();
_map.clear();
}
/**
* Determines if this set contains the specified object.
* Equality is determined by equals, not compareTo.
*/
public boolean contains(Object o) {
return (_map.get(o) != null); //some equal key exists in the map
}
public boolean equals(Object o) {
if(o==null)
return false;
if(o==this)
return true;
if(!( o instanceof FixedSizeSortedSet))
return false;
FixedSizeSortedSet other = (FixedSizeSortedSet)o;
return (_sortedSet.equals(other._sortedSet) && _map.equals(other._map));
}
public int hashCode() {
return _sortedSet.hashCode() + 37*_map.hashCode();
}
public boolean isEmpty() {
Assert.that(_sortedSet.isEmpty()==_map.isEmpty());
return _sortedSet.isEmpty();
}
public Iterator iterator() {
return new FSSSIterator();
}
public int size() {
if( _sortedSet.size() != _map.size() )
invariantsBroken(null, null);
return _sortedSet.size();
}
/**
* Notification that the invariants have broken, triggers an error.
*/
private void invariantsBroken(Object key, Object value) {
String mapBefore = _map.toString();
String setBefore = _sortedSet.toString();
String mapSizeBefore = "" + _map.size();
String setSizeBefore = "" + _sortedSet.size();
stabilize();
String mapAfter = _map.toString();
String setAfter = _sortedSet.toString();
String mapSizeAfter = "" + _map.size();
String setSizeAfter = "" + _sortedSet.size();
Assert.silent(false,
"key: " + key + ", value: " + value +
"\nbefore stabilization: " +
"\nsize of map: " + mapSizeBefore + ", set: " + setSizeBefore +
"\nmap: " + mapBefore +
"\nset: " + setBefore +
"\nafter stabilization: " +
"\nsize of map " + mapSizeAfter + ", set: " + setSizeAfter +
"\nmap: " + mapAfter +
"\nset: " + setAfter);
}
/**
* Stabilizes the two data structures so that the invariants of this
* class are consistent. This should never normally be done, but until
* we can find what is causing the data to go out of synch, we need
* to clean up the structures to prevent errors from going out of control.
*/
private void stabilize() {
// First clean up the map for any entries that may not be in the set.
for(Iterator iter = _map.entrySet().iterator(); iter.hasNext(); ) {
Map.Entry entry = (Map.Entry)iter.next();
// If the set does not contain the value of this entry, remove it
// from the map.
if( !_sortedSet.contains(entry.getValue()) )
iter.remove();
}
// Then clean up the set for any entries that may not be in the map.
Collection values = _map.values();
for(Iterator iter = _sortedSet.iterator(); iter.hasNext(); ) {
Object o = iter.next();
// If the values of the map do not contain this entry, remove it
// from the set.
if( !values.contains(o) )
iter.remove();
}
}
private class FSSSIterator implements Iterator {
private final Iterator _setIterator;
private Object _current;
public FSSSIterator() {
_setIterator=_sortedSet.iterator();
}
public boolean hasNext() {
return _setIterator.hasNext();
}
public Object next() {
_current = _setIterator.next();
return _current;
}
public void remove() {
_setIterator.remove();
_map.remove(_current);
_current=null;
}
}
}