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);
  }

}