/*
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.imagepipeline.testing;
/**
* A queue of nodes sorted by timestamp for the purpose of implementing a scheduled executor.
* Used for {@link ScheduledQueue}.
*
* @param <T> the type of node
*/
public class DeltaQueue<T> {
/**
* A node in the queue.
*
* @param <T> the type of node
*/
private static class Node<T> {
public final T value;
public long delay;
public Node<T> next = null;
public Node(T value, long nanos) {
this.value = value;
this.delay = nanos;
}
}
private Node<T> head = null;
private int size;
/**
* Gets whether the queue is empty.
*
* @return whether the queue is empty
*/
public boolean isEmpty() {
return head == null;
}
/**
* Gets whether there are items in the queue.
*
* @return whether there are items in the queue
*/
public boolean isNotEmpty() {
return !isEmpty();
}
/**
* Gets the next item in the queue without removing it.
*
* @return the next item in the queue
*/
public T next() {
return head.value;
}
/**
* Gets the delay until the next item in the queue.
*
* @return the delay until the next item
*/
public long delay() {
return head.delay;
}
/**
* Adds a node to the queue.
*
* @param delay the delay
* @param value the node to add
*/
public void add(long delay, T value) {
Node<T> newNode = new Node<T>(value, delay);
Node<T> prev = null;
Node<T> next = head;
while (next != null && next.delay <= newNode.delay) {
newNode.delay -= next.delay;
prev = next;
next = next.next;
}
if (prev == null) {
head = newNode;
} else {
prev.next = newNode;
}
if (next != null) {
next.delay -= newNode.delay;
newNode.next = next;
}
size++;
}
/**
* Simulates the passage of time.
*
* @param timeUnits the units of time that are desired to have passed
* @return the time units that were not yet consumed.
*/
public long tick(long timeUnits) {
if (head == null) {
return 0L;
} else if (head.delay >= timeUnits) {
head.delay -= timeUnits;
return 0L;
} else {
long leftover = timeUnits - head.delay;
head.delay = 0L;
return leftover;
}
}
/**
* Pops the next element off the queue. Only valid to call if the head is ready to be pop'd
* because of the passage of time.
*
* @return the next element off the queue.
*/
public T pop() {
if (head.delay > 0) {
throw new IllegalStateException("cannot pop the head element when it has a non-zero delay");
}
T popped = head.value;
head = head.next;
size--;
return popped;
}
/**
* Removes the specified element from the queue.
*
* @param element the element to remove
* @return whether the element was removed
*/
public boolean remove(T element) {
Node<T> prev = null;
Node<T> node = head;
while (node != null && node.value != element) {
prev = node;
node = node.next;
}
if (node == null) {
return false;
}
if (node.next != null) {
node.next.delay += node.delay;
}
if (prev == null) {
head = node.next;
} else {
prev.next = node.next;
}
size--;
return true;
}
/**
* Gets the number of items in the queue. This returns in constant time.
*
* @return number of elements in the queue
*/
public int size() {
return size;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName())
.append("[");
Node<T> node = head;
while (node != null) {
if (node != head) {
sb.append(", ");
}
sb.append("+")
.append(node.delay)
.append(": ")
.append(node.value);
node = node.next;
}
sb.append("]");
return sb.toString();
}
}