package uk.ac.rhul.cs.cl1.support;
import uk.ac.rhul.cs.utils.Ordered;
import java.util.*;
/**
* Queue subclass that stores <code>Ordered<?></code> elements and ensures that inserted
* elements are removed from the queue in ascending order of their sequence numbers.
*
* <p>Internally, the queue maintains a counter that stores the expected sequence number of
* the next element to be popped from the queue. When an item is inserted into the queue,
* its sequence number is compared with the internal counter. If the sequence number of the
* inserted element is <em>less</em> than the internal counter, an exception is thrown.
* If the sequence number of the inserted element is <em>equal</em> to the internal counter,
* the element is stored in the queue and the counter is increased one. If the sequence number
* of the inserted element is <em>greater</em> than the internal counter, the element is
* put aside into a heap and will be inserted into the queue if the internal counter
* reaches the sequence number of the element.</p>
*/
public class OrderMaintainingQueue<T> extends AbstractQueue<Ordered<T>> implements Queue<Ordered<T>> {
/**
* Internal counter that stores the sequence number of the next element that
* can be inserted immediately into the queue.
*/
private int nextSequenceNumber;
/**
* Queue that stores the items that are actually inserted into the queue.
*/
private ArrayDeque<Ordered<T>> items;
/**
* Heap that stores pending items waiting to be inserted into the queue when the
* next sequence number of the queue reaches their own sequence numbers.
*/
private PriorityQueue<Ordered<T>> pendingItems;
/**
* Constructor.
*/
public OrderMaintainingQueue() {
super();
clear();
}
/**
* Clears the queue and resets the sequence number counter to zero.
*/
public void clear() {
nextSequenceNumber = 0;
if (items == null) {
items = new ArrayDeque<Ordered<T>>();
} else {
items.clear();
}
if (pendingItems == null) {
pendingItems = new PriorityQueue<Ordered<T>>();
} else {
pendingItems.clear();
}
}
@Override
public Iterator<Ordered<T>> iterator() {
return items.iterator();
}
@Override
public int size() {
return items.size();
}
@Override
public boolean offer(Ordered<T> item) {
if (item.sequenceNumber == nextSequenceNumber) {
// This is easy, just put the item into the actual queue.
items.add(item);
nextSequenceNumber++;
// Try to move some pending items to the actual queue.
flushPendingItems();
} else if (item.sequenceNumber < nextSequenceNumber) {
// This item should have been seen before, so throw an exception.
throw new IllegalArgumentException("item with sequence number " + item.sequenceNumber +
" inserted into an order-maintaining queue with expected sequence number = " + nextSequenceNumber);
} else {
// This item has to wait until all the items with smaller sequence numbers
// arrive.
pendingItems.add(item);
}
return true;
}
@Override
public Ordered<T> poll() {
return items.poll();
}
@Override
public Ordered<T> peek() {
return items.peek();
}
/**
* Tries to move pending items to the actual queue if possible. This method must be
* called every time <code>nextSequenceNumber</code> is increased.
*/
private void flushPendingItems() {
while (!pendingItems.isEmpty()) {
Ordered<T> nextPendingItem = pendingItems.peek();
if (nextPendingItem.sequenceNumber == nextSequenceNumber) {
items.add(nextPendingItem);
pendingItems.remove();
nextSequenceNumber++;
} else {
break;
}
}
}
}