RoundRobinQueue.java

package gov.usgs.earthquake.util;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Queue;

/**
 * An abstract base class for round-robin queueing.
 *
 * Sub classes should implement the {@link #getQueueId(Object)} to control how
 * objects are added to queues.
 *
 * @param <T>
 *            type of object being queued.
 */
public class RoundRobinQueue<T> implements Queue<T> {

	/**
	 * Map of queues, keyed by queue id (as determined by
	 * {@link #getQueueId(Object)}).
	 */
	private final HashMap<String, LinkedList<T>> queueMap = new HashMap<String, LinkedList<T>>();

	/**
	 * List of queues, same as in map, in round-robin order.
	 */
	private final LinkedList<LinkedList<T>> queueList = new LinkedList<LinkedList<T>>();

	/** Default constructor. */
	public RoundRobinQueue() {
	}

	/**
	 * This method determines which queue an object uses.
	 *
	 * @param object
	 *            the object being added.
	 * @return id of the queue where object should be added.
	 */
	protected String getQueueId(T object) {
		return object.toString();
	}

	/** ===================== BLOCKING QUEUE METHODS ======================= **/

	/**
	 * Add an item to the queue.
	 *
	 * @param e
	 *            item to add
	 * @return true if added.
	 */
	@Override
	public boolean add(T e) {
		// find queue
		String queueId = getQueueId(e);
		LinkedList<T> queue = queueMap.get(queueId);
		if (queue == null) {
			// create queue
			queue = new LinkedList<T>();
			queueMap.put(queueId, queue);
			queueList.add(queue);
		}
		// add to queue
		return queue.add(e);
	}

	/**
	 * Add an item to the queue, if possible.
	 *
	 * @param e
	 *            item to add
	 * @return true if added, false otherwise.
	 */
	@Override
	public boolean offer(T e) {
		try {
			// this could in theory throw an unchecked exception
			return this.add(e);
		} catch (Exception ex) {
			return false;
		}
	}

	/**
	 * Retrieves and removes the head of this queue.
	 *
	 * @return first element in queue.
	 * @throws NoSuchElementException
	 *             if queue is empty.
	 */
	@Override
	public T remove() {
		if (queueList.size() == 0) {
			throw new NoSuchElementException();
		}

		T next = null;
		// find queue
		LinkedList<T> nextQueue = queueList.removeFirst();
		// take first item
		next = nextQueue.removeFirst();
		// reschedule queue
		if (nextQueue.size() == 0) {
			// queue is empty, remove
			String queueId = getQueueId(next);
			queueMap.remove(queueId);
		} else {
			// move to end of round robin list
			queueList.add(nextQueue);
		}
		return next;
	}

	/**
	 * Retrieves, but does not remove, the head of this queue. This method
	 * differs from the {@link #peek()} method only in that it throws an
	 * exception if this queue is empty.
	 *
	 * @return the head of this queue.
	 * @throws NoSuchElementException
	 *             if this queue is empty.
	 */
	@Override
	public T element() {
		if (queueList.size() == 0) {
			throw new NoSuchElementException();
		}
		LinkedList<T> nextQueue = queueList.getFirst();
		return nextQueue.getFirst();
	}

	/**
	 * Retrieves and removes the head of this queue.
	 *
	 * @return the head of this queue, or null if this queue is empty.
	 */
	@Override
	public T poll() {
		try {
			return remove();
		} catch (NoSuchElementException nsee) {
			return null;
		}
	}

	/**
	 * Retrieves, but does not remove, the head of this queue, returning null if
	 * this queue is empty.
	 *
	 * @return the head of this queue, or null if this queue is empty.
	 */
	@Override
	public T peek() {
		try {
			return element();
		} catch (NoSuchElementException nsee) {
			return null;
		}
	}

	/** ======================= COLLECTION METHODS ========================= **/

	@Override
	public boolean addAll(Collection<? extends T> c) {
		Iterator<? extends T> iter = c.iterator();
		boolean added = false;
		while (iter.hasNext()) {
			added = add(iter.next()) || added;
		}
		return added;
	}

	@Override
	public void clear() {
		queueList.clear();
		queueMap.clear();
	}

	@Override
	public boolean containsAll(Collection<?> c) {
		Iterator<?> iter = c.iterator();
		while (iter.hasNext()) {
			if (!contains(iter.next())) {
				return false;
			}
		}
		return true;
	}

	@Override
	public boolean isEmpty() {
		return queueList.isEmpty();
	}

	@Override
	public boolean removeAll(Collection<?> c) {
		Iterator<?> iter = c.iterator();
		boolean removed = false;
		while (iter.hasNext()) {
			removed = remove(iter.next()) || removed;
		}
		return removed;
	}

	@Override
	public int size() {
		int size = 0;
		Iterator<LinkedList<T>> iter = queueList.iterator();
		while (iter.hasNext()) {
			size = size + iter.next().size();
		}
		return size;
	}

	@Override
	public boolean contains(Object o) {
		try {
			@SuppressWarnings("unchecked")
			String queueId = getQueueId((T) o);
			LinkedList<T> queue = queueMap.get(queueId);
			return queue.contains(o);
		} catch (Exception e) {
			return false;
		}
	}

	@Override
	public boolean remove(Object o) {
		try {
			@SuppressWarnings("unchecked")
			String queueId = getQueueId((T) o);
			LinkedList<T> queue = queueMap.get(queueId);
			boolean removed = queue.remove(o);
			if (queue.isEmpty()) {
				queueMap.remove(queueId);
				queueList.remove(queue);
			}
			return removed;
		} catch (Exception e) {
			return false;
		}
	}

	/** ==================== HORRIBLY INEFFICIENT =========================== */

	/**
	 * Deep copy of another RoundRobinQueue.
	 *
	 * This method is used for semi-destructive iteration methods.
	 *
	 * NOTE: this assumes {@link #getQueueId(Object)} behaves the same for this
	 * and that.
	 *
	 * @param that a RoundRobinQueue to make a deep copy of
	 */
	public RoundRobinQueue(final RoundRobinQueue<T> that) {
		Iterator<LinkedList<T>> iter = that.queueList.iterator();
		while (iter.hasNext()) {
			// copy list
			LinkedList<T> copy = new LinkedList<T>(iter.next());
			// add to round robin list
			this.queueList.add(copy);
			// add to map
			String queueId = that.getQueueId(copy.get(0));
			this.queueMap.put(queueId, copy);
		}
	}

	/**
	 * Flatten queue to a list.
	 *
	 * Creates a copy (see {@link #RoundRobinQueue(RoundRobinQueue)}, then
	 * builds list by polling until it is empty.
	 *
	 * @return list of all items currently in queue.
	 */
	public List<T> toList() {
		ArrayList<T> list = new ArrayList<T>(this.size());
		RoundRobinQueue<T> copy = new RoundRobinQueue<T>(this);
		while (true) {
			T next = copy.poll();
			if (next == null) {
				// done
				break;
			}
			list.add(next);
		}
		return list;
	}

	@Override
	public Iterator<T> iterator() {
		return this.toList().iterator();
	}

	@Override
	public Object[] toArray() {
		return this.toList().toArray();
	}

	@Override
	public <T2> T2[] toArray(T2[] array) {
		return this.toList().toArray(array);
	}

	@Override
	public boolean retainAll(Collection<?> c) {
		List<T> toremove = this.toList();
		toremove.removeAll(c);
		return this.removeAll(toremove);
	}

}