package com.limegroup.gnutella.util;
import java.util.Comparator;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.SortedSet;
import java.util.TreeSet;
import com.limegroup.gnutella.Assert;
/**
* A priority queue with bounded size. Similar to BinaryHeap, but implemented
* with a balanced tree instead of a binary heap. This results in some
* subtle differences:
*
* <ol>
* <li>FixedsizePriorityQueue guarantees that the lowest priority element
* is ejected when exceeding capacity. BinaryHeap provides no such
* guarantees.
* <li>Fetching the max element takes O(lg N) time, where N is the number of
* elements. Compare with O(1) for BinaryHeap. Extracting and adding
* elements is still O(lg N) time.
* <li>FixedsizePriorityQueue can provide operations for extracting the minimum
* and the maximum element. Note, however, that this is still considered
* a "max heap", for reasons in (1).
* <li>FixedsizePriorityQueue REQUIRES an explicit Comparator; it won't
* use the natural ordering of values.
* </ol>
*
* <b>This class is not synchronized; that is up to the user.</b><p>
*
* @see BinaryHeap
*/
public class FixedsizePriorityQueue {
/**
* The underlying data structure.
* INVARIANT: tree.size()<=capacity
* INVARIANT: all elements of tree instanceof Node
*/
private SortedSet /* of Node */ tree;
/** The maximum number of elements to hold. */
private int capacity;
/** Used to sort data. Note that tree is actually sorted by Node'
* natural ordering. */
private Comparator comparator;
/** Used to allocate Node.myID. */
private static int nextID=0;
/**
* Wraps data to guarantee that no two Nodes are ever equal. This
* is necessary to allow multiple nodes with same priority. See
* http://developer.java.sun.com/developer/bugParade/bugs/4229181.html
*/
private final class Node implements Comparable {
/** The underlying data. */
private final Object data;
/** Used to guarantee two nodes are never equal. */
private final int myID;
Node(Object data) {
this.data=data;
this.myID=nextID++; //allocate unique ID
}
public Object getData() {
return data;
}
public int compareTo(Object o) {
Node other=(Node)o;
//Compare by priority (primary key).
int c=comparator.compare(this.getData(), other.getData());
if (c!=0)
return c;
else
//Compare by ID.
return this.myID-other.myID;
}
public boolean equals(Object o) {
if (! (o instanceof Node))
return false;
return compareTo(o)==0;
}
public String toString() {
return data.toString();
}
}
/**
* Creates a new FixedsizePriorityQueue that will hold at most
* <tt>capacity</tt> elements.
* @param comparator expresses priority. Note that
* comaparator.compareTo(a,b)==0 does not imply that a.equals(b).
* @param capacity the maximum number of elements
* @exception IllegalArgumentException capacity negative
*/
public FixedsizePriorityQueue(Comparator comparator, int capacity)
throws IllegalArgumentException {
this.comparator=comparator;
if (capacity<=0)
throw new IllegalArgumentException();
tree=new TreeSet();
this.capacity=capacity;
}
/**
* Adds x to this, possibly removing some lower priority entry if necessary
* to ensure this.size()<=this.capacity(). If this has capacity, x will be
* added even if already in this (possibly with a different priority).
*
* @param x the entry to add
* @param priority the priority of x, with higher numbers corresponding
* to higher priority
* @return the element ejected, possibly x, or null if none
*/
public Object insert(Object x) {
repOk();
Node node=new Node(x);
if (size()<capacity()) {
//a) Size less than capacity. Just add x.
boolean added=tree.add(node);
Assert.that(added);
repOk();
return null;
} else {
//Ensure size does not exceeed capacity.
//Micro-optimizations are possible.
Node smallest=(Node)tree.first();
if (node.compareTo(smallest)>0) {
//b) x larger than smallest of this: remove smallest and add x
tree.remove(smallest);
boolean added=tree.add(node);
Assert.that(added);
repOk();
return smallest.getData();
} else {
//c) Otherwise do nothing.
repOk();
return x;
}
}
}
/**
* Returns the highest priority element of this.
* @exception NoSuchElementException this.size()==0
*/
public Object getMax() throws NoSuchElementException {
return ((Node)tree.last()).getData();
}
/**
* Returns the lowest priority element of this.
* @exception NoSuchElementException this.size()==0
*/
public Object getMin() throws NoSuchElementException {
return ((Node)tree.first()).getData();
}
/**
* Returns true if this contains o. Runs in O(N) time, where N is
* number of elements in this.
*
* @param true this contains a x s.t. o.equals(x). Note that
* priority is ignored in this operation.
*/
public boolean contains(Object o) {
//You can't just look up o in tree, as tree is sorted by priority, which
//isn't necessarily consistent with equals.
for (Iterator iter=tree.iterator(); iter.hasNext(); ) {
if (o.equals(((Node)iter.next()).getData()))
return true;
}
return false;
}
/**
* Removes the first occurence of o. Runs in O(N) time, where N is
* number of elements in this.
*
* @param true this contained an x s.t. o.equals(x). Note that
* priority is ignored in this operation.
*/
public boolean remove(Object o) {
//You can't just look up o in tree, as tree is sorted by priority, which
//isn't necessarily consistent with equals.
for (Iterator iter=tree.iterator(); iter.hasNext(); ) {
if (o.equals(((Node)iter.next()).getData())) {
iter.remove();
return true;
}
}
return false;
}
/**
* Returns an iterator of the elements in this, from <b>worst to best</b>.
*/
public Iterator iterator() {
return new DataIterator();
}
/** Applies getData() to elements of tree.iterator(). */
private class DataIterator implements Iterator {
Iterator delegate=tree.iterator();
public boolean hasNext() {
return delegate.hasNext();
}
public Object next() {
return ((Node)delegate.next()).getData();
}
public void remove() {
delegate.remove();
}
}
/**
* Returns the number of elements in this.
*/
public int size() {
return tree.size();
}
/**
* Returns the maximum number of elements this can hold.
* @return the value passed to this constructor
*/
public int capacity() {
return capacity;
}
static boolean DEBUG=false;
protected void repOk() {
if (!DEBUG)
return;
Assert.that(size()<=capacity());
for (Iterator iter=tree.iterator(); iter.hasNext(); ) {
Assert.that(iter.next() instanceof Node);
}
}
public String toString() {
return tree.toString();
}
}