package org.limewire.collection;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.limewire.service.ErrorService;
/**
* Gives a sorted {@link Set} of elements with a maximum size. Elements are sorted
* upon insertion to the list, but only a fixed number of unique items are kept.
* Therefore, a newly inserted element can push out items depending on its
* insertion location. All inserted elements must implement the
* {@link Comparable} interface. Additionally, <code>FixedSizeSortedSet</code>
* does not accept duplicate elements.
* <p>
* {@link #hashCode()} and {@link #equals(Object)} are used to determine if an
* existing item needs to be replaced, but <code>compareTo</code> is
* used to determine which items are ejected. <code>compareTo</code> CAN be
* different from <code>hashCode</code> and <code>equals</code> (which is
* explicitly against the strong suggestion of <code>compareTo</code> and
* <code>equals</code>, but is necessary for <code>FixedSizeSortedSet</code> to
* function).
* <p>
* Note: <code>FixedSizeSortedSet</code> can use a class that has a natural
* ordering that is inconsistent with equals.
* <p>
* <code>FixedSizeSortedSet<code> is not thread-safe.
<pre>
//Consistent with equals
public class MyPersonName implements Comparable {
private int age ;
private String name;
MyPersonName(String name, int age){
this.name = name;
this.age = age;
}
public int age(){
return age;
}
public String name(){
return name;
}
public int compareTo(Object o){
MyPersonName p = (MyPersonName) o;
return this.name.compareTo(p.name());
}
public boolean equals(Object o){
MyPersonName p = (MyPersonName) o;
return name.equals(p.name());
}
public int hashCode(){
return 37*name.hashCode();
}
public String toString(){
return name + "'s age: " + age;
}
}
//Note: This class has a natural ordering that is inconsistent with equals
public class MyPersonAge implements Comparable {
private int age ;
private String name;
MyPersonAge(String name, int age){
this.name = name;
this.age = age;
}
public int age(){
return age;
}
public String name(){
return name;
}
public int compareTo(Object o){
MyPersonAge p = (MyPersonAge) o;
return this.age - p.age() ;
}
public boolean equals(Object o){
MyPersonAge p = (MyPersonAge) o;
return name.equals(p.name());
}
public int hashCode(){
return 37*name.hashCode();
}
public String toString(){
return name + "'s age: " + age;
}
}
void sampleCodeFixedSizeSortedSet(){
//compareTo by Age
System.out.println("When max. size met, evict people by oldest person.");
FixedSizeSortedSet<MyPersonAge> age = new FixedSizeSortedSet<MyPersonAge>(5);
age.add(new MyPersonAge("Abby", 23));
if(!age.add(new MyPersonAge("Abby", 27)))
System.out.println("Abby already contained in the collection; 23 is replaced with 27");
age.add(new MyPersonAge("Chris", 20));
age.add(new MyPersonAge("Bob", 28));
age.add(new MyPersonAge("Dan", 25));
age.add(new MyPersonAge("Eric", 24));
//max reached
age.add(new MyPersonAge("Fred", 22));
for(MyPersonAge o : age)
System.out.println(o.toString());
System.out.println("");
//compareTo by Name
System.out.println("When max. size met, evict people by alphabetical name.");
FixedSizeSortedSet<MyPersonName> name = new FixedSizeSortedSet<MyPersonName>(5);
name.add(new MyPersonName("Abby", 23));
if(!name.add(new MyPersonName("Abby", 27)))
System.out.println("Abby already contained in the collection; 23 is replaced with 27");
name.add(new MyPersonName("Chris", 20));
name.add(new MyPersonName("Bob", 28));
name.add(new MyPersonName("Dan", 25));
name.add(new MyPersonName("Eric", 24));
//max reached
name.add(new MyPersonName("Fred", 22));
for(MyPersonName o : name)
System.out.println(o.toString());
}
Output:
When max. size met, evict people by oldest person.
Abby already contained in the collection; 23 is replaced with 27
Chris's age: 20
Fred's age: 22
Eric's age: 24
Dan's age: 25
Abby's age: 27
When max. size met, evict people by alphabetical name.
Abby already contained in the collection; 23 is replaced with 27
Abby's age: 27
Bob's age: 28
Chris's age: 20
Dan's age: 25
Fred's age: 22
</pre>
*/
/*
* 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<E> implements Iterable<E> {
/**
* 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<E> _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<E, E> _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<E>();
_map = new HashMap<E, E>();
}
/**
* Constructs a FixedSizeSortedSet with the specified comparator
* for the SortedSet and a maximum size of 50.
*/
public FixedSizeSortedSet(Comparator<? super E> c) {
this(c,50);
}
/**
* Constructs a FixedSizeSortedSet with the specified comparator
* and maximum size.
*/
public FixedSizeSortedSet(Comparator<? super E> c, int maxSize) {
_maxSize = maxSize;
_sortedSet = new TreeSet<E>(c);
_map = new HashMap<E, E>();
}
////////////////////////Sorted Set methods///////////////////////
@Override
@SuppressWarnings("unchecked")
public Object clone() throws CloneNotSupportedException {
FixedSizeSortedSet<E> ret = new FixedSizeSortedSet<E>(_maxSize);
ret._sortedSet = (SortedSet<E>)((TreeSet<E>)_sortedSet).clone();
ret._map = (Map<E, E>)((HashMap<E, E>)_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(E o) {
if(o==null)
return false;
E 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
E 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<? extends E> c) {
boolean ret = false;
for(E e : c) {
ret |= add(e);
}
return ret;
}
/**
* Retrieves the element that has an equals comparison with this
* object and is in this FixedSizeSortedSet.
*/
public E get(E o) {
return _map.get(o);
}
/**
* Returns the last element in the sorted set.
*/
public E last() {
return _sortedSet.last();
}
/**
* Returns the first element in the sorted set.
*/
public E first() {
return _sortedSet.first();
}
/**
* Removes the specified object from this sorted set.
* Equality is determined by equals, not compareTo.
*/
public boolean remove(E o) {
E 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.
*/
@SuppressWarnings({"SuspiciousMethodCalls"})
public boolean contains(Object o) {
return (_map.get(o) != null); //some equal key exists in the map
}
@Override
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));
}
@Override
public int hashCode() {
return _sortedSet.hashCode() + 37*_map.hashCode();
}
public boolean isEmpty() {
assert _sortedSet.isEmpty() == _map.isEmpty();
return _sortedSet.isEmpty();
}
public Iterator<E> 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(E key, E 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();
ErrorService.error(new IllegalStateException(
"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.
*/
@SuppressWarnings({"SuspiciousMethodCalls"})
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<E> {
private final Iterator<E> _setIterator;
private E _current;
public FSSSIterator() {
_setIterator=_sortedSet.iterator();
}
public boolean hasNext() {
return _setIterator.hasNext();
}
public E next() {
_current = _setIterator.next();
return _current;
}
public void remove() {
_setIterator.remove();
_map.remove(_current);
_current=null;
}
}
}