package com.limegroup.gnutella.util; import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.NoSuchElementException; import com.limegroup.gnutella.Assert; /** * A very simple fixed-size double-ended queue, i.e., a circular buffer. * The fixed size is intentional, not the result of laziness; use this * data structure when you want to use a fix amount of resources. * For a minimal amount of efficiency, the internal buffer is only * allocated on the first insertion or retrieval, allowing lots of * Buffers to be created that may not be used. * This is not thread-safe. */ public class Buffer implements Cloneable { /** * The abstraction function is * [ buf[head], buf[head+1], ..., buf[tail-1] ] if head<=tail * or * [ buf[head], buf[head+1], ..., buf[size-1], * buf[0], buf[1], ..., buf[tail-1] ] otherwise * * Note that buf[head] is the location of the head, and * buf[tail] is just past the location of the tail. This * means that there is always one unused element of the array. * See p. 202 of _Introduction to Algorithms_ by Cormen, * Leiserson, Rivest for details. * * Also note that size is really the MAX size of this+1, i.e., * the capacity, not the current size. * * INVARIANT: buf.length=size * 0<=head, tail<size * size>=2 */ private final int size; private Object buf[]; private int head; private int tail; /** * @requires size>=1 * @effects creates a new, empty buffer that can hold * size elements. */ public Buffer(int size) { Assert.that(size>=1); //one element of buf unused this.size=size+1; // lazily allocate buffer. //buf=new Object[size+1]; head=0; tail=0; } /** "Copy constructor": constructs a new shallow copy of other. */ public Buffer(Buffer other) { this.size=other.size; this.head=other.head; this.tail=other.tail; if(other.buf != null) { this.buf=new Object[other.buf.length]; System.arraycopy(other.buf, 0, this.buf, 0, other.buf.length); } } /** Initializes the internal buf if necessary. */ private void initialize() { if(buf == null) buf = new Object[size+1]; } /** Returns true iff this is empty. */ public boolean isEmpty() { return head==tail; } /** Returns true iff this is full, e.g., adding another element * would force another out. */ public boolean isFull() { return increment(tail)==head; } /** Same as getSize(). */ public final int size() { return getSize(); } /** Returns the number of elements in this. Note that this never * exceeds the value returned by getCapacity. */ public int getSize() { if (head<=tail) //tail-1-head+1 [see abstraction function] return tail-head; else //(size-1-head+1) + (tail-1+1) [see abstraction function] return size-head+tail; } /** Returns the number of elements that this can hold, i.e., the * max size that was passed to the constructor. */ public int getCapacity() { return size-1; } private int decrement(int i) { if (i==0) return size-1; else return i-1; } private int increment(int i) { if (i==(size-1)) return 0; else return i+1; } /** Returns the j s.t. buf[j]=this[i]. */ private int index(int i) throws IndexOutOfBoundsException { if (i<0 || i>=getSize()) throw new IndexOutOfBoundsException("index: " + i); return (i+head) % size; } /** If i<0 or i>=getSize(), throws IndexOutOfBoundsException. * Else returns this[i] */ public Object get(int i) throws IndexOutOfBoundsException { initialize(); return buf[index(i)]; } /* * @modifies this[i] * @effects If i<0 or i>=getSize(), throws IndexOutOfBoundsException * and does not modify this. Else this[i]=o. */ public void set(int i, Object o) throws IndexOutOfBoundsException { initialize(); buf[index(i)]=o; } /** Same as addFirst(x). */ public Object add(Object x) { return addFirst(x); } /** * @modifies this * @effects adds x to the head of this, removing the tail * if necessary so that the number of elements in this is less than * or equal to the maximum size. Returns the element removed, or null * if none was removed. */ public Object addFirst(Object x) { initialize(); Object ret=null; if (isFull()) ret=removeLast(); head=decrement(head); buf[head]=x; return ret; } /** * @modifies this * @effects adds x to the tail of this, removing the head * if necessary so that the number of elements in this is less than * or equal to the maximum size. Returns the element removed, or null * if none was removed. */ public Object addLast(Object x) { initialize(); Object ret=null; if (isFull()) ret=removeFirst(); buf[tail]=x; tail=increment(tail); return ret; } /** * Returns true if the input object x is in the buffer. */ public boolean contains(Object x) { Iterator iterator = iterator(); while (iterator.hasNext()) if (iterator.next().equals(x)) return true; return false; } /** * Returns the head of this, or throws NoSuchElementException if * this is empty. */ public Object first() throws NoSuchElementException { if (isEmpty()) throw new NoSuchElementException(); return buf[head]; } /** * Returns the tail of this, or throws NoSuchElementException if * this is empty. */ public Object last() throws NoSuchElementException { if (isEmpty()) throw new NoSuchElementException(); return buf[decrement(tail)]; } /** * @modifies this * @effects Removes and returns the head of this, or throws * NoSuchElementException if this is empty. */ public Object removeFirst() throws NoSuchElementException { if (isEmpty()) throw new NoSuchElementException(); Object ret=buf[head]; buf[head]=null; //optimization: don't retain removed values head=increment(head); return ret; } /** * @modifies this * @effects Removes and returns the tail of this, or throws * NoSuchElementException if this is empty. */ public Object removeLast() throws NoSuchElementException { if (isEmpty()) throw new NoSuchElementException(); tail=decrement(tail); Object ret=buf[tail]; buf[tail]=null; //optimization: don't retain removed values return ret; } /** * @modifies this * @effects Removes and returns the i'th element of this, or * throws IndexOutOfBoundsException if i is not a valid index * of this. In the worst case, this runs in linear time with * respect to size(). */ public Object remove(int i) throws IndexOutOfBoundsException { Object ret=get(i); //Shift all elements to left. This could be micro-optimized. for (int j=index(i); j!=tail; j=increment(j)) { buf[j]=buf[increment(j)]; } //Adjust tail pointer accordingly. tail=decrement(tail); buf[tail] = null; return ret; } /** * @modifies this * @effects removes the first occurrence of x in this, * if any, as determined by .equals. Returns true if any * elements were removed. In the worst case, this runs in linear * time with respect to size(). */ public boolean remove(Object x) { for (int i=0; i<getSize(); i++) { if (x.equals(get(i))) { remove(i); return true; } } return false; } /** * @modifies this * @effects removes all occurrences of x in this, * if any, as determined by .equals. Returns true if any * elements were removed. In the worst case, this runs in linear * time with respect to size(). */ public boolean removeAll(Object x) { boolean ret=false; for (int i=0; i<getSize(); i++) { if (x.equals(get(i))) { remove(i); i--; ret=true; } } return ret; } /** * @modifies this * @effects removes all elements of this. */ public void clear() { while (!isEmpty()) removeFirst(); } /** * @effects returns an iterator that yields the elements of this, in * order, from head to tail. * @requires this not modified will iterator in use. */ public Iterator iterator() { // will either throw NoSuchElementException // or already be initialized. return new BufferIterator(); } private class BufferIterator extends UnmodifiableIterator { /** The index of the next element to yield. */ int i; /** Defensive programming; detect modifications while * iterator in use. */ int oldHead; int oldTail; BufferIterator() { i=head; oldHead=head; oldTail=tail; } public boolean hasNext() { ensureNoModifications(); return i!=tail; } public Object next() throws NoSuchElementException { ensureNoModifications(); if (!hasNext()) throw new NoSuchElementException(); Object ret=buf[i]; i=increment(i); return ret; } private void ensureNoModifications() { if (oldHead!=head || oldTail!=tail) throw new ConcurrentModificationException(); } } /** Returns a shallow copy of this, of type Buffer */ public Object clone() { return new Buffer(this); } public String toString() { StringBuffer buf=new StringBuffer(); buf.append("["); boolean isFirst=true; for (Iterator iter=iterator(); iter.hasNext(); ) { if (! isFirst) buf.append(", "); else isFirst=false; buf.append(iter.next().toString()); } buf.append("]"); return buf.toString(); } }