package com.limegroup.gnutella.util; import java.util.Iterator; import java.util.NoSuchElementException; /** * A discrete-case priority queue. Designed to be a replacement for BinaryHeap * for the special case when there are only a small number of positive * priorities, where larger numbers are higher priority. Unless otherwise * noted, all methods have the same specifications as BinaryHeap. This also has * a few additional methods not found in BinaryHeap. <b>This class is not * synchronized.</b> */ public class BucketQueue implements Cloneable { /** * Within each bucket, elements at the FRONT are newer then the back. It is * assumed that buckets is very small; otherwise additional state could * speed up some of the operations. */ private Buffer[] buckets; /** * The size, stored for efficiency reasons. * INVARIANT: size=buckets[0].size()+...+buckets[buckets.length-1].size() */ private int size=0; /** * @effects a new queue with the given number of priorities, and * the given number of entries PER PRIORITY. Hence 0 through * priorities-1 are the legal priorities, and there are up to * capacityPerPriority*priorities elements in the queue. * @exception IllegalArgumentException priorities or capacityPerPriority * is non-positive. */ public BucketQueue(int priorities, int capacityPerPriority) throws IllegalArgumentException { if (priorities<=0) throw new IllegalArgumentException( "Bad priorities: "+priorities); if (capacityPerPriority<=0) throw new IllegalArgumentException( "Bad capacity: "+capacityPerPriority); this.buckets=new Buffer[priorities]; for (int i=0; i<buckets.length; i++) { buckets[i]=new Buffer(capacityPerPriority); } } /** * @effects makes a new queue that will hold up to capacities[i] * elements of priority i. Hence the legal priorities are 0 * through capacities.length-1 * @exception IllegalArgumentException capacities.length<=0 or * capacities[i]<=0 for any i */ public BucketQueue(int[] capacities) throws IllegalArgumentException { if (capacities.length<=0) throw new IllegalArgumentException(); this.buckets=new Buffer[capacities.length]; for (int i=0; i<buckets.length; i++) { if (capacities[i]<=0) throw new IllegalArgumentException( "Non-positive capacity: "+capacities[i]); buckets[i]=new Buffer(capacities[i]); } } /** "Copy constructor": constructs a a new shallow copy of other. */ public BucketQueue(BucketQueue other) { //Note that we can't just shallowly clone other.buckets this.buckets=new Buffer[other.buckets.length]; for (int i=0; i<this.buckets.length; i++) { this.buckets[i]=new Buffer(other.buckets[i]); //clone } this.size=other.size; } /** * Removes all elements from the queue. */ public void clear() { repOk(); for (int i=0; i<buckets.length; i++) buckets[i].clear(); size=0; repOk(); } /** * @modifies this * @effects adds o to this, removing and returning some older element of * same or lesser priority as needed * @exception IllegalArgumentException priority is not a legal priority, * as determined by this' constructor */ public Object insert(Object o, int priority) { repOk(); if(priority < 0 || priority >= buckets.length) { throw new IllegalArgumentException("Bad priority: "+priority); } Object ret = buckets[priority].addFirst(o); if (ret == null) size++; //Maintain invariant repOk(); return ret; } /** * @modifies this * @effects removes all o' s.t. o'.equals(o). Note that p's * priority is ignored. Returns true if any elements were removed. */ public boolean removeAll(Object o) { repOk(); //1. For each bucket, remove o, noting if any elements were removed. boolean ret=false; for (int i=0; i<buckets.length; i++) { ret=ret | buckets[i].removeAll(o); } //2. Maintain size invariant. The problem is that removeAll() can //remove multiple elements from this. As a slight optimization, we //could incrementally update size by looking at buckets[i].getSize() //before and after the call to removeAll(..). But I favor simplicity. if (ret) { this.size=0; for (int i=0; i<buckets.length; i++) this.size+=buckets[i].getSize(); } repOk(); return ret; } public Object extractMax() throws NoSuchElementException { repOk(); try { for (int i=buckets.length-1; i>=0 ;i--) { if (! buckets[i].isEmpty()) { size--; return buckets[i].removeFirst(); } } throw new NoSuchElementException(); } finally { repOk(); } } public Object getMax() throws NoSuchElementException { //TODO: we can optimize this by storing the position of the first //non-empty bucket. for (int i=buckets.length-1; i>=0 ;i--) { if (! buckets[i].isEmpty()) { return buckets[i].first(); } } throw new NoSuchElementException(); } public int size() { return size; } /** * @effects returns the number of entries with the given priority. * @exception IllegalArgumentException priority is not a legal priority, * as determined by this' constructor */ public int size(int priority) throws IllegalArgumentException { if(priority < 0 || priority >= buckets.length) { throw new IllegalArgumentException("Bad priority: "+priority); } return buckets[priority].getSize(); } public boolean isEmpty() { return size()==0; } /** * @requires this not modified while iterator in use * @effects yields the elements of this exactly once, from highest priority * to lowest priority. Within each priority level, newer elements are * yielded before older ones. */ public Iterator iterator() { return new BucketQueueIterator(buckets.length-1, this.size()); } /** * @requires this not modified while iterator in use * @effects yields the best n elements from startPriority down to to lowest * priority. Within each priority level, newer elements are yielded before * older ones, and each element is yielded exactly once. May yield fewer * than n elements. * @exception IllegalArgumentException startPriority is not a legal priority * as determined by this' constructor */ public Iterator iterator(int startPriority, int n) throws IllegalArgumentException { if (startPriority<0 || startPriority>=buckets.length) throw new IllegalArgumentException("Bad priority: "+startPriority); return new BucketQueueIterator(startPriority, n); } private class BucketQueueIterator extends UnmodifiableIterator { private Iterator currentIterator; private int currentBucket; private int left; /** * @requires buckets.length>0 * @effects creates an iterator that yields the best * n elements. */ public BucketQueueIterator(int startPriority, int n) { this.currentBucket=startPriority; this.currentIterator=buckets[currentBucket].iterator(); this.left=n; } public synchronized boolean hasNext() { if (left<=0) return false; if (currentIterator.hasNext()) return true; if (currentBucket<0) return false; //Find non-empty bucket. Note the "benevolent side effect". //(Changes internal state, but not visible to caller.) for (currentBucket-- ; currentBucket>=0 ; currentBucket--) { currentIterator=buckets[currentBucket].iterator(); if (currentIterator.hasNext()) return true; } return false; } public synchronized Object next() { //This relies on the benevolent side effects of hasNext. if (! hasNext()) throw new NoSuchElementException(); left--; return currentIterator.next(); } } /** Returns a shallow copy of this, of type BucketQueue */ public Object clone() { return new BucketQueue(this); } private void repOk() { /* int count=0; for (int i=0; i<buckets.length; i++) { count+=buckets[i].getSize(); } Assert.that(count==size); */ } public String toString() { StringBuffer buf=new StringBuffer(); buf.append("["); for (int i=buckets.length-1; i>=0; i--) { if (i!=buckets.length-1) buf.append(", "); buf.append(buckets[i].toString()); } buf.append("]"); return buf.toString(); } }